@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
package/dist/src/cli/index.js
CHANGED
|
@@ -12,8 +12,8 @@ var __decorateClass = (decorators, target, key, kind) => {
|
|
|
12
12
|
var __decorateParam = (index2, decorator) => (target, key) => decorator(target, key, index2);
|
|
13
13
|
|
|
14
14
|
// src/cli/index.ts
|
|
15
|
-
import { readFileSync as
|
|
16
|
-
import { join as
|
|
15
|
+
import { readFileSync as readFileSync9 } from "fs";
|
|
16
|
+
import { join as join13 } from "path";
|
|
17
17
|
import { Builtins, Cli, Command as Command13 } from "clipanion";
|
|
18
18
|
|
|
19
19
|
// src/cli/noun-module.ts
|
|
@@ -1081,7 +1081,7 @@ import { parse as parseYaml } from "yaml";
|
|
|
1081
1081
|
// src/schema/entity-definition.schema.ts
|
|
1082
1082
|
import { z as z3 } from "zod";
|
|
1083
1083
|
|
|
1084
|
-
// runtime/subsystems/
|
|
1084
|
+
// runtime/subsystems/integration/integration-field-diff.protocol.ts
|
|
1085
1085
|
import { z } from "zod";
|
|
1086
1086
|
var FieldDiffValueSchema = z.object({
|
|
1087
1087
|
from: z.unknown(),
|
|
@@ -1089,7 +1089,7 @@ var FieldDiffValueSchema = z.object({
|
|
|
1089
1089
|
});
|
|
1090
1090
|
var FieldDiffSchema = z.record(z.string(), FieldDiffValueSchema);
|
|
1091
1091
|
|
|
1092
|
-
// runtime/subsystems/
|
|
1092
|
+
// runtime/subsystems/integration/detection-config.schema.ts
|
|
1093
1093
|
import { z as z2 } from "zod";
|
|
1094
1094
|
var FieldMappingSchema = z2.object({
|
|
1095
1095
|
source: z2.string().min(1),
|
|
@@ -1147,21 +1147,21 @@ var DetectionConfigSchema = z2.discriminatedUnion("mode", [
|
|
|
1147
1147
|
WebhookModeSchema
|
|
1148
1148
|
]);
|
|
1149
1149
|
|
|
1150
|
-
// runtime/subsystems/
|
|
1151
|
-
var
|
|
1152
|
-
var
|
|
1153
|
-
var
|
|
1154
|
-
var
|
|
1155
|
-
var
|
|
1156
|
-
var
|
|
1157
|
-
var
|
|
1150
|
+
// runtime/subsystems/integration/integration.tokens.ts
|
|
1151
|
+
var INTEGRATION_CHANGE_SOURCE = "INTEGRATION_CHANGE_SOURCE";
|
|
1152
|
+
var INTEGRATION_CURSOR_STORE = "INTEGRATION_CURSOR_STORE";
|
|
1153
|
+
var INTEGRATION_FIELD_DIFFER = "INTEGRATION_FIELD_DIFFER";
|
|
1154
|
+
var INTEGRATION_SINK = "INTEGRATION_SINK";
|
|
1155
|
+
var INTEGRATION_RUN_RECORDER = "INTEGRATION_RUN_RECORDER";
|
|
1156
|
+
var INTEGRATION_MODULE_OPTIONS = "INTEGRATION_MODULE_OPTIONS";
|
|
1157
|
+
var INTEGRATION_MULTI_TENANT = "INTEGRATION_MULTI_TENANT";
|
|
1158
1158
|
|
|
1159
|
-
// runtime/subsystems/
|
|
1159
|
+
// runtime/subsystems/integration/integration-errors.ts
|
|
1160
1160
|
var MissingTenantIdError = class extends Error {
|
|
1161
1161
|
name = "MissingTenantIdError";
|
|
1162
1162
|
constructor(operation) {
|
|
1163
1163
|
super(
|
|
1164
|
-
`Missing tenantId for
|
|
1164
|
+
`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.`
|
|
1165
1165
|
);
|
|
1166
1166
|
}
|
|
1167
1167
|
};
|
|
@@ -1172,7 +1172,7 @@ function assertTenantId(tenantId, options) {
|
|
|
1172
1172
|
}
|
|
1173
1173
|
}
|
|
1174
1174
|
|
|
1175
|
-
// runtime/subsystems/
|
|
1175
|
+
// runtime/subsystems/integration/integration-audit.schema.ts
|
|
1176
1176
|
import {
|
|
1177
1177
|
pgEnum,
|
|
1178
1178
|
pgTable,
|
|
@@ -1185,39 +1185,39 @@ import {
|
|
|
1185
1185
|
index,
|
|
1186
1186
|
uniqueIndex
|
|
1187
1187
|
} from "drizzle-orm/pg-core";
|
|
1188
|
-
var
|
|
1188
|
+
var integrationRunDirectionEnum = pgEnum("integration_run_direction", [
|
|
1189
1189
|
"inbound",
|
|
1190
1190
|
"outbound"
|
|
1191
1191
|
]);
|
|
1192
|
-
var
|
|
1192
|
+
var integrationRunActionEnum = pgEnum("integration_run_action", [
|
|
1193
1193
|
"poll",
|
|
1194
1194
|
"cdc",
|
|
1195
1195
|
"webhook",
|
|
1196
1196
|
"manual",
|
|
1197
1197
|
"writeback"
|
|
1198
1198
|
]);
|
|
1199
|
-
var
|
|
1199
|
+
var integrationRunStatusEnum = pgEnum("integration_run_status", [
|
|
1200
1200
|
"running",
|
|
1201
1201
|
"success",
|
|
1202
1202
|
"no_changes",
|
|
1203
1203
|
"failed"
|
|
1204
1204
|
]);
|
|
1205
|
-
var
|
|
1205
|
+
var integrationRunItemOperationEnum = pgEnum("integration_run_item_operation", [
|
|
1206
1206
|
"created",
|
|
1207
1207
|
"updated",
|
|
1208
1208
|
"deleted",
|
|
1209
1209
|
"noop"
|
|
1210
1210
|
]);
|
|
1211
|
-
var
|
|
1211
|
+
var integrationRunItemStatusEnum = pgEnum("integration_run_item_status", [
|
|
1212
1212
|
"success",
|
|
1213
1213
|
"failed",
|
|
1214
1214
|
"skipped"
|
|
1215
1215
|
]);
|
|
1216
|
-
var
|
|
1217
|
-
"
|
|
1216
|
+
var integrationSubscriptions = pgTable(
|
|
1217
|
+
"integration_subscriptions",
|
|
1218
1218
|
{
|
|
1219
1219
|
id: uuid("id").primaryKey().defaultRandom(),
|
|
1220
|
-
|
|
1220
|
+
connectionId: text("connection_id").notNull(),
|
|
1221
1221
|
adapter: text("adapter").notNull(),
|
|
1222
1222
|
domain: text("domain").notNull(),
|
|
1223
1223
|
externalRef: text("external_ref"),
|
|
@@ -1232,8 +1232,8 @@ var syncSubscriptions = pgTable(
|
|
|
1232
1232
|
* successful run advances it.
|
|
1233
1233
|
*/
|
|
1234
1234
|
cursor: jsonb("cursor").$type(),
|
|
1235
|
-
|
|
1236
|
-
/** Runtime-enforced when `
|
|
1235
|
+
lastIntegrationAt: timestamp("last_integration_at", { withTimezone: true }),
|
|
1236
|
+
/** Runtime-enforced when `INTEGRATION_MULTI_TENANT` is true; see SYNC-6. */
|
|
1237
1237
|
tenantId: text("tenant_id"),
|
|
1238
1238
|
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
|
|
1239
1239
|
updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow()
|
|
@@ -1242,31 +1242,31 @@ var syncSubscriptions = pgTable(
|
|
|
1242
1242
|
/**
|
|
1243
1243
|
* Composite uniqueness per the epic shape. `external_ref` is nullable;
|
|
1244
1244
|
* Postgres treats NULLs as distinct in a UNIQUE constraint, which means
|
|
1245
|
-
* two rows with the same `(
|
|
1245
|
+
* two rows with the same `(connection_id, adapter, domain)` and NULL
|
|
1246
1246
|
* external_ref are allowed. That's intentional — a subscription with
|
|
1247
1247
|
* NULL external_ref covers the full domain, and duplicates there would
|
|
1248
1248
|
* be a consumer-layer modeling issue, not a schema concern.
|
|
1249
1249
|
*/
|
|
1250
|
-
|
|
1251
|
-
t.
|
|
1250
|
+
uqIntegrationSubscriptionTuple: uniqueIndex("uq_integration_subscriptions_tuple").on(
|
|
1251
|
+
t.connectionId,
|
|
1252
1252
|
t.adapter,
|
|
1253
1253
|
t.domain,
|
|
1254
1254
|
t.externalRef
|
|
1255
1255
|
),
|
|
1256
1256
|
/** Scheduling query: list enabled subscriptions ordered by staleness. */
|
|
1257
|
-
|
|
1258
|
-
"
|
|
1259
|
-
).on(t.enabled, t.
|
|
1257
|
+
idxIntegrationSubscriptionsEnabledLastIntegration: index(
|
|
1258
|
+
"idx_integration_subscriptions_enabled_last_integration"
|
|
1259
|
+
).on(t.enabled, t.lastIntegrationAt)
|
|
1260
1260
|
})
|
|
1261
1261
|
);
|
|
1262
|
-
var
|
|
1263
|
-
"
|
|
1262
|
+
var integrationRuns = pgTable(
|
|
1263
|
+
"integration_runs",
|
|
1264
1264
|
{
|
|
1265
1265
|
id: uuid("id").primaryKey().defaultRandom(),
|
|
1266
|
-
subscriptionId: uuid("subscription_id").notNull().references(() =>
|
|
1267
|
-
direction:
|
|
1268
|
-
action:
|
|
1269
|
-
status:
|
|
1266
|
+
subscriptionId: uuid("subscription_id").notNull().references(() => integrationSubscriptions.id, { onDelete: "cascade" }),
|
|
1267
|
+
direction: integrationRunDirectionEnum("direction").notNull(),
|
|
1268
|
+
action: integrationRunActionEnum("action").notNull(),
|
|
1269
|
+
status: integrationRunStatusEnum("status").notNull().default("running"),
|
|
1270
1270
|
recordsFound: integer("records_found").notNull().default(0),
|
|
1271
1271
|
recordsProcessed: integer("records_processed").notNull().default(0),
|
|
1272
1272
|
cursorBefore: jsonb("cursor_before").$type(),
|
|
@@ -1275,31 +1275,31 @@ var syncRuns = pgTable(
|
|
|
1275
1275
|
error: text("error"),
|
|
1276
1276
|
startedAt: timestamp("started_at", { withTimezone: true }).notNull().defaultNow(),
|
|
1277
1277
|
completedAt: timestamp("completed_at", { withTimezone: true }),
|
|
1278
|
-
/** Runtime-enforced when `
|
|
1278
|
+
/** Runtime-enforced when `INTEGRATION_MULTI_TENANT` is true; see SYNC-6. */
|
|
1279
1279
|
tenantId: text("tenant_id")
|
|
1280
1280
|
},
|
|
1281
1281
|
(t) => ({
|
|
1282
1282
|
/** Timeline read: "most recent runs for this subscription". */
|
|
1283
|
-
|
|
1284
|
-
"
|
|
1283
|
+
idxIntegrationRunsSubscriptionStartedAt: index(
|
|
1284
|
+
"idx_integration_runs_subscription_started_at"
|
|
1285
1285
|
).on(t.subscriptionId, t.startedAt),
|
|
1286
1286
|
/** Stale-run sweeper: "runs that started > N minutes ago and are still running". */
|
|
1287
|
-
|
|
1287
|
+
idxIntegrationRunsStatusStartedAt: index("idx_integration_runs_status_started_at").on(
|
|
1288
1288
|
t.status,
|
|
1289
1289
|
t.startedAt
|
|
1290
1290
|
)
|
|
1291
1291
|
})
|
|
1292
1292
|
);
|
|
1293
|
-
var
|
|
1294
|
-
"
|
|
1293
|
+
var integrationRunItems = pgTable(
|
|
1294
|
+
"integration_run_items",
|
|
1295
1295
|
{
|
|
1296
1296
|
id: uuid("id").primaryKey().defaultRandom(),
|
|
1297
|
-
|
|
1297
|
+
integrationRunId: uuid("integration_run_id").notNull().references(() => integrationRuns.id, { onDelete: "cascade" }),
|
|
1298
1298
|
entityType: text("entity_type").notNull(),
|
|
1299
1299
|
externalId: text("external_id").notNull(),
|
|
1300
1300
|
localId: text("local_id"),
|
|
1301
|
-
operation:
|
|
1302
|
-
status:
|
|
1301
|
+
operation: integrationRunItemOperationEnum("operation").notNull(),
|
|
1302
|
+
status: integrationRunItemStatusEnum("status").notNull(),
|
|
1303
1303
|
/**
|
|
1304
1304
|
* Structured per-field diff — ADR-0003 shape enforced by
|
|
1305
1305
|
* `FieldDiffSchema.parse` at the recorder service layer.
|
|
@@ -1313,23 +1313,23 @@ var syncRunItems = pgTable(
|
|
|
1313
1313
|
title: text("title"),
|
|
1314
1314
|
error: text("error"),
|
|
1315
1315
|
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
|
|
1316
|
-
/** Runtime-enforced when `
|
|
1316
|
+
/** Runtime-enforced when `INTEGRATION_MULTI_TENANT` is true; see SYNC-6. */
|
|
1317
1317
|
tenantId: text("tenant_id")
|
|
1318
1318
|
},
|
|
1319
1319
|
(t) => ({
|
|
1320
1320
|
/** Ordered timeline within a run. */
|
|
1321
|
-
|
|
1322
|
-
t.
|
|
1321
|
+
idxIntegrationRunItemsRunCreatedAt: index("idx_integration_run_items_run_created_at").on(
|
|
1322
|
+
t.integrationRunId,
|
|
1323
1323
|
t.createdAt
|
|
1324
1324
|
),
|
|
1325
|
-
/** Per-record history: "every
|
|
1326
|
-
|
|
1327
|
-
"
|
|
1325
|
+
/** Per-record history: "every integration that touched opportunity/$extId". */
|
|
1326
|
+
idxIntegrationRunItemsEntityExternal: index(
|
|
1327
|
+
"idx_integration_run_items_entity_external"
|
|
1328
1328
|
).on(t.entityType, t.externalId)
|
|
1329
1329
|
})
|
|
1330
1330
|
);
|
|
1331
1331
|
|
|
1332
|
-
// runtime/subsystems/
|
|
1332
|
+
// runtime/subsystems/integration/integration-cursor-store.memory-backend.ts
|
|
1333
1333
|
import { Injectable } from "@nestjs/common";
|
|
1334
1334
|
var MemoryCursorStore = class {
|
|
1335
1335
|
/**
|
|
@@ -1340,7 +1340,7 @@ var MemoryCursorStore = class {
|
|
|
1340
1340
|
/**
|
|
1341
1341
|
* Seedable subscription metadata for `listAll` — the memory backend
|
|
1342
1342
|
* stores only `subscriptionId → cursor` in its write path, so the
|
|
1343
|
-
* snapshot shape (`
|
|
1343
|
+
* snapshot shape (`connectionId`, `adapter`, `domain`, `externalRef`,
|
|
1344
1344
|
* timestamps) has no natural source without test seeding. Tests populate
|
|
1345
1345
|
* this map; unseeded entries get empty-string metadata and `new Date(0)`
|
|
1346
1346
|
* timestamps so the shape stays stable. Production paths go through the
|
|
@@ -1360,12 +1360,12 @@ var MemoryCursorStore = class {
|
|
|
1360
1360
|
const meta = this.subscriptions.get(subscriptionId);
|
|
1361
1361
|
snapshots.push({
|
|
1362
1362
|
subscriptionId,
|
|
1363
|
-
|
|
1363
|
+
connectionId: meta?.connectionId ?? "",
|
|
1364
1364
|
adapter: meta?.adapter ?? "",
|
|
1365
1365
|
domain: meta?.domain ?? "",
|
|
1366
1366
|
externalRef: meta?.externalRef ?? null,
|
|
1367
1367
|
cursor: cursor ?? null,
|
|
1368
|
-
|
|
1368
|
+
lastIntegrationAt: meta?.lastIntegrationAt ?? null,
|
|
1369
1369
|
updatedAt: meta?.updatedAt ?? /* @__PURE__ */ new Date(0),
|
|
1370
1370
|
tenantId: null
|
|
1371
1371
|
});
|
|
@@ -1384,7 +1384,7 @@ MemoryCursorStore = __decorateClass([
|
|
|
1384
1384
|
Injectable()
|
|
1385
1385
|
], MemoryCursorStore);
|
|
1386
1386
|
|
|
1387
|
-
// runtime/subsystems/
|
|
1387
|
+
// runtime/subsystems/integration/integration-run-recorder.memory-backend.ts
|
|
1388
1388
|
import { Injectable as Injectable2 } from "@nestjs/common";
|
|
1389
1389
|
var MemoryRunRecorder = class {
|
|
1390
1390
|
/**
|
|
@@ -1393,14 +1393,14 @@ var MemoryRunRecorder = class {
|
|
|
1393
1393
|
*/
|
|
1394
1394
|
runs = /* @__PURE__ */ new Map();
|
|
1395
1395
|
/**
|
|
1396
|
-
* Items keyed by `
|
|
1397
|
-
* mirrors the timeline the `(
|
|
1396
|
+
* Items keyed by `integration_run_id`, array order matches insertion order —
|
|
1397
|
+
* mirrors the timeline the `(integration_run_id, created_at)` index produces
|
|
1398
1398
|
* in Postgres.
|
|
1399
1399
|
*/
|
|
1400
1400
|
items = /* @__PURE__ */ new Map();
|
|
1401
1401
|
/**
|
|
1402
1402
|
* Seedable subscription metadata — tests populate this to make
|
|
1403
|
-
* `listRecent` return meaningful `
|
|
1403
|
+
* `listRecent` return meaningful `connectionId` values. The memory
|
|
1404
1404
|
* backend doesn't track subscriptions on its own (only runs + items), so
|
|
1405
1405
|
* this map is the intentional extension point: tests write entries for
|
|
1406
1406
|
* the subscription ids they use, production code never touches it.
|
|
@@ -1429,10 +1429,10 @@ var MemoryRunRecorder = class {
|
|
|
1429
1429
|
}
|
|
1430
1430
|
async recordItem(input) {
|
|
1431
1431
|
FieldDiffSchema.parse(input.changedFields);
|
|
1432
|
-
const bucket = this.items.get(input.
|
|
1432
|
+
const bucket = this.items.get(input.integrationRunId);
|
|
1433
1433
|
if (!bucket) {
|
|
1434
1434
|
throw new Error(
|
|
1435
|
-
`MemoryRunRecorder.recordItem: no run started for id '${input.
|
|
1435
|
+
`MemoryRunRecorder.recordItem: no run started for id '${input.integrationRunId}'. Call startRun(...) first.`
|
|
1436
1436
|
);
|
|
1437
1437
|
}
|
|
1438
1438
|
bucket.push(input);
|
|
@@ -1458,10 +1458,10 @@ var MemoryRunRecorder = class {
|
|
|
1458
1458
|
return filtered.sort((a, b) => b.startedAt.getTime() - a.startedAt.getTime()).slice(0, limit).map((r) => ({
|
|
1459
1459
|
id: r.id,
|
|
1460
1460
|
subscriptionId: r.subscriptionId,
|
|
1461
|
-
//
|
|
1461
|
+
// connectionId is only knowable if the test seeded subscriptions
|
|
1462
1462
|
// metadata; empty string otherwise. The Drizzle backend resolves
|
|
1463
1463
|
// it via JOIN, which is the production path.
|
|
1464
|
-
|
|
1464
|
+
connectionId: this.subscriptions.get(r.subscriptionId)?.connectionId ?? "",
|
|
1465
1465
|
status: r.status,
|
|
1466
1466
|
startedAt: r.startedAt,
|
|
1467
1467
|
completedAt: r.completedAt,
|
|
@@ -1489,7 +1489,7 @@ MemoryRunRecorder = __decorateClass([
|
|
|
1489
1489
|
Injectable2()
|
|
1490
1490
|
], MemoryRunRecorder);
|
|
1491
1491
|
|
|
1492
|
-
// runtime/subsystems/
|
|
1492
|
+
// runtime/subsystems/integration/deep-equal.differ.ts
|
|
1493
1493
|
import { Injectable as Injectable3 } from "@nestjs/common";
|
|
1494
1494
|
var DEFAULT_IGNORE_FIELDS = /* @__PURE__ */ new Set([
|
|
1495
1495
|
"id",
|
|
@@ -1588,9 +1588,9 @@ function deepEqualObject(a, b) {
|
|
|
1588
1588
|
return true;
|
|
1589
1589
|
}
|
|
1590
1590
|
|
|
1591
|
-
// runtime/subsystems/
|
|
1591
|
+
// runtime/subsystems/integration/execute-integration.use-case.ts
|
|
1592
1592
|
import { Inject, Injectable as Injectable4, Logger, Optional } from "@nestjs/common";
|
|
1593
|
-
var
|
|
1593
|
+
var ExecuteIntegrationUseCase = class {
|
|
1594
1594
|
constructor(source, cursors, differ, sink, recorder, multiTenant = false) {
|
|
1595
1595
|
this.source = source;
|
|
1596
1596
|
this.cursors = cursors;
|
|
@@ -1605,7 +1605,7 @@ var ExecuteSyncUseCase = class {
|
|
|
1605
1605
|
sink;
|
|
1606
1606
|
recorder;
|
|
1607
1607
|
multiTenant;
|
|
1608
|
-
logger = new Logger(
|
|
1608
|
+
logger = new Logger(ExecuteIntegrationUseCase.name);
|
|
1609
1609
|
async execute(input) {
|
|
1610
1610
|
assertTenantId(input.tenantId, {
|
|
1611
1611
|
multiTenant: this.multiTenant,
|
|
@@ -1640,10 +1640,10 @@ var ExecuteSyncUseCase = class {
|
|
|
1640
1640
|
recordsFailed++;
|
|
1641
1641
|
const message = err instanceof Error ? err.message : String(err);
|
|
1642
1642
|
this.logger.warn(
|
|
1643
|
-
`
|
|
1643
|
+
`integration item failed: subscription=${input.subscription.id} externalId=${change.externalId}: ${message}`
|
|
1644
1644
|
);
|
|
1645
1645
|
await this.recorder.recordItem({
|
|
1646
|
-
|
|
1646
|
+
integrationRunId: runId,
|
|
1647
1647
|
entityType: input.subscription.domain,
|
|
1648
1648
|
externalId: change.externalId,
|
|
1649
1649
|
operation: change.operation === "deleted" ? "deleted" : "updated",
|
|
@@ -1666,7 +1666,7 @@ var ExecuteSyncUseCase = class {
|
|
|
1666
1666
|
status = "failed";
|
|
1667
1667
|
runError = err instanceof Error ? err.message : String(err);
|
|
1668
1668
|
this.logger.error(
|
|
1669
|
-
`
|
|
1669
|
+
`integration source failed: subscription=${input.subscription.id}: ${runError}`
|
|
1670
1670
|
);
|
|
1671
1671
|
}
|
|
1672
1672
|
if (cursorAdvanced && latestCursor !== null && latestCursor !== void 0) {
|
|
@@ -1711,7 +1711,7 @@ var ExecuteSyncUseCase = class {
|
|
|
1711
1711
|
change.externalId
|
|
1712
1712
|
);
|
|
1713
1713
|
await this.recorder.recordItem({
|
|
1714
|
-
|
|
1714
|
+
integrationRunId: runId,
|
|
1715
1715
|
entityType: input.subscription.domain,
|
|
1716
1716
|
externalId: change.externalId,
|
|
1717
1717
|
localId: result?.id ?? null,
|
|
@@ -1734,7 +1734,7 @@ var ExecuteSyncUseCase = class {
|
|
|
1734
1734
|
if (diff === "noop") {
|
|
1735
1735
|
if (!this.sink.reprojectsOnNoop) {
|
|
1736
1736
|
await this.recorder.recordItem({
|
|
1737
|
-
|
|
1737
|
+
integrationRunId: runId,
|
|
1738
1738
|
entityType: input.subscription.domain,
|
|
1739
1739
|
externalId: change.externalId,
|
|
1740
1740
|
localId: null,
|
|
@@ -1751,7 +1751,7 @@ var ExecuteSyncUseCase = class {
|
|
|
1751
1751
|
input.provider
|
|
1752
1752
|
);
|
|
1753
1753
|
await this.recorder.recordItem({
|
|
1754
|
-
|
|
1754
|
+
integrationRunId: runId,
|
|
1755
1755
|
entityType: input.subscription.domain,
|
|
1756
1756
|
externalId: change.externalId,
|
|
1757
1757
|
localId: noopLocalId,
|
|
@@ -1768,7 +1768,7 @@ var ExecuteSyncUseCase = class {
|
|
|
1768
1768
|
input.provider
|
|
1769
1769
|
);
|
|
1770
1770
|
await this.recorder.recordItem({
|
|
1771
|
-
|
|
1771
|
+
integrationRunId: runId,
|
|
1772
1772
|
entityType: input.subscription.domain,
|
|
1773
1773
|
externalId: change.externalId,
|
|
1774
1774
|
localId,
|
|
@@ -1779,25 +1779,25 @@ var ExecuteSyncUseCase = class {
|
|
|
1779
1779
|
});
|
|
1780
1780
|
}
|
|
1781
1781
|
};
|
|
1782
|
-
|
|
1782
|
+
ExecuteIntegrationUseCase = __decorateClass([
|
|
1783
1783
|
Injectable4(),
|
|
1784
|
-
__decorateParam(0, Inject(
|
|
1785
|
-
__decorateParam(1, Inject(
|
|
1786
|
-
__decorateParam(2, Inject(
|
|
1787
|
-
__decorateParam(3, Inject(
|
|
1788
|
-
__decorateParam(4, Inject(
|
|
1784
|
+
__decorateParam(0, Inject(INTEGRATION_CHANGE_SOURCE)),
|
|
1785
|
+
__decorateParam(1, Inject(INTEGRATION_CURSOR_STORE)),
|
|
1786
|
+
__decorateParam(2, Inject(INTEGRATION_FIELD_DIFFER)),
|
|
1787
|
+
__decorateParam(3, Inject(INTEGRATION_SINK)),
|
|
1788
|
+
__decorateParam(4, Inject(INTEGRATION_RUN_RECORDER)),
|
|
1789
1789
|
__decorateParam(5, Optional()),
|
|
1790
|
-
__decorateParam(5, Inject(
|
|
1791
|
-
],
|
|
1790
|
+
__decorateParam(5, Inject(INTEGRATION_MULTI_TENANT))
|
|
1791
|
+
], ExecuteIntegrationUseCase);
|
|
1792
1792
|
|
|
1793
|
-
// runtime/subsystems/
|
|
1793
|
+
// runtime/subsystems/integration/integration-cursor-store.drizzle-backend.ts
|
|
1794
1794
|
import { Inject as Inject2, Injectable as Injectable5, Optional as Optional2 } from "@nestjs/common";
|
|
1795
1795
|
import { and, desc, eq } from "drizzle-orm";
|
|
1796
1796
|
|
|
1797
1797
|
// runtime/constants/tokens.ts
|
|
1798
1798
|
var DRIZZLE = "DRIZZLE";
|
|
1799
1799
|
|
|
1800
|
-
// runtime/subsystems/
|
|
1800
|
+
// runtime/subsystems/integration/integration-cursor-store.drizzle-backend.ts
|
|
1801
1801
|
var PostgresCursorStore = class {
|
|
1802
1802
|
constructor(db, multiTenant) {
|
|
1803
1803
|
this.db = db;
|
|
@@ -1807,15 +1807,15 @@ var PostgresCursorStore = class {
|
|
|
1807
1807
|
multiTenant;
|
|
1808
1808
|
async get(subscriptionId, tenantId) {
|
|
1809
1809
|
const where = this.buildWhere(subscriptionId, tenantId, "cursor.get");
|
|
1810
|
-
const rows = await this.db.select({ cursor:
|
|
1810
|
+
const rows = await this.db.select({ cursor: integrationSubscriptions.cursor }).from(integrationSubscriptions).where(where).limit(1);
|
|
1811
1811
|
if (rows.length === 0) return null;
|
|
1812
1812
|
return rows[0]?.cursor ?? null;
|
|
1813
1813
|
}
|
|
1814
1814
|
async put(subscriptionId, cursor, tenantId) {
|
|
1815
1815
|
const where = this.buildWhere(subscriptionId, tenantId, "cursor.put");
|
|
1816
|
-
await this.db.update(
|
|
1816
|
+
await this.db.update(integrationSubscriptions).set({
|
|
1817
1817
|
cursor,
|
|
1818
|
-
|
|
1818
|
+
lastIntegrationAt: /* @__PURE__ */ new Date(),
|
|
1819
1819
|
updatedAt: /* @__PURE__ */ new Date()
|
|
1820
1820
|
}).where(where);
|
|
1821
1821
|
}
|
|
@@ -1824,26 +1824,26 @@ var PostgresCursorStore = class {
|
|
|
1824
1824
|
multiTenant: this.multiTenant,
|
|
1825
1825
|
operation: "cursor.listAll"
|
|
1826
1826
|
});
|
|
1827
|
-
const where = this.multiTenant ? eq(
|
|
1827
|
+
const where = this.multiTenant ? eq(integrationSubscriptions.tenantId, tenantId) : void 0;
|
|
1828
1828
|
const rows = await this.db.select({
|
|
1829
|
-
id:
|
|
1830
|
-
|
|
1831
|
-
adapter:
|
|
1832
|
-
domain:
|
|
1833
|
-
externalRef:
|
|
1834
|
-
cursor:
|
|
1835
|
-
|
|
1836
|
-
updatedAt:
|
|
1837
|
-
tenantId:
|
|
1838
|
-
}).from(
|
|
1829
|
+
id: integrationSubscriptions.id,
|
|
1830
|
+
connectionId: integrationSubscriptions.connectionId,
|
|
1831
|
+
adapter: integrationSubscriptions.adapter,
|
|
1832
|
+
domain: integrationSubscriptions.domain,
|
|
1833
|
+
externalRef: integrationSubscriptions.externalRef,
|
|
1834
|
+
cursor: integrationSubscriptions.cursor,
|
|
1835
|
+
lastIntegrationAt: integrationSubscriptions.lastIntegrationAt,
|
|
1836
|
+
updatedAt: integrationSubscriptions.updatedAt,
|
|
1837
|
+
tenantId: integrationSubscriptions.tenantId
|
|
1838
|
+
}).from(integrationSubscriptions).where(where).orderBy(desc(integrationSubscriptions.updatedAt));
|
|
1839
1839
|
return rows.map((row) => ({
|
|
1840
1840
|
subscriptionId: row.id,
|
|
1841
|
-
|
|
1841
|
+
connectionId: row.connectionId,
|
|
1842
1842
|
adapter: row.adapter,
|
|
1843
1843
|
domain: row.domain,
|
|
1844
1844
|
externalRef: row.externalRef,
|
|
1845
1845
|
cursor: row.cursor ?? null,
|
|
1846
|
-
|
|
1846
|
+
lastIntegrationAt: row.lastIntegrationAt,
|
|
1847
1847
|
updatedAt: row.updatedAt,
|
|
1848
1848
|
tenantId: row.tenantId
|
|
1849
1849
|
}));
|
|
@@ -1860,24 +1860,24 @@ var PostgresCursorStore = class {
|
|
|
1860
1860
|
});
|
|
1861
1861
|
if (this.multiTenant) {
|
|
1862
1862
|
return and(
|
|
1863
|
-
eq(
|
|
1864
|
-
eq(
|
|
1863
|
+
eq(integrationSubscriptions.id, subscriptionId),
|
|
1864
|
+
eq(integrationSubscriptions.tenantId, tenantId)
|
|
1865
1865
|
);
|
|
1866
1866
|
}
|
|
1867
|
-
return eq(
|
|
1867
|
+
return eq(integrationSubscriptions.id, subscriptionId);
|
|
1868
1868
|
}
|
|
1869
1869
|
};
|
|
1870
1870
|
PostgresCursorStore = __decorateClass([
|
|
1871
1871
|
Injectable5(),
|
|
1872
1872
|
__decorateParam(0, Inject2(DRIZZLE)),
|
|
1873
1873
|
__decorateParam(1, Optional2()),
|
|
1874
|
-
__decorateParam(1, Inject2(
|
|
1874
|
+
__decorateParam(1, Inject2(INTEGRATION_MULTI_TENANT))
|
|
1875
1875
|
], PostgresCursorStore);
|
|
1876
1876
|
|
|
1877
|
-
// runtime/subsystems/
|
|
1877
|
+
// runtime/subsystems/integration/integration-run-recorder.drizzle-backend.ts
|
|
1878
1878
|
import { Inject as Inject3, Injectable as Injectable6, Optional as Optional3 } from "@nestjs/common";
|
|
1879
1879
|
import { and as and2, desc as desc2, eq as eq2 } from "drizzle-orm";
|
|
1880
|
-
var
|
|
1880
|
+
var DrizzleIntegrationRunRecorder = class {
|
|
1881
1881
|
constructor(db, multiTenant) {
|
|
1882
1882
|
this.db = db;
|
|
1883
1883
|
this.multiTenant = multiTenant ?? false;
|
|
@@ -1889,17 +1889,17 @@ var DrizzleSyncRunRecorder = class {
|
|
|
1889
1889
|
multiTenant: this.multiTenant,
|
|
1890
1890
|
operation: "startRun"
|
|
1891
1891
|
});
|
|
1892
|
-
const rows = await this.db.insert(
|
|
1892
|
+
const rows = await this.db.insert(integrationRuns).values({
|
|
1893
1893
|
subscriptionId: input.subscriptionId,
|
|
1894
1894
|
direction: input.direction,
|
|
1895
1895
|
action: input.action,
|
|
1896
1896
|
status: "running",
|
|
1897
1897
|
cursorBefore: input.cursorBefore ?? null,
|
|
1898
1898
|
tenantId: input.tenantId ?? null
|
|
1899
|
-
}).returning({ id:
|
|
1899
|
+
}).returning({ id: integrationRuns.id });
|
|
1900
1900
|
const id = rows[0]?.id;
|
|
1901
1901
|
if (!id) {
|
|
1902
|
-
throw new Error("
|
|
1902
|
+
throw new Error("DrizzleIntegrationRunRecorder: INSERT RETURNING produced no id");
|
|
1903
1903
|
}
|
|
1904
1904
|
return { id };
|
|
1905
1905
|
}
|
|
@@ -1909,8 +1909,8 @@ var DrizzleSyncRunRecorder = class {
|
|
|
1909
1909
|
operation: "recordItem"
|
|
1910
1910
|
});
|
|
1911
1911
|
FieldDiffSchema.parse(input.changedFields);
|
|
1912
|
-
await this.db.insert(
|
|
1913
|
-
|
|
1912
|
+
await this.db.insert(integrationRunItems).values({
|
|
1913
|
+
integrationRunId: input.integrationRunId,
|
|
1914
1914
|
entityType: input.entityType,
|
|
1915
1915
|
externalId: input.externalId,
|
|
1916
1916
|
localId: input.localId ?? null,
|
|
@@ -1929,29 +1929,29 @@ var DrizzleSyncRunRecorder = class {
|
|
|
1929
1929
|
});
|
|
1930
1930
|
const conditions = [];
|
|
1931
1931
|
if (subscriptionId !== void 0) {
|
|
1932
|
-
conditions.push(eq2(
|
|
1932
|
+
conditions.push(eq2(integrationRuns.subscriptionId, subscriptionId));
|
|
1933
1933
|
}
|
|
1934
1934
|
if (this.multiTenant) {
|
|
1935
|
-
conditions.push(eq2(
|
|
1935
|
+
conditions.push(eq2(integrationRuns.tenantId, tenantId));
|
|
1936
1936
|
}
|
|
1937
1937
|
const where = conditions.length === 0 ? void 0 : conditions.length === 1 ? conditions[0] : and2(...conditions);
|
|
1938
1938
|
const rows = await this.db.select({
|
|
1939
|
-
id:
|
|
1940
|
-
subscriptionId:
|
|
1941
|
-
|
|
1942
|
-
status:
|
|
1943
|
-
startedAt:
|
|
1944
|
-
completedAt:
|
|
1945
|
-
recordsProcessed:
|
|
1946
|
-
tenantId:
|
|
1947
|
-
}).from(
|
|
1948
|
-
|
|
1949
|
-
eq2(
|
|
1950
|
-
).where(where).orderBy(desc2(
|
|
1939
|
+
id: integrationRuns.id,
|
|
1940
|
+
subscriptionId: integrationRuns.subscriptionId,
|
|
1941
|
+
connectionId: integrationSubscriptions.connectionId,
|
|
1942
|
+
status: integrationRuns.status,
|
|
1943
|
+
startedAt: integrationRuns.startedAt,
|
|
1944
|
+
completedAt: integrationRuns.completedAt,
|
|
1945
|
+
recordsProcessed: integrationRuns.recordsProcessed,
|
|
1946
|
+
tenantId: integrationRuns.tenantId
|
|
1947
|
+
}).from(integrationRuns).innerJoin(
|
|
1948
|
+
integrationSubscriptions,
|
|
1949
|
+
eq2(integrationRuns.subscriptionId, integrationSubscriptions.id)
|
|
1950
|
+
).where(where).orderBy(desc2(integrationRuns.startedAt)).limit(limit);
|
|
1951
1951
|
return rows.map((row) => ({
|
|
1952
1952
|
id: row.id,
|
|
1953
1953
|
subscriptionId: row.subscriptionId,
|
|
1954
|
-
|
|
1954
|
+
connectionId: row.connectionId,
|
|
1955
1955
|
status: row.status,
|
|
1956
1956
|
startedAt: row.startedAt,
|
|
1957
1957
|
completedAt: row.completedAt,
|
|
@@ -1960,7 +1960,7 @@ var DrizzleSyncRunRecorder = class {
|
|
|
1960
1960
|
}));
|
|
1961
1961
|
}
|
|
1962
1962
|
async completeRun(runId, input) {
|
|
1963
|
-
await this.db.update(
|
|
1963
|
+
await this.db.update(integrationRuns).set({
|
|
1964
1964
|
status: input.status,
|
|
1965
1965
|
recordsFound: input.recordsFound,
|
|
1966
1966
|
recordsProcessed: input.recordsProcessed,
|
|
@@ -1968,27 +1968,27 @@ var DrizzleSyncRunRecorder = class {
|
|
|
1968
1968
|
durationMs: input.durationMs,
|
|
1969
1969
|
error: input.error ?? null,
|
|
1970
1970
|
completedAt: /* @__PURE__ */ new Date()
|
|
1971
|
-
}).where(eq2(
|
|
1971
|
+
}).where(eq2(integrationRuns.id, runId));
|
|
1972
1972
|
}
|
|
1973
1973
|
};
|
|
1974
|
-
|
|
1974
|
+
DrizzleIntegrationRunRecorder = __decorateClass([
|
|
1975
1975
|
Injectable6(),
|
|
1976
1976
|
__decorateParam(0, Inject3(DRIZZLE)),
|
|
1977
1977
|
__decorateParam(1, Optional3()),
|
|
1978
|
-
__decorateParam(1, Inject3(
|
|
1979
|
-
],
|
|
1978
|
+
__decorateParam(1, Inject3(INTEGRATION_MULTI_TENANT))
|
|
1979
|
+
], DrizzleIntegrationRunRecorder);
|
|
1980
1980
|
|
|
1981
|
-
// runtime/subsystems/
|
|
1981
|
+
// runtime/subsystems/integration/integration.module.ts
|
|
1982
1982
|
import { Module } from "@nestjs/common";
|
|
1983
|
-
var
|
|
1983
|
+
var IntegrationModule = class {
|
|
1984
1984
|
static forRoot(options) {
|
|
1985
1985
|
const multiTenant = options.multiTenant ?? false;
|
|
1986
1986
|
const sharedProviders = [
|
|
1987
|
-
{ provide:
|
|
1988
|
-
{ provide:
|
|
1987
|
+
{ provide: INTEGRATION_MODULE_OPTIONS, useValue: options },
|
|
1988
|
+
{ provide: INTEGRATION_MULTI_TENANT, useValue: multiTenant },
|
|
1989
1989
|
// Default differ — consumers can override by binding a different
|
|
1990
|
-
// `IFieldDiffer<T>` to `
|
|
1991
|
-
{ provide:
|
|
1990
|
+
// `IFieldDiffer<T>` to `INTEGRATION_FIELD_DIFFER` in their feature module.
|
|
1991
|
+
{ provide: INTEGRATION_FIELD_DIFFER, useValue: new DeepEqualDiffer() }
|
|
1992
1992
|
];
|
|
1993
1993
|
const backendProviders = options.backend === "memory" ? [
|
|
1994
1994
|
// Wired as singletons via `useValue` so tests can pull
|
|
@@ -1996,38 +1996,38 @@ var SyncModule = class {
|
|
|
1996
1996
|
// direct assertions. Matches JOB-4 / MemoryJobStore shape.
|
|
1997
1997
|
{ provide: MemoryCursorStore, useValue: new MemoryCursorStore() },
|
|
1998
1998
|
{
|
|
1999
|
-
provide:
|
|
1999
|
+
provide: INTEGRATION_CURSOR_STORE,
|
|
2000
2000
|
useExisting: MemoryCursorStore
|
|
2001
2001
|
},
|
|
2002
2002
|
{ provide: MemoryRunRecorder, useValue: new MemoryRunRecorder() },
|
|
2003
2003
|
{
|
|
2004
|
-
provide:
|
|
2004
|
+
provide: INTEGRATION_RUN_RECORDER,
|
|
2005
2005
|
useExisting: MemoryRunRecorder
|
|
2006
2006
|
}
|
|
2007
2007
|
] : [
|
|
2008
2008
|
// Drizzle backends — injected with DRIZZLE (provided by the
|
|
2009
|
-
// consumer's DrizzleModule) + the
|
|
2009
|
+
// consumer's DrizzleModule) + the INTEGRATION_MULTI_TENANT flag
|
|
2010
2010
|
// we bound above.
|
|
2011
|
-
{ provide:
|
|
2012
|
-
{ provide:
|
|
2011
|
+
{ provide: INTEGRATION_CURSOR_STORE, useClass: PostgresCursorStore },
|
|
2012
|
+
{ provide: INTEGRATION_RUN_RECORDER, useClass: DrizzleIntegrationRunRecorder }
|
|
2013
2013
|
];
|
|
2014
2014
|
return {
|
|
2015
|
-
module:
|
|
2015
|
+
module: IntegrationModule,
|
|
2016
2016
|
global: true,
|
|
2017
2017
|
providers: [...sharedProviders, ...backendProviders],
|
|
2018
2018
|
exports: [
|
|
2019
|
-
|
|
2020
|
-
|
|
2021
|
-
|
|
2022
|
-
|
|
2023
|
-
|
|
2019
|
+
INTEGRATION_MODULE_OPTIONS,
|
|
2020
|
+
INTEGRATION_MULTI_TENANT,
|
|
2021
|
+
INTEGRATION_FIELD_DIFFER,
|
|
2022
|
+
INTEGRATION_CURSOR_STORE,
|
|
2023
|
+
INTEGRATION_RUN_RECORDER
|
|
2024
2024
|
]
|
|
2025
2025
|
};
|
|
2026
2026
|
}
|
|
2027
2027
|
};
|
|
2028
|
-
|
|
2028
|
+
IntegrationModule = __decorateClass([
|
|
2029
2029
|
Module({})
|
|
2030
|
-
],
|
|
2030
|
+
], IntegrationModule);
|
|
2031
2031
|
|
|
2032
2032
|
// src/schema/entity-definition.schema.ts
|
|
2033
2033
|
var FieldTypeSchema = z3.enum([
|
|
@@ -2335,21 +2335,21 @@ var AnyQueryDeclarationSchema = z3.union([
|
|
|
2335
2335
|
SearchQueryDeclarationSchema,
|
|
2336
2336
|
QueryDeclarationSchema
|
|
2337
2337
|
]);
|
|
2338
|
-
var
|
|
2338
|
+
var IntegrationDirectionSchema = z3.enum([
|
|
2339
2339
|
"inbound",
|
|
2340
2340
|
"outbound",
|
|
2341
2341
|
"bidirectional"
|
|
2342
2342
|
]);
|
|
2343
|
-
var
|
|
2343
|
+
var ProviderIntegrationSchema = z3.object({
|
|
2344
2344
|
remote_entity: z3.string(),
|
|
2345
|
-
direction:
|
|
2345
|
+
direction: IntegrationDirectionSchema,
|
|
2346
2346
|
cdc: z3.boolean().optional().default(false),
|
|
2347
2347
|
field_mapping: z3.record(z3.string(), z3.string()).optional(),
|
|
2348
2348
|
read_only_fields: z3.array(z3.string()).optional()
|
|
2349
2349
|
});
|
|
2350
|
-
var
|
|
2350
|
+
var IntegrationConfigSchema = z3.object({
|
|
2351
2351
|
electric: z3.boolean().optional().default(false),
|
|
2352
|
-
providers: z3.record(z3.string(),
|
|
2352
|
+
providers: z3.record(z3.string(), ProviderIntegrationSchema).optional()
|
|
2353
2353
|
});
|
|
2354
2354
|
var EventDeclarationSchema = z3.object({
|
|
2355
2355
|
name: z3.string().regex(/^[a-z][a-z0-9_]*$/, "Event name must be snake_case"),
|
|
@@ -2449,22 +2449,52 @@ var EntityDefinitionSchema = z3.object({
|
|
|
2449
2449
|
// v2: Declarative query generation (ADR-005)
|
|
2450
2450
|
// Generates repository + service + use case methods from declarations
|
|
2451
2451
|
queries: z3.array(AnyQueryDeclarationSchema).optional(),
|
|
2452
|
-
// v2: Integration
|
|
2453
|
-
// Electric SQL + provider
|
|
2454
|
-
|
|
2452
|
+
// v2: Integration integration configuration (CODEGEN-EVOLUTION-PLAN Phase 2)
|
|
2453
|
+
// Electric SQL + provider integration (Salesforce, HubSpot, etc.)
|
|
2454
|
+
integration: IntegrationConfigSchema.optional(),
|
|
2455
2455
|
// ADR-033.1: Provider-keyed change-source detection.
|
|
2456
2456
|
//
|
|
2457
2457
|
// Map of provider name → DetectionConfig. Single-provider entities use
|
|
2458
2458
|
// a one-key map; multi-provider entities list each provider as a
|
|
2459
2459
|
// separate key. Each value is an independent `DetectionConfig` (its
|
|
2460
2460
|
// own mode/cursor/mapping/filters) sourced from the canonical schema
|
|
2461
|
-
// in `runtime/subsystems/
|
|
2461
|
+
// in `runtime/subsystems/integration` so this validator and the runtime
|
|
2462
2462
|
// parser stay in lockstep.
|
|
2463
2463
|
//
|
|
2464
2464
|
// Within-file cross-check (ADR-033.1 §6): every key here must also
|
|
2465
|
-
// appear in `
|
|
2465
|
+
// appear in `integration.providers` — see the superRefine on
|
|
2466
2466
|
// `EntityDefinitionSchema` below.
|
|
2467
2467
|
detection: z3.record(z3.string(), DetectionConfigSchema).optional(),
|
|
2468
|
+
// RFC-0001 §1/§8: the integration *surface* this entity belongs to
|
|
2469
|
+
// (e.g. 'calendar', 'mail', 'crm'). Surfaces span provider contexts
|
|
2470
|
+
// (ADR-0006) — one Google OAuth feeds calendar+mail+transcript. The union
|
|
2471
|
+
// of `surface:` values across all entity YAML is the closed set that a
|
|
2472
|
+
// provider's `surfaces:` must be a subset of (cross-checked in
|
|
2473
|
+
// src/parser/validate-providers.ts). Optional: entities without an
|
|
2474
|
+
// integration surface omit it. The surface-package *emission* convention
|
|
2475
|
+
// is Track C (#329); this field is only the declarative input both tracks
|
|
2476
|
+
// read.
|
|
2477
|
+
surface: z3.string().optional(),
|
|
2478
|
+
// Bounded-context declaration (ADR-0004) — "which bounded context this
|
|
2479
|
+
// entity belongs to". This is the DURABLE decision; it is a plain
|
|
2480
|
+
// bounded-context slug, NOT a folder knob. Different features consume it:
|
|
2481
|
+
//
|
|
2482
|
+
// - #403 (this PR, the FIRST consumer): drives the generated code's
|
|
2483
|
+
// module output folder. clean-lite-ps nests the entity's module under
|
|
2484
|
+
// `<modules>/<context>/<entity>/` so same-context entities group
|
|
2485
|
+
// together; untagged entities stay flat (`<modules>/<entity>/`).
|
|
2486
|
+
// - ADR-0004 (deferred): a later `naming: prefix | schema` knob reads
|
|
2487
|
+
// this SAME field to drive the Postgres physical layout —
|
|
2488
|
+
// `prefix` → `pgTable('<context>__<table>')`, then the flip to
|
|
2489
|
+
// `schema` → `pgSchema('<context>').table('<table>')`. NOT wired here;
|
|
2490
|
+
// #403 makes no table/column/schema changes.
|
|
2491
|
+
//
|
|
2492
|
+
// Sibling to `surface:` and orthogonal to it (ADR-0006): context = model
|
|
2493
|
+
// cohesion (which domain), surface = vendor composition (which integration).
|
|
2494
|
+
context: z3.string().regex(
|
|
2495
|
+
/^[a-z][a-z0-9_]*$/,
|
|
2496
|
+
"context must be lowercase snake_case (e.g. 'integration')"
|
|
2497
|
+
).optional(),
|
|
2468
2498
|
// v2: Domain event declarations (CODEGEN-EVOLUTION-PLAN Phase 2)
|
|
2469
2499
|
// Generates typed event classes, handlers, and queue registration
|
|
2470
2500
|
events: z3.array(EventDeclarationSchema).optional(),
|
|
@@ -2483,7 +2513,18 @@ var EntityDefinitionSchema = z3.object({
|
|
|
2483
2513
|
).optional(),
|
|
2484
2514
|
// v2: Analytics / semantic layer configuration
|
|
2485
2515
|
// Cube.js measure packs, custom cube name, and metric definitions
|
|
2486
|
-
analytics: AnalyticsBlockSchema.optional()
|
|
2516
|
+
analytics: AnalyticsBlockSchema.optional(),
|
|
2517
|
+
// Composite (multi-column) unique indexes (#356). Single-column uniqueness
|
|
2518
|
+
// is `unique: true` on the field itself; this declares constraints that
|
|
2519
|
+
// span 2+ columns, e.g. UNIQUE (conversation_id, sequence). Emitted as a
|
|
2520
|
+
// `uniqueIndex(...).on(...)` entry in the generated entity's pgTable
|
|
2521
|
+
// extra-config callback. `name` defaults to <table>_<col1>_<col2>_uniq.
|
|
2522
|
+
unique_indexes: z3.array(
|
|
2523
|
+
z3.object({
|
|
2524
|
+
fields: z3.array(z3.string()).min(2, "unique_indexes entries span 2+ columns \u2014 use `unique: true` on the field for single-column uniqueness"),
|
|
2525
|
+
name: z3.string().optional()
|
|
2526
|
+
}).strict()
|
|
2527
|
+
).optional()
|
|
2487
2528
|
}).strict().refine(
|
|
2488
2529
|
(data) => !data.eav_value_table || typeof data.eav_definition_table === "string",
|
|
2489
2530
|
{
|
|
@@ -2492,13 +2533,13 @@ var EntityDefinitionSchema = z3.object({
|
|
|
2492
2533
|
}
|
|
2493
2534
|
).superRefine((entity, ctx) => {
|
|
2494
2535
|
if (!entity.detection) return;
|
|
2495
|
-
const declared = new Set(Object.keys(entity.
|
|
2536
|
+
const declared = new Set(Object.keys(entity.integration?.providers ?? {}));
|
|
2496
2537
|
for (const provider of Object.keys(entity.detection)) {
|
|
2497
2538
|
if (!declared.has(provider)) {
|
|
2498
2539
|
ctx.addIssue({
|
|
2499
2540
|
code: "custom",
|
|
2500
2541
|
path: ["detection", provider],
|
|
2501
|
-
message: `Provider '${provider}' used in detection: but not declared in
|
|
2542
|
+
message: `Provider '${provider}' used in detection: but not declared in integration.providers. Known providers: ${[...declared].join(", ")}`
|
|
2502
2543
|
});
|
|
2503
2544
|
}
|
|
2504
2545
|
}
|
|
@@ -2910,6 +2951,60 @@ var JunctionDefinitionSchema = z6.object({
|
|
|
2910
2951
|
}
|
|
2911
2952
|
);
|
|
2912
2953
|
|
|
2954
|
+
// src/schema/provider-definition.schema.ts
|
|
2955
|
+
import { z as z7 } from "zod";
|
|
2956
|
+
var IMPORT_REF_RE = /^[^#\s]+#[A-Za-z_$][A-Za-z0-9_$]*$/;
|
|
2957
|
+
var ImportRefSchema = z7.string().regex(
|
|
2958
|
+
IMPORT_REF_RE,
|
|
2959
|
+
"must be an 'import-path#Export' reference (e.g. '@app/foo/bar.strategy#BarStrategy')"
|
|
2960
|
+
);
|
|
2961
|
+
function parseImportRef(ref) {
|
|
2962
|
+
const hash = ref.indexOf("#");
|
|
2963
|
+
return { path: ref.slice(0, hash), exportName: ref.slice(hash + 1) };
|
|
2964
|
+
}
|
|
2965
|
+
var AuthTypeSchema = z7.enum(["oauth2", "api-key", "app-password"]);
|
|
2966
|
+
var AuthSchema = z7.object({
|
|
2967
|
+
type: AuthTypeSchema,
|
|
2968
|
+
// Class implementing the auth subsystem's strategy contract (ADR-031).
|
|
2969
|
+
// Pre-flight verified against a real export at codegen time.
|
|
2970
|
+
strategy: ImportRefSchema,
|
|
2971
|
+
// Required (and non-empty) iff type === 'oauth2'; see refine below.
|
|
2972
|
+
scopes: z7.array(z7.string()).optional()
|
|
2973
|
+
}).strict().refine(
|
|
2974
|
+
(a) => a.type !== "oauth2" || a.scopes !== void 0 && a.scopes.length > 0,
|
|
2975
|
+
{
|
|
2976
|
+
message: "auth.scopes is required and must be non-empty when auth.type is 'oauth2'",
|
|
2977
|
+
path: ["scopes"]
|
|
2978
|
+
}
|
|
2979
|
+
);
|
|
2980
|
+
var ClientSchema = z7.object({
|
|
2981
|
+
// API client class. Pre-flight verified against a real export.
|
|
2982
|
+
class: ImportRefSchema,
|
|
2983
|
+
base_url: z7.string().url("client.base_url must be an absolute URL")
|
|
2984
|
+
}).strict();
|
|
2985
|
+
var ProviderDefinitionSchema = z7.object({
|
|
2986
|
+
// Provider id — the canonical string used as detection: keys, audit rows,
|
|
2987
|
+
// subscription rows. kebab/lower; unique across definitions/providers/
|
|
2988
|
+
// (uniqueness is a cross-file check in validate-providers.ts).
|
|
2989
|
+
slug: z7.string().regex(
|
|
2990
|
+
/^[a-z][a-z0-9-]*$/,
|
|
2991
|
+
"slug must be kebab-case lower (e.g. 'google', 'hubspot')"
|
|
2992
|
+
),
|
|
2993
|
+
display_name: z7.string().optional(),
|
|
2994
|
+
auth: AuthSchema,
|
|
2995
|
+
client: ClientSchema,
|
|
2996
|
+
// Surfaces this provider serves (ADR-0006: surfaces span contexts — one
|
|
2997
|
+
// Google OAuth feeds calendar+mail+transcript). Each must reference a real
|
|
2998
|
+
// `surface:` declared on some entity; that cross-check is in
|
|
2999
|
+
// validate-providers.ts. Non-empty enforced here.
|
|
3000
|
+
surfaces: z7.array(z7.string()).min(1, "surfaces must list at least one surface"),
|
|
3001
|
+
// Optional auth lifecycle hints consumed by provider-module emission (D2).
|
|
3002
|
+
// `refresh_behavior` is left as a free string in D1 — its domain firms up
|
|
3003
|
+
// when D2 consumes it; carrying it now keeps the YAML lossless.
|
|
3004
|
+
token_lifetime: z7.number().int().positive().optional(),
|
|
3005
|
+
refresh_behavior: z7.string().optional()
|
|
3006
|
+
}).strict();
|
|
3007
|
+
|
|
2913
3008
|
// src/utils/yaml-loader.ts
|
|
2914
3009
|
function loadEntityFromYaml(filePath) {
|
|
2915
3010
|
if (!existsSync6(filePath)) {
|
|
@@ -2963,6 +3058,19 @@ function formatZodErrors(error) {
|
|
|
2963
3058
|
return `${err.message} ${location}`;
|
|
2964
3059
|
});
|
|
2965
3060
|
}
|
|
3061
|
+
function loadEntitiesFromYaml(filePaths) {
|
|
3062
|
+
const successes = [];
|
|
3063
|
+
const failures = [];
|
|
3064
|
+
for (const filePath of filePaths) {
|
|
3065
|
+
const result = loadEntityFromYaml(filePath);
|
|
3066
|
+
if (result.success) {
|
|
3067
|
+
successes.push(result);
|
|
3068
|
+
} else {
|
|
3069
|
+
failures.push(result);
|
|
3070
|
+
}
|
|
3071
|
+
}
|
|
3072
|
+
return { successes, failures };
|
|
3073
|
+
}
|
|
2966
3074
|
function loadRelationshipFromYaml(filePath) {
|
|
2967
3075
|
if (!existsSync6(filePath)) {
|
|
2968
3076
|
return {
|
|
@@ -3098,6 +3206,64 @@ function loadJunctionFromYaml(filePath) {
|
|
|
3098
3206
|
filePath
|
|
3099
3207
|
};
|
|
3100
3208
|
}
|
|
3209
|
+
function loadProviderFromYaml(filePath) {
|
|
3210
|
+
if (!existsSync6(filePath)) {
|
|
3211
|
+
return {
|
|
3212
|
+
success: false,
|
|
3213
|
+
error: `File not found: ${filePath}`,
|
|
3214
|
+
filePath
|
|
3215
|
+
};
|
|
3216
|
+
}
|
|
3217
|
+
let content;
|
|
3218
|
+
try {
|
|
3219
|
+
content = readFileSync4(filePath, "utf-8");
|
|
3220
|
+
} catch (err) {
|
|
3221
|
+
return {
|
|
3222
|
+
success: false,
|
|
3223
|
+
error: `Failed to read file: ${filePath}`,
|
|
3224
|
+
details: [err instanceof Error ? err.message : String(err)],
|
|
3225
|
+
filePath
|
|
3226
|
+
};
|
|
3227
|
+
}
|
|
3228
|
+
let parsed;
|
|
3229
|
+
try {
|
|
3230
|
+
parsed = parseYaml(content);
|
|
3231
|
+
} catch (err) {
|
|
3232
|
+
return {
|
|
3233
|
+
success: false,
|
|
3234
|
+
error: `Invalid YAML syntax in ${filePath}`,
|
|
3235
|
+
details: [err instanceof Error ? err.message : String(err)],
|
|
3236
|
+
filePath
|
|
3237
|
+
};
|
|
3238
|
+
}
|
|
3239
|
+
const result = ProviderDefinitionSchema.safeParse(parsed);
|
|
3240
|
+
if (!result.success) {
|
|
3241
|
+
return {
|
|
3242
|
+
success: false,
|
|
3243
|
+
error: `Validation failed for ${filePath}`,
|
|
3244
|
+
details: formatZodErrors(result.error),
|
|
3245
|
+
filePath
|
|
3246
|
+
};
|
|
3247
|
+
}
|
|
3248
|
+
return {
|
|
3249
|
+
success: true,
|
|
3250
|
+
definition: result.data,
|
|
3251
|
+
filePath
|
|
3252
|
+
};
|
|
3253
|
+
}
|
|
3254
|
+
function loadProvidersFromYaml(filePaths) {
|
|
3255
|
+
const successes = [];
|
|
3256
|
+
const failures = [];
|
|
3257
|
+
for (const filePath of filePaths) {
|
|
3258
|
+
const result = loadProviderFromYaml(filePath);
|
|
3259
|
+
if (result.success) {
|
|
3260
|
+
successes.push(result);
|
|
3261
|
+
} else {
|
|
3262
|
+
failures.push(result);
|
|
3263
|
+
}
|
|
3264
|
+
}
|
|
3265
|
+
return { successes, failures };
|
|
3266
|
+
}
|
|
3101
3267
|
function detectYamlType(filePath) {
|
|
3102
3268
|
if (!existsSync6(filePath)) return "unknown";
|
|
3103
3269
|
try {
|
|
@@ -3182,14 +3348,14 @@ function transformToEntity(result) {
|
|
|
3182
3348
|
entity.relationships.set(name, relationship);
|
|
3183
3349
|
}
|
|
3184
3350
|
}
|
|
3185
|
-
if (definition.
|
|
3186
|
-
const
|
|
3187
|
-
const
|
|
3188
|
-
electric:
|
|
3351
|
+
if (definition.integration) {
|
|
3352
|
+
const integrationDef = definition.integration;
|
|
3353
|
+
const parsedIntegration = {
|
|
3354
|
+
electric: integrationDef.electric ?? false
|
|
3189
3355
|
};
|
|
3190
|
-
if (
|
|
3191
|
-
|
|
3192
|
-
for (const [providerName, providerDef] of Object.entries(
|
|
3356
|
+
if (integrationDef.providers) {
|
|
3357
|
+
parsedIntegration.providers = {};
|
|
3358
|
+
for (const [providerName, providerDef] of Object.entries(integrationDef.providers)) {
|
|
3193
3359
|
const parsedProvider = {
|
|
3194
3360
|
remoteEntity: providerDef.remote_entity,
|
|
3195
3361
|
direction: providerDef.direction,
|
|
@@ -3201,10 +3367,10 @@ function transformToEntity(result) {
|
|
|
3201
3367
|
if (providerDef.read_only_fields) {
|
|
3202
3368
|
parsedProvider.readOnlyFields = providerDef.read_only_fields;
|
|
3203
3369
|
}
|
|
3204
|
-
|
|
3370
|
+
parsedIntegration.providers[providerName] = parsedProvider;
|
|
3205
3371
|
}
|
|
3206
3372
|
}
|
|
3207
|
-
entity.
|
|
3373
|
+
entity.integration = parsedIntegration;
|
|
3208
3374
|
}
|
|
3209
3375
|
if (definition.events) {
|
|
3210
3376
|
entity.events = definition.events.map((ev) => ({
|
|
@@ -3477,6 +3643,160 @@ function resolveRelationshipReferences(relationshipDefs, entities) {
|
|
|
3477
3643
|
return issues;
|
|
3478
3644
|
}
|
|
3479
3645
|
|
|
3646
|
+
// src/parser/validate-providers.ts
|
|
3647
|
+
import { existsSync as existsSync7, readFileSync as readFileSync5, statSync as statSync3 } from "fs";
|
|
3648
|
+
import { isAbsolute, join as join8, resolve as resolve3 } from "path";
|
|
3649
|
+
import ts from "typescript";
|
|
3650
|
+
function collectEntitySurfaces(entities) {
|
|
3651
|
+
const surfaces = /* @__PURE__ */ new Set();
|
|
3652
|
+
for (const e of entities) {
|
|
3653
|
+
if (e.surface) surfaces.add(e.surface);
|
|
3654
|
+
}
|
|
3655
|
+
return surfaces;
|
|
3656
|
+
}
|
|
3657
|
+
function resolveImportRef(ref, opts) {
|
|
3658
|
+
const { path: path34, exportName } = parseImportRef(ref);
|
|
3659
|
+
const file = resolveModuleFile(path34, opts);
|
|
3660
|
+
if (!file) {
|
|
3661
|
+
return { status: "module-not-found", resolvedFrom: opts.sourceRoot };
|
|
3662
|
+
}
|
|
3663
|
+
const content = readFileSync5(file, "utf-8");
|
|
3664
|
+
const { names: names2, hasWildcard } = collectExportedNames(file, content);
|
|
3665
|
+
if (names2.has(exportName) || hasWildcard) {
|
|
3666
|
+
return { status: "ok", file };
|
|
3667
|
+
}
|
|
3668
|
+
return { status: "export-not-found", file };
|
|
3669
|
+
}
|
|
3670
|
+
function resolveModuleFile(importPath, opts) {
|
|
3671
|
+
let base = null;
|
|
3672
|
+
for (const [alias, target] of Object.entries(opts.aliases ?? {})) {
|
|
3673
|
+
if (importPath === alias || importPath.startsWith(`${alias}/`)) {
|
|
3674
|
+
const rest = importPath.slice(alias.length);
|
|
3675
|
+
base = join8(target, rest);
|
|
3676
|
+
break;
|
|
3677
|
+
}
|
|
3678
|
+
}
|
|
3679
|
+
if (base === null) {
|
|
3680
|
+
base = isAbsolute(importPath) ? importPath : resolve3(opts.sourceRoot, importPath);
|
|
3681
|
+
}
|
|
3682
|
+
const candidates = [
|
|
3683
|
+
base,
|
|
3684
|
+
`${base}.ts`,
|
|
3685
|
+
`${base}.tsx`,
|
|
3686
|
+
join8(base, "index.ts"),
|
|
3687
|
+
join8(base, "index.tsx")
|
|
3688
|
+
];
|
|
3689
|
+
for (const c of candidates) {
|
|
3690
|
+
if (existsSync7(c) && statSync3(c).isFile()) return c;
|
|
3691
|
+
}
|
|
3692
|
+
return null;
|
|
3693
|
+
}
|
|
3694
|
+
function collectExportedNames(fileName, content) {
|
|
3695
|
+
const sf = ts.createSourceFile(
|
|
3696
|
+
fileName,
|
|
3697
|
+
content,
|
|
3698
|
+
ts.ScriptTarget.Latest,
|
|
3699
|
+
/* setParentNodes */
|
|
3700
|
+
true
|
|
3701
|
+
);
|
|
3702
|
+
const names2 = /* @__PURE__ */ new Set();
|
|
3703
|
+
let hasWildcard = false;
|
|
3704
|
+
const hasExportModifier = (node) => ts.canHaveModifiers(node) && (ts.getModifiers(node)?.some((m) => m.kind === ts.SyntaxKind.ExportKeyword) ?? false);
|
|
3705
|
+
sf.forEachChild((node) => {
|
|
3706
|
+
if (hasExportModifier(node)) {
|
|
3707
|
+
if ((ts.isClassDeclaration(node) || ts.isFunctionDeclaration(node) || ts.isInterfaceDeclaration(node) || ts.isTypeAliasDeclaration(node) || ts.isEnumDeclaration(node) || ts.isModuleDeclaration(node)) && node.name && ts.isIdentifier(node.name)) {
|
|
3708
|
+
names2.add(node.name.text);
|
|
3709
|
+
} else if (ts.isVariableStatement(node)) {
|
|
3710
|
+
for (const decl of node.declarationList.declarations) {
|
|
3711
|
+
if (ts.isIdentifier(decl.name)) names2.add(decl.name.text);
|
|
3712
|
+
}
|
|
3713
|
+
}
|
|
3714
|
+
}
|
|
3715
|
+
if (ts.isExportDeclaration(node)) {
|
|
3716
|
+
if (!node.exportClause) {
|
|
3717
|
+
hasWildcard = true;
|
|
3718
|
+
} else if (ts.isNamedExports(node.exportClause)) {
|
|
3719
|
+
for (const el of node.exportClause.elements) names2.add(el.name.text);
|
|
3720
|
+
} else if (ts.isNamespaceExport(node.exportClause)) {
|
|
3721
|
+
names2.add(node.exportClause.name.text);
|
|
3722
|
+
}
|
|
3723
|
+
}
|
|
3724
|
+
});
|
|
3725
|
+
return { names: names2, hasWildcard };
|
|
3726
|
+
}
|
|
3727
|
+
function validateProviders(providers, opts) {
|
|
3728
|
+
const issues = [];
|
|
3729
|
+
const knownSurfaces = new Set(opts.entitySurfaces);
|
|
3730
|
+
const slugToFiles = /* @__PURE__ */ new Map();
|
|
3731
|
+
for (const p of providers) {
|
|
3732
|
+
const files = slugToFiles.get(p.definition.slug) ?? [];
|
|
3733
|
+
files.push(p.filePath);
|
|
3734
|
+
slugToFiles.set(p.definition.slug, files);
|
|
3735
|
+
}
|
|
3736
|
+
for (const [slug, files] of slugToFiles) {
|
|
3737
|
+
if (files.length > 1) {
|
|
3738
|
+
for (const file of files) {
|
|
3739
|
+
const others = files.filter((f) => f !== file);
|
|
3740
|
+
issues.push({
|
|
3741
|
+
severity: "error",
|
|
3742
|
+
type: "provider_duplicate_slug",
|
|
3743
|
+
message: `provider slug '${slug}' is declared more than once (also in: ${others.join(", ")})`,
|
|
3744
|
+
path: file
|
|
3745
|
+
});
|
|
3746
|
+
}
|
|
3747
|
+
}
|
|
3748
|
+
}
|
|
3749
|
+
for (const { definition, filePath } of providers) {
|
|
3750
|
+
const { slug } = definition;
|
|
3751
|
+
for (const surface of definition.surfaces) {
|
|
3752
|
+
if (!knownSurfaces.has(surface)) {
|
|
3753
|
+
const known = [...knownSurfaces].sort().join(", ") || "(none declared)";
|
|
3754
|
+
issues.push({
|
|
3755
|
+
severity: "error",
|
|
3756
|
+
type: "provider_unknown_surface",
|
|
3757
|
+
message: `provider ${slug}: surface '${surface}' is not declared by any entity (known surfaces: ${known})`,
|
|
3758
|
+
path: filePath
|
|
3759
|
+
});
|
|
3760
|
+
}
|
|
3761
|
+
}
|
|
3762
|
+
if (!opts.skipImportCheck) {
|
|
3763
|
+
if (!opts.sourceRoot) {
|
|
3764
|
+
throw new Error(
|
|
3765
|
+
"validateProviders: sourceRoot is required for the import pre-flight check (or set skipImportCheck: true)"
|
|
3766
|
+
);
|
|
3767
|
+
}
|
|
3768
|
+
const resolveOpts = {
|
|
3769
|
+
sourceRoot: opts.sourceRoot,
|
|
3770
|
+
aliases: opts.aliases
|
|
3771
|
+
};
|
|
3772
|
+
const refs = [
|
|
3773
|
+
{ field: "auth.strategy", ref: definition.auth.strategy },
|
|
3774
|
+
{ field: "client.class", ref: definition.client.class }
|
|
3775
|
+
];
|
|
3776
|
+
for (const { field, ref } of refs) {
|
|
3777
|
+
const result = resolveImportRef(ref, resolveOpts);
|
|
3778
|
+
if (result.status === "module-not-found") {
|
|
3779
|
+
issues.push({
|
|
3780
|
+
severity: "error",
|
|
3781
|
+
type: "provider_import_unresolved",
|
|
3782
|
+
message: `provider ${slug}: ${field} '${ref}' not found \u2014 module could not be resolved from ${result.resolvedFrom}`,
|
|
3783
|
+
path: filePath
|
|
3784
|
+
});
|
|
3785
|
+
} else if (result.status === "export-not-found") {
|
|
3786
|
+
const { exportName } = parseImportRef(ref);
|
|
3787
|
+
issues.push({
|
|
3788
|
+
severity: "error",
|
|
3789
|
+
type: "provider_import_unresolved",
|
|
3790
|
+
message: `provider ${slug}: ${field} '${ref}' not found \u2014 export '${exportName}' is missing from ${result.file}`,
|
|
3791
|
+
path: filePath
|
|
3792
|
+
});
|
|
3793
|
+
}
|
|
3794
|
+
}
|
|
3795
|
+
}
|
|
3796
|
+
}
|
|
3797
|
+
return issues;
|
|
3798
|
+
}
|
|
3799
|
+
|
|
3480
3800
|
// src/analyzer/graph-builder.ts
|
|
3481
3801
|
function inferCardinality(type) {
|
|
3482
3802
|
switch (type) {
|
|
@@ -3833,8 +4153,8 @@ function checkConsistency(graph) {
|
|
|
3833
4153
|
if (entity.queries !== void 0) {
|
|
3834
4154
|
issues.push(...checkQueryFieldReferences(entity));
|
|
3835
4155
|
}
|
|
3836
|
-
if (entity.
|
|
3837
|
-
issues.push(...
|
|
4156
|
+
if (entity.integration !== void 0) {
|
|
4157
|
+
issues.push(...checkIntegrationFieldMappingReferences(entity));
|
|
3838
4158
|
issues.push(...checkExternalIdTrackingCollision(entity));
|
|
3839
4159
|
}
|
|
3840
4160
|
}
|
|
@@ -4070,19 +4390,19 @@ function checkQueryFieldReferences(entity) {
|
|
|
4070
4390
|
}
|
|
4071
4391
|
return issues;
|
|
4072
4392
|
}
|
|
4073
|
-
function
|
|
4393
|
+
function checkIntegrationFieldMappingReferences(entity) {
|
|
4074
4394
|
const issues = [];
|
|
4075
4395
|
const availableFields = getAvailableFieldNames(entity);
|
|
4076
4396
|
const availableSet = new Set(availableFields);
|
|
4077
|
-
for (const [providerName, provider] of Object.entries(entity.
|
|
4397
|
+
for (const [providerName, provider] of Object.entries(entity.integration?.providers ?? {})) {
|
|
4078
4398
|
for (const fieldName of Object.keys(provider.fieldMapping ?? {})) {
|
|
4079
4399
|
if (!availableSet.has(fieldName)) {
|
|
4080
4400
|
issues.push({
|
|
4081
4401
|
severity: "warning",
|
|
4082
|
-
type: "
|
|
4402
|
+
type: "unknown_integration_field_mapping",
|
|
4083
4403
|
entity: entity.name,
|
|
4084
4404
|
field: fieldName,
|
|
4085
|
-
message: `
|
|
4405
|
+
message: `Integration field mapping references unknown field "${fieldName}" for provider "${providerName}" in entity "${entity.name}"`
|
|
4086
4406
|
});
|
|
4087
4407
|
}
|
|
4088
4408
|
}
|
|
@@ -4090,10 +4410,10 @@ function checkSyncFieldMappingReferences(entity) {
|
|
|
4090
4410
|
if (!availableSet.has(fieldName)) {
|
|
4091
4411
|
issues.push({
|
|
4092
4412
|
severity: "warning",
|
|
4093
|
-
type: "
|
|
4413
|
+
type: "unknown_integration_field_mapping",
|
|
4094
4414
|
entity: entity.name,
|
|
4095
4415
|
field: fieldName,
|
|
4096
|
-
message: `
|
|
4416
|
+
message: `Integration field mapping references unknown field "${fieldName}" for provider "${providerName}" in entity "${entity.name}"`
|
|
4097
4417
|
});
|
|
4098
4418
|
}
|
|
4099
4419
|
}
|
|
@@ -4104,14 +4424,14 @@ function checkExternalIdTrackingCollision(entity) {
|
|
|
4104
4424
|
const issues = [];
|
|
4105
4425
|
const hasExternalIdTracking = entity.behaviors.includes("external_id_tracking");
|
|
4106
4426
|
if (!hasExternalIdTracking) return issues;
|
|
4107
|
-
for (const [providerName, provider] of Object.entries(entity.
|
|
4427
|
+
for (const [providerName, provider] of Object.entries(entity.integration?.providers ?? {})) {
|
|
4108
4428
|
if (provider.fieldMapping && "external_id" in provider.fieldMapping) {
|
|
4109
4429
|
issues.push({
|
|
4110
4430
|
severity: "warning",
|
|
4111
4431
|
type: "external_id_tracking_collision",
|
|
4112
4432
|
entity: entity.name,
|
|
4113
4433
|
field: "external_id",
|
|
4114
|
-
message: `Entity "${entity.name}" has external_id_tracking behavior and also maps "external_id" in
|
|
4434
|
+
message: `Entity "${entity.name}" has external_id_tracking behavior and also maps "external_id" in integration field_mapping for provider "${providerName}". The behavior-added field may collide with the mapped field.`
|
|
4115
4435
|
});
|
|
4116
4436
|
}
|
|
4117
4437
|
}
|
|
@@ -4290,28 +4610,28 @@ function createSuggestion(path34) {
|
|
|
4290
4610
|
|
|
4291
4611
|
// src/analyzer/manifest.ts
|
|
4292
4612
|
import { createHash } from "crypto";
|
|
4293
|
-
import { readFileSync as
|
|
4294
|
-
import { join as
|
|
4613
|
+
import { readFileSync as readFileSync6, writeFileSync, existsSync as existsSync8, mkdirSync, readdirSync as readdirSync6, statSync as statSync4 } from "fs";
|
|
4614
|
+
import { join as join9 } from "path";
|
|
4295
4615
|
var MANIFEST_FILE = "manifest.json";
|
|
4296
4616
|
var MANIFEST_VERSION = 1;
|
|
4297
4617
|
function getManifestDir() {
|
|
4298
4618
|
return process.env.CODEGEN_MANIFEST_DIR || ".codegen";
|
|
4299
4619
|
}
|
|
4300
4620
|
function getManifestPaths(projectRoot) {
|
|
4301
|
-
const dir =
|
|
4302
|
-
const file =
|
|
4621
|
+
const dir = join9(projectRoot, getManifestDir());
|
|
4622
|
+
const file = join9(dir, MANIFEST_FILE);
|
|
4303
4623
|
return { dir, file };
|
|
4304
4624
|
}
|
|
4305
4625
|
async function computeEntityFilesHash(entitiesDir) {
|
|
4306
|
-
if (!
|
|
4626
|
+
if (!existsSync8(entitiesDir)) {
|
|
4307
4627
|
return createHash("sha256").update("").digest("hex");
|
|
4308
4628
|
}
|
|
4309
4629
|
const yamlFiles = [];
|
|
4310
4630
|
function walkDir(dir) {
|
|
4311
4631
|
const entries = readdirSync6(dir);
|
|
4312
4632
|
for (const entry of entries) {
|
|
4313
|
-
const fullPath =
|
|
4314
|
-
const stat =
|
|
4633
|
+
const fullPath = join9(dir, entry);
|
|
4634
|
+
const stat = statSync4(fullPath);
|
|
4315
4635
|
if (stat.isDirectory()) {
|
|
4316
4636
|
walkDir(fullPath);
|
|
4317
4637
|
} else if (stat.isFile() && (entry.endsWith(".yaml") || entry.endsWith(".yml"))) {
|
|
@@ -4323,7 +4643,7 @@ async function computeEntityFilesHash(entitiesDir) {
|
|
|
4323
4643
|
yamlFiles.sort();
|
|
4324
4644
|
const hash = createHash("sha256");
|
|
4325
4645
|
for (const file of yamlFiles) {
|
|
4326
|
-
const content =
|
|
4646
|
+
const content = readFileSync6(file, "utf-8");
|
|
4327
4647
|
hash.update(file);
|
|
4328
4648
|
hash.update(content);
|
|
4329
4649
|
}
|
|
@@ -4331,11 +4651,11 @@ async function computeEntityFilesHash(entitiesDir) {
|
|
|
4331
4651
|
}
|
|
4332
4652
|
function readManifest(projectRoot) {
|
|
4333
4653
|
const { file } = getManifestPaths(projectRoot);
|
|
4334
|
-
if (!
|
|
4654
|
+
if (!existsSync8(file)) {
|
|
4335
4655
|
return null;
|
|
4336
4656
|
}
|
|
4337
4657
|
try {
|
|
4338
|
-
const content =
|
|
4658
|
+
const content = readFileSync6(file, "utf-8");
|
|
4339
4659
|
const manifest = JSON.parse(content);
|
|
4340
4660
|
if (manifest.version !== MANIFEST_VERSION) {
|
|
4341
4661
|
return null;
|
|
@@ -4347,7 +4667,7 @@ function readManifest(projectRoot) {
|
|
|
4347
4667
|
}
|
|
4348
4668
|
function writeManifest(projectRoot, manifest) {
|
|
4349
4669
|
const { dir, file } = getManifestPaths(projectRoot);
|
|
4350
|
-
if (!
|
|
4670
|
+
if (!existsSync8(dir)) {
|
|
4351
4671
|
mkdirSync(dir, { recursive: true });
|
|
4352
4672
|
}
|
|
4353
4673
|
const content = JSON.stringify(manifest, null, 2);
|
|
@@ -5442,8 +5762,8 @@ var BasePattern = definePattern({
|
|
|
5442
5762
|
});
|
|
5443
5763
|
|
|
5444
5764
|
// src/patterns/library/junction.pattern.ts
|
|
5445
|
-
import { z as
|
|
5446
|
-
var JunctionPatternConfigSchema =
|
|
5765
|
+
import { z as z8 } from "zod";
|
|
5766
|
+
var JunctionPatternConfigSchema = z8.object({}).strict();
|
|
5447
5767
|
var JunctionPattern = definePattern({
|
|
5448
5768
|
name: "Junction",
|
|
5449
5769
|
description: "Explicit many-to-many junction with role + temporal + sourcing metadata",
|
|
@@ -5489,30 +5809,30 @@ var MetadataPattern = definePattern({
|
|
|
5489
5809
|
description: "History-tracked metadata rows \u2014 entity-id + type scoped lookups"
|
|
5490
5810
|
});
|
|
5491
5811
|
|
|
5492
|
-
// src/patterns/library/
|
|
5493
|
-
var
|
|
5494
|
-
name: "
|
|
5812
|
+
// src/patterns/library/integrated.pattern.ts
|
|
5813
|
+
var IntegratedPattern = definePattern({
|
|
5814
|
+
name: "Integrated",
|
|
5495
5815
|
extends: ["Base"],
|
|
5496
|
-
repositoryClass: "
|
|
5497
|
-
serviceClass: "
|
|
5498
|
-
repositoryImport: "@shared/base-classes/
|
|
5499
|
-
serviceImport: "@shared/base-classes/
|
|
5816
|
+
repositoryClass: "IntegratedEntityRepository",
|
|
5817
|
+
serviceClass: "IntegratedEntityService",
|
|
5818
|
+
repositoryImport: "@shared/base-classes/integrated-entity-repository",
|
|
5819
|
+
serviceImport: "@shared/base-classes/integrated-entity-service",
|
|
5500
5820
|
repositoryInheritedMethods: [
|
|
5501
5821
|
"findById, findByIds, list, count, exists, create, update, delete, upsertMany",
|
|
5502
5822
|
"findByExternalId, findManyByExternalIds, findAllByUserId, findVisibleByUserId",
|
|
5503
|
-
"
|
|
5823
|
+
"integrationUpsertOne, findByExternalIdProjected, softDeleteByExternalId, integrationUpsert"
|
|
5504
5824
|
],
|
|
5505
5825
|
serviceInheritedMethods: [
|
|
5506
5826
|
"findById, findByIds, list, count, exists, create, update, delete",
|
|
5507
5827
|
"findByExternalId, findAllByUserId, findVisibleByUserId"
|
|
5508
5828
|
],
|
|
5509
5829
|
impliedBehaviors: ["external_id_tracking"],
|
|
5510
|
-
description: "External CRM/system
|
|
5830
|
+
description: "External CRM/system integration columns and integrationUpsert methods"
|
|
5511
5831
|
});
|
|
5512
5832
|
|
|
5513
5833
|
// src/patterns/library/index.ts
|
|
5514
5834
|
registerLibraryPattern(BasePattern);
|
|
5515
|
-
registerLibraryPattern(
|
|
5835
|
+
registerLibraryPattern(IntegratedPattern);
|
|
5516
5836
|
registerLibraryPattern(ActivityPattern);
|
|
5517
5837
|
registerLibraryPattern(KnowledgePattern);
|
|
5518
5838
|
registerLibraryPattern(MetadataPattern);
|
|
@@ -5572,9 +5892,9 @@ function validateEntities(entitiesDir) {
|
|
|
5572
5892
|
|
|
5573
5893
|
// src/cli/shared/hygen.ts
|
|
5574
5894
|
import { execSync } from "child_process";
|
|
5575
|
-
import { join as
|
|
5895
|
+
import { join as join10 } from "path";
|
|
5576
5896
|
function defaultTemplateRoot() {
|
|
5577
|
-
return
|
|
5897
|
+
return join10(import.meta.dirname, "..", "..", "..", "templates");
|
|
5578
5898
|
}
|
|
5579
5899
|
function quoteArg(a) {
|
|
5580
5900
|
if (a === "" || /[\s"'$`\\]/.test(a)) {
|
|
@@ -5698,7 +6018,9 @@ function collectEntities(entitiesDir) {
|
|
|
5698
6018
|
const def = result.definition;
|
|
5699
6019
|
entities.push({
|
|
5700
6020
|
name: def.entity.name,
|
|
5701
|
-
plural: def.entity.plural
|
|
6021
|
+
plural: def.entity.plural,
|
|
6022
|
+
// #403: top-level `context:` nests the module folder; undefined → flat.
|
|
6023
|
+
context: def.context
|
|
5702
6024
|
});
|
|
5703
6025
|
}
|
|
5704
6026
|
entities.sort((a, b) => a.name.localeCompare(b.name));
|
|
@@ -5754,11 +6076,12 @@ function entityFilePaths(info, architecture, backendSrc) {
|
|
|
5754
6076
|
const pluralKebab = toKebabCase(plural);
|
|
5755
6077
|
if (architecture === "clean-lite-ps") {
|
|
5756
6078
|
const prefix = backendSrc && backendSrc !== "." ? `${backendSrc}/` : "";
|
|
6079
|
+
const ctxSeg = info.context ? `${info.context}/` : "";
|
|
5757
6080
|
return {
|
|
5758
|
-
moduleFile: `${prefix}modules/${plural}/${plural}.module.ts`,
|
|
6081
|
+
moduleFile: `${prefix}modules/${ctxSeg}${plural}/${plural}.module.ts`,
|
|
5759
6082
|
moduleClass: `${toPascalCase(plural)}Module`,
|
|
5760
6083
|
// Drizzle entity schema lives alongside the entity file in clean-lite-ps.
|
|
5761
|
-
schemaFile: `${prefix}modules/${plural}/${name}.entity.ts`
|
|
6084
|
+
schemaFile: `${prefix}modules/${ctxSeg}${plural}/${name}.entity.ts`
|
|
5762
6085
|
};
|
|
5763
6086
|
}
|
|
5764
6087
|
return {
|
|
@@ -5879,22 +6202,22 @@ var HEADER2 = `// AUTO-GENERATED by @pattern-stack/codegen. Do not edit.
|
|
|
5879
6202
|
function collectScopeableNames(entitiesDir) {
|
|
5880
6203
|
if (!fs3.existsSync(entitiesDir)) return [];
|
|
5881
6204
|
const files = findYamlFiles(entitiesDir);
|
|
5882
|
-
const
|
|
6205
|
+
const names2 = [];
|
|
5883
6206
|
for (const file of files) {
|
|
5884
6207
|
const result = loadEntityFromYaml(file);
|
|
5885
6208
|
if (!result.success) continue;
|
|
5886
6209
|
if (result.definition.entity.scopeable === true) {
|
|
5887
|
-
|
|
6210
|
+
names2.push(result.definition.entity.name);
|
|
5888
6211
|
}
|
|
5889
6212
|
}
|
|
5890
|
-
|
|
5891
|
-
return
|
|
6213
|
+
names2.sort((a, b) => a.localeCompare(b));
|
|
6214
|
+
return names2;
|
|
5892
6215
|
}
|
|
5893
|
-
function buildScopeEntityTypeContent(
|
|
5894
|
-
if (
|
|
6216
|
+
function buildScopeEntityTypeContent(names2) {
|
|
6217
|
+
if (names2.length === 0) {
|
|
5895
6218
|
return HEADER2 + "\nexport type ScopeEntityType = never;\n\nexport const SCOPE_ENTITY_TYPES = [] as const;\n";
|
|
5896
6219
|
}
|
|
5897
|
-
const quoted =
|
|
6220
|
+
const quoted = names2.map((n) => `'${n}'`);
|
|
5898
6221
|
const unionLiteral = quoted.join(" | ");
|
|
5899
6222
|
const tupleLiteral = quoted.join(", ");
|
|
5900
6223
|
return HEADER2 + `
|
|
@@ -5953,8 +6276,8 @@ var SUBSYSTEMS = [
|
|
|
5953
6276
|
defaultBackend: "local"
|
|
5954
6277
|
},
|
|
5955
6278
|
{
|
|
5956
|
-
name: "
|
|
5957
|
-
description: "External-system
|
|
6279
|
+
name: "integration",
|
|
6280
|
+
description: "External-system integration engine (IChangeSource<T> + orchestrator + audit log)",
|
|
5958
6281
|
backends: ["drizzle", "memory"],
|
|
5959
6282
|
defaultBackend: "drizzle"
|
|
5960
6283
|
},
|
|
@@ -5977,7 +6300,7 @@ var SUBSYSTEMS = [
|
|
|
5977
6300
|
{
|
|
5978
6301
|
// OBS-7 / ADR-025. Combiner subsystem — no schema, no worker, no
|
|
5979
6302
|
// generated/ dir. `ObservabilityModule` composes sibling read ports
|
|
5980
|
-
// (events/jobs/bridge/
|
|
6303
|
+
// (events/jobs/bridge/integration) via @Optional() DI. The `combiner`
|
|
5981
6304
|
// pseudo-backend is parallel to `openapi-config`'s `config-only`.
|
|
5982
6305
|
name: "observability",
|
|
5983
6306
|
description: "Observability combiner \u2014 composes sibling read ports via @Optional() DI (ADR-025)",
|
|
@@ -5999,14 +6322,14 @@ var SUBSYSTEMS = [
|
|
|
5999
6322
|
{
|
|
6000
6323
|
// #287. Auth-integrations starter (PR #290) — vendored from
|
|
6001
6324
|
// `examples/auth-integrations/`, NOT from `runtime/subsystems/`.
|
|
6002
|
-
// Bundles a canonical `
|
|
6003
|
-
//
|
|
6325
|
+
// Bundles a canonical `connection` entity yaml + the three
|
|
6326
|
+
// connection-store-port adapters + the `ConnectionsService`
|
|
6004
6327
|
// facade. Single-backend (drizzle); the runtime adapters call
|
|
6005
|
-
// directly into the codegen-emitted `
|
|
6328
|
+
// directly into the codegen-emitted `ConnectionService` from the
|
|
6006
6329
|
// entity layer. Detection: presence of
|
|
6007
|
-
// `<
|
|
6330
|
+
// `<vendorRoot>/connections/connections-auth.module.ts`.
|
|
6008
6331
|
name: "auth-integrations",
|
|
6009
|
-
description: "Vendored
|
|
6332
|
+
description: "Vendored connection entity + adapters (consumes auth subsystem)",
|
|
6010
6333
|
backends: ["drizzle"],
|
|
6011
6334
|
defaultBackend: "drizzle"
|
|
6012
6335
|
}
|
|
@@ -6017,7 +6340,7 @@ var SUBSYSTEM_MODULE_FILE = {
|
|
|
6017
6340
|
jobs: "jobs-domain.module.ts",
|
|
6018
6341
|
cache: "cache.module.ts",
|
|
6019
6342
|
storage: "storage.module.ts",
|
|
6020
|
-
|
|
6343
|
+
integration: "integration.module.ts",
|
|
6021
6344
|
bridge: "bridge.module.ts",
|
|
6022
6345
|
observability: "observability.module.ts",
|
|
6023
6346
|
auth: "auth.module.ts"
|
|
@@ -6099,11 +6422,8 @@ async function detectSubsystemStatesImpl(ctx) {
|
|
|
6099
6422
|
const pathsAny = ctx.config?.paths;
|
|
6100
6423
|
const modulesConfigured = pathsAny?.modules_dir;
|
|
6101
6424
|
const vendorRoot = typeof modulesConfigured === "string" && modulesConfigured.length > 0 ? path6.resolve(ctx.cwd, modulesConfigured) : path6.resolve(ctx.cwd, backendSrc, "modules");
|
|
6102
|
-
const sharedConfigured = pathsAny?.shared;
|
|
6103
|
-
const sharedRoot = typeof sharedConfigured === "string" && sharedConfigured.length > 0 ? path6.resolve(ctx.cwd, sharedConfigured) : path6.resolve(ctx.cwd, backendSrc, "shared");
|
|
6104
6425
|
const candidates = [
|
|
6105
|
-
path6.join(vendorRoot, "
|
|
6106
|
-
path6.join(sharedRoot, "integrations", "integrations-auth.module.ts")
|
|
6426
|
+
path6.join(vendorRoot, "connections", "connections-auth.module.ts")
|
|
6107
6427
|
];
|
|
6108
6428
|
for (const moduleFile of candidates) {
|
|
6109
6429
|
if (fs4.existsSync(moduleFile)) {
|
|
@@ -6217,24 +6537,24 @@ var COMPOSERS = {
|
|
|
6217
6537
|
]
|
|
6218
6538
|
};
|
|
6219
6539
|
},
|
|
6220
|
-
|
|
6540
|
+
integration: ({ subsystemsRel, cfg }) => {
|
|
6221
6541
|
const backend = cfg?.backend ?? "drizzle";
|
|
6222
6542
|
const multiTenant = Boolean(cfg?.multi_tenant);
|
|
6223
6543
|
return {
|
|
6224
6544
|
imports: [
|
|
6225
|
-
`import {
|
|
6545
|
+
`import { IntegrationModule } from '${subsystemsRel}/integration/integration.module';`
|
|
6226
6546
|
],
|
|
6227
6547
|
calls: [
|
|
6228
|
-
`
|
|
6548
|
+
` IntegrationModule.forRoot(${quoteOpts({ backend, multiTenant })}),`
|
|
6229
6549
|
]
|
|
6230
6550
|
};
|
|
6231
6551
|
}
|
|
6232
6552
|
};
|
|
6233
|
-
var COMPOSABLE_ORDER = ["events", "jobs", "bridge", "
|
|
6553
|
+
var COMPOSABLE_ORDER = ["events", "jobs", "bridge", "integration"];
|
|
6234
6554
|
var HEADER3 = `// AUTO-GENERATED by @pattern-stack/codegen. DO NOT EDIT.
|
|
6235
6555
|
// Subsystem composition barrel \u2014 reflects \`subsystems.install\` in
|
|
6236
6556
|
// codegen.config.yaml and the per-subsystem option blocks
|
|
6237
|
-
// (\`events:\`, \`jobs:\`, \`bridge:\`, \`
|
|
6557
|
+
// (\`events:\`, \`jobs:\`, \`bridge:\`, \`integration:\`).
|
|
6238
6558
|
//
|
|
6239
6559
|
// Wire into AppModule once:
|
|
6240
6560
|
//
|
|
@@ -6308,7 +6628,7 @@ async function regenerateSubsystemBarrel(opts) {
|
|
|
6308
6628
|
// src/cli/shared/bridge-registry-generator.ts
|
|
6309
6629
|
import fs6 from "fs";
|
|
6310
6630
|
import path9 from "path";
|
|
6311
|
-
import
|
|
6631
|
+
import ts2 from "typescript";
|
|
6312
6632
|
var HEADER4 = `// AUTO-GENERATED by @pattern-stack/codegen. Do not edit.
|
|
6313
6633
|
// Run \`codegen entity new --all\` to refresh.
|
|
6314
6634
|
`;
|
|
@@ -6384,34 +6704,34 @@ function findHandlerFiles(dir) {
|
|
|
6384
6704
|
function extractTriggersFromSourceFile(sourceFile, filePath) {
|
|
6385
6705
|
const triggers = [];
|
|
6386
6706
|
function visit(node) {
|
|
6387
|
-
if (
|
|
6388
|
-
const modifiers =
|
|
6707
|
+
if (ts2.isClassDeclaration(node)) {
|
|
6708
|
+
const modifiers = ts2.canHaveDecorators(node) ? ts2.getDecorators(node) ?? [] : [];
|
|
6389
6709
|
for (const decorator of modifiers) {
|
|
6390
6710
|
const call = decorator.expression;
|
|
6391
|
-
if (!
|
|
6392
|
-
if (!
|
|
6711
|
+
if (!ts2.isCallExpression(call)) continue;
|
|
6712
|
+
if (!ts2.isIdentifier(call.expression)) continue;
|
|
6393
6713
|
if (call.expression.text !== "JobHandler") continue;
|
|
6394
6714
|
const [typeArg, metaArg] = call.arguments;
|
|
6395
|
-
if (!typeArg || !
|
|
6396
|
-
if (!metaArg || !
|
|
6715
|
+
if (!typeArg || !ts2.isStringLiteralLike(typeArg)) continue;
|
|
6716
|
+
if (!metaArg || !ts2.isObjectLiteralExpression(metaArg)) continue;
|
|
6397
6717
|
const jobType = typeArg.text;
|
|
6398
6718
|
const triggersProp = metaArg.properties.find(
|
|
6399
|
-
(p) =>
|
|
6719
|
+
(p) => ts2.isPropertyAssignment(p) && ts2.isIdentifier(p.name) && p.name.text === "triggers"
|
|
6400
6720
|
);
|
|
6401
6721
|
if (!triggersProp) continue;
|
|
6402
|
-
if (!
|
|
6722
|
+
if (!ts2.isArrayLiteralExpression(triggersProp.initializer)) continue;
|
|
6403
6723
|
triggersProp.initializer.elements.forEach((el, index2) => {
|
|
6404
|
-
if (!
|
|
6724
|
+
if (!ts2.isObjectLiteralExpression(el)) return;
|
|
6405
6725
|
const eventProp = el.properties.find(
|
|
6406
|
-
(p) =>
|
|
6726
|
+
(p) => ts2.isPropertyAssignment(p) && ts2.isIdentifier(p.name) && p.name.text === "event"
|
|
6407
6727
|
);
|
|
6408
6728
|
const mapProp = el.properties.find(
|
|
6409
|
-
(p) =>
|
|
6729
|
+
(p) => ts2.isPropertyAssignment(p) && ts2.isIdentifier(p.name) && p.name.text === "map"
|
|
6410
6730
|
);
|
|
6411
6731
|
const whenProp = el.properties.find(
|
|
6412
|
-
(p) =>
|
|
6732
|
+
(p) => ts2.isPropertyAssignment(p) && ts2.isIdentifier(p.name) && p.name.text === "when"
|
|
6413
6733
|
);
|
|
6414
|
-
if (!eventProp || !
|
|
6734
|
+
if (!eventProp || !ts2.isStringLiteralLike(eventProp.initializer)) return;
|
|
6415
6735
|
if (!mapProp) return;
|
|
6416
6736
|
const { line } = sourceFile.getLineAndCharacterOfPosition(el.getStart(sourceFile));
|
|
6417
6737
|
triggers.push({
|
|
@@ -6426,7 +6746,7 @@ function extractTriggersFromSourceFile(sourceFile, filePath) {
|
|
|
6426
6746
|
});
|
|
6427
6747
|
}
|
|
6428
6748
|
}
|
|
6429
|
-
|
|
6749
|
+
ts2.forEachChild(node, visit);
|
|
6430
6750
|
}
|
|
6431
6751
|
visit(sourceFile);
|
|
6432
6752
|
return triggers;
|
|
@@ -6436,13 +6756,13 @@ function scanHandlerFiles(handlersDir) {
|
|
|
6436
6756
|
const out = [];
|
|
6437
6757
|
for (const filePath of files) {
|
|
6438
6758
|
const text2 = fs6.readFileSync(filePath, "utf8");
|
|
6439
|
-
const sourceFile =
|
|
6759
|
+
const sourceFile = ts2.createSourceFile(
|
|
6440
6760
|
filePath,
|
|
6441
6761
|
text2,
|
|
6442
|
-
|
|
6762
|
+
ts2.ScriptTarget.Latest,
|
|
6443
6763
|
/* setParentNodes */
|
|
6444
6764
|
true,
|
|
6445
|
-
|
|
6765
|
+
ts2.ScriptKind.TS
|
|
6446
6766
|
);
|
|
6447
6767
|
out.push(...extractTriggersFromSourceFile(sourceFile, filePath));
|
|
6448
6768
|
}
|
|
@@ -6741,8 +7061,8 @@ function emitTypeImports(imports) {
|
|
|
6741
7061
|
}
|
|
6742
7062
|
const out = [];
|
|
6743
7063
|
for (const specifier of [...grouped.keys()].sort()) {
|
|
6744
|
-
const
|
|
6745
|
-
out.push(`import type { ${
|
|
7064
|
+
const names2 = [...grouped.get(specifier)].sort();
|
|
7065
|
+
out.push(`import type { ${names2.join(", ")} } from '${specifier}';`);
|
|
6746
7066
|
}
|
|
6747
7067
|
return out;
|
|
6748
7068
|
}
|
|
@@ -6755,8 +7075,8 @@ function emitValueImports(imports) {
|
|
|
6755
7075
|
}
|
|
6756
7076
|
const out = [];
|
|
6757
7077
|
for (const specifier of [...grouped.keys()].sort()) {
|
|
6758
|
-
const
|
|
6759
|
-
out.push(`import { ${
|
|
7078
|
+
const names2 = [...grouped.get(specifier)].sort();
|
|
7079
|
+
out.push(`import { ${names2.join(", ")} } from '${specifier}';`);
|
|
6760
7080
|
}
|
|
6761
7081
|
return out;
|
|
6762
7082
|
}
|
|
@@ -6788,13 +7108,13 @@ function buildTokensTs(pattern) {
|
|
|
6788
7108
|
lines.push("");
|
|
6789
7109
|
for (const i of emitTypeImports(typeImports)) lines.push(i);
|
|
6790
7110
|
lines.push("");
|
|
6791
|
-
for (const { names } of registries) {
|
|
6792
|
-
lines.push(`export const ${
|
|
7111
|
+
for (const { names: names2 } of registries) {
|
|
7112
|
+
lines.push(`export const ${names2.tokenConst} = Symbol('${names2.tokenConst}');`);
|
|
6793
7113
|
}
|
|
6794
7114
|
lines.push("");
|
|
6795
|
-
for (const { reg, names } of registries) {
|
|
7115
|
+
for (const { reg, names: names2 } of registries) {
|
|
6796
7116
|
lines.push(
|
|
6797
|
-
`export type ${
|
|
7117
|
+
`export type ${names2.mapType} = Map<${reg.keyType}, ${reg.valueType}>;`
|
|
6798
7118
|
);
|
|
6799
7119
|
}
|
|
6800
7120
|
lines.push("");
|
|
@@ -6808,8 +7128,8 @@ function buildProvidersTs(pattern) {
|
|
|
6808
7128
|
providerImports.push({ specifier: e.providerImport, name: e.provider });
|
|
6809
7129
|
}
|
|
6810
7130
|
}
|
|
6811
|
-
const tokenValueImports = registries.map(({ names }) =>
|
|
6812
|
-
const mapTypeImports = registries.map(({ names }) =>
|
|
7131
|
+
const tokenValueImports = registries.map(({ names: names2 }) => names2.tokenConst);
|
|
7132
|
+
const mapTypeImports = registries.map(({ names: names2 }) => names2.mapType);
|
|
6813
7133
|
const typeImports = [];
|
|
6814
7134
|
for (const { reg } of registries) {
|
|
6815
7135
|
typeImports.push({ specifier: reg.keyTypeImport, name: reg.keyType });
|
|
@@ -6834,24 +7154,24 @@ function buildProvidersTs(pattern) {
|
|
|
6834
7154
|
`export function build${pascal}RegistryProviders(opts?: ${optsType}): Provider[] {`
|
|
6835
7155
|
);
|
|
6836
7156
|
lines.push(" return [");
|
|
6837
|
-
for (const { reg, names } of registries) {
|
|
7157
|
+
for (const { reg, names: names2 } of registries) {
|
|
6838
7158
|
const factoryArgs = reg.entries.map((e, i) => `${camelArg(e.provider, i)}: ${e.provider}`).join(", ");
|
|
6839
7159
|
lines.push(" {");
|
|
6840
|
-
lines.push(` provide: ${
|
|
7160
|
+
lines.push(` provide: ${names2.tokenConst},`);
|
|
6841
7161
|
lines.push(` useFactory: (${factoryArgs}) => {`);
|
|
6842
|
-
lines.push(` const base: ${
|
|
7162
|
+
lines.push(` const base: ${names2.mapType} = new Map<${reg.keyType}, ${reg.valueType}>([`);
|
|
6843
7163
|
for (const [i, e] of reg.entries.entries()) {
|
|
6844
7164
|
lines.push(
|
|
6845
7165
|
` ['${e.key}' as ${reg.keyType}, ${camelArg(e.provider, i)}],`
|
|
6846
7166
|
);
|
|
6847
7167
|
}
|
|
6848
7168
|
lines.push(" ]);");
|
|
6849
|
-
lines.push(` if (opts?.${
|
|
6850
|
-
lines.push(` for (const [k, v] of Object.entries(opts.${
|
|
7169
|
+
lines.push(` if (opts?.${names2.overrideField}) {`);
|
|
7170
|
+
lines.push(` for (const [k, v] of Object.entries(opts.${names2.overrideField})) {`);
|
|
6851
7171
|
lines.push(` if (v !== undefined) base.set(k as ${reg.keyType}, v as ${reg.valueType});`);
|
|
6852
7172
|
lines.push(" }");
|
|
6853
7173
|
lines.push(" }");
|
|
6854
|
-
lines.push(` return base as ${
|
|
7174
|
+
lines.push(` return base as ${names2.mapType};`);
|
|
6855
7175
|
lines.push(" },");
|
|
6856
7176
|
const injectList = reg.entries.map((e) => e.provider).join(", ");
|
|
6857
7177
|
lines.push(` inject: [${injectList}],`);
|
|
@@ -6881,8 +7201,8 @@ function buildDispatcherTs(pattern) {
|
|
|
6881
7201
|
providerImports.push({ specifier: e.providerImport, name: e.provider });
|
|
6882
7202
|
}
|
|
6883
7203
|
}
|
|
6884
|
-
const tokenValues = registries.map(({ names }) =>
|
|
6885
|
-
const mapTypes = registries.map(({ names }) =>
|
|
7204
|
+
const tokenValues = registries.map(({ names: names2 }) => names2.tokenConst);
|
|
7205
|
+
const mapTypes = registries.map(({ names: names2 }) => names2.mapType);
|
|
6886
7206
|
const lines = [];
|
|
6887
7207
|
lines.push(HEADER5.trimEnd());
|
|
6888
7208
|
lines.push("");
|
|
@@ -6900,21 +7220,21 @@ function buildDispatcherTs(pattern) {
|
|
|
6900
7220
|
lines.push("@Injectable()");
|
|
6901
7221
|
lines.push(`export class ${className} {`);
|
|
6902
7222
|
lines.push(" constructor(");
|
|
6903
|
-
for (const { names } of registries) {
|
|
6904
|
-
const fieldName =
|
|
7223
|
+
for (const { names: names2 } of registries) {
|
|
7224
|
+
const fieldName = names2.method === "select" ? "registry" : `${camelFromMethod(names2.method)}Registry`;
|
|
6905
7225
|
lines.push(
|
|
6906
|
-
` @Inject(${
|
|
7226
|
+
` @Inject(${names2.tokenConst}) protected readonly ${fieldName}: ${names2.mapType},`
|
|
6907
7227
|
);
|
|
6908
7228
|
}
|
|
6909
7229
|
lines.push(" ) {}");
|
|
6910
7230
|
lines.push("");
|
|
6911
|
-
for (const { reg, names } of registries) {
|
|
6912
|
-
const fieldName =
|
|
7231
|
+
for (const { reg, names: names2 } of registries) {
|
|
7232
|
+
const fieldName = names2.method === "select" ? "registry" : `${camelFromMethod(names2.method)}Registry`;
|
|
6913
7233
|
for (const e of reg.entries) {
|
|
6914
|
-
lines.push(` ${
|
|
7234
|
+
lines.push(` ${names2.method}(key: '${e.key}'): ${e.provider};`);
|
|
6915
7235
|
}
|
|
6916
|
-
lines.push(` ${
|
|
6917
|
-
lines.push(` ${
|
|
7236
|
+
lines.push(` ${names2.method}(key: ${reg.keyType}): ${reg.valueType};`);
|
|
7237
|
+
lines.push(` ${names2.method}(key: ${reg.keyType}): ${reg.valueType} {`);
|
|
6918
7238
|
lines.push(` const entry = this.${fieldName}.get(key);`);
|
|
6919
7239
|
lines.push(
|
|
6920
7240
|
` if (!entry) throw new ${errorClass}(\`Unknown ${reg.keyType}: \${String(key)}\`);`
|
|
@@ -6956,7 +7276,7 @@ function buildModuleTs(pattern) {
|
|
|
6956
7276
|
typeImports.push({ specifier: reg.keyTypeImport, name: reg.keyType });
|
|
6957
7277
|
typeImports.push({ specifier: reg.valueTypeImport, name: reg.valueType });
|
|
6958
7278
|
}
|
|
6959
|
-
const tokenValues = registries.map(({ names }) =>
|
|
7279
|
+
const tokenValues = registries.map(({ names: names2 }) => names2.tokenConst);
|
|
6960
7280
|
const lines = [];
|
|
6961
7281
|
lines.push(HEADER5.trimEnd());
|
|
6962
7282
|
lines.push("");
|
|
@@ -6967,9 +7287,9 @@ function buildModuleTs(pattern) {
|
|
|
6967
7287
|
lines.push(`import { ${tokenValues.sort().join(", ")} } from './tokens.js';`);
|
|
6968
7288
|
lines.push("");
|
|
6969
7289
|
lines.push(`export interface ${optsType} {`);
|
|
6970
|
-
for (const { reg, names } of registries) {
|
|
7290
|
+
for (const { reg, names: names2 } of registries) {
|
|
6971
7291
|
lines.push(
|
|
6972
|
-
` ${
|
|
7292
|
+
` ${names2.overrideField}?: Partial<Record<${reg.keyType}, ${reg.valueType}>>;`
|
|
6973
7293
|
);
|
|
6974
7294
|
}
|
|
6975
7295
|
lines.push("}");
|
|
@@ -7083,7 +7403,7 @@ import fs8 from "fs";
|
|
|
7083
7403
|
import path11 from "path";
|
|
7084
7404
|
|
|
7085
7405
|
// src/parser/load-events.ts
|
|
7086
|
-
import { basename as basename2, resolve as
|
|
7406
|
+
import { basename as basename2, resolve as resolve4 } from "path";
|
|
7087
7407
|
function loadErrorToIssue2(error) {
|
|
7088
7408
|
const issues = [];
|
|
7089
7409
|
issues.push({
|
|
@@ -7113,7 +7433,7 @@ function stripYamlExt(file) {
|
|
|
7113
7433
|
function loadEvents(eventsDir, entityNames) {
|
|
7114
7434
|
const events = [];
|
|
7115
7435
|
const issues = [];
|
|
7116
|
-
const resolvedDir =
|
|
7436
|
+
const resolvedDir = resolve4(eventsDir);
|
|
7117
7437
|
let files;
|
|
7118
7438
|
try {
|
|
7119
7439
|
files = findYamlFiles(resolvedDir);
|
|
@@ -7755,6 +8075,564 @@ function validateEntityEmits(entities, events) {
|
|
|
7755
8075
|
return issues;
|
|
7756
8076
|
}
|
|
7757
8077
|
|
|
8078
|
+
// src/cli/shared/provider-module-generator.ts
|
|
8079
|
+
import { existsSync as existsSync9, mkdirSync as mkdirSync2, readFileSync as readFileSync7, statSync as statSync5, writeFileSync as writeFileSync2 } from "fs";
|
|
8080
|
+
import { dirname, isAbsolute as isAbsolute2, join as join11, resolve as resolve5 } from "path";
|
|
8081
|
+
function providerPascalCase(slug) {
|
|
8082
|
+
return slug.split("-").filter(Boolean).map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join("");
|
|
8083
|
+
}
|
|
8084
|
+
function providerConstantCase(slug) {
|
|
8085
|
+
return slug.replace(/-/g, "_").toUpperCase();
|
|
8086
|
+
}
|
|
8087
|
+
function providerModuleBanner(sourceYaml) {
|
|
8088
|
+
return `// @generated by @pattern-stack/codegen from ${sourceYaml} \u2014 DO NOT EDIT.
|
|
8089
|
+
// Hand edits are overwritten on re-emit. Regenerate with \`bun run codegen\`. To change this provider, edit its \`definitions/providers/*.yaml\`.`;
|
|
8090
|
+
}
|
|
8091
|
+
function generateProviderModule(def, sourceYaml) {
|
|
8092
|
+
const { slug } = def;
|
|
8093
|
+
const Pascal = providerPascalCase(slug);
|
|
8094
|
+
const CONST = providerConstantCase(slug);
|
|
8095
|
+
const strategy = parseImportRef(def.auth.strategy);
|
|
8096
|
+
const client = parseImportRef(def.client.class);
|
|
8097
|
+
const strategyToken = `${CONST}_AUTH_STRATEGY`;
|
|
8098
|
+
const clientToken = `${CONST}_CLIENT`;
|
|
8099
|
+
const surfaces = def.surfaces.join(", ");
|
|
8100
|
+
const title = def.display_name ? `${def.display_name} (\`${slug}\`)` : `\`${slug}\``;
|
|
8101
|
+
return `${providerModuleBanner(sourceYaml)}
|
|
8102
|
+
/**
|
|
8103
|
+
* Provider module for ${title}.
|
|
8104
|
+
*
|
|
8105
|
+
* Surfaces: ${surfaces}.
|
|
8106
|
+
*
|
|
8107
|
+
* Wires the declared auth strategy and API client under the provider-specific
|
|
8108
|
+
* DI tokens \`${strategyToken}\` and \`${clientToken}\`. Per the auth subsystem
|
|
8109
|
+
* contract, strategies are registered under provider-specific tokens rather
|
|
8110
|
+
* than a single \`AUTH_STRATEGY\`. Registry aggregation (RFC-0001 \xA73) is
|
|
8111
|
+
* emitted by a later codegen step.
|
|
8112
|
+
*/
|
|
8113
|
+
import { Module } from '@nestjs/common';
|
|
8114
|
+
import { ${strategy.exportName} } from '${strategy.path}';
|
|
8115
|
+
import { ${client.exportName} } from '${client.path}';
|
|
8116
|
+
|
|
8117
|
+
/** DI token for the \`${slug}\` provider's auth strategy. */
|
|
8118
|
+
export const ${strategyToken} = Symbol('${strategyToken}');
|
|
8119
|
+
/** DI token for the \`${slug}\` provider's API client. */
|
|
8120
|
+
export const ${clientToken} = Symbol('${clientToken}');
|
|
8121
|
+
|
|
8122
|
+
@Module({
|
|
8123
|
+
providers: [
|
|
8124
|
+
${strategy.exportName},
|
|
8125
|
+
${client.exportName},
|
|
8126
|
+
{ provide: ${strategyToken}, useExisting: ${strategy.exportName} },
|
|
8127
|
+
{ provide: ${clientToken}, useExisting: ${client.exportName} },
|
|
8128
|
+
],
|
|
8129
|
+
exports: [
|
|
8130
|
+
${strategy.exportName},
|
|
8131
|
+
${client.exportName},
|
|
8132
|
+
${strategyToken},
|
|
8133
|
+
${clientToken},
|
|
8134
|
+
],
|
|
8135
|
+
})
|
|
8136
|
+
export class ${Pascal}ProviderModule {}
|
|
8137
|
+
`;
|
|
8138
|
+
}
|
|
8139
|
+
function resolveTsconfigAliases(consumerRoot) {
|
|
8140
|
+
const tsconfigPath = join11(consumerRoot, "tsconfig.json");
|
|
8141
|
+
if (!existsSync9(tsconfigPath)) return null;
|
|
8142
|
+
let parsed;
|
|
8143
|
+
try {
|
|
8144
|
+
parsed = JSON.parse(stripJsonComments(readFileSync7(tsconfigPath, "utf-8")));
|
|
8145
|
+
} catch {
|
|
8146
|
+
return null;
|
|
8147
|
+
}
|
|
8148
|
+
const compilerOptions = parsed.compilerOptions ?? {};
|
|
8149
|
+
const baseUrl = compilerOptions.baseUrl ?? ".";
|
|
8150
|
+
const sourceRoot = isAbsolute2(baseUrl) ? baseUrl : resolve5(consumerRoot, baseUrl);
|
|
8151
|
+
const aliases = {};
|
|
8152
|
+
for (const [pattern, targets] of Object.entries(compilerOptions.paths ?? {})) {
|
|
8153
|
+
if (!Array.isArray(targets) || targets.length === 0) continue;
|
|
8154
|
+
const aliasKey = pattern.replace(/\/\*$/, "");
|
|
8155
|
+
const target = targets[0].replace(/\/\*$/, "");
|
|
8156
|
+
aliases[aliasKey] = isAbsolute2(target) ? target : resolve5(sourceRoot, target);
|
|
8157
|
+
}
|
|
8158
|
+
return { sourceRoot, aliases };
|
|
8159
|
+
}
|
|
8160
|
+
function stripJsonComments(input) {
|
|
8161
|
+
return input.replace(/\/\*[\s\S]*?\*\//g, "").replace(/(^|[^:])\/\/.*$/gm, "$1");
|
|
8162
|
+
}
|
|
8163
|
+
function generateProviderModules(opts) {
|
|
8164
|
+
const base = {
|
|
8165
|
+
providersDir: opts.providersDir,
|
|
8166
|
+
skipped: false,
|
|
8167
|
+
written: [],
|
|
8168
|
+
loadFailures: [],
|
|
8169
|
+
issues: []
|
|
8170
|
+
};
|
|
8171
|
+
if (!existsSync9(opts.providersDir) || !statSync5(opts.providersDir).isDirectory()) {
|
|
8172
|
+
return { ...base, skipped: true };
|
|
8173
|
+
}
|
|
8174
|
+
const files = findYamlFiles(opts.providersDir);
|
|
8175
|
+
if (files.length === 0) return { ...base, skipped: true };
|
|
8176
|
+
const { successes, failures } = loadProvidersFromYaml(files);
|
|
8177
|
+
const loaded = successes.map((s) => ({
|
|
8178
|
+
definition: s.definition,
|
|
8179
|
+
filePath: s.filePath
|
|
8180
|
+
}));
|
|
8181
|
+
const issues = failures.map((f) => ({
|
|
8182
|
+
severity: "error",
|
|
8183
|
+
type: "provider_load_failed",
|
|
8184
|
+
message: `${f.error}${f.details?.length ? ` \u2014 ${f.details.join("; ")}` : ""}`,
|
|
8185
|
+
path: f.filePath
|
|
8186
|
+
}));
|
|
8187
|
+
issues.push(
|
|
8188
|
+
...validateProviders(loaded, {
|
|
8189
|
+
entitySurfaces: opts.entitySurfaces,
|
|
8190
|
+
sourceRoot: opts.sourceRoot,
|
|
8191
|
+
aliases: opts.aliases,
|
|
8192
|
+
skipImportCheck: opts.skipImportCheck
|
|
8193
|
+
})
|
|
8194
|
+
);
|
|
8195
|
+
if (issues.some((i) => i.severity === "error")) {
|
|
8196
|
+
return { ...base, loadFailures: failures, issues };
|
|
8197
|
+
}
|
|
8198
|
+
const written = [];
|
|
8199
|
+
for (const { definition, filePath } of loaded) {
|
|
8200
|
+
const sourceYaml = relativeSource(filePath);
|
|
8201
|
+
const content = generateProviderModule(definition, sourceYaml);
|
|
8202
|
+
const outPath = join11(
|
|
8203
|
+
opts.outputRoot,
|
|
8204
|
+
definition.slug,
|
|
8205
|
+
`${definition.slug}.provider.module.ts`
|
|
8206
|
+
);
|
|
8207
|
+
if (!opts.dryRun) {
|
|
8208
|
+
writeIfChanged(outPath, content);
|
|
8209
|
+
}
|
|
8210
|
+
written.push(outPath);
|
|
8211
|
+
}
|
|
8212
|
+
return { ...base, written, loadFailures: failures, issues };
|
|
8213
|
+
}
|
|
8214
|
+
function writeIfChanged(outPath, content) {
|
|
8215
|
+
if (existsSync9(outPath) && readFileSync7(outPath, "utf-8") === content) return;
|
|
8216
|
+
mkdirSync2(dirname(outPath), { recursive: true });
|
|
8217
|
+
writeFileSync2(outPath, content);
|
|
8218
|
+
}
|
|
8219
|
+
function relativeSource(filePath) {
|
|
8220
|
+
const marker = "definitions/providers/";
|
|
8221
|
+
const idx = filePath.lastIndexOf(marker);
|
|
8222
|
+
if (idx !== -1) return filePath.slice(idx);
|
|
8223
|
+
const slash = filePath.lastIndexOf("/");
|
|
8224
|
+
return slash === -1 ? filePath : `definitions/providers/${filePath.slice(slash + 1)}`;
|
|
8225
|
+
}
|
|
8226
|
+
|
|
8227
|
+
// src/cli/shared/adapter-emission-generator.ts
|
|
8228
|
+
import {
|
|
8229
|
+
existsSync as existsSync10,
|
|
8230
|
+
mkdirSync as mkdirSync3,
|
|
8231
|
+
readFileSync as readFileSync8,
|
|
8232
|
+
statSync as statSync6,
|
|
8233
|
+
writeFileSync as writeFileSync3
|
|
8234
|
+
} from "fs";
|
|
8235
|
+
import { dirname as dirname2, join as join12 } from "path";
|
|
8236
|
+
var SURFACE_REGISTRY = {
|
|
8237
|
+
crm: {
|
|
8238
|
+
packageName: "@pattern-stack/codegen-crm",
|
|
8239
|
+
portType: "CrmPort",
|
|
8240
|
+
capabilitiesType: "CrmCapabilities",
|
|
8241
|
+
noCapsConst: "NO_CRM_CAPABILITIES",
|
|
8242
|
+
l2Ports: [
|
|
8243
|
+
{
|
|
8244
|
+
prop: "fields",
|
|
8245
|
+
type: "IFieldDefinitionReader",
|
|
8246
|
+
method: "list",
|
|
8247
|
+
params: "(_integrationId, _entity)",
|
|
8248
|
+
capFlag: "fieldDefinitions"
|
|
8249
|
+
},
|
|
8250
|
+
{
|
|
8251
|
+
prop: "picklists",
|
|
8252
|
+
type: "IPicklistReader",
|
|
8253
|
+
method: "values",
|
|
8254
|
+
params: "(_integrationId, _entity, _fieldId)",
|
|
8255
|
+
capFlag: "picklists"
|
|
8256
|
+
},
|
|
8257
|
+
{
|
|
8258
|
+
prop: "associations",
|
|
8259
|
+
type: "IAssociationReader",
|
|
8260
|
+
method: "list",
|
|
8261
|
+
params: "(_integrationId, _fromEntity, _fromId, _toEntity)",
|
|
8262
|
+
capFlag: "associations"
|
|
8263
|
+
}
|
|
8264
|
+
]
|
|
8265
|
+
},
|
|
8266
|
+
// Interaction surfaces (#416) are incremental-read with no L2 sub-ports — the
|
|
8267
|
+
// port composes L1 (auth + sources) + a capabilities descriptor whose only
|
|
8268
|
+
// field is `entities`; reads go through the change-source registry. So
|
|
8269
|
+
// `l2Ports: []` and the scaffold emits no L2 readers/stubs/capability flags.
|
|
8270
|
+
calendar: {
|
|
8271
|
+
packageName: "@pattern-stack/codegen-calendar",
|
|
8272
|
+
portType: "CalendarPort",
|
|
8273
|
+
capabilitiesType: "CalendarCapabilities",
|
|
8274
|
+
noCapsConst: "NO_CALENDAR_CAPABILITIES",
|
|
8275
|
+
l2Ports: []
|
|
8276
|
+
},
|
|
8277
|
+
mail: {
|
|
8278
|
+
packageName: "@pattern-stack/codegen-mail",
|
|
8279
|
+
portType: "MailPort",
|
|
8280
|
+
capabilitiesType: "MailCapabilities",
|
|
8281
|
+
noCapsConst: "NO_MAIL_CAPABILITIES",
|
|
8282
|
+
l2Ports: []
|
|
8283
|
+
},
|
|
8284
|
+
transcript: {
|
|
8285
|
+
packageName: "@pattern-stack/codegen-transcript",
|
|
8286
|
+
portType: "TranscriptPort",
|
|
8287
|
+
capabilitiesType: "TranscriptCapabilities",
|
|
8288
|
+
noCapsConst: "NO_TRANSCRIPT_CAPABILITIES",
|
|
8289
|
+
l2Ports: []
|
|
8290
|
+
}
|
|
8291
|
+
};
|
|
8292
|
+
var SCAFFOLD_SENTINEL = "// <CODEGEN-SCAFFOLD-V1>";
|
|
8293
|
+
function generatedBanner(sourceDesc) {
|
|
8294
|
+
return `// @generated by @pattern-stack/codegen from ${sourceDesc} \u2014 DO NOT EDIT.
|
|
8295
|
+
// Hand edits are overwritten on re-emit. Regenerate with \`bun run codegen\`.`;
|
|
8296
|
+
}
|
|
8297
|
+
function collectEntitiesBySurface(entities) {
|
|
8298
|
+
const bySurface = /* @__PURE__ */ new Map();
|
|
8299
|
+
for (const e of entities) {
|
|
8300
|
+
if (!e.surface) continue;
|
|
8301
|
+
const list = bySurface.get(e.surface) ?? [];
|
|
8302
|
+
list.push(e.entity.name);
|
|
8303
|
+
bySurface.set(e.surface, list);
|
|
8304
|
+
}
|
|
8305
|
+
for (const [surface, list] of bySurface) {
|
|
8306
|
+
bySurface.set(surface, [...list].sort());
|
|
8307
|
+
}
|
|
8308
|
+
return bySurface;
|
|
8309
|
+
}
|
|
8310
|
+
function names(providerSlug, surface) {
|
|
8311
|
+
const providerPascal = providerPascalCase(providerSlug);
|
|
8312
|
+
const providerConst = providerConstantCase(providerSlug);
|
|
8313
|
+
const surfacePascal = providerPascalCase(surface);
|
|
8314
|
+
const surfaceConst = providerConstantCase(surface);
|
|
8315
|
+
return {
|
|
8316
|
+
providerPascal,
|
|
8317
|
+
providerConst,
|
|
8318
|
+
surfacePascal,
|
|
8319
|
+
surfaceConst,
|
|
8320
|
+
adapterClass: `${providerPascal}${surfacePascal}Adapter`,
|
|
8321
|
+
adapterModuleClass: `${providerPascal}${surfacePascal}AdapterModule`,
|
|
8322
|
+
providerModuleClass: `${providerPascal}ProviderModule`,
|
|
8323
|
+
aggregatorClass: `${surfacePascal}AdaptersModule`,
|
|
8324
|
+
strategyToken: `${providerConst}_AUTH_STRATEGY`,
|
|
8325
|
+
clientToken: `${providerConst}_CLIENT`,
|
|
8326
|
+
contributionsToken: `${surfaceConst}_ADAPTER_CONTRIBUTIONS`,
|
|
8327
|
+
entitySourcesToken: `${surfaceConst}_ENTITY_SOURCES`
|
|
8328
|
+
};
|
|
8329
|
+
}
|
|
8330
|
+
function generateAdapterScaffold(def, surface, entities) {
|
|
8331
|
+
const spec = SURFACE_REGISTRY[surface];
|
|
8332
|
+
if (!spec) throw new Error(`no surface package for '${surface}'`);
|
|
8333
|
+
const n = names(def.slug, surface);
|
|
8334
|
+
const client = parseImportRef(def.client.class);
|
|
8335
|
+
const entitiesLiteral = entities.length ? `[${entities.map((e) => `'${e}'`).join(", ")}]` : "[]";
|
|
8336
|
+
const surfaceTypeImports = [
|
|
8337
|
+
spec.portType,
|
|
8338
|
+
...spec.l2Ports.map((p) => p.type),
|
|
8339
|
+
spec.capabilitiesType
|
|
8340
|
+
].map((t) => ` ${t},`).join("\n");
|
|
8341
|
+
const capabilityBody = [
|
|
8342
|
+
` ...${spec.noCapsConst},`,
|
|
8343
|
+
...spec.l2Ports.map((p) => ` ${p.capFlag}: true,`),
|
|
8344
|
+
` entities: ${entitiesLiteral},`
|
|
8345
|
+
].join("\n");
|
|
8346
|
+
const l2Members = spec.l2Ports.map(
|
|
8347
|
+
(p) => ` /** L2 \u2014 fill in the provider-specific implementation. */
|
|
8348
|
+
readonly ${p.prop}: ${p.type} = {
|
|
8349
|
+
${p.method}: async ${p.params} => {
|
|
8350
|
+
throw new Error('not implemented: ${n.adapterClass}.${p.prop}.${p.method}');
|
|
8351
|
+
},
|
|
8352
|
+
};`
|
|
8353
|
+
).join("\n\n");
|
|
8354
|
+
const l2Section = l2Members ? `
|
|
8355
|
+
${l2Members}
|
|
8356
|
+
` : "";
|
|
8357
|
+
return `${SCAFFOLD_SENTINEL}
|
|
8358
|
+
// Scaffolded once by @pattern-stack/codegen, then author-owned. Re-running
|
|
8359
|
+
// codegen detects the sentinel above and SKIPS this file \u2014 your edits are safe.
|
|
8360
|
+
// Source: definitions/providers/${def.slug}.yaml (surface: ${surface}).
|
|
8361
|
+
import { Inject, Injectable } from '@nestjs/common';
|
|
8362
|
+
import type {
|
|
8363
|
+
${surfaceTypeImports}
|
|
8364
|
+
} from '${spec.packageName}';
|
|
8365
|
+
import { ${spec.noCapsConst} } from '${spec.packageName}';
|
|
8366
|
+
import type {
|
|
8367
|
+
IAuthStrategy,
|
|
8368
|
+
IChangeSource,
|
|
8369
|
+
IEntityChangeSourceRegistry,
|
|
8370
|
+
} from '@pattern-stack/codegen/subsystems';
|
|
8371
|
+
import type { ${client.exportName} } from '${client.path}';
|
|
8372
|
+
import { ${n.strategyToken}, ${n.clientToken} } from '../../../providers/${def.slug}/${def.slug}.provider.module';
|
|
8373
|
+
import { ${n.entitySourcesToken} } from '../../${surface}-adapters.tokens';
|
|
8374
|
+
|
|
8375
|
+
@Injectable()
|
|
8376
|
+
export class ${n.adapterClass} implements ${spec.portType} {
|
|
8377
|
+
/** Declared capabilities. \`entities\` derives from \`surface: ${surface}\` entity YAML. */
|
|
8378
|
+
readonly capabilities: ${spec.capabilitiesType} = {
|
|
8379
|
+
${capabilityBody}
|
|
8380
|
+
};
|
|
8381
|
+
|
|
8382
|
+
constructor(
|
|
8383
|
+
@Inject(${n.strategyToken}) readonly auth: IAuthStrategy,
|
|
8384
|
+
@Inject(${n.clientToken}) private readonly client: ${client.exportName},
|
|
8385
|
+
@Inject(${n.entitySourcesToken}) readonly sources: IEntityChangeSourceRegistry,
|
|
8386
|
+
) {}
|
|
8387
|
+
${l2Section}
|
|
8388
|
+
/**
|
|
8389
|
+
* Per-entity change sources this adapter contributes to the ${surface}
|
|
8390
|
+
* registry (ADR-033 \`buildChangeSource\`), keyed by entity name. The
|
|
8391
|
+
* surface aggregator folds these into the \`IEntityChangeSourceRegistry\`
|
|
8392
|
+
* bound under \`${n.entitySourcesToken}\`. Author-owned \u2014 populate one entry
|
|
8393
|
+
* per entity in \`capabilities.entities\`.
|
|
8394
|
+
*/
|
|
8395
|
+
readonly changeSources: Record<string, IChangeSource<unknown>> = {};
|
|
8396
|
+
|
|
8397
|
+
// surface-only methods (optional on ${spec.portType}): add here
|
|
8398
|
+
}
|
|
8399
|
+
`;
|
|
8400
|
+
}
|
|
8401
|
+
function generateAdapterModule(def, surface) {
|
|
8402
|
+
const n = names(def.slug, surface);
|
|
8403
|
+
return `${generatedBanner(`definitions/providers/${def.slug}.yaml (surface: ${surface})`)}
|
|
8404
|
+
import { Module } from '@nestjs/common';
|
|
8405
|
+
import { ${n.providerModuleClass} } from '../../../providers/${def.slug}/${def.slug}.provider.module';
|
|
8406
|
+
import { ${n.adapterClass} } from './${def.slug}-${surface}.adapter';
|
|
8407
|
+
|
|
8408
|
+
@Module({
|
|
8409
|
+
imports: [${n.providerModuleClass}],
|
|
8410
|
+
providers: [${n.adapterClass}],
|
|
8411
|
+
exports: [${n.adapterClass}],
|
|
8412
|
+
})
|
|
8413
|
+
export class ${n.adapterModuleClass} {}
|
|
8414
|
+
`;
|
|
8415
|
+
}
|
|
8416
|
+
function generateAdaptersBarrel(surface, providerSlugs) {
|
|
8417
|
+
const lines = [...providerSlugs].sort().map((slug) => {
|
|
8418
|
+
const n = names(slug, surface);
|
|
8419
|
+
return `export { ${n.adapterModuleClass} } from './${slug}/${slug}-${surface}.adapter.module';`;
|
|
8420
|
+
}).join("\n");
|
|
8421
|
+
return `${generatedBanner(`definitions/providers/*.yaml (surface: ${surface})`)}
|
|
8422
|
+
${lines}
|
|
8423
|
+
`;
|
|
8424
|
+
}
|
|
8425
|
+
function generateSurfaceTokens(surface) {
|
|
8426
|
+
const n = names("__placeholder__", surface);
|
|
8427
|
+
return `${generatedBanner(`surface: ${surface}`)}
|
|
8428
|
+
import type { IChangeSource } from '@pattern-stack/codegen/subsystems';
|
|
8429
|
+
|
|
8430
|
+
/** The assembled list of every ${surface} adapter's contribution. */
|
|
8431
|
+
export const ${n.contributionsToken} = Symbol.for('@app/integrations/${surface}.adapter-contributions');
|
|
8432
|
+
|
|
8433
|
+
/** Resolved registry token \u2014 resolves to a C7 IEntityChangeSourceRegistry. */
|
|
8434
|
+
export const ${n.entitySourcesToken} = Symbol.for('@app/integrations/${surface}.entity-sources');
|
|
8435
|
+
|
|
8436
|
+
/** One provider-adapter's contribution to the surface registry. */
|
|
8437
|
+
export interface AdapterContribution {
|
|
8438
|
+
/** Provider slug. */
|
|
8439
|
+
provider: string;
|
|
8440
|
+
/** Entities this provider serves on this surface \u2192 their change sources. */
|
|
8441
|
+
sources: Record<string, IChangeSource<unknown>>;
|
|
8442
|
+
}
|
|
8443
|
+
`;
|
|
8444
|
+
}
|
|
8445
|
+
function generateSurfaceAggregator(surface, providerSlugs) {
|
|
8446
|
+
const n = names("__placeholder__", surface);
|
|
8447
|
+
const slugs = [...providerSlugs].sort();
|
|
8448
|
+
const per = slugs.map((slug) => names(slug, surface));
|
|
8449
|
+
const moduleClasses = per.map((p) => p.adapterModuleClass);
|
|
8450
|
+
const moduleImport = `import {
|
|
8451
|
+
${moduleClasses.join(",\n ")},
|
|
8452
|
+
} from './adapters';`;
|
|
8453
|
+
const adapterImports = slugs.map((slug) => {
|
|
8454
|
+
const p = names(slug, surface);
|
|
8455
|
+
return `import { ${p.adapterClass} } from './adapters/${slug}/${slug}-${surface}.adapter';`;
|
|
8456
|
+
}).join("\n");
|
|
8457
|
+
const contributionEntries = slugs.map((slug) => {
|
|
8458
|
+
const p = names(slug, surface);
|
|
8459
|
+
return ` { provider: '${slug}', sources: ${lowerFirst(p.adapterClass)}.changeSources },`;
|
|
8460
|
+
}).join("\n");
|
|
8461
|
+
const injectParams = slugs.map((slug) => {
|
|
8462
|
+
const p = names(slug, surface);
|
|
8463
|
+
return `${lowerFirst(p.adapterClass)}: ${p.adapterClass}`;
|
|
8464
|
+
}).join(", ");
|
|
8465
|
+
const injectTokens = per.map((p) => p.adapterClass).join(", ");
|
|
8466
|
+
return `${generatedBanner(`surface: ${surface}`)}
|
|
8467
|
+
import { Module } from '@nestjs/common';
|
|
8468
|
+
import {
|
|
8469
|
+
MemoryEntityChangeSourceRegistry,
|
|
8470
|
+
type IChangeSource,
|
|
8471
|
+
type IEntityChangeSourceRegistry,
|
|
8472
|
+
} from '@pattern-stack/codegen/subsystems';
|
|
8473
|
+
${moduleImport}
|
|
8474
|
+
${adapterImports}
|
|
8475
|
+
import {
|
|
8476
|
+
${n.contributionsToken},
|
|
8477
|
+
${n.entitySourcesToken},
|
|
8478
|
+
type AdapterContribution,
|
|
8479
|
+
} from './${surface}-adapters.tokens';
|
|
8480
|
+
|
|
8481
|
+
/**
|
|
8482
|
+
* Fold every adapter contribution into one entity-keyed registry. Each entity
|
|
8483
|
+
* resolves to exactly one change source; two providers serving the same entity
|
|
8484
|
+
* is an ambiguous-source boot error.
|
|
8485
|
+
*/
|
|
8486
|
+
function provide${n.surfacePascal}EntitySources(
|
|
8487
|
+
contribs: AdapterContribution[],
|
|
8488
|
+
): IEntityChangeSourceRegistry {
|
|
8489
|
+
const merged = new Map<string, IChangeSource<unknown>>();
|
|
8490
|
+
const owner = new Map<string, string>();
|
|
8491
|
+
for (const contrib of contribs ?? []) {
|
|
8492
|
+
for (const [entity, source] of Object.entries(contrib.sources)) {
|
|
8493
|
+
const prior = owner.get(entity);
|
|
8494
|
+
if (prior !== undefined) {
|
|
8495
|
+
throw new Error(
|
|
8496
|
+
\`entity '\${entity}' is served by both '\${prior}' and '\${contrib.provider}' \u2014 ambiguous change source\`,
|
|
8497
|
+
);
|
|
8498
|
+
}
|
|
8499
|
+
owner.set(entity, contrib.provider);
|
|
8500
|
+
merged.set(entity, source);
|
|
8501
|
+
}
|
|
8502
|
+
}
|
|
8503
|
+
return new MemoryEntityChangeSourceRegistry(merged);
|
|
8504
|
+
}
|
|
8505
|
+
|
|
8506
|
+
@Module({
|
|
8507
|
+
imports: [${moduleClasses.join(", ")}],
|
|
8508
|
+
providers: [
|
|
8509
|
+
{
|
|
8510
|
+
provide: ${n.contributionsToken},
|
|
8511
|
+
useFactory: (${injectParams}): AdapterContribution[] => [
|
|
8512
|
+
${contributionEntries}
|
|
8513
|
+
],
|
|
8514
|
+
inject: [${injectTokens}],
|
|
8515
|
+
},
|
|
8516
|
+
{
|
|
8517
|
+
provide: ${n.entitySourcesToken},
|
|
8518
|
+
useFactory: (contributions: AdapterContribution[]) =>
|
|
8519
|
+
provide${n.surfacePascal}EntitySources(contributions),
|
|
8520
|
+
inject: [${n.contributionsToken}],
|
|
8521
|
+
},
|
|
8522
|
+
],
|
|
8523
|
+
exports: [${n.entitySourcesToken}, ${n.contributionsToken}],
|
|
8524
|
+
})
|
|
8525
|
+
export class ${n.aggregatorClass} {}
|
|
8526
|
+
`;
|
|
8527
|
+
}
|
|
8528
|
+
function lowerFirst(s) {
|
|
8529
|
+
return s.length ? s[0].toLowerCase() + s.slice(1) : s;
|
|
8530
|
+
}
|
|
8531
|
+
function generateTypedView(surface, providerSlugs, entities) {
|
|
8532
|
+
const surfacePascal = providerPascalCase(surface);
|
|
8533
|
+
const slugs = [...providerSlugs].sort();
|
|
8534
|
+
const ents = [...entities].sort();
|
|
8535
|
+
const providerUnion = slugs.length ? slugs.map((s) => `'${s}'`).join(" | ") : "never";
|
|
8536
|
+
const entityUnion = ents.length ? ents.map((e) => `'${e}'`).join(" | ") : "never";
|
|
8537
|
+
const mapEntries = slugs.map((s) => ` ${jsKey(s)}: ${surfacePascal}Entity;`).join("\n");
|
|
8538
|
+
return `${generatedBanner(`surface: ${surface}`)}
|
|
8539
|
+
/**
|
|
8540
|
+
* Per-consumer typed view for the \`${surface}\` surface. Surface-scoped unions
|
|
8541
|
+
* + a (provider, entity) validity map for compile-time-checked consumer
|
|
8542
|
+
* use-cases. Replaces ADR-033.2's per-entity provider tuples (RFC-0001 \xA75/\xA78).
|
|
8543
|
+
*/
|
|
8544
|
+
|
|
8545
|
+
/** Providers whose \`surfaces[]\` include \`${surface}\`. */
|
|
8546
|
+
export type ${surfacePascal}Provider = ${providerUnion};
|
|
8547
|
+
|
|
8548
|
+
/** Entities declared with \`surface: ${surface}\`. */
|
|
8549
|
+
export type ${surfacePascal}Entity = ${entityUnion};
|
|
8550
|
+
|
|
8551
|
+
/** Valid entities per provider on this surface. */
|
|
8552
|
+
export interface ${surfacePascal}ProviderEntities {
|
|
8553
|
+
${mapEntries || " // no providers serve this surface yet"}
|
|
8554
|
+
}
|
|
8555
|
+
`;
|
|
8556
|
+
}
|
|
8557
|
+
function jsKey(key) {
|
|
8558
|
+
return /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(key) ? key : `'${key}'`;
|
|
8559
|
+
}
|
|
8560
|
+
function emitAdapters(opts) {
|
|
8561
|
+
const result = {
|
|
8562
|
+
written: [],
|
|
8563
|
+
scaffoldsWritten: [],
|
|
8564
|
+
scaffoldsSkipped: [],
|
|
8565
|
+
skippedSurfaces: []
|
|
8566
|
+
};
|
|
8567
|
+
const entitiesBySurface = collectEntitiesBySurface(opts.entities);
|
|
8568
|
+
const bySurface = /* @__PURE__ */ new Map();
|
|
8569
|
+
for (const { definition } of opts.providers) {
|
|
8570
|
+
for (const surface of definition.surfaces) {
|
|
8571
|
+
if (!SURFACE_REGISTRY[surface]) {
|
|
8572
|
+
result.skippedSurfaces.push({
|
|
8573
|
+
provider: definition.slug,
|
|
8574
|
+
surface,
|
|
8575
|
+
reason: `no surface package for '${surface}' yet \u2014 add codegen-${surface} (Track C) to emit its adapters`
|
|
8576
|
+
});
|
|
8577
|
+
continue;
|
|
8578
|
+
}
|
|
8579
|
+
const list = bySurface.get(surface) ?? [];
|
|
8580
|
+
list.push(definition.slug);
|
|
8581
|
+
bySurface.set(surface, list);
|
|
8582
|
+
}
|
|
8583
|
+
}
|
|
8584
|
+
const defBySlug = new Map(opts.providers.map((p) => [p.definition.slug, p.definition]));
|
|
8585
|
+
for (const [surface, slugs] of bySurface) {
|
|
8586
|
+
const surfaceDir = join12(opts.outputRoot, surface);
|
|
8587
|
+
const adaptersDir = join12(surfaceDir, "adapters");
|
|
8588
|
+
for (const slug of slugs) {
|
|
8589
|
+
const def = defBySlug.get(slug);
|
|
8590
|
+
const providerDir = join12(adaptersDir, slug);
|
|
8591
|
+
const scaffoldPath = join12(providerDir, `${slug}-${surface}.adapter.ts`);
|
|
8592
|
+
const modulePath = join12(providerDir, `${slug}-${surface}.adapter.module.ts`);
|
|
8593
|
+
if (existsSync10(scaffoldPath)) {
|
|
8594
|
+
result.scaffoldsSkipped.push(scaffoldPath);
|
|
8595
|
+
} else {
|
|
8596
|
+
const content = generateAdapterScaffold(
|
|
8597
|
+
def,
|
|
8598
|
+
surface,
|
|
8599
|
+
entitiesBySurface.get(surface) ?? []
|
|
8600
|
+
);
|
|
8601
|
+
if (!opts.dryRun) writeFile(scaffoldPath, content);
|
|
8602
|
+
result.scaffoldsWritten.push(scaffoldPath);
|
|
8603
|
+
}
|
|
8604
|
+
const moduleContent = generateAdapterModule(def, surface);
|
|
8605
|
+
if (!opts.dryRun) writeIfChanged2(modulePath, moduleContent);
|
|
8606
|
+
result.written.push(modulePath);
|
|
8607
|
+
}
|
|
8608
|
+
const barrelPath = join12(adaptersDir, "index.ts");
|
|
8609
|
+
const tokensPath = join12(surfaceDir, `${surface}-adapters.tokens.ts`);
|
|
8610
|
+
const aggregatorPath = join12(surfaceDir, `${surface}-adapters.module.ts`);
|
|
8611
|
+
const typedViewPath = join12(surfaceDir, "types.generated.ts");
|
|
8612
|
+
const files = [
|
|
8613
|
+
[barrelPath, generateAdaptersBarrel(surface, slugs)],
|
|
8614
|
+
[tokensPath, generateSurfaceTokens(surface)],
|
|
8615
|
+
[aggregatorPath, generateSurfaceAggregator(surface, slugs)],
|
|
8616
|
+
[typedViewPath, generateTypedView(surface, slugs, entitiesBySurface.get(surface) ?? [])]
|
|
8617
|
+
];
|
|
8618
|
+
for (const [path34, content] of files) {
|
|
8619
|
+
if (!opts.dryRun) writeIfChanged2(path34, content);
|
|
8620
|
+
result.written.push(path34);
|
|
8621
|
+
}
|
|
8622
|
+
}
|
|
8623
|
+
return result;
|
|
8624
|
+
}
|
|
8625
|
+
function writeFile(outPath, content) {
|
|
8626
|
+
mkdirSync3(dirname2(outPath), { recursive: true });
|
|
8627
|
+
writeFileSync3(outPath, content);
|
|
8628
|
+
}
|
|
8629
|
+
function writeIfChanged2(outPath, content) {
|
|
8630
|
+
if (existsSync10(outPath) && statSync6(outPath).isFile() && readFileSync8(outPath, "utf-8") === content) {
|
|
8631
|
+
return;
|
|
8632
|
+
}
|
|
8633
|
+
writeFile(outPath, content);
|
|
8634
|
+
}
|
|
8635
|
+
|
|
7758
8636
|
// src/cli/shared/events-path.ts
|
|
7759
8637
|
import path12 from "path";
|
|
7760
8638
|
var FALLBACK = "events";
|
|
@@ -8264,6 +9142,92 @@ var EntityNewCommand = class extends Command2 {
|
|
|
8264
9142
|
}
|
|
8265
9143
|
}
|
|
8266
9144
|
}
|
|
9145
|
+
let providerResult = null;
|
|
9146
|
+
try {
|
|
9147
|
+
const providersDir = ctx.config?.paths?.providers != null ? path13.resolve(
|
|
9148
|
+
ctx.cwd,
|
|
9149
|
+
ctx.config.paths.providers
|
|
9150
|
+
) : path13.resolve(ctx.cwd, "definitions/providers");
|
|
9151
|
+
const providerOutputRoot = path13.resolve(
|
|
9152
|
+
ctx.cwd,
|
|
9153
|
+
backendSrcForHandlers,
|
|
9154
|
+
"integrations/providers"
|
|
9155
|
+
);
|
|
9156
|
+
const entitySurfaces = fs9.existsSync(entitiesDir) ? collectEntitySurfaces(
|
|
9157
|
+
loadEntitiesFromYaml(findYamlFiles(entitiesDir)).successes.map(
|
|
9158
|
+
(s) => s.definition
|
|
9159
|
+
)
|
|
9160
|
+
) : /* @__PURE__ */ new Set();
|
|
9161
|
+
const tsAliases = resolveTsconfigAliases(ctx.cwd);
|
|
9162
|
+
providerResult = generateProviderModules({
|
|
9163
|
+
providersDir,
|
|
9164
|
+
outputRoot: providerOutputRoot,
|
|
9165
|
+
entitySurfaces,
|
|
9166
|
+
sourceRoot: tsAliases?.sourceRoot,
|
|
9167
|
+
aliases: tsAliases?.aliases,
|
|
9168
|
+
skipImportCheck: tsAliases === null
|
|
9169
|
+
});
|
|
9170
|
+
if (!providerResult.skipped && !isJsonMode()) {
|
|
9171
|
+
for (const issue of providerResult.issues) {
|
|
9172
|
+
printError(`provider codegen: ${issue.message}`);
|
|
9173
|
+
}
|
|
9174
|
+
if (providerResult.issues.length === 0) {
|
|
9175
|
+
printInfo(
|
|
9176
|
+
`provider modules regenerated (${providerResult.written.length}) \u2192 ${providerOutputRoot}`
|
|
9177
|
+
);
|
|
9178
|
+
}
|
|
9179
|
+
}
|
|
9180
|
+
if (providerResult.issues.some((i) => i.severity === "error") && !this.continueOnError) {
|
|
9181
|
+
return 1;
|
|
9182
|
+
}
|
|
9183
|
+
} catch (err) {
|
|
9184
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
9185
|
+
if (!isJsonMode()) {
|
|
9186
|
+
printWarning(`provider codegen failed \u2014 ${msg}`);
|
|
9187
|
+
}
|
|
9188
|
+
}
|
|
9189
|
+
try {
|
|
9190
|
+
if (providerResult && !providerResult.skipped && providerResult.issues.length === 0) {
|
|
9191
|
+
const providersDir = providerResult.providersDir;
|
|
9192
|
+
const adapterOutputRoot = path13.resolve(
|
|
9193
|
+
ctx.cwd,
|
|
9194
|
+
backendSrcForHandlers,
|
|
9195
|
+
"integrations"
|
|
9196
|
+
);
|
|
9197
|
+
const entityDefs = fs9.existsSync(entitiesDir) ? loadEntitiesFromYaml(findYamlFiles(entitiesDir)).successes.map(
|
|
9198
|
+
(s) => s.definition
|
|
9199
|
+
) : [];
|
|
9200
|
+
const loadedProviders = loadProvidersFromYaml(
|
|
9201
|
+
findYamlFiles(providersDir)
|
|
9202
|
+
).successes.map((s) => ({
|
|
9203
|
+
definition: s.definition,
|
|
9204
|
+
filePath: s.filePath
|
|
9205
|
+
}));
|
|
9206
|
+
const adapterResult = emitAdapters({
|
|
9207
|
+
providers: loadedProviders,
|
|
9208
|
+
entities: entityDefs,
|
|
9209
|
+
outputRoot: adapterOutputRoot
|
|
9210
|
+
});
|
|
9211
|
+
if (!isJsonMode()) {
|
|
9212
|
+
if (adapterResult.written.length || adapterResult.scaffoldsWritten.length) {
|
|
9213
|
+
printInfo(
|
|
9214
|
+
`adapter codegen: ${adapterResult.scaffoldsWritten.length} scaffold(s) + ${adapterResult.written.length} @generated \u2192 ${adapterOutputRoot}`
|
|
9215
|
+
);
|
|
9216
|
+
}
|
|
9217
|
+
for (const s of adapterResult.scaffoldsSkipped) {
|
|
9218
|
+
printInfo(`skipped scaffold ${s} (author-owned)`);
|
|
9219
|
+
}
|
|
9220
|
+
for (const s of adapterResult.skippedSurfaces) {
|
|
9221
|
+
printWarning(`adapter codegen: ${s.reason} (provider ${s.provider})`);
|
|
9222
|
+
}
|
|
9223
|
+
}
|
|
9224
|
+
}
|
|
9225
|
+
} catch (err) {
|
|
9226
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
9227
|
+
if (!isJsonMode()) {
|
|
9228
|
+
printWarning(`adapter codegen failed \u2014 ${msg}`);
|
|
9229
|
+
}
|
|
9230
|
+
}
|
|
8267
9231
|
if (isJsonMode()) {
|
|
8268
9232
|
printJson({
|
|
8269
9233
|
command: "entity new",
|
|
@@ -8627,22 +9591,22 @@ function localsToHygenArgs2(locals) {
|
|
|
8627
9591
|
];
|
|
8628
9592
|
}
|
|
8629
9593
|
|
|
8630
|
-
// src/cli/shared/
|
|
9594
|
+
// src/cli/shared/integration-scaffold-locals.ts
|
|
8631
9595
|
import path16 from "path";
|
|
8632
|
-
function
|
|
9596
|
+
function resolveIntegrationScaffoldLocals(input) {
|
|
8633
9597
|
const { cwd, config } = input;
|
|
8634
9598
|
void input.fileExists;
|
|
8635
|
-
const
|
|
9599
|
+
const integrationBlock = config?.integration ?? {};
|
|
8636
9600
|
const subsystemsRoot = resolveSubsystemsRootFromConfig(cwd, config);
|
|
8637
9601
|
const configPath = path16.resolve(cwd, "codegen.config.yaml");
|
|
8638
9602
|
const schemaPath = path16.resolve(
|
|
8639
9603
|
subsystemsRoot,
|
|
8640
|
-
"
|
|
8641
|
-
"
|
|
9604
|
+
"integration",
|
|
9605
|
+
"integration-audit.schema.ts"
|
|
8642
9606
|
);
|
|
8643
9607
|
return {
|
|
8644
9608
|
appName: path16.basename(cwd),
|
|
8645
|
-
multiTenant: normaliseMultiTenant3(
|
|
9609
|
+
multiTenant: normaliseMultiTenant3(integrationBlock.multi_tenant),
|
|
8646
9610
|
configPath,
|
|
8647
9611
|
schemaPath
|
|
8648
9612
|
};
|
|
@@ -8781,19 +9745,16 @@ function localsToHygenArgs6(locals) {
|
|
|
8781
9745
|
// src/cli/shared/auth-integrations-scaffold-locals.ts
|
|
8782
9746
|
import path20 from "path";
|
|
8783
9747
|
var FALLBACK_BACKEND_SRC4 = "src";
|
|
8784
|
-
var SHARED_DIR_NAME = "shared";
|
|
8785
9748
|
var DEFAULT_MODULES_DIR = "modules";
|
|
8786
9749
|
var DEFAULT_DEFINITIONS_DIR = "definitions/entities";
|
|
8787
9750
|
function resolveAuthIntegrationsScaffoldLocals(input) {
|
|
8788
9751
|
const { cwd, config } = input;
|
|
8789
9752
|
const backendSrc = typeof config?.paths?.backend_src === "string" && config.paths.backend_src.length > 0 ? config.paths.backend_src : FALLBACK_BACKEND_SRC4;
|
|
8790
9753
|
const pathsAny = config?.paths;
|
|
8791
|
-
const sharedConfigured = pathsAny?.shared;
|
|
8792
|
-
const sharedRoot = typeof sharedConfigured === "string" && sharedConfigured.length > 0 ? path20.resolve(cwd, sharedConfigured) : path20.resolve(cwd, backendSrc, SHARED_DIR_NAME);
|
|
8793
9754
|
const modulesConfigured = pathsAny?.modules_dir;
|
|
8794
9755
|
const vendorRoot = typeof modulesConfigured === "string" && modulesConfigured.length > 0 ? path20.resolve(cwd, modulesConfigured) : path20.resolve(cwd, backendSrc, DEFAULT_MODULES_DIR);
|
|
8795
9756
|
const entitiesConfigured = typeof pathsAny?.entities === "string" && pathsAny.entities.length > 0 ? pathsAny.entities : typeof pathsAny?.entities_dir === "string" && pathsAny.entities_dir.length > 0 ? pathsAny.entities_dir : null;
|
|
8796
|
-
const definitionsPath = entitiesConfigured !== null ? path20.resolve(cwd, entitiesConfigured, "
|
|
9757
|
+
const definitionsPath = entitiesConfigured !== null ? path20.resolve(cwd, entitiesConfigured, "connection.yaml") : path20.resolve(cwd, DEFAULT_DEFINITIONS_DIR, "connection.yaml");
|
|
8797
9758
|
const appModulePath = path20.resolve(cwd, backendSrc, "app.module.ts");
|
|
8798
9759
|
let authModuleRegistered = false;
|
|
8799
9760
|
const appModuleSource = input.readFile(appModulePath);
|
|
@@ -8803,7 +9764,6 @@ function resolveAuthIntegrationsScaffoldLocals(input) {
|
|
|
8803
9764
|
return {
|
|
8804
9765
|
appName: path20.basename(cwd),
|
|
8805
9766
|
appModulePath,
|
|
8806
|
-
sharedRoot,
|
|
8807
9767
|
vendorRoot,
|
|
8808
9768
|
definitionsPath,
|
|
8809
9769
|
authModuleRegistered
|
|
@@ -9002,7 +9962,7 @@ function backendFileFilter(backend, subsystemName) {
|
|
|
9002
9962
|
if (subsystemName === "events" && file === "domain-events.schema.ts") {
|
|
9003
9963
|
return false;
|
|
9004
9964
|
}
|
|
9005
|
-
if (subsystemName === "
|
|
9965
|
+
if (subsystemName === "integration" && file === "integration-audit.schema.ts") {
|
|
9006
9966
|
return false;
|
|
9007
9967
|
}
|
|
9008
9968
|
if (subsystemName === "auth" && file === "auth-oauth-state.schema.ts") {
|
|
@@ -9126,7 +10086,7 @@ var SubsystemInstallCommand = class extends Command3 {
|
|
|
9126
10086
|
json: isJsonMode(),
|
|
9127
10087
|
forceConfig: this.forceConfig
|
|
9128
10088
|
}) : null;
|
|
9129
|
-
const
|
|
10089
|
+
const integrationScaffold = desc3.name === "integration" ? runIntegrationScaffold(ctx.cwd, ctx.config, {
|
|
9130
10090
|
dryRun: this.dryRun,
|
|
9131
10091
|
json: isJsonMode(),
|
|
9132
10092
|
forceConfig: this.forceConfig
|
|
@@ -9158,9 +10118,9 @@ var SubsystemInstallCommand = class extends Command3 {
|
|
|
9158
10118
|
);
|
|
9159
10119
|
return 1;
|
|
9160
10120
|
}
|
|
9161
|
-
if (
|
|
10121
|
+
if (integrationScaffold?.configBlockOutcome === "parse-error") {
|
|
9162
10122
|
printError(
|
|
9163
|
-
"codegen.config.yaml is not valid YAML: refusing to inject
|
|
10123
|
+
"codegen.config.yaml is not valid YAML: refusing to inject integration config block. Fix the YAML and re-run."
|
|
9164
10124
|
);
|
|
9165
10125
|
return 1;
|
|
9166
10126
|
}
|
|
@@ -9198,7 +10158,7 @@ var SubsystemInstallCommand = class extends Command3 {
|
|
|
9198
10158
|
},
|
|
9199
10159
|
...jobsScaffold ? { scaffold: jobsScaffold } : {},
|
|
9200
10160
|
...eventsScaffold ? { scaffold: eventsScaffold } : {},
|
|
9201
|
-
...
|
|
10161
|
+
...integrationScaffold ? { scaffold: integrationScaffold } : {},
|
|
9202
10162
|
...bridgeScaffold ? { scaffold: bridgeScaffold } : {},
|
|
9203
10163
|
...observabilityScaffold ? { scaffold: observabilityScaffold } : {},
|
|
9204
10164
|
...authScaffold ? { scaffold: authScaffold } : {}
|
|
@@ -9226,11 +10186,11 @@ var SubsystemInstallCommand = class extends Command3 {
|
|
|
9226
10186
|
console.log(` ${theme.muted(icons.arrow)} ${path22.relative(ctx.cwd, p) || p}`);
|
|
9227
10187
|
}
|
|
9228
10188
|
}
|
|
9229
|
-
if (
|
|
10189
|
+
if (integrationScaffold?.planned?.length) {
|
|
9230
10190
|
printInfo(
|
|
9231
|
-
`
|
|
10191
|
+
`Integration scaffold \u2014 ${integrationScaffold.planned.length} template targets`
|
|
9232
10192
|
);
|
|
9233
|
-
for (const p of
|
|
10193
|
+
for (const p of integrationScaffold.planned) {
|
|
9234
10194
|
console.log(` ${theme.muted(icons.arrow)} ${path22.relative(ctx.cwd, p) || p}`);
|
|
9235
10195
|
}
|
|
9236
10196
|
}
|
|
@@ -9289,14 +10249,14 @@ var SubsystemInstallCommand = class extends Command3 {
|
|
|
9289
10249
|
);
|
|
9290
10250
|
}
|
|
9291
10251
|
}
|
|
9292
|
-
if (
|
|
9293
|
-
if (
|
|
10252
|
+
if (integrationScaffold) {
|
|
10253
|
+
if (integrationScaffold.ok) {
|
|
9294
10254
|
printSuccess(
|
|
9295
|
-
`
|
|
10255
|
+
`integration scaffold applied (config block, schema)`
|
|
9296
10256
|
);
|
|
9297
10257
|
} else {
|
|
9298
10258
|
printWarning(
|
|
9299
|
-
`
|
|
10259
|
+
`integration scaffold (Hygen) failed \u2014 runtime files were written; re-run after fixing: ${integrationScaffold.error ?? "unknown error"}`
|
|
9300
10260
|
);
|
|
9301
10261
|
}
|
|
9302
10262
|
}
|
|
@@ -9343,7 +10303,7 @@ var SubsystemInstallCommand = class extends Command3 {
|
|
|
9343
10303
|
}
|
|
9344
10304
|
if (desc3.name === "observability") {
|
|
9345
10305
|
printInfo(
|
|
9346
|
-
"Register `ObservabilityModule.forRoot()` AFTER Events/Jobs/Bridge/
|
|
10306
|
+
"Register `ObservabilityModule.forRoot()` AFTER Events/Jobs/Bridge/Integration in app.module.ts"
|
|
9347
10307
|
);
|
|
9348
10308
|
} else if (desc3.name === "auth") {
|
|
9349
10309
|
printInfo("auth subsystem installed.");
|
|
@@ -9357,9 +10317,9 @@ var SubsystemInstallCommand = class extends Command3 {
|
|
|
9357
10317
|
`Register ${capitalize(desc3.name)}Module.forRoot({ backend: '${backend}' }) in your app.module.ts`
|
|
9358
10318
|
);
|
|
9359
10319
|
}
|
|
9360
|
-
if (desc3.name === "
|
|
10320
|
+
if (desc3.name === "integration") {
|
|
9361
10321
|
printInfo(
|
|
9362
|
-
`Per-entity: register
|
|
10322
|
+
`Per-entity: register ExecuteIntegrationUseCase + your IChangeSource/IIntegrationSink bindings in a feature module (see IntegrationModule docstring).`
|
|
9363
10323
|
);
|
|
9364
10324
|
}
|
|
9365
10325
|
return 0;
|
|
@@ -9372,7 +10332,7 @@ var SubsystemInstallCommand = class extends Command3 {
|
|
|
9372
10332
|
* `codegen project init`. This method just invokes the
|
|
9373
10333
|
* `subsystem/openapi-config` Hygen action to inject the `openapi:`
|
|
9374
10334
|
* block into `codegen.config.yaml`, honoring the same `--force-config`
|
|
9375
|
-
* semantics as jobs/events/
|
|
10335
|
+
* semantics as jobs/events/integration/bridge.
|
|
9376
10336
|
*/
|
|
9377
10337
|
async executeOpenApiConfig(ctx) {
|
|
9378
10338
|
const configPath = path22.join(ctx.cwd, "codegen.config.yaml");
|
|
@@ -9435,9 +10395,9 @@ var SubsystemInstallCommand = class extends Command3 {
|
|
|
9435
10395
|
*
|
|
9436
10396
|
* Source is `examples/auth-integrations/`, NOT `runtime/subsystems/`,
|
|
9437
10397
|
* so this method short-circuits the `copyRuntime` flow. It vendors the
|
|
9438
|
-
* adapters tree + the canonical `
|
|
10398
|
+
* adapters tree + the canonical `connection.yaml`, then invokes the
|
|
9439
10399
|
* `subsystem auth-integrations` Hygen action to append the
|
|
9440
|
-
* `
|
|
10400
|
+
* `ConnectionsAuthModule` TODO to `app.module.ts`.
|
|
9441
10401
|
*
|
|
9442
10402
|
* Idempotent: pre-existing files are skipped unless `--force` is set.
|
|
9443
10403
|
*/
|
|
@@ -9485,7 +10445,7 @@ var SubsystemInstallCommand = class extends Command3 {
|
|
|
9485
10445
|
}
|
|
9486
10446
|
if (this.dryRun) {
|
|
9487
10447
|
printInfo(
|
|
9488
|
-
`Dry run \u2014 auth-integrations would vendor adapters +
|
|
10448
|
+
`Dry run \u2014 auth-integrations would vendor adapters + connection.yaml + append TODO`
|
|
9489
10449
|
);
|
|
9490
10450
|
for (const p of scaffold.planned) {
|
|
9491
10451
|
console.log(
|
|
@@ -9501,14 +10461,14 @@ var SubsystemInstallCommand = class extends Command3 {
|
|
|
9501
10461
|
);
|
|
9502
10462
|
if (scaffold.authModuleRegistered === false) {
|
|
9503
10463
|
printWarning(
|
|
9504
|
-
"AuthModule.forRoot(...) not detected in app.module.ts. Run `cdp subsystem install auth` first \u2014
|
|
10464
|
+
"AuthModule.forRoot(...) not detected in app.module.ts. Run `cdp subsystem install auth` first \u2014 ConnectionsAuthModule requires ENCRYPTION_KEY from it."
|
|
9505
10465
|
);
|
|
9506
10466
|
}
|
|
9507
10467
|
printInfo("auth-integrations starter vendored.");
|
|
9508
10468
|
printInfo("Next steps:");
|
|
9509
|
-
printInfo(" 1. Run `cdp entity new
|
|
10469
|
+
printInfo(" 1. Run `cdp entity new connection` to scaffold the codegen layer (apps/api/src/modules/connections/connection.service) the adapters import.");
|
|
9510
10470
|
printInfo(" 2. Ensure AuthModule.forRoot(...) is registered in AppModule (run `cdp subsystem install auth` if not).");
|
|
9511
|
-
printInfo(" 3. Wire
|
|
10471
|
+
printInfo(" 3. Wire ConnectionsAuthModule into AppModule (see TODO appended to app.module.ts).");
|
|
9512
10472
|
return 0;
|
|
9513
10473
|
}
|
|
9514
10474
|
};
|
|
@@ -9689,8 +10649,8 @@ function runEventsScaffold(cwd, config, opts) {
|
|
|
9689
10649
|
}
|
|
9690
10650
|
return { ok: true, planned, configBlockOutcome };
|
|
9691
10651
|
}
|
|
9692
|
-
function
|
|
9693
|
-
const locals =
|
|
10652
|
+
function runIntegrationScaffold(cwd, config, opts) {
|
|
10653
|
+
const locals = resolveIntegrationScaffoldLocals({
|
|
9694
10654
|
cwd,
|
|
9695
10655
|
config,
|
|
9696
10656
|
fileExists: (p) => fs11.existsSync(p)
|
|
@@ -9701,7 +10661,7 @@ function runSyncScaffold(cwd, config, opts) {
|
|
|
9701
10661
|
];
|
|
9702
10662
|
const configBlockOutcome = planConfigBlockAction(
|
|
9703
10663
|
locals.configPath,
|
|
9704
|
-
"
|
|
10664
|
+
"integration",
|
|
9705
10665
|
opts.forceConfig
|
|
9706
10666
|
);
|
|
9707
10667
|
if (configBlockOutcome === "parse-error") {
|
|
@@ -9712,7 +10672,7 @@ function runSyncScaffold(cwd, config, opts) {
|
|
|
9712
10672
|
}
|
|
9713
10673
|
const result = invokeHygen({
|
|
9714
10674
|
generator: "subsystem",
|
|
9715
|
-
action: "
|
|
10675
|
+
action: "integration",
|
|
9716
10676
|
cwd,
|
|
9717
10677
|
args: localsToHygenArgs3(locals),
|
|
9718
10678
|
// Suppress Hygen stdout in JSON mode so it doesn't corrupt the JSON output.
|
|
@@ -9728,9 +10688,9 @@ function runSyncScaffold(cwd, config, opts) {
|
|
|
9728
10688
|
}
|
|
9729
10689
|
const configResult = runConfigBlockAction({
|
|
9730
10690
|
cwd,
|
|
9731
|
-
actionFolder: "
|
|
10691
|
+
actionFolder: "integration-config",
|
|
9732
10692
|
configPath: locals.configPath,
|
|
9733
|
-
subsystem: "
|
|
10693
|
+
subsystem: "integration",
|
|
9734
10694
|
outcome: configBlockOutcome,
|
|
9735
10695
|
json: opts.json
|
|
9736
10696
|
});
|
|
@@ -9981,18 +10941,18 @@ function runAuthIntegrationsScaffold(cwd, config, opts) {
|
|
|
9981
10941
|
error: `auth-integrations starter source missing: ${examplesRoot}`
|
|
9982
10942
|
};
|
|
9983
10943
|
}
|
|
9984
|
-
const adaptersSrc = path22.join(examplesRoot, "runtime", "
|
|
9985
|
-
const adaptersDest = path22.join(locals.vendorRoot, "
|
|
9986
|
-
const
|
|
10944
|
+
const adaptersSrc = path22.join(examplesRoot, "runtime", "connections");
|
|
10945
|
+
const adaptersDest = path22.join(locals.vendorRoot, "connections");
|
|
10946
|
+
const connectionYamlSrc = path22.join(
|
|
9987
10947
|
examplesRoot,
|
|
9988
10948
|
"definitions",
|
|
9989
10949
|
"entities",
|
|
9990
|
-
"
|
|
10950
|
+
"connection.yaml"
|
|
9991
10951
|
);
|
|
9992
|
-
const
|
|
10952
|
+
const connectionYamlDest = locals.definitionsPath;
|
|
9993
10953
|
const planned = [
|
|
9994
10954
|
adaptersDest,
|
|
9995
|
-
|
|
10955
|
+
connectionYamlDest,
|
|
9996
10956
|
locals.appModulePath
|
|
9997
10957
|
];
|
|
9998
10958
|
if (opts.dryRun) {
|
|
@@ -10012,18 +10972,18 @@ function runAuthIntegrationsScaffold(cwd, config, opts) {
|
|
|
10012
10972
|
let yamlWritten = false;
|
|
10013
10973
|
let yamlSkipped = false;
|
|
10014
10974
|
try {
|
|
10015
|
-
if (fs11.existsSync(
|
|
10975
|
+
if (fs11.existsSync(connectionYamlDest) && !opts.force) {
|
|
10016
10976
|
yamlSkipped = true;
|
|
10017
|
-
} else if (fs11.existsSync(
|
|
10018
|
-
fs11.mkdirSync(path22.dirname(
|
|
10019
|
-
fs11.copyFileSync(
|
|
10977
|
+
} else if (fs11.existsSync(connectionYamlSrc)) {
|
|
10978
|
+
fs11.mkdirSync(path22.dirname(connectionYamlDest), { recursive: true });
|
|
10979
|
+
fs11.copyFileSync(connectionYamlSrc, connectionYamlDest);
|
|
10020
10980
|
yamlWritten = true;
|
|
10021
10981
|
}
|
|
10022
10982
|
} catch (err) {
|
|
10023
10983
|
return {
|
|
10024
10984
|
ok: false,
|
|
10025
10985
|
planned,
|
|
10026
|
-
error: `failed to vendor
|
|
10986
|
+
error: `failed to vendor connection.yaml: ${err instanceof Error ? err.message : String(err)}`,
|
|
10027
10987
|
authModuleRegistered: locals.authModuleRegistered
|
|
10028
10988
|
};
|
|
10029
10989
|
}
|
|
@@ -10039,16 +10999,16 @@ function runAuthIntegrationsScaffold(cwd, config, opts) {
|
|
|
10039
10999
|
ok: false,
|
|
10040
11000
|
planned,
|
|
10041
11001
|
error: result.stderr?.trim() || "hygen exited non-zero",
|
|
10042
|
-
written: adapterCopy.written.concat(yamlWritten ? [
|
|
10043
|
-
skipped: adapterCopy.skipped.concat(yamlSkipped ? [
|
|
11002
|
+
written: adapterCopy.written.concat(yamlWritten ? [connectionYamlDest] : []),
|
|
11003
|
+
skipped: adapterCopy.skipped.concat(yamlSkipped ? [connectionYamlDest] : []),
|
|
10044
11004
|
authModuleRegistered: locals.authModuleRegistered
|
|
10045
11005
|
};
|
|
10046
11006
|
}
|
|
10047
11007
|
return {
|
|
10048
11008
|
ok: true,
|
|
10049
11009
|
planned,
|
|
10050
|
-
written: adapterCopy.written.concat(yamlWritten ? [
|
|
10051
|
-
skipped: adapterCopy.skipped.concat(yamlSkipped ? [
|
|
11010
|
+
written: adapterCopy.written.concat(yamlWritten ? [connectionYamlDest] : []),
|
|
11011
|
+
skipped: adapterCopy.skipped.concat(yamlSkipped ? [connectionYamlDest] : []),
|
|
10052
11012
|
authModuleRegistered: locals.authModuleRegistered
|
|
10053
11013
|
};
|
|
10054
11014
|
}
|
|
@@ -10151,7 +11111,7 @@ var SubsystemRemoveCommand = class extends Command3 {
|
|
|
10151
11111
|
"auth-integrations is vendored under <modules>/integrations/ alongside the codegen-emitted entity layer \u2014 not auto-removable here."
|
|
10152
11112
|
);
|
|
10153
11113
|
printInfo(
|
|
10154
|
-
"To uninstall: remove the integrations/ directory and the
|
|
11114
|
+
"To uninstall: remove the integrations/ directory and the ConnectionsAuthModule registration from app.module.ts by hand."
|
|
10155
11115
|
);
|
|
10156
11116
|
return 1;
|
|
10157
11117
|
}
|
|
@@ -10270,11 +11230,11 @@ var VENDORED_RUNTIME_FILES = [
|
|
|
10270
11230
|
// Ambient tenant scope — imported by base-repository.ts (scopePredicate)
|
|
10271
11231
|
{ runtime: "base-classes/tenant-context.ts", target: "src/shared/base-classes/tenant-context.ts" },
|
|
10272
11232
|
{ runtime: "base-classes/base-service.ts", target: "src/shared/base-classes/base-service.ts" },
|
|
10273
|
-
{ runtime: "base-classes/
|
|
10274
|
-
{ runtime: "base-classes/
|
|
10275
|
-
// Inbound-
|
|
10276
|
-
{ runtime: "base-classes/
|
|
10277
|
-
{ runtime: "base-classes/junction-
|
|
11233
|
+
{ runtime: "base-classes/integrated-entity-repository.ts", target: "src/shared/base-classes/integrated-entity-repository.ts" },
|
|
11234
|
+
{ runtime: "base-classes/integrated-entity-service.ts", target: "src/shared/base-classes/integrated-entity-service.ts" },
|
|
11235
|
+
// Inbound-integration write surface (#374) — deps of integrated/junction repos
|
|
11236
|
+
{ runtime: "base-classes/integration-upsert-config.ts", target: "src/shared/base-classes/integration-upsert-config.ts" },
|
|
11237
|
+
{ runtime: "base-classes/junction-integration-repository.ts", target: "src/shared/base-classes/junction-integration-repository.ts" },
|
|
10278
11238
|
{ runtime: "base-classes/activity-entity-repository.ts", target: "src/shared/base-classes/activity-entity-repository.ts" },
|
|
10279
11239
|
{ runtime: "base-classes/activity-entity-service.ts", target: "src/shared/base-classes/activity-entity-service.ts" },
|
|
10280
11240
|
{ runtime: "base-classes/metadata-entity-repository.ts", target: "src/shared/base-classes/metadata-entity-repository.ts" },
|
|
@@ -10539,7 +11499,7 @@ function exampleEntityYaml() {
|
|
|
10539
11499
|
#
|
|
10540
11500
|
# entity:
|
|
10541
11501
|
# name: account
|
|
10542
|
-
# pattern:
|
|
11502
|
+
# pattern: Integrated # Base | Integrated | Activity | Metadata | Knowledge (or app-defined)
|
|
10543
11503
|
#
|
|
10544
11504
|
# fields:
|
|
10545
11505
|
# name:
|
|
@@ -10587,7 +11547,7 @@ var REQUIRED_ALIASES = {
|
|
|
10587
11547
|
"@modules/*": ["./src/modules/*"],
|
|
10588
11548
|
"@generated/*": ["./src/generated/*"]
|
|
10589
11549
|
};
|
|
10590
|
-
function
|
|
11550
|
+
function stripJsonComments2(raw) {
|
|
10591
11551
|
let out = "";
|
|
10592
11552
|
let i = 0;
|
|
10593
11553
|
let inString = false;
|
|
@@ -10635,7 +11595,7 @@ var REQUIRED_COMPILER_OPTIONS = {
|
|
|
10635
11595
|
function mergeTsconfig(raw) {
|
|
10636
11596
|
let parsed;
|
|
10637
11597
|
try {
|
|
10638
|
-
parsed = JSON.parse(
|
|
11598
|
+
parsed = JSON.parse(stripJsonComments2(raw));
|
|
10639
11599
|
} catch (err) {
|
|
10640
11600
|
return {
|
|
10641
11601
|
content: raw,
|
|
@@ -11105,8 +12065,8 @@ function ensureMainSwaggerBlock(sourceFile, opts) {
|
|
|
11105
12065
|
/^import\s+\{\s*([^}]+)\s*\}\s+from\s+['"]([^'"]+)['"]\s*;?\s*$/
|
|
11106
12066
|
);
|
|
11107
12067
|
if (match) {
|
|
11108
|
-
const
|
|
11109
|
-
ensureImport(sourceFile, match[2],
|
|
12068
|
+
const names2 = match[1].split(",").map((s) => s.trim()).filter(Boolean);
|
|
12069
|
+
ensureImport(sourceFile, match[2], names2);
|
|
11110
12070
|
} else {
|
|
11111
12071
|
sourceFile.insertStatements(0, importLine);
|
|
11112
12072
|
}
|
|
@@ -12112,10 +13072,10 @@ var ProjectInitCommand = class extends Command7 {
|
|
|
12112
13072
|
};
|
|
12113
13073
|
function askConfirm(question) {
|
|
12114
13074
|
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
12115
|
-
return new Promise((
|
|
13075
|
+
return new Promise((resolve6) => {
|
|
12116
13076
|
rl.question(`${question} [Y/n] `, (answer) => {
|
|
12117
13077
|
rl.close();
|
|
12118
|
-
|
|
13078
|
+
resolve6(answer.trim().toLowerCase() !== "n");
|
|
12119
13079
|
});
|
|
12120
13080
|
});
|
|
12121
13081
|
}
|
|
@@ -13722,7 +14682,7 @@ var junction_default = junctionNoun;
|
|
|
13722
14682
|
// src/cli/commands/events.ts
|
|
13723
14683
|
import fs20 from "fs";
|
|
13724
14684
|
import path32 from "path";
|
|
13725
|
-
import
|
|
14685
|
+
import ts3 from "typescript";
|
|
13726
14686
|
import { Command as Command11, Option as Option11 } from "clipanion";
|
|
13727
14687
|
function scanSourceFileForConsumers(sourceFile, filePath, eventType) {
|
|
13728
14688
|
const tier2 = [];
|
|
@@ -13736,13 +14696,13 @@ function scanSourceFileForConsumers(sourceFile, filePath, eventType) {
|
|
|
13736
14696
|
}
|
|
13737
14697
|
function checkImport(node) {
|
|
13738
14698
|
const moduleSpec = node.moduleSpecifier;
|
|
13739
|
-
if (
|
|
14699
|
+
if (ts3.isStringLiteral(moduleSpec) && moduleSpec.text.includes("subsystems/bridge")) {
|
|
13740
14700
|
hasEventFlowImport = true;
|
|
13741
14701
|
}
|
|
13742
14702
|
const clause = node.importClause;
|
|
13743
14703
|
if (!clause) return;
|
|
13744
14704
|
const named = clause.namedBindings;
|
|
13745
|
-
if (named &&
|
|
14705
|
+
if (named && ts3.isNamedImports(named)) {
|
|
13746
14706
|
for (const el of named.elements) {
|
|
13747
14707
|
const name = el.name.text;
|
|
13748
14708
|
if (name === "EventFlowService" || name === "EVENT_FLOW") {
|
|
@@ -13752,11 +14712,11 @@ function scanSourceFileForConsumers(sourceFile, filePath, eventType) {
|
|
|
13752
14712
|
}
|
|
13753
14713
|
}
|
|
13754
14714
|
function checkCall(node) {
|
|
13755
|
-
if (!
|
|
14715
|
+
if (!ts3.isPropertyAccessExpression(node.expression)) return;
|
|
13756
14716
|
const methodName = node.expression.name.text;
|
|
13757
14717
|
if (methodName !== "publishAndStart" && methodName !== "subscribe") return;
|
|
13758
14718
|
const firstArg = node.arguments[0];
|
|
13759
|
-
if (!firstArg || !
|
|
14719
|
+
if (!firstArg || !ts3.isStringLiteralLike(firstArg)) return;
|
|
13760
14720
|
if (firstArg.text !== eventType) return;
|
|
13761
14721
|
const receiverText = node.expression.expression.getText(sourceFile);
|
|
13762
14722
|
if (methodName === "publishAndStart") {
|
|
@@ -13777,24 +14737,24 @@ function scanSourceFileForConsumers(sourceFile, filePath, eventType) {
|
|
|
13777
14737
|
function findEnclosingClassName(node) {
|
|
13778
14738
|
let cur = node.parent;
|
|
13779
14739
|
while (cur) {
|
|
13780
|
-
if (
|
|
13781
|
-
if (
|
|
14740
|
+
if (ts3.isClassDeclaration(cur) && cur.name) return cur.name.text;
|
|
14741
|
+
if (ts3.isClassExpression(cur) && cur.name) return cur.name.text;
|
|
13782
14742
|
cur = cur.parent;
|
|
13783
14743
|
}
|
|
13784
14744
|
return null;
|
|
13785
14745
|
}
|
|
13786
14746
|
function checkDecoratorOn(member) {
|
|
13787
|
-
const decorators =
|
|
14747
|
+
const decorators = ts3.canHaveDecorators(member) ? ts3.getDecorators(member) ?? [] : [];
|
|
13788
14748
|
for (const decorator of decorators) {
|
|
13789
14749
|
const call = decorator.expression;
|
|
13790
|
-
if (!
|
|
13791
|
-
if (!
|
|
14750
|
+
if (!ts3.isCallExpression(call)) continue;
|
|
14751
|
+
if (!ts3.isIdentifier(call.expression)) continue;
|
|
13792
14752
|
if (call.expression.text !== "OnEvent") continue;
|
|
13793
14753
|
const firstArg = call.arguments[0];
|
|
13794
|
-
if (!firstArg || !
|
|
14754
|
+
if (!firstArg || !ts3.isStringLiteralLike(firstArg)) continue;
|
|
13795
14755
|
if (firstArg.text !== eventType) continue;
|
|
13796
14756
|
const className = findEnclosingClassName(member) ?? "<anonymous>";
|
|
13797
|
-
const memberName =
|
|
14757
|
+
const memberName = ts3.isIdentifier(member.name) ? member.name.text : member.name.getText(sourceFile);
|
|
13798
14758
|
tier1.push({
|
|
13799
14759
|
kind: "on-event",
|
|
13800
14760
|
siteLabel: `${className}.${memberName} @OnEvent('${eventType}')`,
|
|
@@ -13804,12 +14764,12 @@ function scanSourceFileForConsumers(sourceFile, filePath, eventType) {
|
|
|
13804
14764
|
}
|
|
13805
14765
|
}
|
|
13806
14766
|
function visit(node) {
|
|
13807
|
-
if (
|
|
13808
|
-
if (
|
|
13809
|
-
if (
|
|
14767
|
+
if (ts3.isImportDeclaration(node)) checkImport(node);
|
|
14768
|
+
if (ts3.isCallExpression(node)) checkCall(node);
|
|
14769
|
+
if (ts3.isMethodDeclaration(node) || ts3.isPropertyDeclaration(node)) {
|
|
13810
14770
|
checkDecoratorOn(node);
|
|
13811
14771
|
}
|
|
13812
|
-
|
|
14772
|
+
ts3.forEachChild(node, visit);
|
|
13813
14773
|
}
|
|
13814
14774
|
visit(sourceFile);
|
|
13815
14775
|
return { tier2, tier1, hasEventFlowImport };
|
|
@@ -13821,12 +14781,12 @@ function scanDirectoryForConsumers(rootDir, eventType) {
|
|
|
13821
14781
|
let hasEventFlowImport = false;
|
|
13822
14782
|
for (const filePath of files) {
|
|
13823
14783
|
const text2 = fs20.readFileSync(filePath, "utf8");
|
|
13824
|
-
const sourceFile =
|
|
14784
|
+
const sourceFile = ts3.createSourceFile(
|
|
13825
14785
|
filePath,
|
|
13826
14786
|
text2,
|
|
13827
|
-
|
|
14787
|
+
ts3.ScriptTarget.Latest,
|
|
13828
14788
|
true,
|
|
13829
|
-
|
|
14789
|
+
ts3.ScriptKind.TS
|
|
13830
14790
|
);
|
|
13831
14791
|
const result = scanSourceFileForConsumers(sourceFile, filePath, eventType);
|
|
13832
14792
|
tier2.push(...result.tier2);
|
|
@@ -14240,12 +15200,12 @@ async function summary9(ctx) {
|
|
|
14240
15200
|
await reloadRegistry(ctx);
|
|
14241
15201
|
} catch {
|
|
14242
15202
|
}
|
|
14243
|
-
const
|
|
15203
|
+
const names2 = getOrchestrationPatternNames();
|
|
14244
15204
|
return {
|
|
14245
15205
|
title: "orchestration",
|
|
14246
15206
|
body: [
|
|
14247
|
-
`patterns: ${
|
|
14248
|
-
...
|
|
15207
|
+
`patterns: ${names2.length}`,
|
|
15208
|
+
...names2.length > 0 ? [` ${names2.join(", ")}`] : []
|
|
14249
15209
|
]
|
|
14250
15210
|
};
|
|
14251
15211
|
}
|
|
@@ -14294,8 +15254,8 @@ var update_default = UpdateShortcut;
|
|
|
14294
15254
|
// src/cli/index.ts
|
|
14295
15255
|
function readVersion() {
|
|
14296
15256
|
try {
|
|
14297
|
-
const pkgPath =
|
|
14298
|
-
const pkg = JSON.parse(
|
|
15257
|
+
const pkgPath = join13(import.meta.dirname, "..", "..", "package.json");
|
|
15258
|
+
const pkg = JSON.parse(readFileSync9(pkgPath, "utf-8"));
|
|
14299
15259
|
return typeof pkg.version === "string" ? pkg.version : "0.0.0";
|
|
14300
15260
|
} catch {
|
|
14301
15261
|
return "0.0.0";
|