@pattern-stack/codegen 0.10.0 → 0.11.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 +135 -0
- package/README.md +5 -5
- package/consumer-skills/codegen/SKILL.md +2 -2
- package/consumer-skills/events/typed-bus-and-outbox.md +1 -1
- 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 +64 -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/bridge/bridge.module.d.ts +0 -1
- package/dist/runtime/subsystems/bridge/bridge.module.js +294 -710
- package/dist/runtime/subsystems/bridge/bridge.module.js.map +1 -1
- package/dist/runtime/subsystems/bridge/index.d.ts +0 -1
- package/dist/runtime/subsystems/bridge/index.js +248 -664
- package/dist/runtime/subsystems/bridge/index.js.map +1 -1
- package/dist/runtime/subsystems/events/event-bus.drizzle-backend.js +18 -10
- package/dist/runtime/subsystems/events/event-bus.drizzle-backend.js.map +1 -1
- package/dist/runtime/subsystems/events/events.module.js +43 -244
- package/dist/runtime/subsystems/events/events.module.js.map +1 -1
- package/dist/runtime/subsystems/events/index.d.ts +0 -1
- package/dist/runtime/subsystems/events/index.js +39 -241
- package/dist/runtime/subsystems/events/index.js.map +1 -1
- package/dist/runtime/subsystems/index.d.ts +7 -7
- package/dist/runtime/subsystems/index.js +222 -839
- 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/{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 +28 -0
- package/dist/runtime/subsystems/{sync → integration}/index.js +171 -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 +47 -0
- package/dist/runtime/subsystems/integration/integration.tokens.js +18 -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/bullmq.config.d.ts +22 -3
- package/dist/runtime/subsystems/jobs/bullmq.config.js.map +1 -1
- package/dist/runtime/subsystems/jobs/index.d.ts +1 -4
- package/dist/runtime/subsystems/jobs/index.js +87 -506
- package/dist/runtime/subsystems/jobs/index.js.map +1 -1
- package/dist/runtime/subsystems/jobs/job-orchestrator.bullmq-backend.js.map +1 -1
- package/dist/runtime/subsystems/jobs/job-worker.bullmq-backend.js +3 -0
- package/dist/runtime/subsystems/jobs/job-worker.bullmq-backend.js.map +1 -1
- package/dist/runtime/subsystems/jobs/job-worker.module.d.ts +10 -3
- package/dist/runtime/subsystems/jobs/job-worker.module.js +248 -664
- package/dist/runtime/subsystems/jobs/job-worker.module.js.map +1 -1
- package/dist/runtime/subsystems/jobs/jobs-domain.module.d.ts +0 -1
- package/dist/runtime/subsystems/jobs/jobs-domain.module.js +89 -391
- package/dist/runtime/subsystems/jobs/jobs-domain.module.js.map +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 +412 -302
- package/dist/src/cli/index.js.map +1 -1
- package/dist/src/index.d.ts +22 -22
- package/dist/src/index.js +191 -191
- 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 +1 -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/events/event-bus.drizzle-backend.ts +32 -10
- package/runtime/subsystems/events/events.module.ts +38 -6
- package/runtime/subsystems/events/index.ts +7 -1
- package/runtime/subsystems/index.ts +11 -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/{sync/execute-sync.use-case.ts → integration/execute-integration.use-case.ts} +40 -40
- package/runtime/subsystems/{sync → integration}/index.ts +47 -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 +49 -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/jobs/bullmq.config.ts +23 -3
- package/runtime/subsystems/jobs/index.ts +13 -8
- package/runtime/subsystems/jobs/job-worker.bullmq-backend.ts +5 -2
- package/runtime/subsystems/jobs/job-worker.module.ts +27 -7
- package/runtime/subsystems/jobs/jobs-domain.module.ts +27 -2
- 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/backend/modules/core/{sync-source.providers.ejs.t → integration-source.providers.ejs.t} +2 -2
- package/templates/entity/new/clean-lite-ps/entity.ejs.t +1 -1
- package/templates/entity/new/clean-lite-ps/module.ejs.t +1 -1
- package/templates/entity/new/clean-lite-ps/prompt-extension.js +33 -33
- 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/events/domain-events.schema.ejs.t +43 -2
- 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/subsystem/sync/sync-audit.schema.ejs.t +0 -192
- package/templates/subsystem/sync-config/prompt.js +0 -22
- /package/dist/runtime/base-classes/{sync-upsert-config.js.map → integration-upsert-config.js.map} +0 -0
- /package/dist/runtime/subsystems/auth/protocols/{integration-store.js.map → connection-store.js.map} +0 -0
- /package/dist/runtime/subsystems/{sync/sync-change-source.protocol.js.map → integration/integration-change-source.protocol.js.map} +0 -0
- /package/dist/runtime/subsystems/{sync/sync-cursor-store.protocol.js.map → integration/integration-cursor-store.protocol.js.map} +0 -0
- /package/dist/runtime/subsystems/{sync/sync-loopback.protocol.js.map → integration/integration-loopback.protocol.js.map} +0 -0
- /package/dist/runtime/subsystems/{sync/sync-middleware.protocol.js.map → integration/integration-middleware.protocol.js.map} +0 -0
- /package/dist/runtime/subsystems/{sync/sync-run-recorder.protocol.js.map → integration/integration-run-recorder.protocol.js.map} +0 -0
- /package/dist/runtime/subsystems/{sync/sync-sink.protocol.js.map → integration/integration-sink.protocol.js.map} +0 -0
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
---
|
|
2
|
+
to: "<%= schemaPath %>"
|
|
3
|
+
force: true
|
|
4
|
+
---
|
|
5
|
+
<%- typeof generatedBanner !== 'undefined' ? generatedBanner : '' %>
|
|
6
|
+
/**
|
|
7
|
+
* Drizzle schema for the integration subsystem audit/observability tables (SYNC-1).
|
|
8
|
+
*
|
|
9
|
+
* Three tables model end-to-end integration observability, keyed by the single
|
|
10
|
+
* port every integration adapter implements (`IChangeSource<T>` from SYNC-2):
|
|
11
|
+
*
|
|
12
|
+
* - `integration_subscriptions` — owns the cursor per
|
|
13
|
+
* `(connection_id, adapter, domain, external_ref)` tuple. Addressed
|
|
14
|
+
* by id by `ICursorStore` (SYNC-3/SYNC-4).
|
|
15
|
+
* - `integration_runs` — per-run audit log: start/complete, status,
|
|
16
|
+
* cursor before/after, counts, direction + action.
|
|
17
|
+
* - `integration_run_items` — per-record change log with structured
|
|
18
|
+
* `changed_fields` jsonb (ADR-0003). The `FieldDiff` type alias
|
|
19
|
+
* is owned by the integration subsystem's runtime protocol
|
|
20
|
+
* (`integration-field-diff.protocol.ts` from SYNC-2).
|
|
21
|
+
*
|
|
22
|
+
* ## `tenant_id` columns — always emitted
|
|
23
|
+
*
|
|
24
|
+
* `tenant_id` is emitted as a nullable text column on all three tables
|
|
25
|
+
* REGARDLESS of `integration.multi_tenant` — the runtime integration code (cursor store +
|
|
26
|
+
* run recorder) references `tenant_id` unconditionally, so a `multi_tenant:
|
|
27
|
+
* false` consumer that omitted the column failed to type-check (the column
|
|
28
|
+
* was referenced but absent). The `INTEGRATION_MULTI_TENANT` DI flag (SYNC-6) gates
|
|
29
|
+
* non-null *enforcement* at runtime; it does not gate the column's existence
|
|
30
|
+
* (mirrors the jobs subsystem). Under `multi_tenant: false` the column simply
|
|
31
|
+
* stays null.
|
|
32
|
+
*
|
|
33
|
+
* See SYNC-1 / SYNC-6 in epic #60 for the decision rationale.
|
|
34
|
+
*/
|
|
35
|
+
import {
|
|
36
|
+
pgEnum,
|
|
37
|
+
pgTable,
|
|
38
|
+
uuid,
|
|
39
|
+
text,
|
|
40
|
+
jsonb,
|
|
41
|
+
integer,
|
|
42
|
+
boolean,
|
|
43
|
+
timestamp,
|
|
44
|
+
index,
|
|
45
|
+
uniqueIndex,
|
|
46
|
+
} from 'drizzle-orm/pg-core';
|
|
47
|
+
import type { InferSelectModel } from 'drizzle-orm';
|
|
48
|
+
|
|
49
|
+
// NOTE: the `FieldDiff` type alias is imported from the runtime protocol
|
|
50
|
+
// shipped by `subsystem install integration`. If you moved that file, fix this
|
|
51
|
+
// import to point at the new location.
|
|
52
|
+
import type { FieldDiff } from './integration-field-diff.protocol';
|
|
53
|
+
|
|
54
|
+
// ─── Enums ──────────────────────────────────────────────────────────────────
|
|
55
|
+
|
|
56
|
+
export const integrationRunDirectionEnum = pgEnum('integration_run_direction', [
|
|
57
|
+
'inbound',
|
|
58
|
+
'outbound',
|
|
59
|
+
]);
|
|
60
|
+
|
|
61
|
+
export const integrationRunActionEnum = pgEnum('integration_run_action', [
|
|
62
|
+
'poll',
|
|
63
|
+
'cdc',
|
|
64
|
+
'webhook',
|
|
65
|
+
'manual',
|
|
66
|
+
'writeback',
|
|
67
|
+
]);
|
|
68
|
+
|
|
69
|
+
export const integrationRunStatusEnum = pgEnum('integration_run_status', [
|
|
70
|
+
'running',
|
|
71
|
+
'success',
|
|
72
|
+
'no_changes',
|
|
73
|
+
'failed',
|
|
74
|
+
]);
|
|
75
|
+
|
|
76
|
+
export const integrationRunItemOperationEnum = pgEnum('integration_run_item_operation', [
|
|
77
|
+
'created',
|
|
78
|
+
'updated',
|
|
79
|
+
'deleted',
|
|
80
|
+
'noop',
|
|
81
|
+
]);
|
|
82
|
+
|
|
83
|
+
export const integrationRunItemStatusEnum = pgEnum('integration_run_item_status', [
|
|
84
|
+
'success',
|
|
85
|
+
'failed',
|
|
86
|
+
'skipped',
|
|
87
|
+
]);
|
|
88
|
+
|
|
89
|
+
// ─── integration_subscriptions ─────────────────────────────────────────────────────
|
|
90
|
+
|
|
91
|
+
export const integrationSubscriptions = pgTable(
|
|
92
|
+
'integration_subscriptions',
|
|
93
|
+
{
|
|
94
|
+
id: uuid('id').primaryKey().defaultRandom(),
|
|
95
|
+
connectionId: text('connection_id').notNull(),
|
|
96
|
+
adapter: text('adapter').notNull(),
|
|
97
|
+
domain: text('domain').notNull(),
|
|
98
|
+
externalRef: text('external_ref'),
|
|
99
|
+
enabled: boolean('enabled').notNull().default(true),
|
|
100
|
+
config: jsonb('config').notNull().default({}).$type<Record<string, unknown>>(),
|
|
101
|
+
cursor: jsonb('cursor').$type<unknown>(),
|
|
102
|
+
lastIntegrationAt: timestamp('last_integration_at', { withTimezone: true }),
|
|
103
|
+
tenantId: text('tenant_id'), // always emitted — the runtime integration code (cursor store + run recorder) references tenant_id unconditionally; INTEGRATION_MULTI_TENANT gates enforcement, not the column's existence (mirrors jobs)
|
|
104
|
+
createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),
|
|
105
|
+
updatedAt: timestamp('updated_at', { withTimezone: true }).notNull().defaultNow(),
|
|
106
|
+
},
|
|
107
|
+
(t) => ({
|
|
108
|
+
uqIntegrationSubscriptionTuple: uniqueIndex('uq_integration_subscriptions_tuple').on(
|
|
109
|
+
t.connectionId,
|
|
110
|
+
t.adapter,
|
|
111
|
+
t.domain,
|
|
112
|
+
t.externalRef,
|
|
113
|
+
),
|
|
114
|
+
idxIntegrationSubscriptionsEnabledLastIntegration: index(
|
|
115
|
+
'idx_integration_subscriptions_enabled_last_integration',
|
|
116
|
+
).on(t.enabled, t.lastIntegrationAt),
|
|
117
|
+
}),
|
|
118
|
+
);
|
|
119
|
+
|
|
120
|
+
export type IntegrationSubscriptionRow = InferSelectModel<typeof integrationSubscriptions>;
|
|
121
|
+
|
|
122
|
+
// ─── integration_runs ──────────────────────────────────────────────────────────────
|
|
123
|
+
|
|
124
|
+
export const integrationRuns = pgTable(
|
|
125
|
+
'integration_runs',
|
|
126
|
+
{
|
|
127
|
+
id: uuid('id').primaryKey().defaultRandom(),
|
|
128
|
+
subscriptionId: uuid('subscription_id')
|
|
129
|
+
.notNull()
|
|
130
|
+
.references(() => integrationSubscriptions.id, { onDelete: 'cascade' }),
|
|
131
|
+
direction: integrationRunDirectionEnum('direction').notNull(),
|
|
132
|
+
action: integrationRunActionEnum('action').notNull(),
|
|
133
|
+
status: integrationRunStatusEnum('status').notNull().default('running'),
|
|
134
|
+
recordsFound: integer('records_found').notNull().default(0),
|
|
135
|
+
recordsProcessed: integer('records_processed').notNull().default(0),
|
|
136
|
+
cursorBefore: jsonb('cursor_before').$type<unknown>(),
|
|
137
|
+
cursorAfter: jsonb('cursor_after').$type<unknown>(),
|
|
138
|
+
durationMs: integer('duration_ms'),
|
|
139
|
+
error: text('error'),
|
|
140
|
+
startedAt: timestamp('started_at', { withTimezone: true })
|
|
141
|
+
.notNull()
|
|
142
|
+
.defaultNow(),
|
|
143
|
+
completedAt: timestamp('completed_at', { withTimezone: true }),
|
|
144
|
+
tenantId: text('tenant_id'), // always emitted — the runtime integration code (cursor store + run recorder) references tenant_id unconditionally; INTEGRATION_MULTI_TENANT gates enforcement, not the column's existence (mirrors jobs)
|
|
145
|
+
},
|
|
146
|
+
(t) => ({
|
|
147
|
+
idxIntegrationRunsSubscriptionStartedAt: index(
|
|
148
|
+
'idx_integration_runs_subscription_started_at',
|
|
149
|
+
).on(t.subscriptionId, t.startedAt),
|
|
150
|
+
idxIntegrationRunsStatusStartedAt: index('idx_integration_runs_status_started_at').on(
|
|
151
|
+
t.status,
|
|
152
|
+
t.startedAt,
|
|
153
|
+
),
|
|
154
|
+
}),
|
|
155
|
+
);
|
|
156
|
+
|
|
157
|
+
export type IntegrationRunRow = InferSelectModel<typeof integrationRuns>;
|
|
158
|
+
|
|
159
|
+
// ─── integration_run_items ─────────────────────────────────────────────────────────
|
|
160
|
+
|
|
161
|
+
export const integrationRunItems = pgTable(
|
|
162
|
+
'integration_run_items',
|
|
163
|
+
{
|
|
164
|
+
id: uuid('id').primaryKey().defaultRandom(),
|
|
165
|
+
integrationRunId: uuid('integration_run_id')
|
|
166
|
+
.notNull()
|
|
167
|
+
.references(() => integrationRuns.id, { onDelete: 'cascade' }),
|
|
168
|
+
entityType: text('entity_type').notNull(),
|
|
169
|
+
externalId: text('external_id').notNull(),
|
|
170
|
+
localId: text('local_id'),
|
|
171
|
+
operation: integrationRunItemOperationEnum('operation').notNull(),
|
|
172
|
+
status: integrationRunItemStatusEnum('status').notNull(),
|
|
173
|
+
changedFields: jsonb('changed_fields').notNull().default({}).$type<FieldDiff>(),
|
|
174
|
+
title: text('title'),
|
|
175
|
+
error: text('error'),
|
|
176
|
+
createdAt: timestamp('created_at', { withTimezone: true })
|
|
177
|
+
.notNull()
|
|
178
|
+
.defaultNow(),
|
|
179
|
+
tenantId: text('tenant_id'), // always emitted — the runtime integration code (cursor store + run recorder) references tenant_id unconditionally; INTEGRATION_MULTI_TENANT gates enforcement, not the column's existence (mirrors jobs)
|
|
180
|
+
},
|
|
181
|
+
(t) => ({
|
|
182
|
+
idxIntegrationRunItemsRunCreatedAt: index('idx_integration_run_items_run_created_at').on(
|
|
183
|
+
t.integrationRunId,
|
|
184
|
+
t.createdAt,
|
|
185
|
+
),
|
|
186
|
+
idxIntegrationRunItemsEntityExternal: index(
|
|
187
|
+
'idx_integration_run_items_entity_external',
|
|
188
|
+
).on(t.entityType, t.externalId),
|
|
189
|
+
}),
|
|
190
|
+
);
|
|
191
|
+
|
|
192
|
+
export type IntegrationRunItemRow = InferSelectModel<typeof integrationRunItems>;
|
|
@@ -1,26 +1,26 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Hygen prompt.js — SYNC-7
|
|
2
|
+
* Hygen prompt.js — SYNC-7 integration subsystem scaffold.
|
|
3
3
|
*
|
|
4
|
-
* All locals are resolved by the CLI (src/cli/shared/
|
|
4
|
+
* All locals are resolved by the CLI (src/cli/shared/integration-scaffold-locals.ts)
|
|
5
5
|
* and forwarded as CLI args. This prompt.js coerces boolean-ish strings back
|
|
6
6
|
* into JS booleans so template `<% if (multiTenant) { %>` gates work — Hygen
|
|
7
7
|
* args arrive as strings, and `if ("false")` would render truthy in EJS.
|
|
8
8
|
*
|
|
9
9
|
* Invoked via:
|
|
10
|
-
* bunx hygen subsystem
|
|
10
|
+
* bunx hygen subsystem integration \
|
|
11
11
|
* --configPath <abs> --schemaPath <abs> \
|
|
12
12
|
* --multiTenant <'true'|'false'> --appName <string>
|
|
13
13
|
*
|
|
14
|
-
* Unlike events,
|
|
14
|
+
* Unlike events, integration has NO codegen-emitted artifacts (no generated/ dir,
|
|
15
15
|
* no typed bus facade). So no `generatedKeepPath` local. Consumers that
|
|
16
16
|
* want a typed layer above the orchestrator build it themselves — the
|
|
17
17
|
* subsystem ships the substrate.
|
|
18
18
|
*
|
|
19
|
-
* Intentionally no starter entity YAMLs (
|
|
20
|
-
*
|
|
21
|
-
*
|
|
19
|
+
* Intentionally no starter entity YAMLs (integration_subscription, integration_run,
|
|
20
|
+
* integration_run_item). The subsystem owns those tables directly via SYNC-1's
|
|
21
|
+
* integration-audit.schema.ts; shipping entity YAMLs would generate redundant
|
|
22
22
|
* repositories/services that shadow the subsystem. Matches the epic's
|
|
23
|
-
* Phase 2 timing for `examples/
|
|
23
|
+
* Phase 2 timing for `examples/integration/`.
|
|
24
24
|
*/
|
|
25
25
|
|
|
26
26
|
import { renderGeneratedBanner } from "../../_shared/generated-banner.mjs";
|
|
@@ -39,12 +39,12 @@ export default {
|
|
|
39
39
|
multiTenant: coerceBool(args.multiTenant),
|
|
40
40
|
configPath: args.configPath ?? "codegen.config.yaml",
|
|
41
41
|
schemaPath:
|
|
42
|
-
args.schemaPath ?? "shared/subsystems/
|
|
43
|
-
// @generated DO-NOT-EDIT banner — the
|
|
42
|
+
args.schemaPath ?? "shared/subsystems/integration/integration-audit.schema.ts",
|
|
43
|
+
// @generated DO-NOT-EDIT banner — the integration subsystem schema is
|
|
44
44
|
// force-overwritten on every `subsystem install`.
|
|
45
45
|
generatedBanner: renderGeneratedBanner({
|
|
46
|
-
generator: "subsystem
|
|
47
|
-
seam: "the codegen.config.yaml
|
|
46
|
+
generator: "subsystem integration",
|
|
47
|
+
seam: "the codegen.config.yaml integration block, then re-run `codegen subsystem install`",
|
|
48
48
|
}),
|
|
49
49
|
};
|
|
50
50
|
},
|
|
@@ -2,28 +2,28 @@
|
|
|
2
2
|
to: "<%= configPath %>"
|
|
3
3
|
inject: true
|
|
4
4
|
append: true
|
|
5
|
-
skip_if: "
|
|
5
|
+
skip_if: "integration:"
|
|
6
6
|
---
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
integration:
|
|
9
9
|
# ── Backend selection (core/extension model — see CLAUDE.md) ──
|
|
10
10
|
# 'drizzle' is the production backend (Postgres cursor store +
|
|
11
|
-
#
|
|
11
|
+
# integration_runs / integration_run_items audit log). 'memory' is the in-process
|
|
12
12
|
# test backend (MemoryCursorStore + MemoryRunRecorder).
|
|
13
13
|
backend: drizzle
|
|
14
14
|
|
|
15
15
|
# ── Multi-tenancy (SYNC-6 / ADR-008) ──
|
|
16
16
|
# When true:
|
|
17
17
|
# - the generated schema gains `tenant_id` columns on all three
|
|
18
|
-
#
|
|
19
|
-
# - `
|
|
18
|
+
# integration tables;
|
|
19
|
+
# - `ExecuteIntegrationUseCase.execute(...)` throws `MissingTenantIdError`
|
|
20
20
|
# when called with a null / missing `tenantId`;
|
|
21
|
-
# - `PostgresCursorStore` + `
|
|
21
|
+
# - `PostgresCursorStore` + `DrizzleIntegrationRunRecorder` throw the same
|
|
22
22
|
# error at their write boundary (defense in depth);
|
|
23
23
|
# - `MemoryCursorStore` + `MemoryRunRecorder` accept `tenantId` and
|
|
24
24
|
# record it on their in-memory rows but do not throw — memory
|
|
25
25
|
# state is process-local; cross-tenant isolation there is not
|
|
26
26
|
# meaningful.
|
|
27
|
-
# Enabling post-install requires a reinstall (`subsystem install
|
|
27
|
+
# Enabling post-install requires a reinstall (`subsystem install integration
|
|
28
28
|
# --force --force-config`) plus an Atlas migration.
|
|
29
29
|
multi_tenant: false
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hygen prompt.js — SYNC-7 integration config-block scaffold.
|
|
3
|
+
*
|
|
4
|
+
* Split from `templates/subsystem/integration/` so the CLI can invoke the
|
|
5
|
+
* config-block inject step independently of the rest of the integration scaffold.
|
|
6
|
+
* This lets `subsystem install integration --force` preserve an existing `integration:`
|
|
7
|
+
* block by skipping this action entirely, while `--force-config` opts in
|
|
8
|
+
* to regenerating it.
|
|
9
|
+
*
|
|
10
|
+
* Mirrors `events-config` exactly.
|
|
11
|
+
*
|
|
12
|
+
* Invoked via:
|
|
13
|
+
* bunx hygen subsystem integration-config --configPath <abs>
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
export default {
|
|
17
|
+
prompt: async ({ args }) => {
|
|
18
|
+
return {
|
|
19
|
+
configPath: args.configPath ?? "codegen.config.yaml",
|
|
20
|
+
};
|
|
21
|
+
},
|
|
22
|
+
};
|
|
@@ -6,7 +6,7 @@ unless_exists: true
|
|
|
6
6
|
* Standalone job worker entrypoint — emitted by `codegen subsystem install jobs`.
|
|
7
7
|
*
|
|
8
8
|
* Boots a Nest application context (NO HTTP listener) wiring the full
|
|
9
|
-
* subsystem barrel (`SUBSYSTEM_MODULES` — events + jobs + bridge +
|
|
9
|
+
* subsystem barrel (`SUBSYSTEM_MODULES` — events + jobs + bridge + integration, in
|
|
10
10
|
* dependency order) plus `JobWorkerModule.forRoot({ mode: 'standalone',
|
|
11
11
|
* allPools: true })`. Run with:
|
|
12
12
|
*
|
|
@@ -48,7 +48,7 @@ const SHUTDOWN_TIMEOUT_MS = 30_000;
|
|
|
48
48
|
@Module({
|
|
49
49
|
imports: [
|
|
50
50
|
DatabaseModule,
|
|
51
|
-
// Events + Jobs + Bridge +
|
|
51
|
+
// Events + Jobs + Bridge + Integration (dependency-ordered) from the generated
|
|
52
52
|
// barrel. This is the same composition AppModule imports — keeping the
|
|
53
53
|
// worker's DI graph identical to the HTTP app's so handlers resolve the
|
|
54
54
|
// same way in both processes.
|
|
@@ -6,7 +6,7 @@ skip_if: "ObservabilityModule"
|
|
|
6
6
|
---
|
|
7
7
|
|
|
8
8
|
// TODO: Register ObservabilityModule (combiner subsystem, ADR-025)
|
|
9
|
-
// Add to AppModule.imports AFTER Events/Jobs/Bridge/
|
|
9
|
+
// Add to AppModule.imports AFTER Events/Jobs/Bridge/Integration:
|
|
10
10
|
//
|
|
11
11
|
// import { ObservabilityModule } from '@shared/subsystems/observability';
|
|
12
12
|
// // ...
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
* generated/ dir. The sole template this folder contains is
|
|
17
17
|
* `main-hook.ejs.t`, which appends a COMMENT BLOCK to the user's
|
|
18
18
|
* app.module.ts directing them to register `ObservabilityModule.forRoot()`
|
|
19
|
-
* AFTER Events/Jobs/Bridge/
|
|
19
|
+
* AFTER Events/Jobs/Bridge/Integration. We deliberately don't attempt a regex
|
|
20
20
|
* injection — the module order matters (combiner composes siblings), and
|
|
21
21
|
* a wrong-place inject is worse than a clear TODO comment.
|
|
22
22
|
*/
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
* template's sole job is to inject the `openapi:` block into
|
|
10
10
|
* `codegen.config.yaml`.
|
|
11
11
|
*
|
|
12
|
-
* Mirrors the bridge-config / events-config / jobs-config /
|
|
12
|
+
* Mirrors the bridge-config / events-config / jobs-config / integration-config
|
|
13
13
|
* prompt.js shape. Invoked via:
|
|
14
14
|
* bunx hygen subsystem openapi-config --configPath <abs>
|
|
15
15
|
*/
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../runtime/base-classes/junction-sync-repository.ts","../../../runtime/base-classes/base-repository.ts","../../../runtime/base-classes/tenant-context.ts"],"sourcesContent":["/**\n * JunctionSyncRepository<TEntity, TSyncWrite, TSyncProjection>\n *\n * Base for junction repos that participate in inbound sync (#374). A junction's\n * sync identity is the tuple `(leftId, rightId[, role])` — there is no native\n * `external_id`/`provider` column, so the sync seam's externalId is a COMPOSITE\n * string `<leftExternalId>::<rightExternalId>[::<role>]` (see the static\n * build/parse helpers below).\n *\n * Both parent FKs are resolved STRICTLY in the write path (a missing parent\n * throws → the orchestrator records a failed item and continues). As of #372\n * role-bearing junctions carry a unique constraint over `(left, right, role)`,\n * so the upsert uses `onConflictDoUpdate` (not the legacy select-then-write).\n * Role-less junctions conflict on `(left, right)`.\n */\nimport { and, eq } from 'drizzle-orm';\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nimport type { PgTableWithColumns } from 'drizzle-orm/pg-core';\nimport type { DrizzleTx } from '../types/drizzle';\nimport { BaseRepository } from './base-repository';\n\nexport interface JunctionSyncConfig {\n /** Left endpoint: local FK column (camel) + strict parent table. */\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n left: { column: string; refTable: PgTableWithColumns<any> };\n /** Right endpoint: local FK column (camel) + strict parent table. */\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n right: { column: string; refTable: PgTableWithColumns<any> };\n /** Role column (camel), or null for a role-less (2-part composite) junction. */\n roleColumn: string | null;\n}\n\nexport abstract class JunctionSyncRepository<\n TEntity,\n TSyncWrite,\n TSyncProjection,\n> extends BaseRepository<TEntity> {\n /**\n * Declarative junction sync surface. Concrete repos declare this — the\n * template emits it with live parent-table handles.\n */\n protected abstract readonly syncConfig: JunctionSyncConfig;\n\n /**\n * Upsert ONE junction row by its composite identity, in a single transaction:\n * 1. resolve the REQUIRED left FK (provider-scoped) — STRICT: missing → throws;\n * 2. resolve the REQUIRED right FK (provider-scoped) — STRICT: missing → throws;\n * 3. insert-or-update on the `(left, right[, role])` conflict target.\n *\n * Idempotent. Returns the composite externalId as the projection `id`.\n *\n * @param write parent external ids (`<left>ExternalId`/`<right>ExternalId`)\n * + optional `role` + `userId`\n * @param provider adapter/provider label used to scope the parent lookups\n * @param tx optional outer transaction; when omitted we open our own\n */\n async syncUpsertOne(\n write: TSyncWrite,\n provider: string,\n tx?: DrizzleTx,\n ): Promise<TSyncProjection> {\n const cfg = this.syncConfig;\n const w = write as Record<string, unknown>;\n const leftWriteKey = `${cfg.left.column.replace(/Id$/, '')}ExternalId`;\n const rightWriteKey = `${cfg.right.column.replace(/Id$/, '')}ExternalId`;\n\n const run = async (db: DrizzleTx): Promise<TSyncProjection> => {\n const leftId = await this.resolveStrict(\n db, cfg.left.refTable, w[leftWriteKey] as string, provider, cfg.left.column,\n );\n const rightId = await this.resolveStrict(\n db, cfg.right.refTable, w[rightWriteKey] as string, provider, cfg.right.column,\n );\n\n const now = new Date();\n const role = cfg.roleColumn ? (w['role'] as unknown) : undefined;\n const values: Record<string, unknown> = {\n [cfg.left.column]: leftId,\n [cfg.right.column]: rightId,\n ...(cfg.roleColumn ? { [cfg.roleColumn]: role } : {}),\n ...(this.behaviors.timestamps ? { updatedAt: now } : {}),\n };\n const target = cfg.roleColumn\n ? [this.table[cfg.left.column], this.table[cfg.right.column], this.table[cfg.roleColumn]]\n : [this.table[cfg.left.column], this.table[cfg.right.column]];\n\n const rows = await db\n .insert(this.table)\n .values(values as never)\n .onConflictDoUpdate({\n target,\n set: { ...(this.behaviors.timestamps ? { updatedAt: now } : {}) } as never,\n })\n .returning();\n\n const saved = rows[0] as Record<string, unknown>;\n return this.toProjection(saved as TEntity, w, provider);\n };\n\n return tx ? run(tx) : this.db.transaction((t) => run(t));\n }\n\n /**\n * Canonical-projected lookup by the COMPOSITE externalId, differ-ready. Parses\n * the composite, resolves BOTH parents NON-throwing (→ null), then selects by\n * the identity tuple. Returns `null` on malformed composite / unresolved\n * parent / no row (a missing \"before\" side is a create from the differ's view).\n */\n async findByExternalIdProjected(\n externalId: string,\n provider: string,\n ): Promise<TSyncProjection | null> {\n const cfg = this.syncConfig;\n const parsed = parseCompositeExternalId(externalId, cfg.roleColumn !== null);\n if (!parsed) return null;\n\n const leftId = await this.resolveLoose(this.db, cfg.left.refTable, parsed.left, provider);\n if (leftId === null) return null;\n const rightId = await this.resolveLoose(this.db, cfg.right.refTable, parsed.right, provider);\n if (rightId === null) return null;\n\n const rows = await this.db\n .select()\n .from(this.table)\n .where(this.identityWhere(leftId, rightId, parsed.role))\n .limit(1);\n const row = rows[0] as TEntity | undefined;\n if (!row) return null;\n\n const w: Record<string, unknown> = {\n [`${cfg.left.column.replace(/Id$/, '')}ExternalId`]: parsed.left,\n [`${cfg.right.column.replace(/Id$/, '')}ExternalId`]: parsed.right,\n ...(cfg.roleColumn ? { role: parsed.role } : {}),\n userId: '',\n };\n return this.toProjection(row, w, provider);\n }\n\n /**\n * Hard-delete the junction by composite externalId. Junctions have no\n * `deleted_at` and no external-linkage columns to clear, so a sync \"delete\"\n * removes the row. Resolves both parents NON-throwing, then deletes by the\n * identity tuple. Returns the composite id, or `null` when nothing matched.\n */\n async softDeleteByExternalId(\n externalId: string,\n provider: string,\n tx?: DrizzleTx,\n ): Promise<{ id: string } | null> {\n const cfg = this.syncConfig;\n const parsed = parseCompositeExternalId(externalId, cfg.roleColumn !== null);\n if (!parsed) return null;\n const db = this.runner(tx);\n\n const leftId = await this.resolveLoose(db, cfg.left.refTable, parsed.left, provider);\n if (leftId === null) return null;\n const rightId = await this.resolveLoose(db, cfg.right.refTable, parsed.right, provider);\n if (rightId === null) return null;\n\n const rows = await db\n .delete(this.table)\n .where(this.identityWhere(leftId, rightId, parsed.role))\n .returning({ id: this.table[cfg.left.column] });\n return rows[0] ? { id: externalId } : null;\n }\n\n /**\n * Project a raw junction row to the differ shape: the COMPOSITE externalId\n * `id` (junctions have no surrogate id) plus the local FK columns, role (when\n * role-bearing) and timestamps — matching the emitted\n * `<Junction>SyncProjection` interface (id + leftId + rightId + role? +\n * createdAt + updatedAt). Junction projections are purely structural, so this\n * is fully generic over `syncConfig` — no per-junction override is emitted.\n */\n protected toProjection(\n row: TEntity,\n write: Record<string, unknown>,\n _provider: string,\n ): TSyncProjection {\n const cfg = this.syncConfig;\n const r = row as Record<string, unknown>;\n const leftExt = write[`${cfg.left.column.replace(/Id$/, '')}ExternalId`] as string;\n const rightExt = write[`${cfg.right.column.replace(/Id$/, '')}ExternalId`] as string;\n const role = cfg.roleColumn ? (r[cfg.roleColumn] as string | undefined) : undefined;\n const out: Record<string, unknown> = {\n id: buildCompositeExternalId(leftExt, rightExt, role),\n [cfg.left.column]: r[cfg.left.column],\n [cfg.right.column]: r[cfg.right.column],\n };\n if (cfg.roleColumn) out[cfg.roleColumn] = r[cfg.roleColumn];\n out['createdAt'] = r['createdAt'];\n out['updatedAt'] = r['updatedAt'];\n return out as TSyncProjection;\n }\n\n /** Build the identity WHERE clause `(left, right[, role])`. */\n private identityWhere(leftId: string, rightId: string, role: string | undefined) {\n const cfg = this.syncConfig;\n const conds = [\n eq(this.table[cfg.left.column], leftId),\n eq(this.table[cfg.right.column], rightId),\n ];\n if (cfg.roleColumn && role !== undefined) {\n conds.push(eq(this.table[cfg.roleColumn], role));\n }\n return and(...conds);\n }\n\n /** Resolve a parent id (provider-scoped), throwing when unresolved. */\n private async resolveStrict(\n db: DrizzleTx,\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n refTable: PgTableWithColumns<any>,\n parentExternalId: string,\n provider: string,\n column: string,\n ): Promise<string> {\n const id = await this.resolveLoose(db, refTable, parentExternalId, provider);\n if (!id) {\n throw new Error(\n `${this.constructor.name}.syncUpsertOne: unresolved parent ` +\n `'${parentExternalId}' (provider '${provider}') for '${column}' — ` +\n `parent not synced yet`,\n );\n }\n return id;\n }\n\n /** Resolve a parent id (provider-scoped), returning null when unresolved. */\n private async resolveLoose(\n db: DrizzleTx,\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n refTable: PgTableWithColumns<any>,\n parentExternalId: string | null | undefined,\n provider: string,\n ): Promise<string | null> {\n if (!parentExternalId) return null;\n const rows = await db\n .select({ id: refTable['id'] })\n .from(refTable)\n .where(\n and(\n eq(refTable['provider'], provider),\n eq(refTable['externalId'], parentExternalId),\n ),\n )\n .limit(1);\n return (rows[0]?.id as string | undefined) ?? null;\n }\n}\n\n// ============================================================================\n// Composite externalId — the junction sync seam's deterministic identity.\n//\n// Format: `<leftExternalId>::<rightExternalId>[::<role>]`\n// e.g. `hubspot:42::hubspot:99::employee` (role-bearing)\n// `hubspot:42::hubspot:99` (role-less)\n//\n// Vendor-prefixed ids use a SINGLE colon, so `::` is an unambiguous delimiter.\n// Kept static in the base (replacing the per-repo free functions) so every\n// junction's lookups + its ChangeSource share one definition.\n// ============================================================================\n\n/**\n * Build the composite externalId from the two parent external ids (+ role when\n * the junction is role-bearing).\n */\nexport function buildCompositeExternalId(\n leftExternalId: string,\n rightExternalId: string,\n role?: string,\n): string {\n return role !== undefined\n ? `${leftExternalId}::${rightExternalId}::${role}`\n : `${leftExternalId}::${rightExternalId}`;\n}\n\n/**\n * Parse a composite externalId. `withRole` selects the expected part count\n * (3 when role-bearing, else 2). Returns `null` when the shape doesn't match\n * or any part is empty.\n */\nexport function parseCompositeExternalId(\n externalId: string,\n withRole: boolean,\n): { left: string; right: string; role: string | undefined } | null {\n const parts = externalId.split('::');\n const expected = withRole ? 3 : 2;\n if (parts.length !== expected || parts.some((p) => p.length === 0)) return null;\n return {\n left: parts[0] as string,\n right: parts[1] as string,\n role: withRole ? (parts[2] as string) : undefined,\n };\n}\n","/**\n * BaseRepository<TEntity>\n *\n * Abstract base class providing standard CRUD operations via Drizzle ORM.\n * Every generated repository extends this class.\n *\n * Family-specific bases (CrmEntityRepository, etc.) extend this in v0.1\n * without any changes to BaseRepository.\n *\n * NOT @Injectable — concrete repositories are @Injectable and inject DRIZZLE.\n */\nimport { and, eq, inArray, isNull, sql } from 'drizzle-orm';\nimport type { PgTableWithColumns, PgColumn } from 'drizzle-orm/pg-core';\nimport type { SQL } from 'drizzle-orm';\nimport type { DrizzleClient, DrizzleTx } from '../types/drizzle';\nimport {\n requireRequester,\n tryGetRequester,\n type RequesterScope,\n} from './tenant-context';\n\n// ============================================================================\n// Interfaces\n// ============================================================================\n\n/**\n * Behavior flags for the repository. Controls automatic timestamp injection\n * and soft-delete filtering.\n */\nexport interface BehaviorConfig {\n timestamps: boolean;\n softDelete: boolean;\n userTracking: boolean;\n}\n\n/**\n * Options for the list() method.\n */\nexport interface ListOptions {\n where?: SQL;\n limit?: number;\n offset?: number;\n orderBy?: PgColumn | SQL;\n}\n\n// ============================================================================\n// BaseRepository\n// ============================================================================\n\nexport abstract class BaseRepository<TEntity> {\n /**\n * The Drizzle table schema for this entity.\n * Concrete repositories declare this as a class property.\n */\n protected abstract readonly table: PgTableWithColumns<any>; // eslint-disable-line @typescript-eslint/no-explicit-any\n\n /**\n * Behavior flags controlling automatic behavior injection.\n * Override in concrete repositories to enable behaviors.\n */\n protected readonly behaviors: BehaviorConfig = {\n timestamps: false,\n softDelete: false,\n userTracking: false,\n };\n\n /**\n * Ambient tenant-scope enforcement for `userTracking` repos (see\n * `scopePredicate`). Only has effect when `behaviors.userTracking === true`.\n *\n * - `'lenient'` (default): when no ambient requester context is active,\n * reads/writes are NOT scoped — preserves pre-scoping behavior, so adopting\n * ambient scoping is additive. Scoping kicks in automatically once a\n * boundary installs `withRequester(...)`.\n * - `'strict'`: a missing ambient context throws (`requireRequester`),\n * making a forgotten boundary fail loud instead of silently returning\n * cross-tenant rows. Recommended for new multi-tenant consumers — override\n * in a concrete repo or a family base class.\n */\n protected readonly scopeEnforcement: 'lenient' | 'strict' = 'lenient';\n\n protected readonly db: DrizzleClient;\n\n constructor(db: DrizzleClient) {\n this.db = db;\n }\n\n /**\n * Pick the runner for a write: the caller-supplied transaction handle\n * if present, otherwise the repository's own client. Keeps the `tx`\n * parameter purely additive — callers without a transaction call as\n * before. Used by the write methods below + consumer overrides (e.g.\n * the generated `upsertCurrentValues` on EAV value tables).\n */\n protected runner(tx?: DrizzleTx): DrizzleClient {\n return tx ?? this.db;\n }\n\n // ============================================================================\n // Read Operations\n // ============================================================================\n\n /**\n * Find a single entity by its primary key.\n * Returns null if not found (or soft-deleted when softDelete=true).\n */\n async findById(id: string): Promise<TEntity | null> {\n const rows = await this.baseQuery(eq(this.table['id'], id)).limit(1);\n return (rows[0] as TEntity) ?? null;\n }\n\n /**\n * Find multiple entities by their primary keys.\n * Returns empty array immediately for empty input (avoids DB errors).\n */\n async findByIds(ids: string[]): Promise<TEntity[]> {\n if (ids.length === 0) return [];\n const rows = await this.baseQuery(inArray(this.table['id'], ids));\n return rows as TEntity[];\n }\n\n /**\n * List entities with optional filtering, pagination, and ordering.\n */\n async list(options?: ListOptions): Promise<TEntity[]> {\n let query = this.baseQuery(options?.where);\n\n if (options?.orderBy) {\n query = query.orderBy(options.orderBy as SQL) as typeof query;\n }\n if (options?.limit !== undefined) {\n query = query.limit(options.limit) as typeof query;\n }\n if (options?.offset !== undefined) {\n query = query.offset(options.offset) as typeof query;\n }\n\n const rows = await query;\n return rows as TEntity[];\n }\n\n /**\n * Count entities matching an optional WHERE clause.\n * Soft-deleted rows are always excluded when softDelete=true.\n */\n async count(where?: SQL): Promise<number> {\n let query = this.db\n .select({ count: sql<number>`cast(count(*) as integer)` })\n .from(this.table);\n\n const conditions: SQL[] = [];\n if (this.behaviors.softDelete) {\n conditions.push(isNull(this.table['deletedAt']));\n }\n const scope = this.scopePredicate();\n if (scope) {\n conditions.push(scope);\n }\n if (where) {\n conditions.push(where);\n }\n\n if (conditions.length === 1) {\n query = query.where(conditions[0]) as typeof query;\n } else if (conditions.length > 1) {\n query = query.where(and(...conditions)) as typeof query;\n }\n\n const rows = await query;\n return rows[0]?.count ?? 0;\n }\n\n /**\n * Check whether an entity with the given id exists.\n */\n async exists(id: string): Promise<boolean> {\n const result = await this.findById(id);\n return result !== null;\n }\n\n // ============================================================================\n // Write Operations\n // ============================================================================\n\n /**\n * Insert a new entity. Timestamps are auto-injected when timestamps=true.\n */\n async create(input: Partial<TEntity>, tx?: DrizzleTx): Promise<TEntity> {\n const data = this.withTimestamps(input as Record<string, unknown>, 'create');\n const rows = await this.runner(tx)\n .insert(this.table)\n .values(data as any) // eslint-disable-line @typescript-eslint/no-explicit-any\n .returning();\n return rows[0] as TEntity;\n }\n\n /**\n * Update an existing entity by id. updatedAt is auto-injected when timestamps=true.\n * Returns the updated entity.\n */\n async update(id: string, input: Partial<TEntity>, tx?: DrizzleTx): Promise<TEntity> {\n const data = this.withTimestamps(input as Record<string, unknown>, 'update');\n const rows = await this.runner(tx)\n .update(this.table)\n .set(data as any) // eslint-disable-line @typescript-eslint/no-explicit-any\n .where(this.scopeAnd(eq(this.table['id'], id)))\n .returning();\n return rows[0] as TEntity;\n }\n\n /**\n * Delete an entity by id.\n * - softDelete=true: sets deletedAt to current timestamp\n * - softDelete=false: hard-deletes the row\n */\n async delete(id: string, tx?: DrizzleTx): Promise<void> {\n const runner = this.runner(tx);\n if (this.behaviors.softDelete) {\n await runner\n .update(this.table)\n .set({ deletedAt: new Date() } as any) // eslint-disable-line @typescript-eslint/no-explicit-any\n .where(this.scopeAnd(eq(this.table['id'], id)));\n } else {\n await runner\n .delete(this.table)\n .where(this.scopeAnd(eq(this.table['id'], id)));\n }\n }\n\n /**\n * Insert or update multiple entities.\n * Default naive implementation — family repositories override with\n * proper conflict-target upsert (e.g., CrmEntityRepository).\n */\n async upsertMany(inputs: Array<Partial<TEntity>>, tx?: DrizzleTx): Promise<TEntity[]> {\n return Promise.all(inputs.map((input) => this.create(input, tx)));\n }\n\n // ============================================================================\n // Protected Helpers\n // ============================================================================\n\n /**\n * Base SELECT query that automatically applies the ambient guards —\n * soft-delete exclusion (when `softDelete`) and tenant scope (when\n * `userTracking` + an active requester context) — combined with an optional\n * caller `extra` predicate into a SINGLE `WHERE`.\n *\n * Pass the leaf predicate as `extra` rather than chaining a second\n * `.where(...)`: Drizzle's `.where()` OVERRIDES (does not AND) a prior\n * `.where()` on a `$dynamic()` query, so a chained call would silently drop\n * the soft-delete and scope guards. `baseQuery(extra)` is the safe form.\n */\n protected baseQuery(extra?: SQL) {\n const query = this.db.select().from(this.table).$dynamic();\n const where = this.scopeAnd(extra, { softDelete: this.behaviors.softDelete });\n return where ? query.where(where) : query;\n }\n\n /**\n * Build the ambient tenant-scope predicate for this repo's table.\n *\n * Returns `undefined` (no scoping) when:\n * - `behaviors.userTracking` is false (repo is not user-owned), or\n * - no ambient requester context is active AND `scopeEnforcement` is\n * `'lenient'` (the default — preserves pre-scoping behavior).\n *\n * When a requester context is active, scopes by `user_id` per the ambient\n * scope: `'user'` → `user_id = ctx.userId`; `'org'` → `user_id IN\n * ctx.orgUserIds` (empty list matches nothing — fail-closed); `'superuser'`\n * → no filter. See `tenant-context.ts` for the boundary-install contract.\n */\n protected scopePredicate(): SQL | undefined {\n if (!this.behaviors.userTracking) return undefined;\n const ctx =\n this.scopeEnforcement === 'strict'\n ? requireRequester()\n : tryGetRequester();\n if (!ctx) return undefined;\n const scope: RequesterScope = ctx.scope ?? 'user';\n switch (scope) {\n case 'superuser':\n return undefined;\n case 'org':\n return ctx.orgUserIds && ctx.orgUserIds.length > 0\n ? inArray(this.table['userId'], ctx.orgUserIds as string[])\n : sql`false`;\n case 'user':\n default:\n return eq(this.table['userId'], ctx.userId);\n }\n }\n\n /**\n * Combine the ambient scope predicate (and, optionally, the soft-delete\n * guard) with a caller `extra` predicate into one `SQL`. Returns `undefined`\n * when nothing applies. Used by read + by-id write paths so a single\n * `.where(...)` carries every guard.\n */\n protected scopeAnd(\n extra?: SQL,\n opts?: { softDelete?: boolean },\n ): SQL | undefined {\n const conditions: SQL[] = [];\n if (opts?.softDelete) conditions.push(isNull(this.table['deletedAt']));\n const scope = this.scopePredicate();\n if (scope) conditions.push(scope);\n if (extra) conditions.push(extra);\n if (conditions.length === 0) return undefined;\n if (conditions.length === 1) return conditions[0];\n return and(...conditions);\n }\n\n /**\n * Merge timestamp fields into an input object.\n * - mode='create': adds createdAt and updatedAt\n * - mode='update': adds updatedAt only\n *\n * No-op when timestamps behavior is disabled.\n */\n protected withTimestamps(\n input: Record<string, unknown>,\n mode: 'create' | 'update',\n ): Record<string, unknown> {\n if (!this.behaviors.timestamps) return input;\n const now = new Date();\n if (mode === 'create') {\n return { ...input, createdAt: now, updatedAt: now };\n }\n return { ...input, updatedAt: now };\n }\n\n /**\n * Build a WHERE clause fragment that restricts results to rows whose\n * parent (identified by a belongs_to FK) is not soft-deleted.\n *\n * Use this in custom repository methods when you need \"rows reachable\n * from an active parent\". The default findAll / findById behavior is\n * NOT changed by this helper — opt in explicitly where needed.\n *\n * ADR-021 — Soft-delete cascade: Option A (filter at query time).\n * `on_delete` FK rules do not fire for soft-deletes; use this helper\n * instead of expecting cascade semantics on the DB level.\n *\n * Example:\n * async listActiveMessages(): Promise<Message[]> {\n * return this.list({\n * where: this.activeParentFilter(conversations, this.table['conversationId']),\n * });\n * }\n *\n * @param parentTable The Drizzle table object for the parent entity.\n * @param parentFkColumn The FK column on this (child) table that references parent.id.\n */\n protected activeParentFilter(\n parentTable: PgTableWithColumns<any>, // eslint-disable-line @typescript-eslint/no-explicit-any\n parentFkColumn: PgColumn,\n ): SQL {\n return sql`EXISTS (\n SELECT 1 FROM ${parentTable} p\n WHERE p.id = ${parentFkColumn}\n AND p.deleted_at IS NULL\n )`;\n }\n}\n","/**\n * Ambient requester context — AsyncLocalStorage-backed tenant scope.\n *\n * The alternative to threading `userId`/`organizationId` through every\n * repository/service signature. Set ONCE at each boundary the generated app\n * owns, read implicitly inside `BaseRepository` (see `scopePredicate`).\n *\n * ## Where to set it (boundaries)\n *\n * - HTTP / tRPC handlers — from the authenticated `ctx.user`\n * - OAuth callback controllers — from the authenticated session\n * - Queue/worker `process()` — from the job's owning user after the\n * job's record is loaded\n *\n * Each boundary wraps the rest of the request in `withRequester({ userId,\n * organizationId }, () => ...)`. The context propagates through every `await`\n * to all downstream repo/service calls without being passed explicitly.\n *\n * ## Where to read it\n *\n * - `BaseRepository.scopePredicate()` reads it (via `tryGetRequester` in\n * lenient mode, `requireRequester` in strict mode) and filters every read\n * by the ambient scope when the repo declares `userTracking: true`.\n *\n * ## Why AsyncLocalStorage over an explicit parameter\n *\n * Threading `userId` (and later `organizationId`) through dozens of method\n * signatures is pure parameter pollution. Ambient context also lets a repo\n * make the \"I forgot to scope\" mistake impossible at runtime: in strict mode\n * `requireRequester()` throws when no context is active, surfacing a missing\n * boundary call loudly rather than silently leaking cross-tenant data.\n *\n * ## Not-found semantics\n *\n * When a row exists but belongs to a different requester, scoped reads return\n * `null`/`[]` — identical to \"truly doesn't exist\". No existence oracle;\n * callers throw NotFound uniformly. Standard security practice.\n *\n * ## Testing\n *\n * Tests that exercise scoped repos must wrap the call in `withRequester(...)`.\n * In strict mode an unwrapped call hitting `requireRequester()` throws — by\n * design. In lenient mode (the default) an unwrapped call is simply unscoped.\n */\nimport { AsyncLocalStorage } from 'node:async_hooks';\n\n/**\n * Data-visibility scope. The auth layer decides which scope a request is\n * allowed to claim; the repo trusts whatever the ambient context says.\n *\n * - `'user'`: filter every read by `user_id = ctx.userId`. Default.\n * - `'org'`: filter every read by membership in the requester's org, resolved\n * via `user_id IN (ctx.orgUserIds)` rather than via a per-entity\n * `organization_id` column. Works for every user-owned table and keeps repos\n * single-table — the org member list is pre-resolved at the boundary.\n * - `'superuser'`: no scope filter. Engineering / internal-tools only.\n *\n * AUTHORIZATION (who is allowed to claim each scope) lives in boundary\n * middleware, not in the repo. The repo trusts the ambient context — same\n * trust model as a threaded `userId`.\n */\nexport type RequesterScope = 'user' | 'org' | 'superuser';\n\nexport interface RequesterContext {\n /**\n * The user making the request. Always present — even in `'org'` and\n * `'superuser'` scopes it is the audit-trail \"who actually did this\".\n */\n readonly userId: string;\n /**\n * The organization the requester belongs to. Required when\n * `scope === 'org'`; may be null for `'user'` (users with no org) and for\n * `'superuser'` (cross-org reads).\n */\n readonly organizationId: string | null;\n /**\n * Data-visibility scope. Defaults to `'user'` when omitted.\n */\n readonly scope?: RequesterScope;\n /**\n * For `scope === 'org'`: the list of user IDs in the requester's org,\n * pre-resolved by the boundary middleware that established the `'org'`\n * scope (one `SELECT users.id WHERE organization_id = X` at the trust\n * boundary). Repos use this as a literal `IN (...)` filter — they never\n * JOIN to `users` themselves. Required when `scope === 'org'`.\n */\n readonly orgUserIds?: readonly string[];\n}\n\nconst als = new AsyncLocalStorage<RequesterContext>();\n\n/**\n * Set the ambient requester context for the duration of `fn`. The context\n * propagates through `await` boundaries to all downstream calls. Nesting is\n * fine — an inner `withRequester` overrides the outer for its callback.\n */\nexport function withRequester<T>(\n ctx: RequesterContext,\n fn: () => Promise<T>,\n): Promise<T> {\n return als.run(ctx, fn);\n}\n\n/**\n * Read the ambient requester context. Throws if no context is active — by\n * design. Used by repos in strict scope-enforcement mode; an unwrapped call\n * site is a missing boundary.\n */\nexport function requireRequester(): RequesterContext {\n const ctx = als.getStore();\n if (!ctx) {\n throw new Error(\n 'No requester context active. Wrap the entry point in ' +\n 'withRequester({ userId, organizationId }, fn). See tenant-context.ts.',\n );\n }\n return ctx;\n}\n\n/**\n * Read the ambient requester context without throwing. Returns `undefined`\n * when no context is active. Used by repos in lenient scope-enforcement mode\n * (the default) and by code paths that legitimately run outside a request.\n */\nexport function tryGetRequester(): RequesterContext | undefined {\n return als.getStore();\n}\n\n/**\n * Resolve the effective scope for the ambient context, defaulting to `'user'`.\n */\nexport function requireRequesterScope(): RequesterScope {\n return requireRequester().scope ?? 'user';\n}\n\n/**\n * Convenience helpers for setting scope explicitly. All three preserve\n * `userId` in the context (audit trail) regardless of scope.\n *\n * - `withUserScope`: regular end-user requests. Most call sites.\n * - `withOrgScope`: admin / org-shared resource access. The caller MUST verify\n * the requester's role permits `'org'` before calling — the helper does not\n * enforce authorization. `orgUserIds` is pre-resolved at the boundary.\n * - `withSuperuserScope`: engineering scripts / internal tools. `organizationId`\n * is null (cross-org is the point). Same authorization caveat applies.\n */\nexport function withUserScope<T>(\n userId: string,\n organizationId: string | null,\n fn: () => Promise<T>,\n): Promise<T> {\n return withRequester({ userId, organizationId, scope: 'user' }, fn);\n}\n\nexport function withOrgScope<T>(\n userId: string,\n organizationId: string,\n orgUserIds: readonly string[],\n fn: () => Promise<T>,\n): Promise<T> {\n return withRequester(\n { userId, organizationId, scope: 'org', orgUserIds },\n fn,\n );\n}\n\nexport function withSuperuserScope<T>(\n userId: string,\n fn: () => Promise<T>,\n): Promise<T> {\n return withRequester(\n { userId, organizationId: null, scope: 'superuser' },\n fn,\n );\n}\n"],"mappings":"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","names":["and","eq","eq","and"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
//# sourceMappingURL=sync-upsert-config.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../runtime/base-classes/synced-entity-repository.ts","../../../runtime/base-classes/base-repository.ts","../../../runtime/base-classes/tenant-context.ts"],"sourcesContent":["/**\n * SyncedEntityRepository<TEntity, TSyncWrite, TSyncProjection>\n *\n * Family-specific base for Synced entities (contacts, accounts, opportunities).\n * Adds external ID lookups, user-scoped queries, and the generic inbound-sync\n * write surface (canonical→Drizzle upsert + provider-scoped FK resolution +\n * EAV dual-write seam), driven by the concrete repo's `syncConfig`.\n *\n * The type params default so pre-existing single-param subclasses keep\n * compiling; `pattern: Synced` repos declare all three plus `syncConfig`.\n */\nimport { and, eq, inArray } from 'drizzle-orm';\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nimport type { PgTableWithColumns } from 'drizzle-orm/pg-core';\nimport type { DrizzleTx } from '../types/drizzle';\nimport { BaseRepository } from './base-repository';\nimport type { SyncUpsertConfig, SyncFkResolver } from './sync-upsert-config';\n\nexport abstract class SyncedEntityRepository<\n TEntity,\n TSyncWrite = Partial<TEntity>,\n TSyncProjection = TEntity,\n> extends BaseRepository<TEntity> {\n /**\n * Declarative sync write surface. Concrete (`pattern: Synced`) repositories\n * declare this — the template emits it from the entity's fields + FKs.\n */\n protected abstract readonly syncConfig: SyncUpsertConfig;\n\n /**\n * Find a single entity by its external CRM identifier.\n */\n async findByExternalId(externalId: string): Promise<TEntity | null> {\n const rows = await this.baseQuery()\n .where(eq(this.table['externalId'], externalId))\n .limit(1);\n return (rows[0] as TEntity) ?? null;\n }\n\n /**\n * Find multiple entities by external CRM identifiers.\n */\n async findManyByExternalIds(externalIds: string[]): Promise<TEntity[]> {\n if (externalIds.length === 0) return [];\n const rows = await this.baseQuery()\n .where(inArray(this.table['externalId'], externalIds));\n return rows as TEntity[];\n }\n\n /**\n * Find all entities owned by a specific user.\n */\n async findAllByUserId(userId: string): Promise<TEntity[]> {\n const rows = await this.baseQuery()\n .where(eq(this.table['userId'], userId));\n return rows as TEntity[];\n }\n\n // ==========================================================================\n // Inbound sync (#374) — canonical→Drizzle write + provider-scoped FK\n // resolution + EAV dual-write seam, all inside a SINGLE transaction.\n // Driven entirely by `this.syncConfig`; the per-entity shape lives there.\n // ==========================================================================\n\n /**\n * Upsert ONE entity by its `(provider, externalId)` identity, in a single\n * transaction:\n * 1. resolve each `syncConfig.fkResolvers` FK (provider-scoped). Strict\n * resolvers throw on unresolved; non-strict leave the column null.\n * 2. insert-or-update the canonical columns via `onConflictDoUpdate` on the\n * `conflictTarget`. Resolved FKs are only written into `set` when\n * non-null this run (no-clobber).\n * 3. EAV dual-write of `write.fields` via `writeCustomFields` when\n * `syncConfig.eav` and the bag is non-empty (same tx).\n *\n * Idempotent: a second call with the same identity updates in place. Returns\n * the canonical projection (so the orchestrator records `local_id`).\n *\n * @param write canonical fields + parent external ids + custom-field bag\n * @param provider adapter/provider label persisted + used to scope lookups\n * @param tx optional outer transaction; when omitted we open our own\n */\n async syncUpsertOne(\n write: TSyncWrite,\n provider: string,\n tx?: DrizzleTx,\n ): Promise<TSyncProjection> {\n const cfg = this.syncConfig;\n const w = write as Record<string, unknown>;\n\n const run = async (db: DrizzleTx): Promise<TSyncProjection> => {\n // 1. FK resolution (provider-scoped). Strict → throw; else opportunistic null.\n const resolvedFks: Record<string, string | null> = {};\n for (const fk of cfg.fkResolvers) {\n resolvedFks[fk.column] = await this.resolveFk(db, fk, w[fk.writeKey], provider);\n }\n\n // 2. Canonical → Drizzle insert-or-update by the conflict target.\n const now = new Date();\n const copyThrough: Record<string, unknown> = {};\n for (const col of cfg.writeColumns) copyThrough[col] = w[col];\n\n const values: Record<string, unknown> = {\n externalId: w['externalId'],\n provider,\n ...copyThrough,\n ...resolvedFks,\n ...(this.behaviors.timestamps ? { updatedAt: now } : {}),\n };\n\n // `set` excludes the identity (externalId/provider). Resolved FKs are\n // only written when non-null this run — never clobber a previously\n // resolved parent with null on a later run that dropped the ref.\n const set: Record<string, unknown> = {\n ...copyThrough,\n ...(this.behaviors.timestamps ? { updatedAt: now } : {}),\n };\n for (const fk of cfg.fkResolvers) {\n if (resolvedFks[fk.column] !== null) set[fk.column] = resolvedFks[fk.column];\n }\n\n const rows = await db\n .insert(this.table)\n .values(values as never)\n .onConflictDoUpdate({\n target: cfg.conflictTarget.map((c: string) => this.table[c]),\n set: set as never,\n })\n .returning();\n\n const saved = rows[0] as Record<string, unknown>;\n\n // 3. EAV dual-write seam — same tx. No-op unless the entity opts in.\n const fields = w['fields'] as Record<string, unknown> | undefined;\n if (cfg.eav && fields && Object.keys(fields).length > 0) {\n await this.writeCustomFields(\n db,\n saved['id'] as string,\n w['userId'] as string,\n fields,\n );\n }\n\n return this.toProjection(saved as TEntity);\n };\n\n return tx ? run(tx) : this.db.transaction((t) => run(t));\n }\n\n /**\n * Canonical-projected lookup by external id (differ-ready). Returns `null`\n * when no local row exists. Provider-scoped so a HubSpot id can't match a\n * Salesforce row.\n */\n async findByExternalIdProjected(\n externalId: string,\n provider: string,\n ): Promise<TSyncProjection | null> {\n const rows = await this.db\n .select()\n .from(this.table)\n .where(\n and(\n eq(this.table['provider'], provider),\n eq(this.table['externalId'], externalId),\n ),\n )\n .limit(1);\n const row = rows[0] as TEntity | undefined;\n return row ? this.toProjection(row) : null;\n }\n\n /**\n * Sync \"delete\" by external id, provider-scoped. When `softDelete: true`,\n * sets `deletedAt`. When `softDelete: false`, tombstone-by-clearing: null out\n * `external_id`/`provider` so the row no longer matches future inbound\n * changes while preserving local-id references. Returns `{ id }` or `null`.\n */\n async softDeleteByExternalId(\n externalId: string,\n provider: string,\n tx?: DrizzleTx,\n ): Promise<{ id: string } | null> {\n const db = this.runner(tx);\n const set = this.syncConfig.softDelete\n ? { deletedAt: new Date(), updatedAt: new Date() }\n : { externalId: null, provider: null, updatedAt: new Date() };\n const rows = await db\n .update(this.table)\n .set(set as never)\n .where(\n and(\n eq(this.table['provider'], provider),\n eq(this.table['externalId'], externalId),\n ),\n )\n .returning({ id: this.table['id'] });\n return rows[0] ? { id: rows[0].id as string } : null;\n }\n\n /**\n * Batch sync upsert — concretizes the former abstract stub. Delegates to\n * `syncUpsertOne` per input inside one transaction. Inputs are raw partial\n * rows: provider is read from each input's own `provider` column; rows\n * missing `externalId`/`provider` are skipped.\n */\n async syncUpsert(inputs: Array<Partial<TEntity>>): Promise<TEntity[]> {\n if (inputs.length === 0) return [];\n return this.db.transaction(async (tx) => {\n const out: TEntity[] = [];\n for (const input of inputs) {\n const rec = input as Record<string, unknown>;\n if (!rec['externalId'] || !rec['provider']) continue;\n const proj = await this.syncUpsertOne(\n input as unknown as TSyncWrite,\n rec['provider'] as string,\n tx,\n );\n const id = (proj as Record<string, unknown>)['id'] as string;\n const row = await tx\n .select()\n .from(this.table)\n .where(eq(this.table['id'], id))\n .limit(1);\n out.push(row[0] as TEntity);\n }\n return out;\n });\n }\n\n /**\n * Project a raw row to the canonical differ shape — a generic pick over\n * `syncConfig.projectionColumns`. Override only for synthesized projections\n * (e.g. junctions); entities use this verbatim.\n */\n protected toProjection(row: TEntity): TSyncProjection {\n const r = row as Record<string, unknown>;\n const out: Record<string, unknown> = {};\n for (const col of this.syncConfig.projectionColumns) out[col] = r[col];\n return out as TSyncProjection;\n }\n\n /**\n * EAV dual-write seam (#374, live path lands in #124). No-op by default;\n * `eav: true` entities emit a concrete override that injects\n * `FieldValueService` and delegates to `upsertFieldsTransactional` so the\n * dual-write joins the same tx (`db`). Kept as an explicit hook so the base\n * stays portable (the FieldValueService dependency is eav-only).\n */\n protected async writeCustomFields(\n _db: DrizzleTx,\n _entityId: string,\n _userId: string,\n _fields: Record<string, unknown>,\n ): Promise<void> {\n // Intentionally empty until the entity opts into EAV.\n }\n\n /**\n * Resolve one FK from a parent external id (provider-scoped). `self` resolves\n * against `this.table`. Strict resolvers throw when unresolved; non-strict\n * return null. A null/absent write value short-circuits to null.\n */\n private async resolveFk(\n db: DrizzleTx,\n fk: SyncFkResolver,\n rawExternalId: unknown,\n provider: string,\n ): Promise<string | null> {\n const parentExternalId = rawExternalId as string | null | undefined;\n if (!parentExternalId) {\n if (fk.strict) {\n throw new Error(\n `${this.constructor.name}.syncUpsertOne: missing required parent ` +\n `external id for '${fk.column}' (writeKey '${fk.writeKey}')`,\n );\n }\n return null;\n }\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const refTable: PgTableWithColumns<any> =\n fk.refTable === 'self' ? this.table : fk.refTable;\n const rows = await db\n .select({ id: refTable['id'] })\n .from(refTable)\n .where(\n and(\n eq(refTable['provider'], provider),\n eq(refTable['externalId'], parentExternalId),\n ),\n )\n .limit(1);\n const id = (rows[0]?.id as string | undefined) ?? null;\n if (id === null && fk.strict) {\n throw new Error(\n `${this.constructor.name}.syncUpsertOne: unresolved parent ` +\n `'${parentExternalId}' (provider '${provider}') for '${fk.column}' — ` +\n `parent not synced yet`,\n );\n }\n return id;\n }\n\n /**\n * Find entities visible to a user (ownership + sharing rules).\n * Concrete repositories must implement with visibility logic.\n */\n async findVisibleByUserId(_userId: string): Promise<TEntity[]> {\n throw new Error('findVisibleByUserId not implemented — override in concrete repository');\n }\n}\n","/**\n * BaseRepository<TEntity>\n *\n * Abstract base class providing standard CRUD operations via Drizzle ORM.\n * Every generated repository extends this class.\n *\n * Family-specific bases (CrmEntityRepository, etc.) extend this in v0.1\n * without any changes to BaseRepository.\n *\n * NOT @Injectable — concrete repositories are @Injectable and inject DRIZZLE.\n */\nimport { and, eq, inArray, isNull, sql } from 'drizzle-orm';\nimport type { PgTableWithColumns, PgColumn } from 'drizzle-orm/pg-core';\nimport type { SQL } from 'drizzle-orm';\nimport type { DrizzleClient, DrizzleTx } from '../types/drizzle';\nimport {\n requireRequester,\n tryGetRequester,\n type RequesterScope,\n} from './tenant-context';\n\n// ============================================================================\n// Interfaces\n// ============================================================================\n\n/**\n * Behavior flags for the repository. Controls automatic timestamp injection\n * and soft-delete filtering.\n */\nexport interface BehaviorConfig {\n timestamps: boolean;\n softDelete: boolean;\n userTracking: boolean;\n}\n\n/**\n * Options for the list() method.\n */\nexport interface ListOptions {\n where?: SQL;\n limit?: number;\n offset?: number;\n orderBy?: PgColumn | SQL;\n}\n\n// ============================================================================\n// BaseRepository\n// ============================================================================\n\nexport abstract class BaseRepository<TEntity> {\n /**\n * The Drizzle table schema for this entity.\n * Concrete repositories declare this as a class property.\n */\n protected abstract readonly table: PgTableWithColumns<any>; // eslint-disable-line @typescript-eslint/no-explicit-any\n\n /**\n * Behavior flags controlling automatic behavior injection.\n * Override in concrete repositories to enable behaviors.\n */\n protected readonly behaviors: BehaviorConfig = {\n timestamps: false,\n softDelete: false,\n userTracking: false,\n };\n\n /**\n * Ambient tenant-scope enforcement for `userTracking` repos (see\n * `scopePredicate`). Only has effect when `behaviors.userTracking === true`.\n *\n * - `'lenient'` (default): when no ambient requester context is active,\n * reads/writes are NOT scoped — preserves pre-scoping behavior, so adopting\n * ambient scoping is additive. Scoping kicks in automatically once a\n * boundary installs `withRequester(...)`.\n * - `'strict'`: a missing ambient context throws (`requireRequester`),\n * making a forgotten boundary fail loud instead of silently returning\n * cross-tenant rows. Recommended for new multi-tenant consumers — override\n * in a concrete repo or a family base class.\n */\n protected readonly scopeEnforcement: 'lenient' | 'strict' = 'lenient';\n\n protected readonly db: DrizzleClient;\n\n constructor(db: DrizzleClient) {\n this.db = db;\n }\n\n /**\n * Pick the runner for a write: the caller-supplied transaction handle\n * if present, otherwise the repository's own client. Keeps the `tx`\n * parameter purely additive — callers without a transaction call as\n * before. Used by the write methods below + consumer overrides (e.g.\n * the generated `upsertCurrentValues` on EAV value tables).\n */\n protected runner(tx?: DrizzleTx): DrizzleClient {\n return tx ?? this.db;\n }\n\n // ============================================================================\n // Read Operations\n // ============================================================================\n\n /**\n * Find a single entity by its primary key.\n * Returns null if not found (or soft-deleted when softDelete=true).\n */\n async findById(id: string): Promise<TEntity | null> {\n const rows = await this.baseQuery(eq(this.table['id'], id)).limit(1);\n return (rows[0] as TEntity) ?? null;\n }\n\n /**\n * Find multiple entities by their primary keys.\n * Returns empty array immediately for empty input (avoids DB errors).\n */\n async findByIds(ids: string[]): Promise<TEntity[]> {\n if (ids.length === 0) return [];\n const rows = await this.baseQuery(inArray(this.table['id'], ids));\n return rows as TEntity[];\n }\n\n /**\n * List entities with optional filtering, pagination, and ordering.\n */\n async list(options?: ListOptions): Promise<TEntity[]> {\n let query = this.baseQuery(options?.where);\n\n if (options?.orderBy) {\n query = query.orderBy(options.orderBy as SQL) as typeof query;\n }\n if (options?.limit !== undefined) {\n query = query.limit(options.limit) as typeof query;\n }\n if (options?.offset !== undefined) {\n query = query.offset(options.offset) as typeof query;\n }\n\n const rows = await query;\n return rows as TEntity[];\n }\n\n /**\n * Count entities matching an optional WHERE clause.\n * Soft-deleted rows are always excluded when softDelete=true.\n */\n async count(where?: SQL): Promise<number> {\n let query = this.db\n .select({ count: sql<number>`cast(count(*) as integer)` })\n .from(this.table);\n\n const conditions: SQL[] = [];\n if (this.behaviors.softDelete) {\n conditions.push(isNull(this.table['deletedAt']));\n }\n const scope = this.scopePredicate();\n if (scope) {\n conditions.push(scope);\n }\n if (where) {\n conditions.push(where);\n }\n\n if (conditions.length === 1) {\n query = query.where(conditions[0]) as typeof query;\n } else if (conditions.length > 1) {\n query = query.where(and(...conditions)) as typeof query;\n }\n\n const rows = await query;\n return rows[0]?.count ?? 0;\n }\n\n /**\n * Check whether an entity with the given id exists.\n */\n async exists(id: string): Promise<boolean> {\n const result = await this.findById(id);\n return result !== null;\n }\n\n // ============================================================================\n // Write Operations\n // ============================================================================\n\n /**\n * Insert a new entity. Timestamps are auto-injected when timestamps=true.\n */\n async create(input: Partial<TEntity>, tx?: DrizzleTx): Promise<TEntity> {\n const data = this.withTimestamps(input as Record<string, unknown>, 'create');\n const rows = await this.runner(tx)\n .insert(this.table)\n .values(data as any) // eslint-disable-line @typescript-eslint/no-explicit-any\n .returning();\n return rows[0] as TEntity;\n }\n\n /**\n * Update an existing entity by id. updatedAt is auto-injected when timestamps=true.\n * Returns the updated entity.\n */\n async update(id: string, input: Partial<TEntity>, tx?: DrizzleTx): Promise<TEntity> {\n const data = this.withTimestamps(input as Record<string, unknown>, 'update');\n const rows = await this.runner(tx)\n .update(this.table)\n .set(data as any) // eslint-disable-line @typescript-eslint/no-explicit-any\n .where(this.scopeAnd(eq(this.table['id'], id)))\n .returning();\n return rows[0] as TEntity;\n }\n\n /**\n * Delete an entity by id.\n * - softDelete=true: sets deletedAt to current timestamp\n * - softDelete=false: hard-deletes the row\n */\n async delete(id: string, tx?: DrizzleTx): Promise<void> {\n const runner = this.runner(tx);\n if (this.behaviors.softDelete) {\n await runner\n .update(this.table)\n .set({ deletedAt: new Date() } as any) // eslint-disable-line @typescript-eslint/no-explicit-any\n .where(this.scopeAnd(eq(this.table['id'], id)));\n } else {\n await runner\n .delete(this.table)\n .where(this.scopeAnd(eq(this.table['id'], id)));\n }\n }\n\n /**\n * Insert or update multiple entities.\n * Default naive implementation — family repositories override with\n * proper conflict-target upsert (e.g., CrmEntityRepository).\n */\n async upsertMany(inputs: Array<Partial<TEntity>>, tx?: DrizzleTx): Promise<TEntity[]> {\n return Promise.all(inputs.map((input) => this.create(input, tx)));\n }\n\n // ============================================================================\n // Protected Helpers\n // ============================================================================\n\n /**\n * Base SELECT query that automatically applies the ambient guards —\n * soft-delete exclusion (when `softDelete`) and tenant scope (when\n * `userTracking` + an active requester context) — combined with an optional\n * caller `extra` predicate into a SINGLE `WHERE`.\n *\n * Pass the leaf predicate as `extra` rather than chaining a second\n * `.where(...)`: Drizzle's `.where()` OVERRIDES (does not AND) a prior\n * `.where()` on a `$dynamic()` query, so a chained call would silently drop\n * the soft-delete and scope guards. `baseQuery(extra)` is the safe form.\n */\n protected baseQuery(extra?: SQL) {\n const query = this.db.select().from(this.table).$dynamic();\n const where = this.scopeAnd(extra, { softDelete: this.behaviors.softDelete });\n return where ? query.where(where) : query;\n }\n\n /**\n * Build the ambient tenant-scope predicate for this repo's table.\n *\n * Returns `undefined` (no scoping) when:\n * - `behaviors.userTracking` is false (repo is not user-owned), or\n * - no ambient requester context is active AND `scopeEnforcement` is\n * `'lenient'` (the default — preserves pre-scoping behavior).\n *\n * When a requester context is active, scopes by `user_id` per the ambient\n * scope: `'user'` → `user_id = ctx.userId`; `'org'` → `user_id IN\n * ctx.orgUserIds` (empty list matches nothing — fail-closed); `'superuser'`\n * → no filter. See `tenant-context.ts` for the boundary-install contract.\n */\n protected scopePredicate(): SQL | undefined {\n if (!this.behaviors.userTracking) return undefined;\n const ctx =\n this.scopeEnforcement === 'strict'\n ? requireRequester()\n : tryGetRequester();\n if (!ctx) return undefined;\n const scope: RequesterScope = ctx.scope ?? 'user';\n switch (scope) {\n case 'superuser':\n return undefined;\n case 'org':\n return ctx.orgUserIds && ctx.orgUserIds.length > 0\n ? inArray(this.table['userId'], ctx.orgUserIds as string[])\n : sql`false`;\n case 'user':\n default:\n return eq(this.table['userId'], ctx.userId);\n }\n }\n\n /**\n * Combine the ambient scope predicate (and, optionally, the soft-delete\n * guard) with a caller `extra` predicate into one `SQL`. Returns `undefined`\n * when nothing applies. Used by read + by-id write paths so a single\n * `.where(...)` carries every guard.\n */\n protected scopeAnd(\n extra?: SQL,\n opts?: { softDelete?: boolean },\n ): SQL | undefined {\n const conditions: SQL[] = [];\n if (opts?.softDelete) conditions.push(isNull(this.table['deletedAt']));\n const scope = this.scopePredicate();\n if (scope) conditions.push(scope);\n if (extra) conditions.push(extra);\n if (conditions.length === 0) return undefined;\n if (conditions.length === 1) return conditions[0];\n return and(...conditions);\n }\n\n /**\n * Merge timestamp fields into an input object.\n * - mode='create': adds createdAt and updatedAt\n * - mode='update': adds updatedAt only\n *\n * No-op when timestamps behavior is disabled.\n */\n protected withTimestamps(\n input: Record<string, unknown>,\n mode: 'create' | 'update',\n ): Record<string, unknown> {\n if (!this.behaviors.timestamps) return input;\n const now = new Date();\n if (mode === 'create') {\n return { ...input, createdAt: now, updatedAt: now };\n }\n return { ...input, updatedAt: now };\n }\n\n /**\n * Build a WHERE clause fragment that restricts results to rows whose\n * parent (identified by a belongs_to FK) is not soft-deleted.\n *\n * Use this in custom repository methods when you need \"rows reachable\n * from an active parent\". The default findAll / findById behavior is\n * NOT changed by this helper — opt in explicitly where needed.\n *\n * ADR-021 — Soft-delete cascade: Option A (filter at query time).\n * `on_delete` FK rules do not fire for soft-deletes; use this helper\n * instead of expecting cascade semantics on the DB level.\n *\n * Example:\n * async listActiveMessages(): Promise<Message[]> {\n * return this.list({\n * where: this.activeParentFilter(conversations, this.table['conversationId']),\n * });\n * }\n *\n * @param parentTable The Drizzle table object for the parent entity.\n * @param parentFkColumn The FK column on this (child) table that references parent.id.\n */\n protected activeParentFilter(\n parentTable: PgTableWithColumns<any>, // eslint-disable-line @typescript-eslint/no-explicit-any\n parentFkColumn: PgColumn,\n ): SQL {\n return sql`EXISTS (\n SELECT 1 FROM ${parentTable} p\n WHERE p.id = ${parentFkColumn}\n AND p.deleted_at IS NULL\n )`;\n }\n}\n","/**\n * Ambient requester context — AsyncLocalStorage-backed tenant scope.\n *\n * The alternative to threading `userId`/`organizationId` through every\n * repository/service signature. Set ONCE at each boundary the generated app\n * owns, read implicitly inside `BaseRepository` (see `scopePredicate`).\n *\n * ## Where to set it (boundaries)\n *\n * - HTTP / tRPC handlers — from the authenticated `ctx.user`\n * - OAuth callback controllers — from the authenticated session\n * - Queue/worker `process()` — from the job's owning user after the\n * job's record is loaded\n *\n * Each boundary wraps the rest of the request in `withRequester({ userId,\n * organizationId }, () => ...)`. The context propagates through every `await`\n * to all downstream repo/service calls without being passed explicitly.\n *\n * ## Where to read it\n *\n * - `BaseRepository.scopePredicate()` reads it (via `tryGetRequester` in\n * lenient mode, `requireRequester` in strict mode) and filters every read\n * by the ambient scope when the repo declares `userTracking: true`.\n *\n * ## Why AsyncLocalStorage over an explicit parameter\n *\n * Threading `userId` (and later `organizationId`) through dozens of method\n * signatures is pure parameter pollution. Ambient context also lets a repo\n * make the \"I forgot to scope\" mistake impossible at runtime: in strict mode\n * `requireRequester()` throws when no context is active, surfacing a missing\n * boundary call loudly rather than silently leaking cross-tenant data.\n *\n * ## Not-found semantics\n *\n * When a row exists but belongs to a different requester, scoped reads return\n * `null`/`[]` — identical to \"truly doesn't exist\". No existence oracle;\n * callers throw NotFound uniformly. Standard security practice.\n *\n * ## Testing\n *\n * Tests that exercise scoped repos must wrap the call in `withRequester(...)`.\n * In strict mode an unwrapped call hitting `requireRequester()` throws — by\n * design. In lenient mode (the default) an unwrapped call is simply unscoped.\n */\nimport { AsyncLocalStorage } from 'node:async_hooks';\n\n/**\n * Data-visibility scope. The auth layer decides which scope a request is\n * allowed to claim; the repo trusts whatever the ambient context says.\n *\n * - `'user'`: filter every read by `user_id = ctx.userId`. Default.\n * - `'org'`: filter every read by membership in the requester's org, resolved\n * via `user_id IN (ctx.orgUserIds)` rather than via a per-entity\n * `organization_id` column. Works for every user-owned table and keeps repos\n * single-table — the org member list is pre-resolved at the boundary.\n * - `'superuser'`: no scope filter. Engineering / internal-tools only.\n *\n * AUTHORIZATION (who is allowed to claim each scope) lives in boundary\n * middleware, not in the repo. The repo trusts the ambient context — same\n * trust model as a threaded `userId`.\n */\nexport type RequesterScope = 'user' | 'org' | 'superuser';\n\nexport interface RequesterContext {\n /**\n * The user making the request. Always present — even in `'org'` and\n * `'superuser'` scopes it is the audit-trail \"who actually did this\".\n */\n readonly userId: string;\n /**\n * The organization the requester belongs to. Required when\n * `scope === 'org'`; may be null for `'user'` (users with no org) and for\n * `'superuser'` (cross-org reads).\n */\n readonly organizationId: string | null;\n /**\n * Data-visibility scope. Defaults to `'user'` when omitted.\n */\n readonly scope?: RequesterScope;\n /**\n * For `scope === 'org'`: the list of user IDs in the requester's org,\n * pre-resolved by the boundary middleware that established the `'org'`\n * scope (one `SELECT users.id WHERE organization_id = X` at the trust\n * boundary). Repos use this as a literal `IN (...)` filter — they never\n * JOIN to `users` themselves. Required when `scope === 'org'`.\n */\n readonly orgUserIds?: readonly string[];\n}\n\nconst als = new AsyncLocalStorage<RequesterContext>();\n\n/**\n * Set the ambient requester context for the duration of `fn`. The context\n * propagates through `await` boundaries to all downstream calls. Nesting is\n * fine — an inner `withRequester` overrides the outer for its callback.\n */\nexport function withRequester<T>(\n ctx: RequesterContext,\n fn: () => Promise<T>,\n): Promise<T> {\n return als.run(ctx, fn);\n}\n\n/**\n * Read the ambient requester context. Throws if no context is active — by\n * design. Used by repos in strict scope-enforcement mode; an unwrapped call\n * site is a missing boundary.\n */\nexport function requireRequester(): RequesterContext {\n const ctx = als.getStore();\n if (!ctx) {\n throw new Error(\n 'No requester context active. Wrap the entry point in ' +\n 'withRequester({ userId, organizationId }, fn). See tenant-context.ts.',\n );\n }\n return ctx;\n}\n\n/**\n * Read the ambient requester context without throwing. Returns `undefined`\n * when no context is active. Used by repos in lenient scope-enforcement mode\n * (the default) and by code paths that legitimately run outside a request.\n */\nexport function tryGetRequester(): RequesterContext | undefined {\n return als.getStore();\n}\n\n/**\n * Resolve the effective scope for the ambient context, defaulting to `'user'`.\n */\nexport function requireRequesterScope(): RequesterScope {\n return requireRequester().scope ?? 'user';\n}\n\n/**\n * Convenience helpers for setting scope explicitly. All three preserve\n * `userId` in the context (audit trail) regardless of scope.\n *\n * - `withUserScope`: regular end-user requests. Most call sites.\n * - `withOrgScope`: admin / org-shared resource access. The caller MUST verify\n * the requester's role permits `'org'` before calling — the helper does not\n * enforce authorization. `orgUserIds` is pre-resolved at the boundary.\n * - `withSuperuserScope`: engineering scripts / internal tools. `organizationId`\n * is null (cross-org is the point). Same authorization caveat applies.\n */\nexport function withUserScope<T>(\n userId: string,\n organizationId: string | null,\n fn: () => Promise<T>,\n): Promise<T> {\n return withRequester({ userId, organizationId, scope: 'user' }, fn);\n}\n\nexport function withOrgScope<T>(\n userId: string,\n organizationId: string,\n orgUserIds: readonly string[],\n fn: () => Promise<T>,\n): Promise<T> {\n return withRequester(\n { userId, organizationId, scope: 'org', orgUserIds },\n fn,\n );\n}\n\nexport function withSuperuserScope<T>(\n userId: string,\n fn: () => Promise<T>,\n): Promise<T> {\n return withRequester(\n { userId, organizationId: null, scope: 'superuser' },\n fn,\n );\n}\n"],"mappings":"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","names":["and","eq","inArray","eq","inArray","and"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../runtime/base-classes/lifecycle-events.ts","../../../runtime/base-classes/base-service.ts","../../../runtime/base-classes/synced-entity-service.ts"],"sourcesContent":["/**\n * Lifecycle event emission for BaseService.\n *\n * Ported from pattern-stack/atoms/patterns/services/base.py — the Python\n * BaseService emits LIFECYCLE and CHANGE events on every CRUD operation.\n * This module provides the same capability for the TypeScript codegen stack.\n *\n * Design:\n * - Fire-and-forget: event emission never fails the CRUD operation.\n * - IEventBus is optional: if no EVENT_BUS is injected, emission is silently\n * skipped. This means base classes work in projects that haven't installed\n * the events subsystem.\n * - LIFECYCLE events carry an entity snapshot in payload.\n * - CHANGE events carry per-field old/new diffs.\n * - Controlled per-entity via `emitLifecycleEvents` flag (default: true).\n *\n * @deprecated EVT-7 — Lifecycle events are untyped and emit outside of the\n * CRUD transaction. New work should declare an `emits:` block on the entity\n * and publish typed domain events from use-cases via TYPED_EVENT_BUS inside\n * the same Drizzle transaction. See `docs/specs/EVT-7.md`. This helper is\n * retained for BaseService backward compatibility until all entities have\n * migrated to typed emits.\n */\n\nimport { randomUUID } from 'crypto';\nimport type { IEventBus, DomainEvent } from '../subsystems/events/event-bus.protocol';\n\n// ============================================================================\n// Event categories (subset of pattern-stack's EventCategory)\n// ============================================================================\n\nexport type EventCategory = 'lifecycle' | 'change';\n\n// ============================================================================\n// Helpers\n// ============================================================================\n\n/** System fields excluded from entity snapshots and change diffs. */\nconst SYSTEM_FIELDS = new Set([\n\t'id',\n\t'createdAt',\n\t'updatedAt',\n\t'deletedAt',\n]);\n\n/**\n * Snapshot an entity's field values, excluding system fields.\n * Mirrors pattern-stack's `_get_entity_snapshot()`.\n */\nexport function entitySnapshot(entity: Record<string, unknown>): Record<string, unknown> {\n\tconst snap: Record<string, unknown> = {};\n\tfor (const [key, value] of Object.entries(entity)) {\n\t\tif (!SYSTEM_FIELDS.has(key)) {\n\t\t\tsnap[key] = value;\n\t\t}\n\t}\n\treturn snap;\n}\n\n/**\n * Diff two entity snapshots, returning per-field old/new pairs.\n * Only includes fields that actually changed.\n */\nexport function diffSnapshots(\n\tbefore: Record<string, unknown>,\n\tafter: Record<string, unknown>,\n): Array<{ field: string; oldValue: unknown; newValue: unknown }> {\n\tconst changes: Array<{ field: string; oldValue: unknown; newValue: unknown }> = [];\n\tconst allKeys = new Set([...Object.keys(before), ...Object.keys(after)]);\n\n\tfor (const key of allKeys) {\n\t\tif (SYSTEM_FIELDS.has(key)) continue;\n\t\tconst oldVal = before[key];\n\t\tconst newVal = after[key];\n\t\t// Simple equality — good enough for primitives and nulls.\n\t\t// For deep objects, JSON.stringify comparison.\n\t\tif (oldVal !== newVal && JSON.stringify(oldVal) !== JSON.stringify(newVal)) {\n\t\t\tchanges.push({ field: key, oldValue: oldVal, newValue: newVal });\n\t\t}\n\t}\n\n\treturn changes;\n}\n\n// ============================================================================\n// Event builders\n// ============================================================================\n\nexport function buildLifecycleEvent(\n\tentityName: string,\n\taction: 'created' | 'updated' | 'deleted',\n\tentityId: string,\n\tsnapshot?: Record<string, unknown>,\n): DomainEvent {\n\treturn {\n\t\tid: randomUUID(),\n\t\ttype: `${entityName}.${action}`,\n\t\taggregateId: entityId,\n\t\taggregateType: entityName,\n\t\tpayload: snapshot ? { snapshot } : {},\n\t\toccurredAt: new Date(),\n\t\tmetadata: { category: 'lifecycle' as EventCategory },\n\t};\n}\n\nexport function buildChangeEvents(\n\tentityName: string,\n\tentityId: string,\n\tchanges: Array<{ field: string; oldValue: unknown; newValue: unknown }>,\n): DomainEvent[] {\n\treturn changes.map((c) => ({\n\t\tid: randomUUID(),\n\t\ttype: `${entityName}.field_changed`,\n\t\taggregateId: entityId,\n\t\taggregateType: entityName,\n\t\tpayload: {\n\t\t\tfieldName: c.field,\n\t\t\toldValue: c.oldValue,\n\t\t\tnewValue: c.newValue,\n\t\t},\n\t\toccurredAt: new Date(),\n\t\tmetadata: { category: 'change' as EventCategory },\n\t}));\n}\n\n// ============================================================================\n// Emission helper (fire-and-forget)\n// ============================================================================\n\n/**\n * Emit events to the bus, swallowing errors.\n * Mirrors pattern-stack's `_emit_lifecycle_event()` try/except.\n */\nexport async function emitSafely(\n\teventBus: IEventBus | undefined,\n\tevents: DomainEvent[],\n): Promise<void> {\n\tif (!eventBus || events.length === 0) return;\n\ttry {\n\t\tif (events.length === 1) {\n\t\t\tconst only = events[0];\n\t\t\tif (!only) return;\n\t\t\tawait eventBus.publish(only);\n\t\t} else {\n\t\t\tawait eventBus.publishMany(events);\n\t\t}\n\t} catch {\n\t\t// Log but never fail the CRUD operation.\n\t\t// In production, this would use a structured logger.\n\t\tconsole.warn(`[lifecycle-events] failed to emit ${events.length} event(s)`);\n\t}\n}\n","/**\n * BaseService<TRepo, TEntity>\n *\n * Abstract base class providing 8 CRUD pass-through methods delegating to\n * an injected repository. Every generated service extends this class.\n *\n * Lifecycle event emission (LIFECYCLE + CHANGE categories) is built into\n * create/update/delete — matching pattern-stack's BaseService. Events are\n * fire-and-forget: emission never fails the CRUD operation. If no IEventBus\n * is injected (eventBus is undefined), emission is silently skipped.\n *\n * Generated services set `entityName` and optionally inject `eventBus` via\n * NestJS property injection (@Inject(EVENT_BUS) @Optional()).\n *\n * Note: @Injectable() is applied on concrete services (not here) so that\n * NestJS DI metadata is emitted at the concrete class level. This matches\n * the pattern established by BaseRepository.\n */\n\nimport type { IEventBus } from '../subsystems/events/event-bus.protocol';\nimport {\n entitySnapshot,\n diffSnapshots,\n buildLifecycleEvent,\n buildChangeEvents,\n emitSafely,\n} from './lifecycle-events';\n\n// ============================================================================\n// IBaseRepository interface\n// ============================================================================\n\n/**\n * Structural interface that BaseRepository satisfies.\n * Use this as the TRepo constraint so BaseService is not coupled to the\n * concrete Drizzle-backed BaseRepository.\n */\nexport interface IBaseRepository<TEntity> {\n findById(id: string): Promise<TEntity | null>;\n findByIds(ids: string[]): Promise<TEntity[]>;\n list(options?: unknown): Promise<TEntity[]>;\n count(where?: unknown): Promise<number>;\n exists(id: string): Promise<boolean>;\n create(input: Partial<TEntity>, tx?: unknown): Promise<TEntity>;\n update(id: string, input: Partial<TEntity>, tx?: unknown): Promise<TEntity>;\n delete(id: string, tx?: unknown): Promise<void>;\n}\n\n// ============================================================================\n// BaseService\n// ============================================================================\n\nexport abstract class BaseService<TRepo extends IBaseRepository<TEntity>, TEntity> {\n /**\n * Entity name for event types (e.g., 'account' → 'account.created').\n * Set by generated services. If empty, lifecycle events are skipped.\n */\n protected entityName?: string;\n\n /**\n * Event bus for lifecycle/change event emission.\n * Injected via @Inject(EVENT_BUS) @Optional() on generated services.\n * If undefined (no events subsystem installed), emission is silently skipped.\n */\n protected eventBus?: IEventBus;\n\n /**\n * Whether to emit lifecycle events. Default: true.\n * Override to false in entity YAML via behaviors or in the service class.\n */\n protected emitLifecycleEvents = true;\n\n constructor(protected readonly repository: TRepo) {}\n\n /**\n * Find a single entity by its primary key.\n * Returns null if not found.\n */\n findById(id: string): Promise<TEntity | null> {\n return this.repository.findById(id);\n }\n\n /**\n * Find multiple entities by their primary keys.\n */\n findByIds(ids: string[]): Promise<TEntity[]> {\n return this.repository.findByIds(ids);\n }\n\n /**\n * List entities with optional filtering/pagination options.\n */\n list(options?: unknown): Promise<TEntity[]> {\n return this.repository.list(options);\n }\n\n /**\n * Count entities matching an optional filter.\n */\n count(where?: unknown): Promise<number> {\n return this.repository.count(where);\n }\n\n /**\n * Check whether an entity with the given id exists.\n */\n exists(id: string): Promise<boolean> {\n return this.repository.exists(id);\n }\n\n /**\n * Insert a new entity.\n * Emits a LIFECYCLE 'created' event with entity snapshot.\n */\n async create(input: Partial<TEntity>, tx?: unknown): Promise<TEntity> {\n const result = await this.repository.create(input, tx);\n\n if (this._shouldEmit()) {\n const snap = entitySnapshot(result as Record<string, unknown>);\n const id = (result as Record<string, unknown>).id as string;\n const event = buildLifecycleEvent(this.entityName!, 'created', id, snap);\n void emitSafely(this.eventBus, [event]);\n }\n\n return result;\n }\n\n /**\n * Update an existing entity by id.\n * Emits a LIFECYCLE 'updated' event + CHANGE events for each modified field.\n */\n async update(id: string, input: Partial<TEntity>, tx?: unknown): Promise<TEntity> {\n // Snapshot before for change diffing\n let before: Record<string, unknown> | undefined;\n if (this._shouldEmit()) {\n const existing = await this.repository.findById(id);\n if (existing) {\n before = entitySnapshot(existing as Record<string, unknown>);\n }\n }\n\n const result = await this.repository.update(id, input, tx);\n\n if (this._shouldEmit()) {\n const after = entitySnapshot(result as Record<string, unknown>);\n const events = [\n buildLifecycleEvent(this.entityName!, 'updated', id, after),\n ];\n // Append per-field CHANGE events\n if (before) {\n const changes = diffSnapshots(before, after);\n if (changes.length > 0) {\n events.push(...buildChangeEvents(this.entityName!, id, changes));\n }\n }\n void emitSafely(this.eventBus, events);\n }\n\n return result;\n }\n\n /**\n * Delete an entity by id.\n * Emits a LIFECYCLE 'deleted' event.\n */\n async delete(id: string, tx?: unknown): Promise<void> {\n await this.repository.delete(id, tx);\n\n if (this._shouldEmit()) {\n const event = buildLifecycleEvent(this.entityName!, 'deleted', id);\n void emitSafely(this.eventBus, [event]);\n }\n }\n\n /** Check whether lifecycle event emission is active. */\n private _shouldEmit(): boolean {\n return Boolean(\n this.emitLifecycleEvents &&\n this.entityName &&\n this.eventBus,\n );\n }\n}\n","/**\n * SyncedEntityService<TRepo, TEntity>\n *\n * Family-specific base service for Synced entities.\n * Delegates to a CRM repository that provides external ID lookups\n * and user-scoped queries.\n */\nimport { BaseService, type IBaseRepository } from './base-service';\n\nexport interface ISyncedEntityRepository<TEntity> extends IBaseRepository<TEntity> {\n findByExternalId(externalId: string): Promise<TEntity | null>;\n findManyByExternalIds(externalIds: string[]): Promise<TEntity[]>;\n findAllByUserId(userId: string): Promise<TEntity[]>;\n findVisibleByUserId(userId: string): Promise<TEntity[]>;\n syncUpsert(inputs: Array<Partial<TEntity>>): Promise<TEntity[]>;\n}\n\nexport abstract class SyncedEntityService<\n TRepo extends ISyncedEntityRepository<TEntity>,\n TEntity,\n> extends BaseService<TRepo, TEntity> {\n /**\n * Find a single entity by its external CRM identifier.\n */\n findByExternalId(externalId: string): Promise<TEntity | null> {\n return this.repository.findByExternalId(externalId);\n }\n\n /**\n * Find multiple entities by external CRM identifiers.\n */\n findManyByExternalIds(externalIds: string[]): Promise<TEntity[]> {\n return this.repository.findManyByExternalIds(externalIds);\n }\n\n /**\n * Find all entities owned by a specific user.\n */\n findAllByUser(userId: string): Promise<TEntity[]> {\n return this.repository.findAllByUserId(userId);\n }\n\n /**\n * Find entities visible to a user (ownership + sharing rules).\n * Concrete services may override with domain-specific visibility logic.\n */\n findVisibleByUser(userId: string): Promise<TEntity[]> {\n return this.repository.findVisibleByUserId(userId);\n }\n}\n"],"mappings":"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","names":[]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
//# sourceMappingURL=integration-store.js.map
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
// runtime/subsystems/auth/runtime/integration-broken.error.ts
|
|
2
|
-
var IntegrationBrokenError = class extends Error {
|
|
3
|
-
constructor(integrationId, errorCode, errorDescription) {
|
|
4
|
-
super(
|
|
5
|
-
`Integration ${integrationId} broken: ${errorCode} - ${errorDescription}`
|
|
6
|
-
);
|
|
7
|
-
this.integrationId = integrationId;
|
|
8
|
-
this.errorCode = errorCode;
|
|
9
|
-
this.errorDescription = errorDescription;
|
|
10
|
-
this.name = "IntegrationBrokenError";
|
|
11
|
-
}
|
|
12
|
-
integrationId;
|
|
13
|
-
errorCode;
|
|
14
|
-
errorDescription;
|
|
15
|
-
};
|
|
16
|
-
export {
|
|
17
|
-
IntegrationBrokenError
|
|
18
|
-
};
|
|
19
|
-
//# sourceMappingURL=integration-broken.error.js.map
|