Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 0 additions & 2 deletions apps/engine/src/__generated__/openapi.d.ts

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 0 additions & 7 deletions apps/engine/src/__generated__/openapi.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 0 additions & 2 deletions apps/service/src/__generated__/openapi.d.ts

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 0 additions & 7 deletions apps/service/src/__generated__/openapi.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

15 changes: 3 additions & 12 deletions apps/visualizer/src/lib/pglite.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,7 @@

import { PGlite } from '@electric-sql/pglite'
import { useEffect, useState, useCallback, useRef } from 'react'
import {
SpecParser,
OPENAPI_RESOURCE_TABLE_ALIASES,
type ParsedResourceTable,
} from '@stripe/sync-source-stripe/browser'
import { SpecParser, type ParsedResourceTable } from '@stripe/sync-source-stripe/browser'

type PGliteInstance = InstanceType<typeof PGlite>
type QueryResult = Awaited<ReturnType<PGliteInstance['query']>>
Expand Down Expand Up @@ -154,13 +150,8 @@ function generateSchema(spec: Record<string, unknown>): {
for (const schemaDef of Object.values(schemas)) {
const resourceId = (schemaDef as Record<string, unknown>)['x-resourceId']
if (!resourceId || typeof resourceId !== 'string') continue
const alias = OPENAPI_RESOURCE_TABLE_ALIASES[resourceId]
if (alias) {
allTableNames.add(alias)
} else {
const normalized = resourceId.toLowerCase().replace(/\./g, '_')
allTableNames.add(normalized.endsWith('s') ? normalized : `${normalized}s`)
}
const normalized = resourceId.toLowerCase().replace(/\./g, '_')
allTableNames.add(normalized.endsWith('s') ? normalized : `${normalized}s`)
}

const parser = new SpecParser()
Expand Down
6 changes: 2 additions & 4 deletions e2e/conformance.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,8 +95,7 @@ describe.each(sources)('source: $name', ({ name, mod: initialMod }) => {
it('spec().config is a valid JSON Schema object', async () => {
const src = mod.default as Record<string, Function>
const spec = (await collectFirst(src.spec(), 'spec')).spec
expect(spec.config.type).toBe('object')
expect(typeof spec.config.properties).toBe('object')
expect(spec.config.type === 'object' || Array.isArray(spec.config.anyOf)).toBe(true)
})

it('exports a named configSchema (Zod schema)', () => {
Expand Down Expand Up @@ -146,8 +145,7 @@ describe.each(destinations)('destination: $name', ({ name, mod: initialMod }) =>
it('spec().config is a valid JSON Schema object', async () => {
const dest = mod.default as Record<string, Function>
const spec = (await collectFirst(dest.spec(), 'spec')).spec
expect(spec.config.type).toBe('object')
expect(typeof spec.config.properties).toBe('object')
expect(spec.config.type === 'object' || Array.isArray(spec.config.anyOf)).toBe(true)
})

it('exports a named configSchema (Zod schema)', () => {
Expand Down
4 changes: 2 additions & 2 deletions packages/openapi/__tests__/listFnResolver.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,8 @@ describe('SpecParser.discoverListEndpoints', () => {
supportsStartingAfter: true,
supportsEndingBefore: true,
})
expect(endpoints.get('early_fraud_warning')).toEqual({
tableName: 'early_fraud_warning',
expect(endpoints.get('radar_early_fraud_warning')).toEqual({
tableName: 'radar_early_fraud_warning',
resourceId: 'radar.early_fraud_warning',
apiPath: '/v1/radar/early_fraud_warnings',
supportsCreatedFilter: true,
Expand Down
18 changes: 9 additions & 9 deletions packages/openapi/__tests__/specParser.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,16 @@ import { minimalStripeOpenApiSpec } from './fixtures/minimalSpec'
import type { OpenApiSpec } from '../../types'

describe('SpecParser', () => {
it('parses aliased resources into deterministic tables and column types', () => {
it('parses resources into deterministic tables and column types', () => {
const parser = new SpecParser()
const parsed = parser.parse(minimalStripeOpenApiSpec, {
allowedTables: ['checkout_session', 'customer', 'early_fraud_warning'],
allowedTables: ['checkout_session', 'customer', 'radar_early_fraud_warning'],
})

expect(parsed.tables.map((table) => table.tableName)).toEqual([
'checkout_session',
'customer',
'early_fraud_warning',
'radar_early_fraud_warning',
])

const customers = parsed.tables.find((table) => table.tableName === 'customer')
Expand Down Expand Up @@ -709,14 +709,14 @@ describe('SpecParser', () => {

const tableNames = parsed.tables.map((t) => t.tableName)
expect(tableNames).toEqual([
'active_entitlement',
'checkout_session',
'customer',
'early_fraud_warning',
'feature',
'entitlements_active_entitlement',
'entitlements_feature',
'plan',
'price',
'product',
'radar_early_fraud_warning',
'subscription_item',
'v2_core_account',
'v2_core_event_destination',
Expand Down Expand Up @@ -828,11 +828,11 @@ describe('SpecParser', () => {
expect(tableNames).toContain('customer')
})

it('resolves table name aliases from x-resourceId during discovery', () => {
it('derives table names from x-resourceId via dot-to-underscore', () => {
const parser = new SpecParser()
const parsed = parser.parse(minimalStripeOpenApiSpec)

const earlyFraud = parsed.tables.find((t) => t.tableName === 'early_fraud_warning')
const earlyFraud = parsed.tables.find((t) => t.tableName === 'radar_early_fraud_warning')
expect(earlyFraud).toBeDefined()
expect(earlyFraud?.resourceId).toBe('radar.early_fraud_warning')

Expand All @@ -853,7 +853,7 @@ describe('SpecParser.discoverSyncableTables', () => {
expect(tables).toContain('product')
expect(tables).toContain('plan')
expect(tables).toContain('checkout_session')
expect(tables).toContain('early_fraud_warning')
expect(tables).toContain('radar_early_fraud_warning')
})

it('excludes resources that are listable but have no webhook events', () => {
Expand Down
2 changes: 1 addition & 1 deletion packages/openapi/browser.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// Browser-safe entry. Excludes specFetchHelper (which imports node:fs / node:path)
// so consumers in webpack/Next.js client bundles can import SpecParser without errors.

export { SpecParser, OPENAPI_RESOURCE_TABLE_ALIASES, resolveTableName } from './specParser.js'
export { SpecParser, resolveTableName } from './specParser.js'
export type { ListEndpoint, NestedEndpoint } from './specParser.js'
export { parsedTableToJsonSchema } from './jsonSchemaConverter.js'
export type {
Expand Down
2 changes: 1 addition & 1 deletion packages/openapi/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
export type * from './types.js'
export { SpecParser, OPENAPI_RESOURCE_TABLE_ALIASES, resolveTableName } from './specParser.js'
export { SpecParser, resolveTableName } from './specParser.js'
export type { CreateEndpoint, ListEndpoint, NestedEndpoint } from './specParser.js'

export {
Expand Down
11 changes: 0 additions & 11 deletions packages/openapi/runtimeMappings.ts

This file was deleted.

21 changes: 8 additions & 13 deletions packages/openapi/specParser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import type {
ParsedOpenApiSpec,
ScalarType,
} from './types.js'
import { OPENAPI_RESOURCE_TABLE_ALIASES } from './runtimeMappings.js'

const SCHEMA_REF_PREFIX = '#/components/schemas/'
const CRUD_SUFFIXES = ['.created', '.updated', '.deleted'] as const
Expand All @@ -22,16 +21,12 @@ const RESERVED_COLUMNS = new Set([
'deleted',
])

export { OPENAPI_RESOURCE_TABLE_ALIASES }

/**
* Resolve a Stripe x-resourceId to a canonical table name.
* Singular, snake_cased, with version namespace dots converted to underscores.
* Optional `aliases` map allows callers to override specific resourceIds.
*/
export function resolveTableName(
resourceId: string,
aliases: Record<string, string> = OPENAPI_RESOURCE_TABLE_ALIASES
): string {
export function resolveTableName(resourceId: string, aliases: Record<string, string> = {}): string {
const alias = aliases[resourceId]
if (alias) return alias
return resourceId.toLowerCase().replace(/[.]/g, '_')
Expand Down Expand Up @@ -105,7 +100,7 @@ export class SpecParser {
throw new Error('OpenAPI spec is missing components.schemas')
}

const aliases = { ...OPENAPI_RESOURCE_TABLE_ALIASES, ...(options.resourceAliases ?? {}) }
const aliases = options.resourceAliases ?? {}
const excluded = new Set(options.excludedTables ?? [])
const allowedTables = options.allowedTables
? new Set(options.allowedTables.filter((t) => !excluded.has(t)))
Expand Down Expand Up @@ -198,7 +193,7 @@ export class SpecParser {
excluded?: ReadonlySet<string>
} = {}
): ParsedOpenApiSpec {
const aliases = { ...OPENAPI_RESOURCE_TABLE_ALIASES, ...(options.aliases ?? {}) }
const aliases = options.aliases ?? {}
const syncableTables = this.discoverSyncableTables(spec, {
aliases,
excluded: options.excluded,
Expand All @@ -220,7 +215,7 @@ export class SpecParser {
excluded?: ReadonlySet<string>
} = {}
): Set<string> {
const aliases = { ...OPENAPI_RESOURCE_TABLE_ALIASES, ...(options.aliases ?? {}) }
const aliases = options.aliases ?? {}
const excluded = options.excluded ?? new Set<string>()
const listableIds = this.discoverListableResourceIds(spec, { includeNested: true })
const webhookIds = this.discoverWebhookUpdatableResourceIds(spec, listableIds)
Expand All @@ -240,7 +235,7 @@ export class SpecParser {
*/
discoverListEndpoints(
spec: OpenApiSpec,
aliases: Record<string, string> = OPENAPI_RESOURCE_TABLE_ALIASES
aliases: Record<string, string> = {}
): Map<string, ListEndpoint> {
const endpoints = new Map<string, ListEndpoint>()
for (const raw of this.iterListPaths(spec)) {
Expand Down Expand Up @@ -270,7 +265,7 @@ export class SpecParser {
discoverNestedEndpoints(
spec: OpenApiSpec,
topLevelEndpoints: Map<string, ListEndpoint>,
aliases: Record<string, string> = OPENAPI_RESOURCE_TABLE_ALIASES
aliases: Record<string, string> = {}
): NestedEndpoint[] {
const topLevelByPath = new Map<string, ListEndpoint>()
for (const endpoint of topLevelEndpoints.values()) {
Expand Down Expand Up @@ -304,7 +299,7 @@ export class SpecParser {
*/
discoverCreateEndpoints(
spec: OpenApiSpec,
aliases: Record<string, string> = OPENAPI_RESOURCE_TABLE_ALIASES
aliases: Record<string, string> = {}
): Map<string, CreateEndpoint> {
const endpoints = new Map<string, CreateEndpoint>()
for (const raw of this.iterListPaths(spec)) {
Expand Down
4 changes: 0 additions & 4 deletions packages/source-stripe/.env.sample
Original file line number Diff line number Diff line change
Expand Up @@ -38,10 +38,6 @@ BACKFILL_RELATED_ENTITIES=true
# Max number of connections for the Postgres connection pool, higher value lead to more concurrent queries, but also more load on the database (connections are expensive)
MAX_POSTGRES_CONNECTIONS=20

# If true, the webhook data is not used and instead the webhook is just a trigger to fetch the entity from Stripe again. This ensures that a race condition with failed webhooks can never accidentally overwrite the data with an older state.
# Default:
REVALIDATE_OBJECTS_VIA_STRIPE_API=payment_intent,invoice,customer,subscription

# optional
# Disable the automated database migrations on app startup
# Default: false
Expand Down
6 changes: 3 additions & 3 deletions packages/source-stripe/src/__snapshots__/catalog.test.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,8 @@ exports[`catalogFromOpenApi stream list > all listable tables (no webhook filter
"credit_note",
"customer",
"dispute",
"early_fraud_warning",
"entitlements_feature",
"event",
"feature",
"file",
"file_link",
"financial_connections_account",
Expand Down Expand Up @@ -51,6 +50,7 @@ exports[`catalogFromOpenApi stream list > all listable tables (no webhook filter
"product",
"promotion_code",
"quote",
"radar_early_fraud_warning",
"radar_value_list",
"refund",
"reporting_report_run",
Expand Down Expand Up @@ -95,7 +95,6 @@ exports[`catalogFromOpenApi stream list > default: only tables with webhook even
"credit_note",
"customer",
"dispute",
"early_fraud_warning",
"file",
"financial_connections_account",
"identity_verification_session",
Expand All @@ -117,6 +116,7 @@ exports[`catalogFromOpenApi stream list > default: only tables with webhook even
"product",
"promotion_code",
"quote",
"radar_early_fraud_warning",
"refund",
"reporting_report_run",
"reporting_report_type",
Expand Down
15 changes: 4 additions & 11 deletions packages/source-stripe/src/catalog.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { describe, it, expect } from 'vitest'
import { SpecParser, OPENAPI_RESOURCE_TABLE_ALIASES } from '@stripe/sync-openapi'
import { SpecParser } from '@stripe/sync-openapi'
import { buildResourceRegistry } from './resourceRegistry.js'
import { catalogFromOpenApi } from './catalog.js'
import { resolveOpenApiSpec, BUNDLED_API_VERSION } from '@stripe/sync-openapi'
Expand All @@ -10,9 +10,7 @@ describe('catalogFromOpenApi stream list', () => {

it('default: only tables with webhook events', async () => {
const { spec, apiVersion } = await resolved
const parsed = parser.parse(spec, {
resourceAliases: OPENAPI_RESOURCE_TABLE_ALIASES,
})
const parsed = parser.parse(spec)
const allowedTables = new Set(parsed.tables.map((t) => t.tableName))
const registry = buildResourceRegistry(
spec,
Expand All @@ -31,9 +29,7 @@ describe('catalogFromOpenApi stream list', () => {

it('every stream in the catalog has supports_realtime_sync = true', async () => {
const { spec, apiVersion } = await resolved
const parsed = parser.parse(spec, {
resourceAliases: OPENAPI_RESOURCE_TABLE_ALIASES,
})
const parsed = parser.parse(spec)
const allowedTables = new Set(parsed.tables.map((t) => t.tableName))
const registry = buildResourceRegistry(
spec,
Expand All @@ -54,7 +50,6 @@ describe('catalogFromOpenApi stream list', () => {
const { spec, apiVersion } = await resolved
const allRegistry = buildResourceRegistry(spec, 'sk_test_fake', apiVersion)
const parsed = parser.parse(spec, {
resourceAliases: OPENAPI_RESOURCE_TABLE_ALIASES,
allowedTables: Object.values(allRegistry).map((r) => r.tableName),
})
const registry = buildResourceRegistry(
Expand All @@ -74,9 +69,7 @@ describe('catalogFromOpenApi stream list', () => {

it('every stream has json_schema (no ghost tables)', async () => {
const { spec, apiVersion } = await resolved
const parsed = parser.parse(spec, {
resourceAliases: OPENAPI_RESOURCE_TABLE_ALIASES,
})
const parsed = parser.parse(spec)
const allowedTables = new Set(parsed.tables.map((t) => t.tableName))
const registry = buildResourceRegistry(
spec,
Expand Down
Loading
Loading