@pattern-stack/codegen 0.10.1 → 0.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +122 -0
- package/README.md +5 -5
- package/consumer-skills/codegen/SKILL.md +2 -2
- package/consumer-skills/{sync → integration}/SKILL.md +29 -29
- package/consumer-skills/{sync → integration}/audit-and-detection.md +22 -22
- package/consumer-skills/{sync → integration}/change-sources-and-sinks.md +60 -60
- package/consumer-skills/subsystems/SKILL.md +8 -8
- package/consumer-skills/subsystems/wiring-and-order.md +7 -7
- package/dist/runtime/base-classes/index.d.ts +4 -4
- package/dist/runtime/base-classes/index.js +35 -35
- package/dist/runtime/base-classes/index.js.map +1 -1
- package/dist/runtime/base-classes/{synced-entity-repository.d.ts → integrated-entity-repository.d.ts} +15 -15
- package/dist/runtime/base-classes/{synced-entity-repository.js → integrated-entity-repository.js} +21 -21
- package/dist/runtime/base-classes/integrated-entity-repository.js.map +1 -0
- package/dist/runtime/base-classes/{synced-entity-service.d.ts → integrated-entity-service.d.ts} +6 -6
- package/dist/runtime/base-classes/{synced-entity-service.js → integrated-entity-service.js} +4 -4
- package/dist/runtime/base-classes/integrated-entity-service.js.map +1 -0
- package/dist/runtime/base-classes/{sync-upsert-config.d.ts → integration-upsert-config.d.ts} +13 -13
- package/dist/runtime/base-classes/integration-upsert-config.js +1 -0
- package/dist/runtime/base-classes/{junction-sync-repository.d.ts → junction-integration-repository.d.ts} +11 -11
- package/dist/runtime/base-classes/{junction-sync-repository.js → junction-integration-repository.js} +15 -15
- package/dist/runtime/base-classes/junction-integration-repository.js.map +1 -0
- package/dist/runtime/subsystems/auth/auth-oauth-state.schema.js.map +1 -1
- package/dist/runtime/subsystems/auth/auth.module.d.ts +4 -4
- package/dist/runtime/subsystems/auth/auth.module.js +3 -3
- package/dist/runtime/subsystems/auth/auth.module.js.map +1 -1
- package/dist/runtime/subsystems/auth/auth.tokens.d.ts +8 -8
- package/dist/runtime/subsystems/auth/auth.tokens.js +6 -6
- package/dist/runtime/subsystems/auth/auth.tokens.js.map +1 -1
- package/dist/runtime/subsystems/auth/backends/state-store.drizzle-backend.js.map +1 -1
- package/dist/runtime/subsystems/auth/controllers/auth.controller.d.ts +2 -2
- package/dist/runtime/subsystems/auth/controllers/auth.controller.js +3 -3
- package/dist/runtime/subsystems/auth/controllers/auth.controller.js.map +1 -1
- package/dist/runtime/subsystems/auth/index.d.ts +3 -3
- package/dist/runtime/subsystems/auth/index.js +40 -40
- package/dist/runtime/subsystems/auth/index.js.map +1 -1
- package/dist/runtime/subsystems/auth/middleware/requester-context.js.map +1 -1
- package/dist/runtime/subsystems/auth/protocols/auth-strategy.d.ts +3 -3
- package/dist/runtime/subsystems/auth/protocols/{integration-store.d.ts → connection-store.d.ts} +20 -20
- package/dist/runtime/subsystems/auth/protocols/connection-store.js +1 -0
- package/dist/runtime/subsystems/auth/protocols/provider-strategy.d.ts +3 -3
- package/dist/runtime/subsystems/auth/runtime/{integration-broken.error.d.ts → connection-broken.error.d.ts} +5 -5
- package/dist/runtime/subsystems/auth/runtime/connection-broken.error.js +19 -0
- package/dist/runtime/subsystems/auth/runtime/connection-broken.error.js.map +1 -0
- package/dist/runtime/subsystems/auth/runtime/oauth2-refresh.strategy.d.ts +10 -10
- package/dist/runtime/subsystems/auth/runtime/oauth2-refresh.strategy.js +28 -28
- package/dist/runtime/subsystems/auth/runtime/oauth2-refresh.strategy.js.map +1 -1
- package/dist/runtime/subsystems/auth/runtime/with-auth-retry.d.ts +1 -1
- package/dist/runtime/subsystems/auth/runtime/with-auth-retry.js +3 -3
- package/dist/runtime/subsystems/auth/runtime/with-auth-retry.js.map +1 -1
- package/dist/runtime/subsystems/index.d.ts +11 -7
- package/dist/runtime/subsystems/index.js +1041 -67
- package/dist/runtime/subsystems/index.js.map +1 -1
- package/dist/runtime/subsystems/{sync → integration}/build-change-source.d.ts +3 -3
- package/dist/runtime/subsystems/{sync → integration}/build-change-source.js +3 -3
- package/dist/runtime/subsystems/integration/build-change-source.js.map +1 -0
- package/dist/runtime/subsystems/{sync → integration}/deep-equal.differ.d.ts +2 -2
- package/dist/runtime/subsystems/{sync → integration}/deep-equal.differ.js +1 -1
- package/dist/runtime/subsystems/integration/deep-equal.differ.js.map +1 -0
- package/dist/runtime/subsystems/{sync → integration}/detection-config.schema.d.ts +3 -3
- package/dist/runtime/subsystems/{sync → integration}/detection-config.schema.js +1 -1
- package/dist/runtime/subsystems/integration/detection-config.schema.js.map +1 -0
- package/dist/runtime/subsystems/integration/entity-change-source-registry.memory.d.ts +25 -0
- package/dist/runtime/subsystems/integration/entity-change-source-registry.memory.js +34 -0
- package/dist/runtime/subsystems/integration/entity-change-source-registry.memory.js.map +1 -0
- package/dist/runtime/subsystems/integration/entity-change-source-registry.protocol.d.ts +53 -0
- package/dist/runtime/subsystems/integration/entity-change-source-registry.protocol.js +13 -0
- package/dist/runtime/subsystems/integration/entity-change-source-registry.protocol.js.map +1 -0
- package/dist/runtime/subsystems/{sync/execute-sync.use-case.d.ts → integration/execute-integration.use-case.d.ts} +13 -13
- package/dist/runtime/subsystems/{sync/execute-sync.use-case.js → integration/execute-integration.use-case.js} +30 -30
- package/dist/runtime/subsystems/integration/execute-integration.use-case.js.map +1 -0
- package/dist/runtime/subsystems/integration/index.d.ts +30 -0
- package/dist/runtime/subsystems/{sync → integration}/index.js +206 -171
- package/dist/runtime/subsystems/integration/index.js.map +1 -0
- package/dist/runtime/subsystems/{sync/sync-audit.schema.d.ts → integration/integration-audit.schema.d.ts} +64 -64
- package/dist/runtime/subsystems/{sync/sync-audit.schema.js → integration/integration-audit.schema.js} +47 -47
- package/dist/runtime/subsystems/integration/integration-audit.schema.js.map +1 -0
- package/dist/runtime/subsystems/{sync/sync-change-source.protocol.d.ts → integration/integration-change-source.protocol.d.ts} +10 -10
- package/dist/runtime/subsystems/integration/integration-change-source.protocol.js +1 -0
- package/dist/runtime/subsystems/{sync/sync-cursor-store.drizzle-backend.d.ts → integration/integration-cursor-store.drizzle-backend.d.ts} +1 -1
- package/dist/runtime/subsystems/{sync/sync-cursor-store.drizzle-backend.js → integration/integration-cursor-store.drizzle-backend.js} +65 -65
- package/dist/runtime/subsystems/integration/integration-cursor-store.drizzle-backend.js.map +1 -0
- package/dist/runtime/subsystems/{sync/sync-cursor-store.memory-backend.d.ts → integration/integration-cursor-store.memory-backend.d.ts} +6 -6
- package/dist/runtime/subsystems/{sync/sync-cursor-store.memory-backend.js → integration/integration-cursor-store.memory-backend.js} +5 -5
- package/dist/runtime/subsystems/integration/integration-cursor-store.memory-backend.js.map +1 -0
- package/dist/runtime/subsystems/{sync/sync-cursor-store.protocol.d.ts → integration/integration-cursor-store.protocol.d.ts} +13 -13
- package/dist/runtime/subsystems/integration/integration-cursor-store.protocol.js +1 -0
- package/dist/runtime/subsystems/{sync/sync-errors.d.ts → integration/integration-errors.d.ts} +2 -2
- package/dist/runtime/subsystems/{sync/sync-errors.js → integration/integration-errors.js} +3 -3
- package/dist/runtime/subsystems/integration/integration-errors.js.map +1 -0
- package/dist/runtime/subsystems/{sync/sync-field-diff.protocol.d.ts → integration/integration-field-diff.protocol.d.ts} +2 -2
- package/dist/runtime/subsystems/{sync/sync-field-diff.protocol.js → integration/integration-field-diff.protocol.js} +2 -2
- package/dist/runtime/subsystems/integration/integration-field-diff.protocol.js.map +1 -0
- package/dist/runtime/subsystems/{sync/sync-loopback.protocol.d.ts → integration/integration-loopback.protocol.d.ts} +2 -2
- package/dist/runtime/subsystems/integration/integration-loopback.protocol.js +1 -0
- package/dist/runtime/subsystems/{sync/sync-middleware.protocol.d.ts → integration/integration-middleware.protocol.d.ts} +5 -5
- package/dist/runtime/subsystems/integration/integration-middleware.protocol.js +1 -0
- package/dist/runtime/subsystems/{sync/sync-run-recorder.drizzle-backend.d.ts → integration/integration-run-recorder.drizzle-backend.d.ts} +5 -5
- package/dist/runtime/subsystems/{sync/sync-run-recorder.drizzle-backend.js → integration/integration-run-recorder.drizzle-backend.js} +73 -73
- package/dist/runtime/subsystems/integration/integration-run-recorder.drizzle-backend.js.map +1 -0
- package/dist/runtime/subsystems/{sync/sync-run-recorder.memory-backend.d.ts → integration/integration-run-recorder.memory-backend.d.ts} +15 -15
- package/dist/runtime/subsystems/{sync/sync-run-recorder.memory-backend.js → integration/integration-run-recorder.memory-backend.js} +11 -11
- package/dist/runtime/subsystems/integration/integration-run-recorder.memory-backend.js.map +1 -0
- package/dist/runtime/subsystems/{sync/sync-run-recorder.protocol.d.ts → integration/integration-run-recorder.protocol.d.ts} +25 -25
- package/dist/runtime/subsystems/integration/integration-run-recorder.protocol.js +1 -0
- package/dist/runtime/subsystems/{sync/sync-sink.protocol.d.ts → integration/integration-sink.protocol.d.ts} +5 -5
- package/dist/runtime/subsystems/integration/integration-sink.protocol.js +1 -0
- package/dist/runtime/subsystems/{sync/sync.module.d.ts → integration/integration.module.d.ts} +24 -24
- package/dist/runtime/subsystems/{sync/sync.module.js → integration/integration.module.js} +132 -132
- package/dist/runtime/subsystems/integration/integration.module.js.map +1 -0
- package/dist/runtime/subsystems/integration/integration.tokens.d.ts +60 -0
- package/dist/runtime/subsystems/integration/integration.tokens.js +20 -0
- package/dist/runtime/subsystems/integration/integration.tokens.js.map +1 -0
- package/dist/runtime/subsystems/{sync → integration}/loopback.middleware.d.ts +5 -5
- package/dist/runtime/subsystems/{sync → integration}/loopback.middleware.js +1 -1
- package/dist/runtime/subsystems/integration/loopback.middleware.js.map +1 -0
- package/dist/runtime/subsystems/{sync → integration}/poll-change-source.d.ts +5 -5
- package/dist/runtime/subsystems/{sync → integration}/poll-change-source.js +1 -1
- package/dist/runtime/subsystems/integration/poll-change-source.js.map +1 -0
- package/dist/runtime/subsystems/{sync → integration}/webhook-change-source.d.ts +5 -5
- package/dist/runtime/subsystems/{sync → integration}/webhook-change-source.js +1 -1
- package/dist/runtime/subsystems/integration/webhook-change-source.js.map +1 -0
- package/dist/runtime/subsystems/jobs/job-worker.module.d.ts +1 -1
- package/dist/runtime/subsystems/observability/index.d.ts +4 -4
- package/dist/runtime/subsystems/observability/index.js +11 -11
- package/dist/runtime/subsystems/observability/index.js.map +1 -1
- package/dist/runtime/subsystems/observability/observability.module.d.ts +2 -2
- package/dist/runtime/subsystems/observability/observability.module.js +11 -11
- package/dist/runtime/subsystems/observability/observability.module.js.map +1 -1
- package/dist/runtime/subsystems/observability/observability.protocol.d.ts +11 -11
- package/dist/runtime/subsystems/observability/observability.service.d.ts +6 -6
- package/dist/runtime/subsystems/observability/observability.service.js +11 -11
- package/dist/runtime/subsystems/observability/observability.service.js.map +1 -1
- package/dist/runtime/subsystems/observability/observability.tokens.d.ts +1 -1
- package/dist/runtime/subsystems/observability/observability.tokens.js.map +1 -1
- package/dist/runtime/subsystems/observability/reporters/bridge-metrics.reporter.d.ts +3 -3
- package/dist/runtime/subsystems/observability/reporters/bridge-metrics.reporter.js.map +1 -1
- package/dist/runtime/subsystems/observability/reporters/index.d.ts +3 -3
- package/dist/runtime/subsystems/observability/reporters/index.js.map +1 -1
- package/dist/src/cli/index.js +1336 -376
- package/dist/src/cli/index.js.map +1 -1
- package/dist/src/index.d.ts +70 -22
- package/dist/src/index.js +290 -194
- package/dist/src/index.js.map +1 -1
- package/examples/auth-integrations/README.md +32 -32
- package/examples/auth-integrations/definitions/entities/{integration.yaml → connection.yaml} +10 -10
- package/examples/auth-integrations/runtime/{integrations/adapters/integration-grant-sink.adapter.ts → connections/adapters/connection-grant-sink.adapter.ts} +7 -7
- package/examples/auth-integrations/runtime/{integrations/adapters/integration-reader.adapter.ts → connections/adapters/connection-reader.adapter.ts} +10 -10
- package/examples/auth-integrations/runtime/{integrations/adapters/integration-token-writer.adapter.ts → connections/adapters/connection-token-writer.adapter.ts} +11 -11
- package/examples/auth-integrations/runtime/connections/connections-auth.module.ts +81 -0
- package/examples/auth-integrations/runtime/{integrations/facade/integrations.service.ts → connections/facade/connections.service.ts} +35 -35
- package/examples/auth-integrations/runtime/{integrations → connections}/oauth/use-cases/create-or-update-from-oauth-grant.use-case.ts +11 -11
- package/examples/auth-integrations/runtime/{integrations/oauth/use-cases/disconnect-integration.use-case.ts → connections/oauth/use-cases/disconnect-connection.use-case.ts} +6 -6
- package/examples/auth-integrations/runtime/connections/oauth/use-cases/list-user-connections.use-case.ts +21 -0
- package/examples/auth-integrations/runtime/connections/oauth/use-cases/mark-connection-requires-reauth.use-case.ts +21 -0
- package/package.json +9 -1
- package/runtime/base-classes/index.ts +8 -8
- package/runtime/base-classes/{synced-entity-repository.ts → integrated-entity-repository.ts} +36 -36
- package/runtime/base-classes/{synced-entity-service.ts → integrated-entity-service.ts} +6 -6
- package/runtime/base-classes/{sync-upsert-config.ts → integration-upsert-config.ts} +12 -12
- package/runtime/base-classes/{junction-sync-repository.ts → junction-integration-repository.ts} +28 -28
- package/runtime/subsystems/auth/auth-oauth-state.schema.ts +1 -1
- package/runtime/subsystems/auth/auth.module.ts +4 -4
- package/runtime/subsystems/auth/auth.tokens.ts +7 -7
- package/runtime/subsystems/auth/controllers/auth.controller.ts +7 -7
- package/runtime/subsystems/auth/index.ts +19 -19
- package/runtime/subsystems/auth/protocols/auth-strategy.ts +3 -3
- package/runtime/subsystems/auth/protocols/{integration-store.ts → connection-store.ts} +19 -19
- package/runtime/subsystems/auth/protocols/provider-strategy.ts +2 -2
- package/runtime/subsystems/auth/runtime/{integration-broken.error.ts → connection-broken.error.ts} +5 -5
- package/runtime/subsystems/auth/runtime/oauth2-refresh.strategy.ts +35 -35
- package/runtime/subsystems/auth/runtime/with-auth-retry.ts +3 -3
- package/runtime/subsystems/index.ts +26 -11
- package/runtime/subsystems/{sync → integration}/build-change-source.ts +3 -3
- package/runtime/subsystems/{sync → integration}/deep-equal.differ.ts +7 -7
- package/runtime/subsystems/{sync → integration}/detection-config.schema.ts +3 -3
- package/runtime/subsystems/integration/entity-change-source-registry.memory.ts +40 -0
- package/runtime/subsystems/integration/entity-change-source-registry.protocol.ts +59 -0
- package/runtime/subsystems/{sync/execute-sync.use-case.ts → integration/execute-integration.use-case.ts} +40 -40
- package/runtime/subsystems/{sync → integration}/index.ts +56 -47
- package/runtime/subsystems/{sync/sync-audit.schema.ts → integration/integration-audit.schema.ts} +61 -61
- package/runtime/subsystems/{sync/sync-change-source.protocol.ts → integration/integration-change-source.protocol.ts} +9 -9
- package/runtime/subsystems/{sync/sync-cursor-store.drizzle-backend.ts → integration/integration-cursor-store.drizzle-backend.ts} +30 -30
- package/runtime/subsystems/{sync/sync-cursor-store.memory-backend.ts → integration/integration-cursor-store.memory-backend.ts} +9 -9
- package/runtime/subsystems/{sync/sync-cursor-store.protocol.ts → integration/integration-cursor-store.protocol.ts} +13 -13
- package/runtime/subsystems/{sync/sync-errors.ts → integration/integration-errors.ts} +3 -3
- package/runtime/subsystems/{sync/sync-field-diff.protocol.ts → integration/integration-field-diff.protocol.ts} +2 -2
- package/runtime/subsystems/{sync/sync-loopback.protocol.ts → integration/integration-loopback.protocol.ts} +2 -2
- package/runtime/subsystems/{sync/sync-middleware.protocol.ts → integration/integration-middleware.protocol.ts} +6 -6
- package/runtime/subsystems/{sync/sync-run-recorder.drizzle-backend.ts → integration/integration-run-recorder.drizzle-backend.ts} +39 -39
- package/runtime/subsystems/{sync/sync-run-recorder.memory-backend.ts → integration/integration-run-recorder.memory-backend.ts} +23 -23
- package/runtime/subsystems/{sync/sync-run-recorder.protocol.ts → integration/integration-run-recorder.protocol.ts} +25 -25
- package/runtime/subsystems/{sync/sync-sink.protocol.ts → integration/integration-sink.protocol.ts} +4 -4
- package/runtime/subsystems/{sync/sync.module.ts → integration/integration.module.ts} +48 -48
- package/runtime/subsystems/integration/integration.tokens.ts +63 -0
- package/runtime/subsystems/{sync → integration}/loopback.middleware.ts +5 -5
- package/runtime/subsystems/{sync → integration}/poll-change-source.ts +7 -7
- package/runtime/subsystems/{sync → integration}/webhook-change-source.ts +7 -7
- package/runtime/subsystems/observability/index.ts +1 -1
- package/runtime/subsystems/observability/observability.module.ts +2 -2
- package/runtime/subsystems/observability/observability.protocol.ts +11 -11
- package/runtime/subsystems/observability/observability.service.ts +13 -13
- package/runtime/subsystems/observability/observability.tokens.ts +1 -1
- package/src/patterns/library/index.ts +4 -4
- package/src/patterns/library/{synced.pattern.ts → integrated.pattern.ts} +12 -12
- package/src/patterns/library/junction.pattern.ts +1 -1
- package/src/patterns/pattern-definition.ts +3 -3
- package/templates/entity/new/backend/modules/core/{sync-source.ejs.t → integration-source.ejs.t} +6 -6
- package/templates/entity/new/clean-lite-ps/entity.ejs.t +12 -3
- package/templates/entity/new/clean-lite-ps/module.ejs.t +1 -1
- package/templates/entity/new/clean-lite-ps/prompt-extension.js +243 -60
- package/templates/entity/new/clean-lite-ps/repository.ejs.t +27 -27
- package/templates/entity/new/frontend/collections/collection.ejs.t +26 -1
- package/templates/entity/new/frontend/collections/collections-base.ejs.t +11 -0
- package/templates/entity/new/frontend/entity/combined.ejs.t +31 -1
- package/templates/entity/new/prompt.js +27 -15
- package/templates/junction/new/entity.ejs.t +1 -1
- package/templates/junction/new/prompt.js +24 -24
- package/templates/junction/new/repository.ejs.t +19 -19
- package/templates/subsystem/auth/auth-oauth-state.schema.ejs.t +2 -2
- package/templates/subsystem/auth-config/prompt.js +1 -1
- package/templates/subsystem/auth-integrations/app-module-hook.ejs.t +5 -5
- package/templates/subsystem/bridge/prompt.js +1 -1
- package/templates/subsystem/integration/integration-audit.schema.ejs.t +192 -0
- package/templates/subsystem/{sync → integration}/prompt.js +12 -12
- package/templates/subsystem/{sync-config/codegen-config-sync-block.ejs.t → integration-config/codegen-config-integration-block.ejs.t} +7 -7
- package/templates/subsystem/integration-config/prompt.js +22 -0
- package/templates/subsystem/jobs/worker.ejs.t +2 -2
- package/templates/subsystem/observability/main-hook.ejs.t +1 -1
- package/templates/subsystem/observability/prompt.js +1 -1
- package/templates/subsystem/openapi-config/prompt.js +1 -1
- package/dist/runtime/base-classes/junction-sync-repository.js.map +0 -1
- package/dist/runtime/base-classes/sync-upsert-config.js +0 -1
- package/dist/runtime/base-classes/synced-entity-repository.js.map +0 -1
- package/dist/runtime/base-classes/synced-entity-service.js.map +0 -1
- package/dist/runtime/subsystems/auth/protocols/integration-store.js +0 -1
- package/dist/runtime/subsystems/auth/runtime/integration-broken.error.js +0 -19
- package/dist/runtime/subsystems/auth/runtime/integration-broken.error.js.map +0 -1
- package/dist/runtime/subsystems/sync/build-change-source.js.map +0 -1
- package/dist/runtime/subsystems/sync/deep-equal.differ.js.map +0 -1
- package/dist/runtime/subsystems/sync/detection-config.schema.js.map +0 -1
- package/dist/runtime/subsystems/sync/execute-sync.use-case.js.map +0 -1
- package/dist/runtime/subsystems/sync/index.d.ts +0 -28
- package/dist/runtime/subsystems/sync/index.js.map +0 -1
- package/dist/runtime/subsystems/sync/loopback.middleware.js.map +0 -1
- package/dist/runtime/subsystems/sync/poll-change-source.js.map +0 -1
- package/dist/runtime/subsystems/sync/sync-audit.schema.js.map +0 -1
- package/dist/runtime/subsystems/sync/sync-change-source.protocol.js +0 -1
- package/dist/runtime/subsystems/sync/sync-cursor-store.drizzle-backend.js.map +0 -1
- package/dist/runtime/subsystems/sync/sync-cursor-store.memory-backend.js.map +0 -1
- package/dist/runtime/subsystems/sync/sync-cursor-store.protocol.js +0 -1
- package/dist/runtime/subsystems/sync/sync-errors.js.map +0 -1
- package/dist/runtime/subsystems/sync/sync-field-diff.protocol.js.map +0 -1
- package/dist/runtime/subsystems/sync/sync-loopback.protocol.js +0 -1
- package/dist/runtime/subsystems/sync/sync-middleware.protocol.js +0 -1
- package/dist/runtime/subsystems/sync/sync-run-recorder.drizzle-backend.js.map +0 -1
- package/dist/runtime/subsystems/sync/sync-run-recorder.memory-backend.js.map +0 -1
- package/dist/runtime/subsystems/sync/sync-run-recorder.protocol.js +0 -1
- package/dist/runtime/subsystems/sync/sync-sink.protocol.js +0 -1
- package/dist/runtime/subsystems/sync/sync.module.js.map +0 -1
- package/dist/runtime/subsystems/sync/sync.tokens.d.ts +0 -47
- package/dist/runtime/subsystems/sync/sync.tokens.js +0 -18
- package/dist/runtime/subsystems/sync/sync.tokens.js.map +0 -1
- package/dist/runtime/subsystems/sync/webhook-change-source.js.map +0 -1
- package/examples/auth-integrations/runtime/integrations/integrations-auth.module.ts +0 -81
- package/examples/auth-integrations/runtime/integrations/oauth/use-cases/list-user-integrations.use-case.ts +0 -21
- package/examples/auth-integrations/runtime/integrations/oauth/use-cases/mark-integration-requires-reauth.use-case.ts +0 -21
- package/runtime/subsystems/sync/sync.tokens.ts +0 -49
- package/templates/entity/new/backend/modules/core/sync-source.providers.ejs.t +0 -18
- package/templates/subsystem/sync/sync-audit.schema.ejs.t +0 -192
- package/templates/subsystem/sync-config/prompt.js +0 -22
- /package/dist/runtime/base-classes/{sync-upsert-config.js.map → integration-upsert-config.js.map} +0 -0
- /package/dist/runtime/subsystems/auth/protocols/{integration-store.js.map → connection-store.js.map} +0 -0
- /package/dist/runtime/subsystems/{sync/sync-change-source.protocol.js.map → integration/integration-change-source.protocol.js.map} +0 -0
- /package/dist/runtime/subsystems/{sync/sync-cursor-store.protocol.js.map → integration/integration-cursor-store.protocol.js.map} +0 -0
- /package/dist/runtime/subsystems/{sync/sync-loopback.protocol.js.map → integration/integration-loopback.protocol.js.map} +0 -0
- /package/dist/runtime/subsystems/{sync/sync-middleware.protocol.js.map → integration/integration-middleware.protocol.js.map} +0 -0
- /package/dist/runtime/subsystems/{sync/sync-run-recorder.protocol.js.map → integration/integration-run-recorder.protocol.js.map} +0 -0
- /package/dist/runtime/subsystems/{sync/sync-sink.protocol.js.map → integration/integration-sink.protocol.js.map} +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../runtime/subsystems/integration/integration-audit.schema.ts"],"sourcesContent":["/**\n * Drizzle schema for the integration subsystem audit/observability tables (SYNC-1).\n *\n * Three tables model end-to-end integration observability, keyed by the single port\n * every integration adapter implements (`IChangeSource<T>` from SYNC-2):\n *\n * - `integration_subscriptions` — owns the cursor per\n * `(connection_id, adapter, domain, external_ref)` tuple. Addressed\n * by id by `ICursorStore` (SYNC-3/SYNC-4).\n * - `integration_runs` — per-run audit log: start/complete, status,\n * cursor before/after, counts, direction (inbound|outbound),\n * action (poll|cdc|webhook|manual|writeback).\n * - `integration_run_items` — per-record change log with structured\n * `changed_fields` jsonb enforced by the Zod `FieldDiffSchema`\n * contract (ADR-0003; protocol lives in SYNC-2's\n * integration-field-diff.protocol.ts).\n *\n * Design calls (vs. issue #126 open questions):\n *\n * - `integration_subscriptions` ships in the subsystem (not consumer-owned).\n * Rationale: SYNC-4's `PostgresCursorStore` needs to read/write this\n * table directly; making it consumer-owned would require consumers to\n * hand-wire a shape the backend already knows. The row is addressable\n * by id and scoped by the uniqueness tuple; consumers can still\n * query/list it freely. Same stance as `job_run` being subsystem-\n * owned while remaining consumer-queryable.\n *\n * - `tenant_id` is always emitted on the three tables as nullable text.\n * The `INTEGRATION_MULTI_TENANT` DI flag (SYNC-6) is what enforces the\n * non-null + cross-tenant-isolation contract at the service/orchestrator\n * boundary. This mirrors JOB-1/JOB-8's final shape — runtime guard, not\n * a scaffold-time conditional column. Keeps the schema file uniform\n * across single-tenant and multi-tenant deployments.\n *\n * - `changed_fields` on `integration_run_items` is typed via the Zod-inferred\n * `FieldDiff` shape from SYNC-2 (`{ [fieldName]: { from, to } }`). The\n * recorder service (SYNC-5) validates every write against\n * `FieldDiffSchema.parse` so consumers can rely on the shape.\n */\nimport {\n pgEnum,\n pgTable,\n uuid,\n text,\n jsonb,\n integer,\n boolean,\n timestamp,\n index,\n uniqueIndex,\n} from 'drizzle-orm/pg-core';\nimport type { InferSelectModel } from 'drizzle-orm';\n\nimport type { FieldDiff } from './integration-field-diff.protocol';\n\n// ─── Enums ──────────────────────────────────────────────────────────────────\n\n/**\n * Direction of a integration run relative to local state.\n *\n * - `inbound` — external → local (the common case: SFDC poll → local DB).\n * - `outbound` — local → external (writeback; deferred per epic but the\n * column shape is reserved so future writeback runs share the audit log).\n */\nexport const integrationRunDirectionEnum = pgEnum('integration_run_direction', [\n 'inbound',\n 'outbound',\n]);\n\n/**\n * How the run detected upstream changes. Maps 1:1 to the `Change.source`\n * provenance on inbound runs; `manual` captures operator-triggered re-integrations\n * and `writeback` captures outbound runs.\n */\nexport const integrationRunActionEnum = pgEnum('integration_run_action', [\n 'poll',\n 'cdc',\n 'webhook',\n 'manual',\n 'writeback',\n]);\n\n/**\n * Lifecycle status of a integration run.\n *\n * - `running` — in-flight; recorder has started but not completed.\n * - `success` — completed with at least one change processed.\n * - `no_changes` — completed cleanly, no upstream changes found.\n * - `failed` — errored before completion; `error` column carries the\n * message. `records_processed` may be non-zero (partial progress).\n */\nexport const integrationRunStatusEnum = pgEnum('integration_run_status', [\n 'running',\n 'success',\n 'no_changes',\n 'failed',\n]);\n\n/**\n * Operation applied per record. Mirrors `Change<T>.operation` from SYNC-2,\n * plus the recorder's own `'noop'` for changes that matched existing state.\n */\nexport const integrationRunItemOperationEnum = pgEnum('integration_run_item_operation', [\n 'created',\n 'updated',\n 'deleted',\n 'noop',\n]);\n\n/**\n * Per-record status within a run. `skipped` captures loopback-detected echoes\n * of the local system's own writes (see `ILoopbackFingerprintStore` in the\n * epic), which record the external_id but intentionally do not touch local\n * state.\n */\nexport const integrationRunItemStatusEnum = pgEnum('integration_run_item_status', [\n 'success',\n 'failed',\n 'skipped',\n]);\n\n// ─── integration_subscriptions ─────────────────────────────────────────────────────\n\n/**\n * One cursor owner per (integration, adapter, domain, external_ref).\n *\n * - `connection_id` — opaque id of the connected account/instance. E.g.\n * the SFDC org id for polling strategies, the GitHub installation id\n * for webhook strategies.\n * - `adapter` — short adapter label, e.g. `'salesforce'`, `'hubspot'`.\n * - `domain` — canonical entity domain this subscription tracks,\n * e.g. `'opportunity'`, `'contact'`.\n * - `external_ref` — optional upstream scope (e.g. a filter id, a\n * webhook subscription id). NULL when the subscription covers the\n * entire domain.\n *\n * The cursor shape is opaque jsonb — strategies type it internally (poll:\n * `{ systemModstamp }`, cdc: `{ replayId }`, webhook: `{ ts }`). Overwritten\n * by `ICursorStore.put(id, cursor)`.\n */\nexport const integrationSubscriptions = pgTable(\n 'integration_subscriptions',\n {\n id: uuid('id').primaryKey().defaultRandom(),\n connectionId: text('connection_id').notNull(),\n adapter: text('adapter').notNull(),\n domain: text('domain').notNull(),\n externalRef: text('external_ref'),\n enabled: boolean('enabled').notNull().default(true),\n /**\n * Per-subscription configuration bag. Strategies type it internally;\n * e.g. polling strategies stash `{ batchSize, highWatermark }` here.\n */\n config: jsonb('config').notNull().default({}).$type<Record<string, unknown>>(),\n /**\n * Opaque cursor persisted by `ICursorStore.put()`. NULL until the first\n * successful run advances it.\n */\n cursor: jsonb('cursor').$type<unknown>(),\n lastIntegrationAt: timestamp('last_integration_at', { withTimezone: true }),\n /** Runtime-enforced when `INTEGRATION_MULTI_TENANT` is true; see SYNC-6. */\n tenantId: text('tenant_id'),\n createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),\n updatedAt: timestamp('updated_at', { withTimezone: true }).notNull().defaultNow(),\n },\n (t) => ({\n /**\n * Composite uniqueness per the epic shape. `external_ref` is nullable;\n * Postgres treats NULLs as distinct in a UNIQUE constraint, which means\n * two rows with the same `(connection_id, adapter, domain)` and NULL\n * external_ref are allowed. That's intentional — a subscription with\n * NULL external_ref covers the full domain, and duplicates there would\n * be a consumer-layer modeling issue, not a schema concern.\n */\n uqIntegrationSubscriptionTuple: uniqueIndex('uq_integration_subscriptions_tuple').on(\n t.connectionId,\n t.adapter,\n t.domain,\n t.externalRef,\n ),\n /** Scheduling query: list enabled subscriptions ordered by staleness. */\n idxIntegrationSubscriptionsEnabledLastIntegration: index(\n 'idx_integration_subscriptions_enabled_last_integration',\n ).on(t.enabled, t.lastIntegrationAt),\n }),\n);\n\nexport type IntegrationSubscriptionRow = InferSelectModel<typeof integrationSubscriptions>;\n\n// ─── integration_runs ──────────────────────────────────────────────────────────────\n\n/**\n * One row per invocation of `ExecuteIntegrationUseCase`. `started_at` is set when\n * the recorder opens the run; `completed_at`, `status`, `records_*`,\n * `cursor_after`, and `duration_ms` are filled on completion.\n *\n * `cursor_before` / `cursor_after` carry the opaque cursor snapshots so the\n * run log is fully self-describing — given a run id, an operator can reason\n * about exactly what window was scanned without cross-referencing another\n * table.\n */\nexport const integrationRuns = pgTable(\n 'integration_runs',\n {\n id: uuid('id').primaryKey().defaultRandom(),\n subscriptionId: uuid('subscription_id')\n .notNull()\n .references(() => integrationSubscriptions.id, { onDelete: 'cascade' }),\n direction: integrationRunDirectionEnum('direction').notNull(),\n action: integrationRunActionEnum('action').notNull(),\n status: integrationRunStatusEnum('status').notNull().default('running'),\n recordsFound: integer('records_found').notNull().default(0),\n recordsProcessed: integer('records_processed').notNull().default(0),\n cursorBefore: jsonb('cursor_before').$type<unknown>(),\n cursorAfter: jsonb('cursor_after').$type<unknown>(),\n durationMs: integer('duration_ms'),\n error: text('error'),\n startedAt: timestamp('started_at', { withTimezone: true })\n .notNull()\n .defaultNow(),\n completedAt: timestamp('completed_at', { withTimezone: true }),\n /** Runtime-enforced when `INTEGRATION_MULTI_TENANT` is true; see SYNC-6. */\n tenantId: text('tenant_id'),\n },\n (t) => ({\n /** Timeline read: \"most recent runs for this subscription\". */\n idxIntegrationRunsSubscriptionStartedAt: index(\n 'idx_integration_runs_subscription_started_at',\n ).on(t.subscriptionId, t.startedAt),\n /** Stale-run sweeper: \"runs that started > N minutes ago and are still running\". */\n idxIntegrationRunsStatusStartedAt: index('idx_integration_runs_status_started_at').on(\n t.status,\n t.startedAt,\n ),\n }),\n);\n\nexport type IntegrationRunRow = InferSelectModel<typeof integrationRuns>;\n\n// ─── integration_run_items ─────────────────────────────────────────────────────────\n\n/**\n * One row per upstream change processed within a run. Captures the canonical\n * decision the orchestrator made (`operation` + `status`), the structured\n * per-field diff (`changed_fields`, ADR-0003), and the local row id\n * (`local_id`) for drill-down joins.\n *\n * `changed_fields` is validated at the recorder layer via `FieldDiffSchema`\n * (SYNC-2) — the $type<FieldDiff> annotation here only documents the shape\n * for Drizzle consumers. The runtime enforcement is non-negotiable: downstream\n * drift-detection queries rely on the `{from, to}` shape per field.\n *\n * `title` is an optional human-readable label captured at write time (e.g.\n * `\"Pinnacle opportunity\"`) so run-log UIs don't need to re-hydrate the\n * canonical record.\n */\nexport const integrationRunItems = pgTable(\n 'integration_run_items',\n {\n id: uuid('id').primaryKey().defaultRandom(),\n integrationRunId: uuid('integration_run_id')\n .notNull()\n .references(() => integrationRuns.id, { onDelete: 'cascade' }),\n entityType: text('entity_type').notNull(),\n externalId: text('external_id').notNull(),\n localId: text('local_id'),\n operation: integrationRunItemOperationEnum('operation').notNull(),\n status: integrationRunItemStatusEnum('status').notNull(),\n /**\n * Structured per-field diff — ADR-0003 shape enforced by\n * `FieldDiffSchema.parse` at the recorder service layer.\n *\n * Shape: `{ [fieldName]: { from: unknown, to: unknown } }`.\n * Empty `{}` for `noop` items; `{ [field]: { from: null, to: <value> } }`\n * for created items; `{ [field]: { from: <value>, to: null } }` for\n * deleted items.\n */\n changedFields: jsonb('changed_fields').notNull().default({}).$type<FieldDiff>(),\n title: text('title'),\n error: text('error'),\n createdAt: timestamp('created_at', { withTimezone: true })\n .notNull()\n .defaultNow(),\n /** Runtime-enforced when `INTEGRATION_MULTI_TENANT` is true; see SYNC-6. */\n tenantId: text('tenant_id'),\n },\n (t) => ({\n /** Ordered timeline within a run. */\n idxIntegrationRunItemsRunCreatedAt: index('idx_integration_run_items_run_created_at').on(\n t.integrationRunId,\n t.createdAt,\n ),\n /** Per-record history: \"every integration that touched opportunity/$extId\". */\n idxIntegrationRunItemsEntityExternal: index(\n 'idx_integration_run_items_entity_external',\n ).on(t.entityType, t.externalId),\n }),\n);\n\nexport type IntegrationRunItemRow = InferSelectModel<typeof integrationRunItems>;\n"],"mappings":"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","names":[]}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Integration subsystem — change-source protocol (port)
|
|
3
3
|
*
|
|
4
|
-
* `IChangeSource<T>` is the hexagonal port every
|
|
5
|
-
* Use cases inject this interface via `
|
|
4
|
+
* `IChangeSource<T>` is the hexagonal port every integration adapter implements.
|
|
5
|
+
* Use cases inject this interface via `INTEGRATION_CHANGE_SOURCE` token. They never
|
|
6
6
|
* depend on a specific backend implementation.
|
|
7
7
|
*
|
|
8
8
|
* Three detection modes (poll / cdc / webhook) converge on this single port
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
* upstream ADR-008 subsystem architecture.
|
|
22
22
|
*/
|
|
23
23
|
/**
|
|
24
|
-
* Provenance of a change record. Maps 1:1 to `
|
|
24
|
+
* Provenance of a change record. Maps 1:1 to `integration_runs.action` so run logs
|
|
25
25
|
* self-identify.
|
|
26
26
|
*/
|
|
27
27
|
type ChangeSource = 'poll' | 'cdc' | 'webhook';
|
|
@@ -54,13 +54,13 @@ interface Change<T> {
|
|
|
54
54
|
readonly providerChangedFields?: string[];
|
|
55
55
|
}
|
|
56
56
|
/**
|
|
57
|
-
* Minimal structural view of a
|
|
57
|
+
* Minimal structural view of a integration-subscription row the port needs.
|
|
58
58
|
*
|
|
59
|
-
* The consumer owns the concrete `
|
|
59
|
+
* The consumer owns the concrete `integration_subscriptions` table (schema lands in
|
|
60
60
|
* SYNC-1). This interface captures only the fields the port itself reads, so
|
|
61
61
|
* adapters can be typed without depending on the consumer's ORM row type.
|
|
62
62
|
*/
|
|
63
|
-
interface
|
|
63
|
+
interface IntegrationSubscriptionView {
|
|
64
64
|
/** Primary key — addresses the cursor in `ICursorStore`. */
|
|
65
65
|
readonly id: string;
|
|
66
66
|
/** Canonical entity domain, e.g. `'opportunity'`, `'contact'`. */
|
|
@@ -69,7 +69,7 @@ interface SyncSubscriptionView {
|
|
|
69
69
|
readonly externalRef?: string | null;
|
|
70
70
|
}
|
|
71
71
|
/**
|
|
72
|
-
* The one port every
|
|
72
|
+
* The one port every integration adapter implements. Mode-specific concerns
|
|
73
73
|
* (scheduling, rate-limiting, ack contracts, credential refresh) stay in the
|
|
74
74
|
* strategy class that implements this interface — this seam is deliberately
|
|
75
75
|
* minimal.
|
|
@@ -90,7 +90,7 @@ interface IChangeSource<T> {
|
|
|
90
90
|
* `cursor` is opaque at this seam — the primitive's cursor strategy types
|
|
91
91
|
* it internally. `null` means "first run, no cursor yet."
|
|
92
92
|
*/
|
|
93
|
-
listChanges(subscription:
|
|
93
|
+
listChanges(subscription: IntegrationSubscriptionView, cursor: unknown | null): AsyncIterable<Change<T>>;
|
|
94
94
|
}
|
|
95
95
|
|
|
96
|
-
export type { Change, ChangeSource, IChangeSource,
|
|
96
|
+
export type { Change, ChangeSource, IChangeSource, IntegrationSubscriptionView };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
//# sourceMappingURL=integration-change-source.protocol.js.map
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { DrizzleClient } from '../../types/drizzle.js';
|
|
2
|
-
import { ICursorStore, CursorSnapshot } from './
|
|
2
|
+
import { ICursorStore, CursorSnapshot } from './integration-cursor-store.protocol.js';
|
|
3
3
|
import 'drizzle-orm/node-postgres';
|
|
4
4
|
|
|
5
5
|
declare class PostgresCursorStore implements ICursorStore {
|
|
@@ -10,14 +10,14 @@ var __decorateClass = (decorators, target, key, kind) => {
|
|
|
10
10
|
};
|
|
11
11
|
var __decorateParam = (index2, decorator) => (target, key) => decorator(target, key, index2);
|
|
12
12
|
|
|
13
|
-
// runtime/subsystems/
|
|
13
|
+
// runtime/subsystems/integration/integration-cursor-store.drizzle-backend.ts
|
|
14
14
|
import { Inject, Injectable, Optional } from "@nestjs/common";
|
|
15
15
|
import { and, desc, eq } from "drizzle-orm";
|
|
16
16
|
|
|
17
17
|
// runtime/constants/tokens.ts
|
|
18
18
|
var DRIZZLE = "DRIZZLE";
|
|
19
19
|
|
|
20
|
-
// runtime/subsystems/
|
|
20
|
+
// runtime/subsystems/integration/integration-audit.schema.ts
|
|
21
21
|
import {
|
|
22
22
|
pgEnum,
|
|
23
23
|
pgTable,
|
|
@@ -30,39 +30,39 @@ import {
|
|
|
30
30
|
index,
|
|
31
31
|
uniqueIndex
|
|
32
32
|
} from "drizzle-orm/pg-core";
|
|
33
|
-
var
|
|
33
|
+
var integrationRunDirectionEnum = pgEnum("integration_run_direction", [
|
|
34
34
|
"inbound",
|
|
35
35
|
"outbound"
|
|
36
36
|
]);
|
|
37
|
-
var
|
|
37
|
+
var integrationRunActionEnum = pgEnum("integration_run_action", [
|
|
38
38
|
"poll",
|
|
39
39
|
"cdc",
|
|
40
40
|
"webhook",
|
|
41
41
|
"manual",
|
|
42
42
|
"writeback"
|
|
43
43
|
]);
|
|
44
|
-
var
|
|
44
|
+
var integrationRunStatusEnum = pgEnum("integration_run_status", [
|
|
45
45
|
"running",
|
|
46
46
|
"success",
|
|
47
47
|
"no_changes",
|
|
48
48
|
"failed"
|
|
49
49
|
]);
|
|
50
|
-
var
|
|
50
|
+
var integrationRunItemOperationEnum = pgEnum("integration_run_item_operation", [
|
|
51
51
|
"created",
|
|
52
52
|
"updated",
|
|
53
53
|
"deleted",
|
|
54
54
|
"noop"
|
|
55
55
|
]);
|
|
56
|
-
var
|
|
56
|
+
var integrationRunItemStatusEnum = pgEnum("integration_run_item_status", [
|
|
57
57
|
"success",
|
|
58
58
|
"failed",
|
|
59
59
|
"skipped"
|
|
60
60
|
]);
|
|
61
|
-
var
|
|
62
|
-
"
|
|
61
|
+
var integrationSubscriptions = pgTable(
|
|
62
|
+
"integration_subscriptions",
|
|
63
63
|
{
|
|
64
64
|
id: uuid("id").primaryKey().defaultRandom(),
|
|
65
|
-
|
|
65
|
+
connectionId: text("connection_id").notNull(),
|
|
66
66
|
adapter: text("adapter").notNull(),
|
|
67
67
|
domain: text("domain").notNull(),
|
|
68
68
|
externalRef: text("external_ref"),
|
|
@@ -77,8 +77,8 @@ var syncSubscriptions = pgTable(
|
|
|
77
77
|
* successful run advances it.
|
|
78
78
|
*/
|
|
79
79
|
cursor: jsonb("cursor").$type(),
|
|
80
|
-
|
|
81
|
-
/** Runtime-enforced when `
|
|
80
|
+
lastIntegrationAt: timestamp("last_integration_at", { withTimezone: true }),
|
|
81
|
+
/** Runtime-enforced when `INTEGRATION_MULTI_TENANT` is true; see SYNC-6. */
|
|
82
82
|
tenantId: text("tenant_id"),
|
|
83
83
|
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
|
|
84
84
|
updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow()
|
|
@@ -87,31 +87,31 @@ var syncSubscriptions = pgTable(
|
|
|
87
87
|
/**
|
|
88
88
|
* Composite uniqueness per the epic shape. `external_ref` is nullable;
|
|
89
89
|
* Postgres treats NULLs as distinct in a UNIQUE constraint, which means
|
|
90
|
-
* two rows with the same `(
|
|
90
|
+
* two rows with the same `(connection_id, adapter, domain)` and NULL
|
|
91
91
|
* external_ref are allowed. That's intentional — a subscription with
|
|
92
92
|
* NULL external_ref covers the full domain, and duplicates there would
|
|
93
93
|
* be a consumer-layer modeling issue, not a schema concern.
|
|
94
94
|
*/
|
|
95
|
-
|
|
96
|
-
t.
|
|
95
|
+
uqIntegrationSubscriptionTuple: uniqueIndex("uq_integration_subscriptions_tuple").on(
|
|
96
|
+
t.connectionId,
|
|
97
97
|
t.adapter,
|
|
98
98
|
t.domain,
|
|
99
99
|
t.externalRef
|
|
100
100
|
),
|
|
101
101
|
/** Scheduling query: list enabled subscriptions ordered by staleness. */
|
|
102
|
-
|
|
103
|
-
"
|
|
104
|
-
).on(t.enabled, t.
|
|
102
|
+
idxIntegrationSubscriptionsEnabledLastIntegration: index(
|
|
103
|
+
"idx_integration_subscriptions_enabled_last_integration"
|
|
104
|
+
).on(t.enabled, t.lastIntegrationAt)
|
|
105
105
|
})
|
|
106
106
|
);
|
|
107
|
-
var
|
|
108
|
-
"
|
|
107
|
+
var integrationRuns = pgTable(
|
|
108
|
+
"integration_runs",
|
|
109
109
|
{
|
|
110
110
|
id: uuid("id").primaryKey().defaultRandom(),
|
|
111
|
-
subscriptionId: uuid("subscription_id").notNull().references(() =>
|
|
112
|
-
direction:
|
|
113
|
-
action:
|
|
114
|
-
status:
|
|
111
|
+
subscriptionId: uuid("subscription_id").notNull().references(() => integrationSubscriptions.id, { onDelete: "cascade" }),
|
|
112
|
+
direction: integrationRunDirectionEnum("direction").notNull(),
|
|
113
|
+
action: integrationRunActionEnum("action").notNull(),
|
|
114
|
+
status: integrationRunStatusEnum("status").notNull().default("running"),
|
|
115
115
|
recordsFound: integer("records_found").notNull().default(0),
|
|
116
116
|
recordsProcessed: integer("records_processed").notNull().default(0),
|
|
117
117
|
cursorBefore: jsonb("cursor_before").$type(),
|
|
@@ -120,31 +120,31 @@ var syncRuns = pgTable(
|
|
|
120
120
|
error: text("error"),
|
|
121
121
|
startedAt: timestamp("started_at", { withTimezone: true }).notNull().defaultNow(),
|
|
122
122
|
completedAt: timestamp("completed_at", { withTimezone: true }),
|
|
123
|
-
/** Runtime-enforced when `
|
|
123
|
+
/** Runtime-enforced when `INTEGRATION_MULTI_TENANT` is true; see SYNC-6. */
|
|
124
124
|
tenantId: text("tenant_id")
|
|
125
125
|
},
|
|
126
126
|
(t) => ({
|
|
127
127
|
/** Timeline read: "most recent runs for this subscription". */
|
|
128
|
-
|
|
129
|
-
"
|
|
128
|
+
idxIntegrationRunsSubscriptionStartedAt: index(
|
|
129
|
+
"idx_integration_runs_subscription_started_at"
|
|
130
130
|
).on(t.subscriptionId, t.startedAt),
|
|
131
131
|
/** Stale-run sweeper: "runs that started > N minutes ago and are still running". */
|
|
132
|
-
|
|
132
|
+
idxIntegrationRunsStatusStartedAt: index("idx_integration_runs_status_started_at").on(
|
|
133
133
|
t.status,
|
|
134
134
|
t.startedAt
|
|
135
135
|
)
|
|
136
136
|
})
|
|
137
137
|
);
|
|
138
|
-
var
|
|
139
|
-
"
|
|
138
|
+
var integrationRunItems = pgTable(
|
|
139
|
+
"integration_run_items",
|
|
140
140
|
{
|
|
141
141
|
id: uuid("id").primaryKey().defaultRandom(),
|
|
142
|
-
|
|
142
|
+
integrationRunId: uuid("integration_run_id").notNull().references(() => integrationRuns.id, { onDelete: "cascade" }),
|
|
143
143
|
entityType: text("entity_type").notNull(),
|
|
144
144
|
externalId: text("external_id").notNull(),
|
|
145
145
|
localId: text("local_id"),
|
|
146
|
-
operation:
|
|
147
|
-
status:
|
|
146
|
+
operation: integrationRunItemOperationEnum("operation").notNull(),
|
|
147
|
+
status: integrationRunItemStatusEnum("status").notNull(),
|
|
148
148
|
/**
|
|
149
149
|
* Structured per-field diff — ADR-0003 shape enforced by
|
|
150
150
|
* `FieldDiffSchema.parse` at the recorder service layer.
|
|
@@ -158,31 +158,31 @@ var syncRunItems = pgTable(
|
|
|
158
158
|
title: text("title"),
|
|
159
159
|
error: text("error"),
|
|
160
160
|
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
|
|
161
|
-
/** Runtime-enforced when `
|
|
161
|
+
/** Runtime-enforced when `INTEGRATION_MULTI_TENANT` is true; see SYNC-6. */
|
|
162
162
|
tenantId: text("tenant_id")
|
|
163
163
|
},
|
|
164
164
|
(t) => ({
|
|
165
165
|
/** Ordered timeline within a run. */
|
|
166
|
-
|
|
167
|
-
t.
|
|
166
|
+
idxIntegrationRunItemsRunCreatedAt: index("idx_integration_run_items_run_created_at").on(
|
|
167
|
+
t.integrationRunId,
|
|
168
168
|
t.createdAt
|
|
169
169
|
),
|
|
170
|
-
/** Per-record history: "every
|
|
171
|
-
|
|
172
|
-
"
|
|
170
|
+
/** Per-record history: "every integration that touched opportunity/$extId". */
|
|
171
|
+
idxIntegrationRunItemsEntityExternal: index(
|
|
172
|
+
"idx_integration_run_items_entity_external"
|
|
173
173
|
).on(t.entityType, t.externalId)
|
|
174
174
|
})
|
|
175
175
|
);
|
|
176
176
|
|
|
177
|
-
// runtime/subsystems/
|
|
178
|
-
var
|
|
177
|
+
// runtime/subsystems/integration/integration.tokens.ts
|
|
178
|
+
var INTEGRATION_MULTI_TENANT = "INTEGRATION_MULTI_TENANT";
|
|
179
179
|
|
|
180
|
-
// runtime/subsystems/
|
|
180
|
+
// runtime/subsystems/integration/integration-errors.ts
|
|
181
181
|
var MissingTenantIdError = class extends Error {
|
|
182
182
|
name = "MissingTenantIdError";
|
|
183
183
|
constructor(operation) {
|
|
184
184
|
super(
|
|
185
|
-
`Missing tenantId for
|
|
185
|
+
`Missing tenantId for integration operation '${operation}'. IntegrationModule is configured with multiTenant: true \u2014 every call must include a non-null tenantId. Either pass the tenantId or disable multi-tenancy on the module.`
|
|
186
186
|
);
|
|
187
187
|
}
|
|
188
188
|
};
|
|
@@ -193,7 +193,7 @@ function assertTenantId(tenantId, options) {
|
|
|
193
193
|
}
|
|
194
194
|
}
|
|
195
195
|
|
|
196
|
-
// runtime/subsystems/
|
|
196
|
+
// runtime/subsystems/integration/integration-cursor-store.drizzle-backend.ts
|
|
197
197
|
var PostgresCursorStore = class {
|
|
198
198
|
constructor(db, multiTenant) {
|
|
199
199
|
this.db = db;
|
|
@@ -203,15 +203,15 @@ var PostgresCursorStore = class {
|
|
|
203
203
|
multiTenant;
|
|
204
204
|
async get(subscriptionId, tenantId) {
|
|
205
205
|
const where = this.buildWhere(subscriptionId, tenantId, "cursor.get");
|
|
206
|
-
const rows = await this.db.select({ cursor:
|
|
206
|
+
const rows = await this.db.select({ cursor: integrationSubscriptions.cursor }).from(integrationSubscriptions).where(where).limit(1);
|
|
207
207
|
if (rows.length === 0) return null;
|
|
208
208
|
return rows[0]?.cursor ?? null;
|
|
209
209
|
}
|
|
210
210
|
async put(subscriptionId, cursor, tenantId) {
|
|
211
211
|
const where = this.buildWhere(subscriptionId, tenantId, "cursor.put");
|
|
212
|
-
await this.db.update(
|
|
212
|
+
await this.db.update(integrationSubscriptions).set({
|
|
213
213
|
cursor,
|
|
214
|
-
|
|
214
|
+
lastIntegrationAt: /* @__PURE__ */ new Date(),
|
|
215
215
|
updatedAt: /* @__PURE__ */ new Date()
|
|
216
216
|
}).where(where);
|
|
217
217
|
}
|
|
@@ -220,26 +220,26 @@ var PostgresCursorStore = class {
|
|
|
220
220
|
multiTenant: this.multiTenant,
|
|
221
221
|
operation: "cursor.listAll"
|
|
222
222
|
});
|
|
223
|
-
const where = this.multiTenant ? eq(
|
|
223
|
+
const where = this.multiTenant ? eq(integrationSubscriptions.tenantId, tenantId) : void 0;
|
|
224
224
|
const rows = await this.db.select({
|
|
225
|
-
id:
|
|
226
|
-
|
|
227
|
-
adapter:
|
|
228
|
-
domain:
|
|
229
|
-
externalRef:
|
|
230
|
-
cursor:
|
|
231
|
-
|
|
232
|
-
updatedAt:
|
|
233
|
-
tenantId:
|
|
234
|
-
}).from(
|
|
225
|
+
id: integrationSubscriptions.id,
|
|
226
|
+
connectionId: integrationSubscriptions.connectionId,
|
|
227
|
+
adapter: integrationSubscriptions.adapter,
|
|
228
|
+
domain: integrationSubscriptions.domain,
|
|
229
|
+
externalRef: integrationSubscriptions.externalRef,
|
|
230
|
+
cursor: integrationSubscriptions.cursor,
|
|
231
|
+
lastIntegrationAt: integrationSubscriptions.lastIntegrationAt,
|
|
232
|
+
updatedAt: integrationSubscriptions.updatedAt,
|
|
233
|
+
tenantId: integrationSubscriptions.tenantId
|
|
234
|
+
}).from(integrationSubscriptions).where(where).orderBy(desc(integrationSubscriptions.updatedAt));
|
|
235
235
|
return rows.map((row) => ({
|
|
236
236
|
subscriptionId: row.id,
|
|
237
|
-
|
|
237
|
+
connectionId: row.connectionId,
|
|
238
238
|
adapter: row.adapter,
|
|
239
239
|
domain: row.domain,
|
|
240
240
|
externalRef: row.externalRef,
|
|
241
241
|
cursor: row.cursor ?? null,
|
|
242
|
-
|
|
242
|
+
lastIntegrationAt: row.lastIntegrationAt,
|
|
243
243
|
updatedAt: row.updatedAt,
|
|
244
244
|
tenantId: row.tenantId
|
|
245
245
|
}));
|
|
@@ -256,20 +256,20 @@ var PostgresCursorStore = class {
|
|
|
256
256
|
});
|
|
257
257
|
if (this.multiTenant) {
|
|
258
258
|
return and(
|
|
259
|
-
eq(
|
|
260
|
-
eq(
|
|
259
|
+
eq(integrationSubscriptions.id, subscriptionId),
|
|
260
|
+
eq(integrationSubscriptions.tenantId, tenantId)
|
|
261
261
|
);
|
|
262
262
|
}
|
|
263
|
-
return eq(
|
|
263
|
+
return eq(integrationSubscriptions.id, subscriptionId);
|
|
264
264
|
}
|
|
265
265
|
};
|
|
266
266
|
PostgresCursorStore = __decorateClass([
|
|
267
267
|
Injectable(),
|
|
268
268
|
__decorateParam(0, Inject(DRIZZLE)),
|
|
269
269
|
__decorateParam(1, Optional()),
|
|
270
|
-
__decorateParam(1, Inject(
|
|
270
|
+
__decorateParam(1, Inject(INTEGRATION_MULTI_TENANT))
|
|
271
271
|
], PostgresCursorStore);
|
|
272
272
|
export {
|
|
273
273
|
PostgresCursorStore
|
|
274
274
|
};
|
|
275
|
-
//# sourceMappingURL=
|
|
275
|
+
//# sourceMappingURL=integration-cursor-store.drizzle-backend.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../runtime/subsystems/integration/integration-cursor-store.drizzle-backend.ts","../../../../runtime/constants/tokens.ts","../../../../runtime/subsystems/integration/integration-audit.schema.ts","../../../../runtime/subsystems/integration/integration.tokens.ts","../../../../runtime/subsystems/integration/integration-errors.ts"],"sourcesContent":["/**\n * PostgresCursorStore — Drizzle-backed `ICursorStore` (SYNC-4).\n *\n * Reads/writes `integration_subscriptions.cursor` directly — no service\n * composition. Consumers that want a service layer around subscriptions\n * wire it themselves; the port's contract is just cursor persistence.\n *\n * ## What `put` stamps\n *\n * `put` writes three columns in one statement: `cursor`, `last_integration_at`,\n * and `updated_at`. Rationale: SYNC-1's scheduling index\n * `(enabled, last_integration_at)` is useless if `last_integration_at` doesn't advance\n * with every cursor put. Every real consumer needs this stamped, so\n * bundling it here avoids every consumer wrapping the port in a service\n * layer just to stamp a timestamp.\n *\n * ## Multi-tenancy\n *\n * When `INTEGRATION_MULTI_TENANT` is true (SYNC-6):\n * - every read/write is scoped by `AND tenant_id = $tenantId`\n * - a null/missing `tenantId` throws `MissingTenantIdError` via the\n * shared `assertTenantId` helper (one message shape across the\n * orchestrator + both backends, SYNC-6)\n * - explicit `null` also throws — matches JOB-8 / EVT-6 strict-enforcement\n *\n * When the flag is off, `tenantId` is ignored. Cross-tenant isolation is\n * the caller's problem in single-tenant deployments.\n */\nimport { Inject, Injectable, Optional } from '@nestjs/common';\nimport { and, desc, eq, type SQL } from 'drizzle-orm';\nimport type { DrizzleClient } from '../../types/drizzle';\nimport { DRIZZLE } from '../../constants/tokens';\nimport type {\n CursorSnapshot,\n ICursorStore,\n} from './integration-cursor-store.protocol';\nimport { integrationSubscriptions } from './integration-audit.schema';\nimport { INTEGRATION_MULTI_TENANT } from './integration.tokens';\nimport { assertTenantId } from './integration-errors';\n\n@Injectable()\nexport class PostgresCursorStore implements ICursorStore {\n private readonly multiTenant: boolean;\n\n constructor(\n @Inject(DRIZZLE) private readonly db: DrizzleClient,\n @Optional() @Inject(INTEGRATION_MULTI_TENANT) multiTenant?: boolean,\n ) {\n this.multiTenant = multiTenant ?? false;\n }\n\n async get(\n subscriptionId: string,\n tenantId?: string | null,\n ): Promise<unknown | null> {\n const where = this.buildWhere(subscriptionId, tenantId, 'cursor.get');\n\n const rows = await this.db\n .select({ cursor: integrationSubscriptions.cursor })\n .from(integrationSubscriptions)\n .where(where)\n .limit(1);\n\n if (rows.length === 0) return null;\n return rows[0]?.cursor ?? null;\n }\n\n async put(\n subscriptionId: string,\n cursor: unknown,\n tenantId?: string | null,\n ): Promise<void> {\n const where = this.buildWhere(subscriptionId, tenantId, 'cursor.put');\n\n await this.db\n .update(integrationSubscriptions)\n .set({\n cursor,\n lastIntegrationAt: new Date(),\n updatedAt: new Date(),\n })\n .where(where);\n }\n\n async listAll(tenantId?: string | null): Promise<CursorSnapshot[]> {\n assertTenantId(tenantId, {\n multiTenant: this.multiTenant,\n operation: 'cursor.listAll',\n });\n\n const where = this.multiTenant\n ? eq(integrationSubscriptions.tenantId, tenantId as string)\n : undefined;\n\n const rows = await this.db\n .select({\n id: integrationSubscriptions.id,\n connectionId: integrationSubscriptions.connectionId,\n adapter: integrationSubscriptions.adapter,\n domain: integrationSubscriptions.domain,\n externalRef: integrationSubscriptions.externalRef,\n cursor: integrationSubscriptions.cursor,\n lastIntegrationAt: integrationSubscriptions.lastIntegrationAt,\n updatedAt: integrationSubscriptions.updatedAt,\n tenantId: integrationSubscriptions.tenantId,\n })\n .from(integrationSubscriptions)\n .where(where)\n .orderBy(desc(integrationSubscriptions.updatedAt));\n\n return rows.map((row) => ({\n subscriptionId: row.id,\n connectionId: row.connectionId,\n adapter: row.adapter,\n domain: row.domain,\n externalRef: row.externalRef,\n cursor: row.cursor ?? null,\n lastIntegrationAt: row.lastIntegrationAt,\n updatedAt: row.updatedAt,\n tenantId: row.tenantId,\n }));\n }\n\n /**\n * Centralized WHERE clause — `get` and `put` share identical semantics.\n * Drift here would let a caller read under one tenancy rule and write\n * under another.\n */\n private buildWhere(\n subscriptionId: string,\n tenantId: string | null | undefined,\n operation: string,\n ): SQL | undefined {\n assertTenantId(tenantId, {\n multiTenant: this.multiTenant,\n operation,\n });\n if (this.multiTenant) {\n return and(\n eq(integrationSubscriptions.id, subscriptionId),\n eq(integrationSubscriptions.tenantId, tenantId as string),\n );\n }\n return eq(integrationSubscriptions.id, subscriptionId);\n }\n}\n","/**\n * NestJS injection tokens\n *\n * Used with @Inject() decorator in concrete repository constructors.\n */\n\n/**\n * Injection token for the Drizzle ORM database client.\n *\n * Usage in concrete repositories:\n * ```typescript\n * constructor(@Inject(DRIZZLE) db: DrizzleClient) { super(db); }\n * ```\n */\nexport const DRIZZLE = 'DRIZZLE' as const;\n\n/**\n * Injection token for the event bus (IEventBus).\n *\n * Optional — only resolved when EventsModule.forRoot() is registered.\n * BaseService uses this with @Optional() to emit lifecycle events\n * without requiring the events subsystem to be installed.\n *\n * Usage in services/use cases:\n * ```typescript\n * @Optional() @Inject(EVENT_BUS) eventBus?: IEventBus\n * ```\n */\nexport const EVENT_BUS = 'EVENT_BUS' as const;\n","/**\n * Drizzle schema for the integration subsystem audit/observability tables (SYNC-1).\n *\n * Three tables model end-to-end integration observability, keyed by the single port\n * every integration adapter implements (`IChangeSource<T>` from SYNC-2):\n *\n * - `integration_subscriptions` — owns the cursor per\n * `(connection_id, adapter, domain, external_ref)` tuple. Addressed\n * by id by `ICursorStore` (SYNC-3/SYNC-4).\n * - `integration_runs` — per-run audit log: start/complete, status,\n * cursor before/after, counts, direction (inbound|outbound),\n * action (poll|cdc|webhook|manual|writeback).\n * - `integration_run_items` — per-record change log with structured\n * `changed_fields` jsonb enforced by the Zod `FieldDiffSchema`\n * contract (ADR-0003; protocol lives in SYNC-2's\n * integration-field-diff.protocol.ts).\n *\n * Design calls (vs. issue #126 open questions):\n *\n * - `integration_subscriptions` ships in the subsystem (not consumer-owned).\n * Rationale: SYNC-4's `PostgresCursorStore` needs to read/write this\n * table directly; making it consumer-owned would require consumers to\n * hand-wire a shape the backend already knows. The row is addressable\n * by id and scoped by the uniqueness tuple; consumers can still\n * query/list it freely. Same stance as `job_run` being subsystem-\n * owned while remaining consumer-queryable.\n *\n * - `tenant_id` is always emitted on the three tables as nullable text.\n * The `INTEGRATION_MULTI_TENANT` DI flag (SYNC-6) is what enforces the\n * non-null + cross-tenant-isolation contract at the service/orchestrator\n * boundary. This mirrors JOB-1/JOB-8's final shape — runtime guard, not\n * a scaffold-time conditional column. Keeps the schema file uniform\n * across single-tenant and multi-tenant deployments.\n *\n * - `changed_fields` on `integration_run_items` is typed via the Zod-inferred\n * `FieldDiff` shape from SYNC-2 (`{ [fieldName]: { from, to } }`). The\n * recorder service (SYNC-5) validates every write against\n * `FieldDiffSchema.parse` so consumers can rely on the shape.\n */\nimport {\n pgEnum,\n pgTable,\n uuid,\n text,\n jsonb,\n integer,\n boolean,\n timestamp,\n index,\n uniqueIndex,\n} from 'drizzle-orm/pg-core';\nimport type { InferSelectModel } from 'drizzle-orm';\n\nimport type { FieldDiff } from './integration-field-diff.protocol';\n\n// ─── Enums ──────────────────────────────────────────────────────────────────\n\n/**\n * Direction of a integration run relative to local state.\n *\n * - `inbound` — external → local (the common case: SFDC poll → local DB).\n * - `outbound` — local → external (writeback; deferred per epic but the\n * column shape is reserved so future writeback runs share the audit log).\n */\nexport const integrationRunDirectionEnum = pgEnum('integration_run_direction', [\n 'inbound',\n 'outbound',\n]);\n\n/**\n * How the run detected upstream changes. Maps 1:1 to the `Change.source`\n * provenance on inbound runs; `manual` captures operator-triggered re-integrations\n * and `writeback` captures outbound runs.\n */\nexport const integrationRunActionEnum = pgEnum('integration_run_action', [\n 'poll',\n 'cdc',\n 'webhook',\n 'manual',\n 'writeback',\n]);\n\n/**\n * Lifecycle status of a integration run.\n *\n * - `running` — in-flight; recorder has started but not completed.\n * - `success` — completed with at least one change processed.\n * - `no_changes` — completed cleanly, no upstream changes found.\n * - `failed` — errored before completion; `error` column carries the\n * message. `records_processed` may be non-zero (partial progress).\n */\nexport const integrationRunStatusEnum = pgEnum('integration_run_status', [\n 'running',\n 'success',\n 'no_changes',\n 'failed',\n]);\n\n/**\n * Operation applied per record. Mirrors `Change<T>.operation` from SYNC-2,\n * plus the recorder's own `'noop'` for changes that matched existing state.\n */\nexport const integrationRunItemOperationEnum = pgEnum('integration_run_item_operation', [\n 'created',\n 'updated',\n 'deleted',\n 'noop',\n]);\n\n/**\n * Per-record status within a run. `skipped` captures loopback-detected echoes\n * of the local system's own writes (see `ILoopbackFingerprintStore` in the\n * epic), which record the external_id but intentionally do not touch local\n * state.\n */\nexport const integrationRunItemStatusEnum = pgEnum('integration_run_item_status', [\n 'success',\n 'failed',\n 'skipped',\n]);\n\n// ─── integration_subscriptions ─────────────────────────────────────────────────────\n\n/**\n * One cursor owner per (integration, adapter, domain, external_ref).\n *\n * - `connection_id` — opaque id of the connected account/instance. E.g.\n * the SFDC org id for polling strategies, the GitHub installation id\n * for webhook strategies.\n * - `adapter` — short adapter label, e.g. `'salesforce'`, `'hubspot'`.\n * - `domain` — canonical entity domain this subscription tracks,\n * e.g. `'opportunity'`, `'contact'`.\n * - `external_ref` — optional upstream scope (e.g. a filter id, a\n * webhook subscription id). NULL when the subscription covers the\n * entire domain.\n *\n * The cursor shape is opaque jsonb — strategies type it internally (poll:\n * `{ systemModstamp }`, cdc: `{ replayId }`, webhook: `{ ts }`). Overwritten\n * by `ICursorStore.put(id, cursor)`.\n */\nexport const integrationSubscriptions = pgTable(\n 'integration_subscriptions',\n {\n id: uuid('id').primaryKey().defaultRandom(),\n connectionId: text('connection_id').notNull(),\n adapter: text('adapter').notNull(),\n domain: text('domain').notNull(),\n externalRef: text('external_ref'),\n enabled: boolean('enabled').notNull().default(true),\n /**\n * Per-subscription configuration bag. Strategies type it internally;\n * e.g. polling strategies stash `{ batchSize, highWatermark }` here.\n */\n config: jsonb('config').notNull().default({}).$type<Record<string, unknown>>(),\n /**\n * Opaque cursor persisted by `ICursorStore.put()`. NULL until the first\n * successful run advances it.\n */\n cursor: jsonb('cursor').$type<unknown>(),\n lastIntegrationAt: timestamp('last_integration_at', { withTimezone: true }),\n /** Runtime-enforced when `INTEGRATION_MULTI_TENANT` is true; see SYNC-6. */\n tenantId: text('tenant_id'),\n createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),\n updatedAt: timestamp('updated_at', { withTimezone: true }).notNull().defaultNow(),\n },\n (t) => ({\n /**\n * Composite uniqueness per the epic shape. `external_ref` is nullable;\n * Postgres treats NULLs as distinct in a UNIQUE constraint, which means\n * two rows with the same `(connection_id, adapter, domain)` and NULL\n * external_ref are allowed. That's intentional — a subscription with\n * NULL external_ref covers the full domain, and duplicates there would\n * be a consumer-layer modeling issue, not a schema concern.\n */\n uqIntegrationSubscriptionTuple: uniqueIndex('uq_integration_subscriptions_tuple').on(\n t.connectionId,\n t.adapter,\n t.domain,\n t.externalRef,\n ),\n /** Scheduling query: list enabled subscriptions ordered by staleness. */\n idxIntegrationSubscriptionsEnabledLastIntegration: index(\n 'idx_integration_subscriptions_enabled_last_integration',\n ).on(t.enabled, t.lastIntegrationAt),\n }),\n);\n\nexport type IntegrationSubscriptionRow = InferSelectModel<typeof integrationSubscriptions>;\n\n// ─── integration_runs ──────────────────────────────────────────────────────────────\n\n/**\n * One row per invocation of `ExecuteIntegrationUseCase`. `started_at` is set when\n * the recorder opens the run; `completed_at`, `status`, `records_*`,\n * `cursor_after`, and `duration_ms` are filled on completion.\n *\n * `cursor_before` / `cursor_after` carry the opaque cursor snapshots so the\n * run log is fully self-describing — given a run id, an operator can reason\n * about exactly what window was scanned without cross-referencing another\n * table.\n */\nexport const integrationRuns = pgTable(\n 'integration_runs',\n {\n id: uuid('id').primaryKey().defaultRandom(),\n subscriptionId: uuid('subscription_id')\n .notNull()\n .references(() => integrationSubscriptions.id, { onDelete: 'cascade' }),\n direction: integrationRunDirectionEnum('direction').notNull(),\n action: integrationRunActionEnum('action').notNull(),\n status: integrationRunStatusEnum('status').notNull().default('running'),\n recordsFound: integer('records_found').notNull().default(0),\n recordsProcessed: integer('records_processed').notNull().default(0),\n cursorBefore: jsonb('cursor_before').$type<unknown>(),\n cursorAfter: jsonb('cursor_after').$type<unknown>(),\n durationMs: integer('duration_ms'),\n error: text('error'),\n startedAt: timestamp('started_at', { withTimezone: true })\n .notNull()\n .defaultNow(),\n completedAt: timestamp('completed_at', { withTimezone: true }),\n /** Runtime-enforced when `INTEGRATION_MULTI_TENANT` is true; see SYNC-6. */\n tenantId: text('tenant_id'),\n },\n (t) => ({\n /** Timeline read: \"most recent runs for this subscription\". */\n idxIntegrationRunsSubscriptionStartedAt: index(\n 'idx_integration_runs_subscription_started_at',\n ).on(t.subscriptionId, t.startedAt),\n /** Stale-run sweeper: \"runs that started > N minutes ago and are still running\". */\n idxIntegrationRunsStatusStartedAt: index('idx_integration_runs_status_started_at').on(\n t.status,\n t.startedAt,\n ),\n }),\n);\n\nexport type IntegrationRunRow = InferSelectModel<typeof integrationRuns>;\n\n// ─── integration_run_items ─────────────────────────────────────────────────────────\n\n/**\n * One row per upstream change processed within a run. Captures the canonical\n * decision the orchestrator made (`operation` + `status`), the structured\n * per-field diff (`changed_fields`, ADR-0003), and the local row id\n * (`local_id`) for drill-down joins.\n *\n * `changed_fields` is validated at the recorder layer via `FieldDiffSchema`\n * (SYNC-2) — the $type<FieldDiff> annotation here only documents the shape\n * for Drizzle consumers. The runtime enforcement is non-negotiable: downstream\n * drift-detection queries rely on the `{from, to}` shape per field.\n *\n * `title` is an optional human-readable label captured at write time (e.g.\n * `\"Pinnacle opportunity\"`) so run-log UIs don't need to re-hydrate the\n * canonical record.\n */\nexport const integrationRunItems = pgTable(\n 'integration_run_items',\n {\n id: uuid('id').primaryKey().defaultRandom(),\n integrationRunId: uuid('integration_run_id')\n .notNull()\n .references(() => integrationRuns.id, { onDelete: 'cascade' }),\n entityType: text('entity_type').notNull(),\n externalId: text('external_id').notNull(),\n localId: text('local_id'),\n operation: integrationRunItemOperationEnum('operation').notNull(),\n status: integrationRunItemStatusEnum('status').notNull(),\n /**\n * Structured per-field diff — ADR-0003 shape enforced by\n * `FieldDiffSchema.parse` at the recorder service layer.\n *\n * Shape: `{ [fieldName]: { from: unknown, to: unknown } }`.\n * Empty `{}` for `noop` items; `{ [field]: { from: null, to: <value> } }`\n * for created items; `{ [field]: { from: <value>, to: null } }` for\n * deleted items.\n */\n changedFields: jsonb('changed_fields').notNull().default({}).$type<FieldDiff>(),\n title: text('title'),\n error: text('error'),\n createdAt: timestamp('created_at', { withTimezone: true })\n .notNull()\n .defaultNow(),\n /** Runtime-enforced when `INTEGRATION_MULTI_TENANT` is true; see SYNC-6. */\n tenantId: text('tenant_id'),\n },\n (t) => ({\n /** Ordered timeline within a run. */\n idxIntegrationRunItemsRunCreatedAt: index('idx_integration_run_items_run_created_at').on(\n t.integrationRunId,\n t.createdAt,\n ),\n /** Per-record history: \"every integration that touched opportunity/$extId\". */\n idxIntegrationRunItemsEntityExternal: index(\n 'idx_integration_run_items_entity_external',\n ).on(t.entityType, t.externalId),\n }),\n);\n\nexport type IntegrationRunItemRow = InferSelectModel<typeof integrationRunItems>;\n","/**\n * Integration subsystem — DI tokens\n *\n * String constants (not Symbols) so they match by value across import\n * boundaries — same convention as the events subsystem (`EVENT_BUS`). The\n * jobs subsystem uses Symbols for its analogous tokens; events and integration\n * stay internally consistent with strings.\n *\n * Usage in use cases:\n * ```ts\n * constructor(\n * @Inject(INTEGRATION_CHANGE_SOURCE) private readonly source: IChangeSource<CanonicalOpportunity>,\n * @Inject(INTEGRATION_CURSOR_STORE) private readonly cursors: ICursorStore,\n * @Inject(INTEGRATION_FIELD_DIFFER) private readonly differ: IFieldDiffer<CanonicalOpportunity>,\n * @Inject(INTEGRATION_SINK) private readonly sink: IIntegrationSink<CanonicalOpportunity>,\n * @Inject(INTEGRATION_RUN_RECORDER) private readonly recorder: IIntegrationRunRecorder,\n * ) {}\n * ```\n *\n * Concrete bindings are registered by `IntegrationModule.forRoot(...)` (SYNC-6).\n */\n\nexport const INTEGRATION_CHANGE_SOURCE = 'INTEGRATION_CHANGE_SOURCE' as const;\nexport const INTEGRATION_CURSOR_STORE = 'INTEGRATION_CURSOR_STORE' as const;\nexport const INTEGRATION_FIELD_DIFFER = 'INTEGRATION_FIELD_DIFFER' as const;\nexport const INTEGRATION_SINK = 'INTEGRATION_SINK' as const;\n\n/**\n * Run-recorder token (SYNC-5). Backed by `IIntegrationRunRecorder`. Drizzle impl\n * lands in SYNC-4; tests provide inline fakes.\n */\nexport const INTEGRATION_RUN_RECORDER = 'INTEGRATION_RUN_RECORDER' as const;\n\n/**\n * Injection token for the resolved `IntegrationModuleOptions` object (SYNC-6).\n *\n * Backends that need to observe module configuration (e.g. `multiTenant`\n * flag, pool filters) inject via this token. Provided automatically by\n * `IntegrationModule.forRoot(...)` / `IntegrationModule.forRootAsync(...)`.\n */\nexport const INTEGRATION_MODULE_OPTIONS = 'INTEGRATION_MODULE_OPTIONS' as const;\n\n/**\n * Injection token for the resolved multi-tenancy flag (SYNC-6).\n *\n * Provided by `IntegrationModule.forRoot(...)` as `options.multiTenant ?? false`.\n * Consumed by `ExecuteIntegrationUseCase` to enforce the tenantId-is-required rule.\n */\nexport const INTEGRATION_MULTI_TENANT = 'INTEGRATION_MULTI_TENANT' as const;\n\n/**\n * Injection token for the entity-keyed `IEntityChangeSourceRegistry` (C7,\n * #336). Bound to the codegen-emitted aggregator that folds per-provider\n * adapter contributions into one registry (RFC-0001 §3, emitted by Track D\n * D3/D4).\n *\n * A string constant, not `Symbol.for(...)`, to match this subsystem's token\n * convention (see file header). The originating issue's code block proposed a\n * `Symbol.for('@pattern-stack/codegen.entity-change-source-registry')` key,\n * predating the sync→integration consolidation onto string tokens; kept as a\n * string here for internal consistency with the other INTEGRATION_* tokens.\n */\nexport const ENTITY_CHANGE_SOURCE_REGISTRY = 'ENTITY_CHANGE_SOURCE_REGISTRY' as const;\n","/**\n * Typed errors + shared boundary helpers for the integration subsystem.\n *\n * Classes (not bare Error) so consumers can `instanceof` them in catch\n * blocks and exception filters can map them to HTTP codes.\n *\n * Mirrors the shape of `events-errors.ts` and `jobs-errors.ts`.\n */\n\n/**\n * Thrown by the Drizzle cursor-store / run-recorder backends AND by the\n * orchestrator entry point when `INTEGRATION_MULTI_TENANT` is enabled but the\n * caller did not supply a non-null `tenantId`. Strict enforcement at the\n * boundary — explicit `null` still throws.\n *\n * Disable multi-tenancy on the module (`multiTenant: false`, the default)\n * to opt out of the requirement entirely.\n *\n * `operation` identifies the call site (e.g. `'cursor.put'`,\n * `'startRun'`, `'execute'`) so the stack-trace message points at the\n * specific boundary that rejected the input.\n */\nexport class MissingTenantIdError extends Error {\n override readonly name = 'MissingTenantIdError';\n constructor(operation: string) {\n super(\n `Missing tenantId for integration operation '${operation}'. IntegrationModule is ` +\n `configured with multiTenant: true — every call must include a ` +\n `non-null tenantId. Either pass the tenantId or disable multi-` +\n `tenancy on the module.`,\n );\n }\n}\n\n/**\n * Shared boundary guard — used at the orchestrator entry AND inside the\n * Drizzle backends. Keeping the check in one function guarantees every\n * `MissingTenantIdError` carries the same message shape regardless of the\n * site that raised it, which makes it easier for consumers to pattern-\n * match on the error in logs/metrics.\n *\n * When `multiTenant` is false, the function is a no-op — `tenantId` may\n * be anything (including `undefined`). When true, `undefined` or `null`\n * throws.\n */\nexport function assertTenantId(\n tenantId: string | null | undefined,\n options: { multiTenant: boolean; operation: string },\n): asserts tenantId is string {\n if (!options.multiTenant) return;\n if (tenantId === undefined || tenantId === null) {\n throw new MissingTenantIdError(options.operation);\n }\n}\n"],"mappings":"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","names":[]}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { ICursorStore, CursorSnapshot } from './
|
|
2
|
-
import {
|
|
3
|
-
import './
|
|
4
|
-
import './
|
|
1
|
+
import { ICursorStore, CursorSnapshot } from './integration-cursor-store.protocol.js';
|
|
2
|
+
import { MemoryIntegrationSubscription } from './integration-run-recorder.memory-backend.js';
|
|
3
|
+
import './integration-run-recorder.protocol.js';
|
|
4
|
+
import './integration-field-diff.protocol.js';
|
|
5
5
|
import 'zod';
|
|
6
6
|
|
|
7
7
|
declare class MemoryCursorStore implements ICursorStore {
|
|
@@ -13,13 +13,13 @@ declare class MemoryCursorStore implements ICursorStore {
|
|
|
13
13
|
/**
|
|
14
14
|
* Seedable subscription metadata for `listAll` — the memory backend
|
|
15
15
|
* stores only `subscriptionId → cursor` in its write path, so the
|
|
16
|
-
* snapshot shape (`
|
|
16
|
+
* snapshot shape (`connectionId`, `adapter`, `domain`, `externalRef`,
|
|
17
17
|
* timestamps) has no natural source without test seeding. Tests populate
|
|
18
18
|
* this map; unseeded entries get empty-string metadata and `new Date(0)`
|
|
19
19
|
* timestamps so the shape stays stable. Production paths go through the
|
|
20
20
|
* Drizzle backend.
|
|
21
21
|
*/
|
|
22
|
-
readonly subscriptions: Map<string,
|
|
22
|
+
readonly subscriptions: Map<string, MemoryIntegrationSubscription>;
|
|
23
23
|
get(subscriptionId: string, _tenantId?: string | null): Promise<unknown | null>;
|
|
24
24
|
put(subscriptionId: string, cursor: unknown, _tenantId?: string | null): Promise<void>;
|
|
25
25
|
listAll(_tenantId?: string | null): Promise<CursorSnapshot[]>;
|
|
@@ -9,7 +9,7 @@ var __decorateClass = (decorators, target, key, kind) => {
|
|
|
9
9
|
return result;
|
|
10
10
|
};
|
|
11
11
|
|
|
12
|
-
// runtime/subsystems/
|
|
12
|
+
// runtime/subsystems/integration/integration-cursor-store.memory-backend.ts
|
|
13
13
|
import { Injectable } from "@nestjs/common";
|
|
14
14
|
var MemoryCursorStore = class {
|
|
15
15
|
/**
|
|
@@ -20,7 +20,7 @@ var MemoryCursorStore = class {
|
|
|
20
20
|
/**
|
|
21
21
|
* Seedable subscription metadata for `listAll` — the memory backend
|
|
22
22
|
* stores only `subscriptionId → cursor` in its write path, so the
|
|
23
|
-
* snapshot shape (`
|
|
23
|
+
* snapshot shape (`connectionId`, `adapter`, `domain`, `externalRef`,
|
|
24
24
|
* timestamps) has no natural source without test seeding. Tests populate
|
|
25
25
|
* this map; unseeded entries get empty-string metadata and `new Date(0)`
|
|
26
26
|
* timestamps so the shape stays stable. Production paths go through the
|
|
@@ -40,12 +40,12 @@ var MemoryCursorStore = class {
|
|
|
40
40
|
const meta = this.subscriptions.get(subscriptionId);
|
|
41
41
|
snapshots.push({
|
|
42
42
|
subscriptionId,
|
|
43
|
-
|
|
43
|
+
connectionId: meta?.connectionId ?? "",
|
|
44
44
|
adapter: meta?.adapter ?? "",
|
|
45
45
|
domain: meta?.domain ?? "",
|
|
46
46
|
externalRef: meta?.externalRef ?? null,
|
|
47
47
|
cursor: cursor ?? null,
|
|
48
|
-
|
|
48
|
+
lastIntegrationAt: meta?.lastIntegrationAt ?? null,
|
|
49
49
|
updatedAt: meta?.updatedAt ?? /* @__PURE__ */ new Date(0),
|
|
50
50
|
tenantId: null
|
|
51
51
|
});
|
|
@@ -66,4 +66,4 @@ MemoryCursorStore = __decorateClass([
|
|
|
66
66
|
export {
|
|
67
67
|
MemoryCursorStore
|
|
68
68
|
};
|
|
69
|
-
//# sourceMappingURL=
|
|
69
|
+
//# sourceMappingURL=integration-cursor-store.memory-backend.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../runtime/subsystems/integration/integration-cursor-store.memory-backend.ts"],"sourcesContent":["/**\n * MemoryCursorStore — in-memory backend for `ICursorStore` (SYNC-3).\n *\n * Test double that lets consumers exercise `ExecuteIntegrationUseCase` (SYNC-5) and\n * other cursor-consuming code paths without Postgres. Mirrors the role of\n * `MemoryEventBus` and `MemoryJobStore`: plain keyed state, tests take a\n * direct reference for `beforeEach` resets.\n *\n * Cursor values are stored by reference — the port's `get`/`put` contract\n * treats them as opaque `unknown`. Callers that want durable value-equality\n * semantics should snapshot via JSON before `put` and reparse after `get`;\n * this is what the Drizzle backend (SYNC-4) does implicitly via jsonb\n * round-trip. The memory backend intentionally does not simulate the\n * serialize/deserialize cycle — consumers who care should test against\n * Postgres.\n *\n * ## Multi-tenancy\n *\n * `tenantId` is accepted but ignored. The memory backend's state is\n * process-local — there's no durable storage where a cross-tenant leak\n * could occur. Tests that want to assert per-tenant isolation should\n * target the Drizzle backend.\n *\n * Not shipped in the upstream consumer; this is a subsystem-first addition for the\n * test surface. Consumed by:\n * - SYNC-5 unit tests (`ExecuteIntegrationUseCase` against synthetic sources)\n * - SYNC-6 module tests (`IntegrationModule.forRoot({ backend: 'memory' })`)\n */\nimport { Injectable } from '@nestjs/common';\nimport type {\n CursorSnapshot,\n ICursorStore,\n} from './integration-cursor-store.protocol';\nimport type { MemoryIntegrationSubscription } from './integration-run-recorder.memory-backend';\n\n@Injectable()\nexport class MemoryCursorStore implements ICursorStore {\n /**\n * Subscription-id → last persisted cursor. Public so tests can inspect\n * or pre-seed state; production callers MUST go through `get`/`put`.\n */\n readonly cursors: Map<string, unknown> = new Map();\n\n /**\n * Seedable subscription metadata for `listAll` — the memory backend\n * stores only `subscriptionId → cursor` in its write path, so the\n * snapshot shape (`connectionId`, `adapter`, `domain`, `externalRef`,\n * timestamps) has no natural source without test seeding. Tests populate\n * this map; unseeded entries get empty-string metadata and `new Date(0)`\n * timestamps so the shape stays stable. Production paths go through the\n * Drizzle backend.\n */\n readonly subscriptions: Map<string, MemoryIntegrationSubscription> = new Map();\n\n async get(\n subscriptionId: string,\n _tenantId?: string | null,\n ): Promise<unknown | null> {\n // `Map.get` returns `undefined` for missing keys; the port contract\n // returns `null`. Normalize here so callers can `=== null`-check.\n const value = this.cursors.get(subscriptionId);\n return value === undefined ? null : value;\n }\n\n async put(\n subscriptionId: string,\n cursor: unknown,\n _tenantId?: string | null,\n ): Promise<void> {\n // Overwrite semantics — matches the port contract and the Drizzle\n // backend's `ON CONFLICT DO UPDATE` behavior.\n this.cursors.set(subscriptionId, cursor);\n }\n\n async listAll(_tenantId?: string | null): Promise<CursorSnapshot[]> {\n // Accepts tenantId for contract symmetry but does not filter on it —\n // the memory backend never enforces tenancy (see class-level comment).\n const snapshots: CursorSnapshot[] = [];\n for (const [subscriptionId, cursor] of this.cursors.entries()) {\n const meta = this.subscriptions.get(subscriptionId);\n snapshots.push({\n subscriptionId,\n connectionId: meta?.connectionId ?? '',\n adapter: meta?.adapter ?? '',\n domain: meta?.domain ?? '',\n externalRef: meta?.externalRef ?? null,\n cursor: cursor ?? null,\n lastIntegrationAt: meta?.lastIntegrationAt ?? null,\n updatedAt: meta?.updatedAt ?? new Date(0),\n tenantId: null,\n });\n }\n return snapshots.sort(\n (a, b) => b.updatedAt.getTime() - a.updatedAt.getTime(),\n );\n }\n\n /** Reset state. Tests call this in `beforeEach`. */\n clear(): void {\n this.cursors.clear();\n this.subscriptions.clear();\n }\n}\n"],"mappings":";;;;;;;;;;;;AA4BA,SAAS,kBAAkB;AAQpB,IAAM,oBAAN,MAAgD;AAAA;AAAA;AAAA;AAAA;AAAA,EAK5C,UAAgC,oBAAI,IAAI;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAWxC,gBAA4D,oBAAI,IAAI;AAAA,EAE7E,MAAM,IACJ,gBACA,WACyB;AAGzB,UAAM,QAAQ,KAAK,QAAQ,IAAI,cAAc;AAC7C,WAAO,UAAU,SAAY,OAAO;AAAA,EACtC;AAAA,EAEA,MAAM,IACJ,gBACA,QACA,WACe;AAGf,SAAK,QAAQ,IAAI,gBAAgB,MAAM;AAAA,EACzC;AAAA,EAEA,MAAM,QAAQ,WAAsD;AAGlE,UAAM,YAA8B,CAAC;AACrC,eAAW,CAAC,gBAAgB,MAAM,KAAK,KAAK,QAAQ,QAAQ,GAAG;AAC7D,YAAM,OAAO,KAAK,cAAc,IAAI,cAAc;AAClD,gBAAU,KAAK;AAAA,QACb;AAAA,QACA,cAAc,MAAM,gBAAgB;AAAA,QACpC,SAAS,MAAM,WAAW;AAAA,QAC1B,QAAQ,MAAM,UAAU;AAAA,QACxB,aAAa,MAAM,eAAe;AAAA,QAClC,QAAQ,UAAU;AAAA,QAClB,mBAAmB,MAAM,qBAAqB;AAAA,QAC9C,WAAW,MAAM,aAAa,oBAAI,KAAK,CAAC;AAAA,QACxC,UAAU;AAAA,MACZ,CAAC;AAAA,IACH;AACA,WAAO,UAAU;AAAA,MACf,CAAC,GAAG,MAAM,EAAE,UAAU,QAAQ,IAAI,EAAE,UAAU,QAAQ;AAAA,IACxD;AAAA,EACF;AAAA;AAAA,EAGA,QAAc;AACZ,SAAK,QAAQ,MAAM;AACnB,SAAK,cAAc,MAAM;AAAA,EAC3B;AACF;AAlEa,oBAAN;AAAA,EADN,WAAW;AAAA,GACC;","names":[]}
|