@pattern-stack/codegen 0.2.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 +67 -0
- package/README.md +214 -0
- package/dist/runtime/analytics/index.d.ts +6 -0
- package/dist/runtime/analytics/index.js +49 -0
- package/dist/runtime/analytics/index.js.map +1 -0
- package/dist/runtime/analytics/metrics.d.ts +75 -0
- package/dist/runtime/analytics/metrics.js +1 -0
- package/dist/runtime/analytics/metrics.js.map +1 -0
- package/dist/runtime/analytics/packs/crm-entity-measures.d.ts +21 -0
- package/dist/runtime/analytics/packs/crm-entity-measures.js +1 -0
- package/dist/runtime/analytics/packs/crm-entity-measures.js.map +1 -0
- package/dist/runtime/analytics/packs/index.d.ts +3 -0
- package/dist/runtime/analytics/packs/index.js +1 -0
- package/dist/runtime/analytics/packs/index.js.map +1 -0
- package/dist/runtime/analytics/packs/monetary-measures.d.ts +21 -0
- package/dist/runtime/analytics/packs/monetary-measures.js +1 -0
- package/dist/runtime/analytics/packs/monetary-measures.js.map +1 -0
- package/dist/runtime/analytics/specs.d.ts +49 -0
- package/dist/runtime/analytics/specs.js +1 -0
- package/dist/runtime/analytics/specs.js.map +1 -0
- package/dist/runtime/analytics/types.d.ts +85 -0
- package/dist/runtime/analytics/types.js +49 -0
- package/dist/runtime/analytics/types.js.map +1 -0
- package/dist/runtime/base-classes/activity-entity-repository.d.ts +26 -0
- package/dist/runtime/base-classes/activity-entity-repository.js +195 -0
- package/dist/runtime/base-classes/activity-entity-repository.js.map +1 -0
- package/dist/runtime/base-classes/activity-entity-service.d.ts +39 -0
- package/dist/runtime/base-classes/activity-entity-service.js +214 -0
- package/dist/runtime/base-classes/activity-entity-service.js.map +1 -0
- package/dist/runtime/base-classes/base-read-use-cases.d.ts +68 -0
- package/dist/runtime/base-classes/base-read-use-cases.js +32 -0
- package/dist/runtime/base-classes/base-read-use-cases.js.map +1 -0
- package/dist/runtime/base-classes/base-repository.d.ts +99 -0
- package/dist/runtime/base-classes/base-repository.js +160 -0
- package/dist/runtime/base-classes/base-repository.js.map +1 -0
- package/dist/runtime/base-classes/base-service.d.ts +98 -0
- package/dist/runtime/base-classes/base-service.js +186 -0
- package/dist/runtime/base-classes/base-service.js.map +1 -0
- package/dist/runtime/base-classes/index.d.ts +18 -0
- package/dist/runtime/base-classes/index.js +617 -0
- package/dist/runtime/base-classes/index.js.map +1 -0
- package/dist/runtime/base-classes/knowledge-entity-repository.d.ts +17 -0
- package/dist/runtime/base-classes/knowledge-entity-repository.js +166 -0
- package/dist/runtime/base-classes/knowledge-entity-repository.js.map +1 -0
- package/dist/runtime/base-classes/knowledge-entity-service.d.ts +15 -0
- package/dist/runtime/base-classes/knowledge-entity-service.js +192 -0
- package/dist/runtime/base-classes/knowledge-entity-service.js.map +1 -0
- package/dist/runtime/base-classes/lifecycle-events.d.ts +49 -0
- package/dist/runtime/base-classes/lifecycle-events.js +76 -0
- package/dist/runtime/base-classes/lifecycle-events.js.map +1 -0
- package/dist/runtime/base-classes/metadata-entity-repository.d.ts +27 -0
- package/dist/runtime/base-classes/metadata-entity-repository.js +212 -0
- package/dist/runtime/base-classes/metadata-entity-repository.js.map +1 -0
- package/dist/runtime/base-classes/metadata-entity-service.d.ts +39 -0
- package/dist/runtime/base-classes/metadata-entity-service.js +214 -0
- package/dist/runtime/base-classes/metadata-entity-service.js.map +1 -0
- package/dist/runtime/base-classes/synced-entity-repository.d.ts +32 -0
- package/dist/runtime/base-classes/synced-entity-repository.js +203 -0
- package/dist/runtime/base-classes/synced-entity-repository.js.map +1 -0
- package/dist/runtime/base-classes/synced-entity-service.d.ts +41 -0
- package/dist/runtime/base-classes/synced-entity-service.js +215 -0
- package/dist/runtime/base-classes/synced-entity-service.js.map +1 -0
- package/dist/runtime/base-classes/with-analytics.d.ts +18 -0
- package/dist/runtime/base-classes/with-analytics.js +11 -0
- package/dist/runtime/base-classes/with-analytics.js.map +1 -0
- package/dist/runtime/constants/tokens.d.ts +29 -0
- package/dist/runtime/constants/tokens.js +8 -0
- package/dist/runtime/constants/tokens.js.map +1 -0
- package/dist/runtime/subsystems/analytics/analytics-query.protocol.d.ts +30 -0
- package/dist/runtime/subsystems/analytics/analytics-query.protocol.js +1 -0
- package/dist/runtime/subsystems/analytics/analytics-query.protocol.js.map +1 -0
- package/dist/runtime/subsystems/analytics/analytics.module.d.ts +34 -0
- package/dist/runtime/subsystems/analytics/analytics.module.js +117 -0
- package/dist/runtime/subsystems/analytics/analytics.module.js.map +1 -0
- package/dist/runtime/subsystems/analytics/analytics.tokens.d.ts +24 -0
- package/dist/runtime/subsystems/analytics/analytics.tokens.js +10 -0
- package/dist/runtime/subsystems/analytics/analytics.tokens.js.map +1 -0
- package/dist/runtime/subsystems/analytics/cube-backend.d.ts +28 -0
- package/dist/runtime/subsystems/analytics/cube-backend.js +71 -0
- package/dist/runtime/subsystems/analytics/cube-backend.js.map +1 -0
- package/dist/runtime/subsystems/analytics/index.d.ts +6 -0
- package/dist/runtime/subsystems/analytics/index.js +122 -0
- package/dist/runtime/subsystems/analytics/index.js.map +1 -0
- package/dist/runtime/subsystems/analytics/noop-backend.d.ts +7 -0
- package/dist/runtime/subsystems/analytics/noop-backend.js +25 -0
- package/dist/runtime/subsystems/analytics/noop-backend.js.map +1 -0
- package/dist/runtime/subsystems/cache/cache.drizzle-backend.d.ts +43 -0
- package/dist/runtime/subsystems/cache/cache.drizzle-backend.js +133 -0
- package/dist/runtime/subsystems/cache/cache.drizzle-backend.js.map +1 -0
- package/dist/runtime/subsystems/cache/cache.memory-backend.d.ts +21 -0
- package/dist/runtime/subsystems/cache/cache.memory-backend.js +100 -0
- package/dist/runtime/subsystems/cache/cache.memory-backend.js.map +1 -0
- package/dist/runtime/subsystems/cache/cache.module.d.ts +37 -0
- package/dist/runtime/subsystems/cache/cache.module.js +272 -0
- package/dist/runtime/subsystems/cache/cache.module.js.map +1 -0
- package/dist/runtime/subsystems/cache/cache.protocol.d.ts +42 -0
- package/dist/runtime/subsystems/cache/cache.protocol.js +1 -0
- package/dist/runtime/subsystems/cache/cache.protocol.js.map +1 -0
- package/dist/runtime/subsystems/cache/cache.schema.d.ts +64 -0
- package/dist/runtime/subsystems/cache/cache.schema.js +18 -0
- package/dist/runtime/subsystems/cache/cache.schema.js.map +1 -0
- package/dist/runtime/subsystems/cache/cache.tokens.d.ts +18 -0
- package/dist/runtime/subsystems/cache/cache.tokens.js +8 -0
- package/dist/runtime/subsystems/cache/cache.tokens.js.map +1 -0
- package/dist/runtime/subsystems/cache/index.d.ts +11 -0
- package/dist/runtime/subsystems/cache/index.js +277 -0
- package/dist/runtime/subsystems/cache/index.js.map +1 -0
- package/dist/runtime/subsystems/events/domain-events.schema.d.ts +187 -0
- package/dist/runtime/subsystems/events/domain-events.schema.js +32 -0
- package/dist/runtime/subsystems/events/domain-events.schema.js.map +1 -0
- package/dist/runtime/subsystems/events/event-bus.drizzle-backend.d.ts +38 -0
- package/dist/runtime/subsystems/events/event-bus.drizzle-backend.js +199 -0
- package/dist/runtime/subsystems/events/event-bus.drizzle-backend.js.map +1 -0
- package/dist/runtime/subsystems/events/event-bus.memory-backend.d.ts +18 -0
- package/dist/runtime/subsystems/events/event-bus.memory-backend.js +71 -0
- package/dist/runtime/subsystems/events/event-bus.memory-backend.js.map +1 -0
- package/dist/runtime/subsystems/events/event-bus.protocol.d.ts +52 -0
- package/dist/runtime/subsystems/events/event-bus.protocol.js +1 -0
- package/dist/runtime/subsystems/events/event-bus.protocol.js.map +1 -0
- package/dist/runtime/subsystems/events/event-bus.redis-backend.d.ts +95 -0
- package/dist/runtime/subsystems/events/event-bus.redis-backend.js +229 -0
- package/dist/runtime/subsystems/events/event-bus.redis-backend.js.map +1 -0
- package/dist/runtime/subsystems/events/events.module.d.ts +46 -0
- package/dist/runtime/subsystems/events/events.module.js +531 -0
- package/dist/runtime/subsystems/events/events.module.js.map +1 -0
- package/dist/runtime/subsystems/events/events.tokens.d.ts +19 -0
- package/dist/runtime/subsystems/events/events.tokens.js +8 -0
- package/dist/runtime/subsystems/events/events.tokens.js.map +1 -0
- package/dist/runtime/subsystems/events/index.d.ts +12 -0
- package/dist/runtime/subsystems/events/index.js +536 -0
- package/dist/runtime/subsystems/events/index.js.map +1 -0
- package/dist/runtime/subsystems/index.d.ts +24 -0
- package/dist/runtime/subsystems/index.js +1643 -0
- package/dist/runtime/subsystems/index.js.map +1 -0
- package/dist/runtime/subsystems/jobs/index.d.ts +14 -0
- package/dist/runtime/subsystems/jobs/index.js +680 -0
- package/dist/runtime/subsystems/jobs/index.js.map +1 -0
- package/dist/runtime/subsystems/jobs/job-queue.bullmq-backend.d.ts +54 -0
- package/dist/runtime/subsystems/jobs/job-queue.bullmq-backend.js +186 -0
- package/dist/runtime/subsystems/jobs/job-queue.bullmq-backend.js.map +1 -0
- package/dist/runtime/subsystems/jobs/job-queue.drizzle-backend.d.ts +38 -0
- package/dist/runtime/subsystems/jobs/job-queue.drizzle-backend.js +228 -0
- package/dist/runtime/subsystems/jobs/job-queue.drizzle-backend.js.map +1 -0
- package/dist/runtime/subsystems/jobs/job-queue.memory-backend.d.ts +12 -0
- package/dist/runtime/subsystems/jobs/job-queue.memory-backend.js +44 -0
- package/dist/runtime/subsystems/jobs/job-queue.memory-backend.js.map +1 -0
- package/dist/runtime/subsystems/jobs/job-queue.protocol.d.ts +48 -0
- package/dist/runtime/subsystems/jobs/job-queue.protocol.js +1 -0
- package/dist/runtime/subsystems/jobs/job-queue.protocol.js.map +1 -0
- package/dist/runtime/subsystems/jobs/job-queue.redis-backend.d.ts +46 -0
- package/dist/runtime/subsystems/jobs/job-queue.redis-backend.js +187 -0
- package/dist/runtime/subsystems/jobs/job-queue.redis-backend.js.map +1 -0
- package/dist/runtime/subsystems/jobs/job-queue.schema.d.ts +237 -0
- package/dist/runtime/subsystems/jobs/job-queue.schema.js +44 -0
- package/dist/runtime/subsystems/jobs/job-queue.schema.js.map +1 -0
- package/dist/runtime/subsystems/jobs/jobs.module.d.ts +18 -0
- package/dist/runtime/subsystems/jobs/jobs.module.js +676 -0
- package/dist/runtime/subsystems/jobs/jobs.module.js.map +1 -0
- package/dist/runtime/subsystems/jobs/jobs.tokens.d.ts +13 -0
- package/dist/runtime/subsystems/jobs/jobs.tokens.js +8 -0
- package/dist/runtime/subsystems/jobs/jobs.tokens.js.map +1 -0
- package/dist/runtime/subsystems/storage/index.d.ts +6 -0
- package/dist/runtime/subsystems/storage/index.js +204 -0
- package/dist/runtime/subsystems/storage/index.js.map +1 -0
- package/dist/runtime/subsystems/storage/storage.local-backend.d.ts +18 -0
- package/dist/runtime/subsystems/storage/storage.local-backend.js +108 -0
- package/dist/runtime/subsystems/storage/storage.local-backend.js.map +1 -0
- package/dist/runtime/subsystems/storage/storage.memory-backend.d.ts +28 -0
- package/dist/runtime/subsystems/storage/storage.memory-backend.js +72 -0
- package/dist/runtime/subsystems/storage/storage.memory-backend.js.map +1 -0
- package/dist/runtime/subsystems/storage/storage.module.d.ts +40 -0
- package/dist/runtime/subsystems/storage/storage.module.js +201 -0
- package/dist/runtime/subsystems/storage/storage.module.js.map +1 -0
- package/dist/runtime/subsystems/storage/storage.protocol.d.ts +69 -0
- package/dist/runtime/subsystems/storage/storage.protocol.js +1 -0
- package/dist/runtime/subsystems/storage/storage.protocol.js.map +1 -0
- package/dist/runtime/subsystems/storage/storage.tokens.d.ts +11 -0
- package/dist/runtime/subsystems/storage/storage.tokens.js +6 -0
- package/dist/runtime/subsystems/storage/storage.tokens.js.map +1 -0
- package/dist/runtime/subsystems/storage/storage.utils.d.ts +9 -0
- package/dist/runtime/subsystems/storage/storage.utils.js +18 -0
- package/dist/runtime/subsystems/storage/storage.utils.js.map +1 -0
- package/dist/runtime/types/drizzle.d.ts +17 -0
- package/dist/runtime/types/drizzle.js +1 -0
- package/dist/runtime/types/drizzle.js.map +1 -0
- package/dist/src/cli/index.d.ts +1 -0
- package/dist/src/cli/index.js +7365 -0
- package/dist/src/cli/index.js.map +1 -0
- package/dist/src/index.d.ts +2384 -0
- package/dist/src/index.js +2198 -0
- package/dist/src/index.js.map +1 -0
- package/package.json +114 -0
- package/templates/broadcast/new/backend-interface.ejs.t +47 -0
- package/templates/broadcast/new/bridge-listener.ejs.t +67 -0
- package/templates/broadcast/new/channel.ejs.t +77 -0
- package/templates/broadcast/new/index.ejs.t +21 -0
- package/templates/broadcast/new/memory-backend.ejs.t +87 -0
- package/templates/broadcast/new/module.ejs.t +57 -0
- package/templates/broadcast/new/prompt.js +268 -0
- package/templates/broadcast/new/websocket-backend.ejs.t +259 -0
- package/templates/entity/new/backend/application/commands/create.ejs.t +55 -0
- package/templates/entity/new/backend/application/commands/delete.ejs.t +45 -0
- package/templates/entity/new/backend/application/commands/grouped-index.ejs.t +149 -0
- package/templates/entity/new/backend/application/commands/index.ejs.t +15 -0
- package/templates/entity/new/backend/application/commands/update.ejs.t +58 -0
- package/templates/entity/new/backend/application/queries/declarative-queries.ejs.t +36 -0
- package/templates/entity/new/backend/application/queries/get-by-id.ejs.t +42 -0
- package/templates/entity/new/backend/application/queries/grouped-index.ejs.t +81 -0
- package/templates/entity/new/backend/application/queries/index.ejs.t +14 -0
- package/templates/entity/new/backend/application/queries/list.ejs.t +36 -0
- package/templates/entity/new/backend/application/schemas/_inject-index.ejs.t +7 -0
- package/templates/entity/new/backend/application/schemas/dto.ejs.t +45 -0
- package/templates/entity/new/backend/database/_inject-index.ejs.t +7 -0
- package/templates/entity/new/backend/database/electric-migration.ejs.t +21 -0
- package/templates/entity/new/backend/database/repository.ejs.t +450 -0
- package/templates/entity/new/backend/database/schema.ejs.t +248 -0
- package/templates/entity/new/backend/domain/_inject-index.ejs.t +12 -0
- package/templates/entity/new/backend/domain/entity.ejs.t +108 -0
- package/templates/entity/new/backend/domain/grouped-index.ejs.t +163 -0
- package/templates/entity/new/backend/domain/index.ejs.t +15 -0
- package/templates/entity/new/backend/domain/repository-interface.ejs.t +71 -0
- package/templates/entity/new/backend/modules/core/_ensure-anchor-tokens.ejs.t +10 -0
- package/templates/entity/new/backend/modules/core/_inject-token.ejs.t +7 -0
- package/templates/entity/new/backend/modules/core/module.ejs.t +67 -0
- package/templates/entity/new/backend/modules/trpc/module.ejs.t +67 -0
- package/templates/entity/new/backend/presentation/controller.ejs.t +201 -0
- package/templates/entity/new/clean-lite-ps/controller.ejs.t +37 -0
- package/templates/entity/new/clean-lite-ps/dto/create.ejs.t +17 -0
- package/templates/entity/new/clean-lite-ps/dto/output.ejs.t +25 -0
- package/templates/entity/new/clean-lite-ps/dto/update.ejs.t +11 -0
- package/templates/entity/new/clean-lite-ps/entity.ejs.t +52 -0
- package/templates/entity/new/clean-lite-ps/index.ejs.t +20 -0
- package/templates/entity/new/clean-lite-ps/module.ejs.t +43 -0
- package/templates/entity/new/clean-lite-ps/prompt-extension.js +617 -0
- package/templates/entity/new/clean-lite-ps/repository.ejs.t +62 -0
- package/templates/entity/new/clean-lite-ps/service.ejs.t +34 -0
- package/templates/entity/new/clean-lite-ps/use-cases/declarative-queries.ejs.t +34 -0
- package/templates/entity/new/clean-lite-ps/use-cases/find-by-id.ejs.t +16 -0
- package/templates/entity/new/clean-lite-ps/use-cases/list.ejs.t +16 -0
- package/templates/entity/new/frontend/_inject-entities-entry.ejs.t +7 -0
- package/templates/entity/new/frontend/_inject-entities-import.ejs.t +7 -0
- package/templates/entity/new/frontend/collections/_ensure-anchor-collections.ejs.t +10 -0
- package/templates/entity/new/frontend/collections/_inject-index.ejs.t +9 -0
- package/templates/entity/new/frontend/collections/_inject-schema-import.ejs.t +9 -0
- package/templates/entity/new/frontend/collections/collection.ejs.t +61 -0
- package/templates/entity/new/frontend/collections/collections-base.ejs.t +24 -0
- package/templates/entity/new/frontend/entity/collection.ejs.t +172 -0
- package/templates/entity/new/frontend/entity/combined.ejs.t +474 -0
- package/templates/entity/new/frontend/entity/fields.ejs.t +104 -0
- package/templates/entity/new/frontend/entity/hooks.ejs.t +73 -0
- package/templates/entity/new/frontend/entity/index.ejs.t +21 -0
- package/templates/entity/new/frontend/entity/mutation-hooks.ejs.t +84 -0
- package/templates/entity/new/frontend/entity/mutations.ejs.t +38 -0
- package/templates/entity/new/frontend/entity/types.ejs.t +59 -0
- package/templates/entity/new/frontend/generated/_inject-index-export.ejs.t +7 -0
- package/templates/entity/new/frontend/generated/_inject-index-import.ejs.t +7 -0
- package/templates/entity/new/frontend/generated/_inject-index-registry.ejs.t +7 -0
- package/templates/entity/new/frontend/store/_inject-collection-import.ejs.t +9 -0
- package/templates/entity/new/frontend/store/_inject-collections.ejs.t +9 -0
- package/templates/entity/new/frontend/store/_inject-entity.ejs.t +9 -0
- package/templates/entity/new/frontend/store/_inject-import.ejs.t +9 -0
- package/templates/entity/new/frontend/store/_inject-lookups.ejs.t +9 -0
- package/templates/entity/new/frontend/store/_inject-resolve.ejs.t +10 -0
- package/templates/entity/new/frontend/store/hooks.ejs.t +72 -0
- package/templates/entity/new/frontend/unified-entity.ejs.t +28 -0
- package/templates/entity/new/prompt.js +1421 -0
- package/templates/relationship/new/controller.ejs.t +36 -0
- package/templates/relationship/new/dto/create.ejs.t +41 -0
- package/templates/relationship/new/dto/output.ejs.t +44 -0
- package/templates/relationship/new/dto/update.ejs.t +10 -0
- package/templates/relationship/new/entity.ejs.t +98 -0
- package/templates/relationship/new/index.ejs.t +19 -0
- package/templates/relationship/new/module.ejs.t +35 -0
- package/templates/relationship/new/prompt.js +682 -0
- package/templates/relationship/new/repository.ejs.t +54 -0
- package/templates/relationship/new/service.ejs.t +31 -0
- package/templates/relationship/new/use-cases/declarative-queries.ejs.t +34 -0
- package/templates/relationship/new/use-cases/find-by-id.ejs.t +16 -0
- package/templates/relationship/new/use-cases/list.ejs.t +16 -0
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
import * as drizzle_orm_pg_core from 'drizzle-orm/pg-core';
|
|
2
|
+
import { InferSelectModel } from 'drizzle-orm';
|
|
3
|
+
|
|
4
|
+
declare const domainEvents: drizzle_orm_pg_core.PgTableWithColumns<{
|
|
5
|
+
name: "domain_events";
|
|
6
|
+
schema: undefined;
|
|
7
|
+
columns: {
|
|
8
|
+
id: drizzle_orm_pg_core.PgColumn<{
|
|
9
|
+
name: "id";
|
|
10
|
+
tableName: "domain_events";
|
|
11
|
+
dataType: "string";
|
|
12
|
+
columnType: "PgUUID";
|
|
13
|
+
data: string;
|
|
14
|
+
driverParam: string;
|
|
15
|
+
notNull: true;
|
|
16
|
+
hasDefault: false;
|
|
17
|
+
isPrimaryKey: true;
|
|
18
|
+
isAutoincrement: false;
|
|
19
|
+
hasRuntimeDefault: false;
|
|
20
|
+
enumValues: undefined;
|
|
21
|
+
baseColumn: never;
|
|
22
|
+
identity: undefined;
|
|
23
|
+
generated: undefined;
|
|
24
|
+
}, {}, {}>;
|
|
25
|
+
type: drizzle_orm_pg_core.PgColumn<{
|
|
26
|
+
name: "type";
|
|
27
|
+
tableName: "domain_events";
|
|
28
|
+
dataType: "string";
|
|
29
|
+
columnType: "PgText";
|
|
30
|
+
data: string;
|
|
31
|
+
driverParam: string;
|
|
32
|
+
notNull: true;
|
|
33
|
+
hasDefault: false;
|
|
34
|
+
isPrimaryKey: false;
|
|
35
|
+
isAutoincrement: false;
|
|
36
|
+
hasRuntimeDefault: false;
|
|
37
|
+
enumValues: [string, ...string[]];
|
|
38
|
+
baseColumn: never;
|
|
39
|
+
identity: undefined;
|
|
40
|
+
generated: undefined;
|
|
41
|
+
}, {}, {}>;
|
|
42
|
+
aggregateId: drizzle_orm_pg_core.PgColumn<{
|
|
43
|
+
name: "aggregate_id";
|
|
44
|
+
tableName: "domain_events";
|
|
45
|
+
dataType: "string";
|
|
46
|
+
columnType: "PgText";
|
|
47
|
+
data: string;
|
|
48
|
+
driverParam: string;
|
|
49
|
+
notNull: true;
|
|
50
|
+
hasDefault: false;
|
|
51
|
+
isPrimaryKey: false;
|
|
52
|
+
isAutoincrement: false;
|
|
53
|
+
hasRuntimeDefault: false;
|
|
54
|
+
enumValues: [string, ...string[]];
|
|
55
|
+
baseColumn: never;
|
|
56
|
+
identity: undefined;
|
|
57
|
+
generated: undefined;
|
|
58
|
+
}, {}, {}>;
|
|
59
|
+
aggregateType: drizzle_orm_pg_core.PgColumn<{
|
|
60
|
+
name: "aggregate_type";
|
|
61
|
+
tableName: "domain_events";
|
|
62
|
+
dataType: "string";
|
|
63
|
+
columnType: "PgText";
|
|
64
|
+
data: string;
|
|
65
|
+
driverParam: string;
|
|
66
|
+
notNull: true;
|
|
67
|
+
hasDefault: false;
|
|
68
|
+
isPrimaryKey: false;
|
|
69
|
+
isAutoincrement: false;
|
|
70
|
+
hasRuntimeDefault: false;
|
|
71
|
+
enumValues: [string, ...string[]];
|
|
72
|
+
baseColumn: never;
|
|
73
|
+
identity: undefined;
|
|
74
|
+
generated: undefined;
|
|
75
|
+
}, {}, {}>;
|
|
76
|
+
payload: drizzle_orm_pg_core.PgColumn<{
|
|
77
|
+
name: "payload";
|
|
78
|
+
tableName: "domain_events";
|
|
79
|
+
dataType: "json";
|
|
80
|
+
columnType: "PgJsonb";
|
|
81
|
+
data: Record<string, unknown>;
|
|
82
|
+
driverParam: unknown;
|
|
83
|
+
notNull: true;
|
|
84
|
+
hasDefault: false;
|
|
85
|
+
isPrimaryKey: false;
|
|
86
|
+
isAutoincrement: false;
|
|
87
|
+
hasRuntimeDefault: false;
|
|
88
|
+
enumValues: undefined;
|
|
89
|
+
baseColumn: never;
|
|
90
|
+
identity: undefined;
|
|
91
|
+
generated: undefined;
|
|
92
|
+
}, {}, {
|
|
93
|
+
$type: Record<string, unknown>;
|
|
94
|
+
}>;
|
|
95
|
+
occurredAt: drizzle_orm_pg_core.PgColumn<{
|
|
96
|
+
name: "occurred_at";
|
|
97
|
+
tableName: "domain_events";
|
|
98
|
+
dataType: "date";
|
|
99
|
+
columnType: "PgTimestamp";
|
|
100
|
+
data: Date;
|
|
101
|
+
driverParam: string;
|
|
102
|
+
notNull: true;
|
|
103
|
+
hasDefault: false;
|
|
104
|
+
isPrimaryKey: false;
|
|
105
|
+
isAutoincrement: false;
|
|
106
|
+
hasRuntimeDefault: false;
|
|
107
|
+
enumValues: undefined;
|
|
108
|
+
baseColumn: never;
|
|
109
|
+
identity: undefined;
|
|
110
|
+
generated: undefined;
|
|
111
|
+
}, {}, {}>;
|
|
112
|
+
processedAt: drizzle_orm_pg_core.PgColumn<{
|
|
113
|
+
name: "processed_at";
|
|
114
|
+
tableName: "domain_events";
|
|
115
|
+
dataType: "date";
|
|
116
|
+
columnType: "PgTimestamp";
|
|
117
|
+
data: Date;
|
|
118
|
+
driverParam: string;
|
|
119
|
+
notNull: false;
|
|
120
|
+
hasDefault: false;
|
|
121
|
+
isPrimaryKey: false;
|
|
122
|
+
isAutoincrement: false;
|
|
123
|
+
hasRuntimeDefault: false;
|
|
124
|
+
enumValues: undefined;
|
|
125
|
+
baseColumn: never;
|
|
126
|
+
identity: undefined;
|
|
127
|
+
generated: undefined;
|
|
128
|
+
}, {}, {}>;
|
|
129
|
+
status: drizzle_orm_pg_core.PgColumn<{
|
|
130
|
+
name: "status";
|
|
131
|
+
tableName: "domain_events";
|
|
132
|
+
dataType: "string";
|
|
133
|
+
columnType: "PgText";
|
|
134
|
+
data: string;
|
|
135
|
+
driverParam: string;
|
|
136
|
+
notNull: true;
|
|
137
|
+
hasDefault: true;
|
|
138
|
+
isPrimaryKey: false;
|
|
139
|
+
isAutoincrement: false;
|
|
140
|
+
hasRuntimeDefault: false;
|
|
141
|
+
enumValues: [string, ...string[]];
|
|
142
|
+
baseColumn: never;
|
|
143
|
+
identity: undefined;
|
|
144
|
+
generated: undefined;
|
|
145
|
+
}, {}, {}>;
|
|
146
|
+
error: drizzle_orm_pg_core.PgColumn<{
|
|
147
|
+
name: "error";
|
|
148
|
+
tableName: "domain_events";
|
|
149
|
+
dataType: "string";
|
|
150
|
+
columnType: "PgText";
|
|
151
|
+
data: string;
|
|
152
|
+
driverParam: string;
|
|
153
|
+
notNull: false;
|
|
154
|
+
hasDefault: false;
|
|
155
|
+
isPrimaryKey: false;
|
|
156
|
+
isAutoincrement: false;
|
|
157
|
+
hasRuntimeDefault: false;
|
|
158
|
+
enumValues: [string, ...string[]];
|
|
159
|
+
baseColumn: never;
|
|
160
|
+
identity: undefined;
|
|
161
|
+
generated: undefined;
|
|
162
|
+
}, {}, {}>;
|
|
163
|
+
metadata: drizzle_orm_pg_core.PgColumn<{
|
|
164
|
+
name: "metadata";
|
|
165
|
+
tableName: "domain_events";
|
|
166
|
+
dataType: "json";
|
|
167
|
+
columnType: "PgJsonb";
|
|
168
|
+
data: Record<string, unknown>;
|
|
169
|
+
driverParam: unknown;
|
|
170
|
+
notNull: false;
|
|
171
|
+
hasDefault: false;
|
|
172
|
+
isPrimaryKey: false;
|
|
173
|
+
isAutoincrement: false;
|
|
174
|
+
hasRuntimeDefault: false;
|
|
175
|
+
enumValues: undefined;
|
|
176
|
+
baseColumn: never;
|
|
177
|
+
identity: undefined;
|
|
178
|
+
generated: undefined;
|
|
179
|
+
}, {}, {
|
|
180
|
+
$type: Record<string, unknown>;
|
|
181
|
+
}>;
|
|
182
|
+
};
|
|
183
|
+
dialect: "pg";
|
|
184
|
+
}>;
|
|
185
|
+
type DomainEventRecord = InferSelectModel<typeof domainEvents>;
|
|
186
|
+
|
|
187
|
+
export { type DomainEventRecord, domainEvents };
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
// runtime/subsystems/events/domain-events.schema.ts
|
|
2
|
+
import {
|
|
3
|
+
jsonb,
|
|
4
|
+
pgTable,
|
|
5
|
+
text,
|
|
6
|
+
timestamp,
|
|
7
|
+
uuid
|
|
8
|
+
} from "drizzle-orm/pg-core";
|
|
9
|
+
var domainEvents = pgTable(
|
|
10
|
+
"domain_events",
|
|
11
|
+
{
|
|
12
|
+
id: uuid("id").primaryKey(),
|
|
13
|
+
type: text("type").notNull(),
|
|
14
|
+
aggregateId: text("aggregate_id").notNull(),
|
|
15
|
+
aggregateType: text("aggregate_type").notNull(),
|
|
16
|
+
payload: jsonb("payload").notNull().$type(),
|
|
17
|
+
occurredAt: timestamp("occurred_at", { withTimezone: true }).notNull(),
|
|
18
|
+
processedAt: timestamp("processed_at", { withTimezone: true }),
|
|
19
|
+
/** Lifecycle status: pending | processed | failed */
|
|
20
|
+
status: text("status").notNull().default("pending"),
|
|
21
|
+
/** Error message from the last failed dispatch attempt. */
|
|
22
|
+
error: text("error"),
|
|
23
|
+
metadata: jsonb("metadata").$type()
|
|
24
|
+
}
|
|
25
|
+
// Indexes: add via migration when deploying
|
|
26
|
+
// - (status, occurred_at) for polling
|
|
27
|
+
// - (aggregate_id, aggregate_type) for replay
|
|
28
|
+
);
|
|
29
|
+
export {
|
|
30
|
+
domainEvents
|
|
31
|
+
};
|
|
32
|
+
//# sourceMappingURL=domain-events.schema.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../runtime/subsystems/events/domain-events.schema.ts"],"sourcesContent":["/**\n * Drizzle schema for the domain_events outbox table.\n *\n * This table backs the DrizzleEventBus. Events are inserted within the\n * same database transaction as the domain write (outbox pattern). A\n * polling process reads unprocessed rows and dispatches to subscribers.\n *\n * Indexes:\n * - (status, occurredAt) — polling query filter\n * - (aggregateId, aggregateType) — event replay per aggregate\n */\nimport {\n jsonb,\n pgTable,\n text,\n timestamp,\n uuid,\n} from 'drizzle-orm/pg-core';\nimport type { InferSelectModel } from 'drizzle-orm';\n\nexport const domainEvents = pgTable(\n 'domain_events',\n {\n id: uuid('id').primaryKey(),\n type: text('type').notNull(),\n aggregateId: text('aggregate_id').notNull(),\n aggregateType: text('aggregate_type').notNull(),\n payload: jsonb('payload').notNull().$type<Record<string, unknown>>(),\n occurredAt: timestamp('occurred_at', { withTimezone: true }).notNull(),\n processedAt: timestamp('processed_at', { withTimezone: true }),\n /** Lifecycle status: pending | processed | failed */\n status: text('status').notNull().default('pending'),\n /** Error message from the last failed dispatch attempt. */\n error: text('error'),\n metadata: jsonb('metadata').$type<Record<string, unknown>>(),\n },\n // Indexes: add via migration when deploying\n // - (status, occurred_at) for polling\n // - (aggregate_id, aggregate_type) for replay\n);\n\nexport type DomainEventRecord = InferSelectModel<typeof domainEvents>;\n"],"mappings":";AAWA;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAGA,IAAM,eAAe;AAAA,EAC1B;AAAA,EACA;AAAA,IACE,IAAI,KAAK,IAAI,EAAE,WAAW;AAAA,IAC1B,MAAM,KAAK,MAAM,EAAE,QAAQ;AAAA,IAC3B,aAAa,KAAK,cAAc,EAAE,QAAQ;AAAA,IAC1C,eAAe,KAAK,gBAAgB,EAAE,QAAQ;AAAA,IAC9C,SAAS,MAAM,SAAS,EAAE,QAAQ,EAAE,MAA+B;AAAA,IACnE,YAAY,UAAU,eAAe,EAAE,cAAc,KAAK,CAAC,EAAE,QAAQ;AAAA,IACrE,aAAa,UAAU,gBAAgB,EAAE,cAAc,KAAK,CAAC;AAAA;AAAA,IAE7D,QAAQ,KAAK,QAAQ,EAAE,QAAQ,EAAE,QAAQ,SAAS;AAAA;AAAA,IAElD,OAAO,KAAK,OAAO;AAAA,IACnB,UAAU,MAAM,UAAU,EAAE,MAA+B;AAAA,EAC7D;AAAA;AAAA;AAAA;AAIF;","names":[]}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { OnModuleInit, OnModuleDestroy } from '@nestjs/common';
|
|
2
|
+
import { IEventBus, DomainEvent, DrizzleTransaction } from './event-bus.protocol.js';
|
|
3
|
+
import { DrizzleClient } from '../../types/drizzle.js';
|
|
4
|
+
import 'drizzle-orm/node-postgres';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* DrizzleEventBus — Postgres-backed event bus using the transactional outbox pattern.
|
|
8
|
+
*
|
|
9
|
+
* Events are inserted into the `domain_events` table within the caller's
|
|
10
|
+
* Drizzle transaction. A background polling loop (started on module init)
|
|
11
|
+
* reads unprocessed events and dispatches them to registered subscribers.
|
|
12
|
+
*
|
|
13
|
+
* When the transaction rolls back, the event is never persisted — no
|
|
14
|
+
* phantom events.
|
|
15
|
+
*
|
|
16
|
+
* This backend is suitable until you need real-time fan-out or very high
|
|
17
|
+
* throughput. At that point, swap the backend for Redis Streams or similar
|
|
18
|
+
* via EventsModule.forRoot({ backend: '...' }) without touching use cases.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
declare class DrizzleEventBus implements IEventBus, OnModuleInit, OnModuleDestroy {
|
|
22
|
+
private readonly db;
|
|
23
|
+
private readonly logger;
|
|
24
|
+
private polling;
|
|
25
|
+
private pollTimer;
|
|
26
|
+
private readonly handlers;
|
|
27
|
+
constructor(db: DrizzleClient);
|
|
28
|
+
onModuleInit(): Promise<void>;
|
|
29
|
+
onModuleDestroy(): Promise<void>;
|
|
30
|
+
publish(event: DomainEvent, tx?: DrizzleTransaction): Promise<void>;
|
|
31
|
+
publishMany(events: DomainEvent[], tx?: DrizzleTransaction): Promise<void>;
|
|
32
|
+
subscribe<T extends DomainEvent = DomainEvent>(eventType: string, handler: (event: T) => Promise<void>): () => void;
|
|
33
|
+
private schedulePoll;
|
|
34
|
+
private processBatch;
|
|
35
|
+
private dispatch;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export { DrizzleEventBus };
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
3
|
+
var __decorateClass = (decorators, target, key, kind) => {
|
|
4
|
+
var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc(target, key) : target;
|
|
5
|
+
for (var i = decorators.length - 1, decorator; i >= 0; i--)
|
|
6
|
+
if (decorator = decorators[i])
|
|
7
|
+
result = (kind ? decorator(target, key, result) : decorator(result)) || result;
|
|
8
|
+
if (kind && result) __defProp(target, key, result);
|
|
9
|
+
return result;
|
|
10
|
+
};
|
|
11
|
+
var __decorateParam = (index, decorator) => (target, key) => decorator(target, key, index);
|
|
12
|
+
|
|
13
|
+
// runtime/subsystems/events/event-bus.drizzle-backend.ts
|
|
14
|
+
import { Injectable, Inject, Logger } from "@nestjs/common";
|
|
15
|
+
import { eq, and, sql } from "drizzle-orm";
|
|
16
|
+
|
|
17
|
+
// runtime/subsystems/events/domain-events.schema.ts
|
|
18
|
+
import {
|
|
19
|
+
jsonb,
|
|
20
|
+
pgTable,
|
|
21
|
+
text,
|
|
22
|
+
timestamp,
|
|
23
|
+
uuid
|
|
24
|
+
} from "drizzle-orm/pg-core";
|
|
25
|
+
var domainEvents = pgTable(
|
|
26
|
+
"domain_events",
|
|
27
|
+
{
|
|
28
|
+
id: uuid("id").primaryKey(),
|
|
29
|
+
type: text("type").notNull(),
|
|
30
|
+
aggregateId: text("aggregate_id").notNull(),
|
|
31
|
+
aggregateType: text("aggregate_type").notNull(),
|
|
32
|
+
payload: jsonb("payload").notNull().$type(),
|
|
33
|
+
occurredAt: timestamp("occurred_at", { withTimezone: true }).notNull(),
|
|
34
|
+
processedAt: timestamp("processed_at", { withTimezone: true }),
|
|
35
|
+
/** Lifecycle status: pending | processed | failed */
|
|
36
|
+
status: text("status").notNull().default("pending"),
|
|
37
|
+
/** Error message from the last failed dispatch attempt. */
|
|
38
|
+
error: text("error"),
|
|
39
|
+
metadata: jsonb("metadata").$type()
|
|
40
|
+
}
|
|
41
|
+
// Indexes: add via migration when deploying
|
|
42
|
+
// - (status, occurred_at) for polling
|
|
43
|
+
// - (aggregate_id, aggregate_type) for replay
|
|
44
|
+
);
|
|
45
|
+
|
|
46
|
+
// runtime/constants/tokens.ts
|
|
47
|
+
var DRIZZLE = "DRIZZLE";
|
|
48
|
+
|
|
49
|
+
// runtime/subsystems/events/event-bus.drizzle-backend.ts
|
|
50
|
+
var POLL_INTERVAL_MS = 1e3;
|
|
51
|
+
var POLL_BATCH_SIZE = 50;
|
|
52
|
+
var MAX_RETRIES = 3;
|
|
53
|
+
var DrizzleEventBus = class {
|
|
54
|
+
constructor(db) {
|
|
55
|
+
this.db = db;
|
|
56
|
+
}
|
|
57
|
+
db;
|
|
58
|
+
logger = new Logger(DrizzleEventBus.name);
|
|
59
|
+
polling = false;
|
|
60
|
+
pollTimer = null;
|
|
61
|
+
handlers = /* @__PURE__ */ new Map();
|
|
62
|
+
// ============================================================================
|
|
63
|
+
// Lifecycle
|
|
64
|
+
// ============================================================================
|
|
65
|
+
async onModuleInit() {
|
|
66
|
+
this.polling = true;
|
|
67
|
+
this.schedulePoll();
|
|
68
|
+
}
|
|
69
|
+
async onModuleDestroy() {
|
|
70
|
+
this.polling = false;
|
|
71
|
+
if (this.pollTimer) {
|
|
72
|
+
clearTimeout(this.pollTimer);
|
|
73
|
+
this.pollTimer = null;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
// ============================================================================
|
|
77
|
+
// IEventBus
|
|
78
|
+
// ============================================================================
|
|
79
|
+
async publish(event, tx) {
|
|
80
|
+
const client = tx ?? this.db;
|
|
81
|
+
await client.insert(domainEvents).values({
|
|
82
|
+
id: event.id,
|
|
83
|
+
type: event.type,
|
|
84
|
+
aggregateId: event.aggregateId,
|
|
85
|
+
aggregateType: event.aggregateType,
|
|
86
|
+
payload: event.payload,
|
|
87
|
+
occurredAt: event.occurredAt,
|
|
88
|
+
processedAt: null,
|
|
89
|
+
status: "pending",
|
|
90
|
+
metadata: event.metadata
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
async publishMany(events, tx) {
|
|
94
|
+
if (events.length === 0) return;
|
|
95
|
+
const client = tx ?? this.db;
|
|
96
|
+
await client.insert(domainEvents).values(
|
|
97
|
+
events.map((e) => ({
|
|
98
|
+
id: e.id,
|
|
99
|
+
type: e.type,
|
|
100
|
+
aggregateId: e.aggregateId,
|
|
101
|
+
aggregateType: e.aggregateType,
|
|
102
|
+
payload: e.payload,
|
|
103
|
+
occurredAt: e.occurredAt,
|
|
104
|
+
processedAt: null,
|
|
105
|
+
status: "pending",
|
|
106
|
+
metadata: e.metadata
|
|
107
|
+
}))
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
subscribe(eventType, handler) {
|
|
111
|
+
if (!this.handlers.has(eventType)) {
|
|
112
|
+
this.handlers.set(eventType, /* @__PURE__ */ new Set());
|
|
113
|
+
}
|
|
114
|
+
const set = this.handlers.get(eventType);
|
|
115
|
+
const h = handler;
|
|
116
|
+
set.add(h);
|
|
117
|
+
return () => {
|
|
118
|
+
set.delete(h);
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
// ============================================================================
|
|
122
|
+
// Polling
|
|
123
|
+
// ============================================================================
|
|
124
|
+
schedulePoll() {
|
|
125
|
+
if (!this.polling) return;
|
|
126
|
+
this.pollTimer = setTimeout(async () => {
|
|
127
|
+
try {
|
|
128
|
+
await this.processBatch();
|
|
129
|
+
} catch (err) {
|
|
130
|
+
this.logger.error(`Poll cycle error: ${err}`);
|
|
131
|
+
} finally {
|
|
132
|
+
this.schedulePoll();
|
|
133
|
+
}
|
|
134
|
+
}, POLL_INTERVAL_MS);
|
|
135
|
+
}
|
|
136
|
+
async processBatch() {
|
|
137
|
+
const rows = await this.db.transaction(async (tx) => {
|
|
138
|
+
return tx.execute(
|
|
139
|
+
sql`SELECT * FROM domain_events WHERE status = 'pending' ORDER BY occurred_at ASC LIMIT ${POLL_BATCH_SIZE} FOR UPDATE SKIP LOCKED`
|
|
140
|
+
);
|
|
141
|
+
}).then((result) => result.rows ?? result);
|
|
142
|
+
for (const row of rows) {
|
|
143
|
+
const event = {
|
|
144
|
+
id: row["id"],
|
|
145
|
+
type: row["type"],
|
|
146
|
+
aggregateId: row["aggregate_id"],
|
|
147
|
+
aggregateType: row["aggregate_type"],
|
|
148
|
+
payload: row["payload"],
|
|
149
|
+
occurredAt: new Date(row["occurred_at"]),
|
|
150
|
+
metadata: row["metadata"]
|
|
151
|
+
};
|
|
152
|
+
let attempt = 0;
|
|
153
|
+
let lastError;
|
|
154
|
+
while (attempt < MAX_RETRIES) {
|
|
155
|
+
try {
|
|
156
|
+
await this.dispatch(event);
|
|
157
|
+
await this.db.update(domainEvents).set({ status: "processed", processedAt: /* @__PURE__ */ new Date() }).where(eq(domainEvents.id, event.id));
|
|
158
|
+
lastError = void 0;
|
|
159
|
+
break;
|
|
160
|
+
} catch (err) {
|
|
161
|
+
lastError = err;
|
|
162
|
+
attempt++;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
if (lastError !== void 0) {
|
|
166
|
+
const errorMessage = lastError instanceof Error ? lastError.message : String(lastError);
|
|
167
|
+
await this.db.update(domainEvents).set({ status: "failed", error: errorMessage }).where(and(eq(domainEvents.id, event.id), eq(domainEvents.status, "pending")));
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
async dispatch(event) {
|
|
172
|
+
const set = this.handlers.get(event.type);
|
|
173
|
+
if (!set) return;
|
|
174
|
+
let firstError;
|
|
175
|
+
for (const handler of set) {
|
|
176
|
+
try {
|
|
177
|
+
await handler(event);
|
|
178
|
+
} catch (err) {
|
|
179
|
+
this.logger.error(
|
|
180
|
+
`Handler error for event type "${event.type}" (id: ${event.id}): ${err}`
|
|
181
|
+
);
|
|
182
|
+
if (firstError === void 0) {
|
|
183
|
+
firstError = err;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
if (firstError !== void 0) {
|
|
188
|
+
throw firstError;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
};
|
|
192
|
+
DrizzleEventBus = __decorateClass([
|
|
193
|
+
Injectable(),
|
|
194
|
+
__decorateParam(0, Inject(DRIZZLE))
|
|
195
|
+
], DrizzleEventBus);
|
|
196
|
+
export {
|
|
197
|
+
DrizzleEventBus
|
|
198
|
+
};
|
|
199
|
+
//# sourceMappingURL=event-bus.drizzle-backend.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../runtime/subsystems/events/event-bus.drizzle-backend.ts","../../../../runtime/subsystems/events/domain-events.schema.ts","../../../../runtime/constants/tokens.ts"],"sourcesContent":["/**\n * DrizzleEventBus — Postgres-backed event bus using the transactional outbox pattern.\n *\n * Events are inserted into the `domain_events` table within the caller's\n * Drizzle transaction. A background polling loop (started on module init)\n * reads unprocessed events and dispatches them to registered subscribers.\n *\n * When the transaction rolls back, the event is never persisted — no\n * phantom events.\n *\n * This backend is suitable until you need real-time fan-out or very high\n * throughput. At that point, swap the backend for Redis Streams or similar\n * via EventsModule.forRoot({ backend: '...' }) without touching use cases.\n */\nimport { Injectable, OnModuleDestroy, OnModuleInit, Inject, Logger } from '@nestjs/common';\nimport { eq, and, sql } from 'drizzle-orm';\nimport type { DomainEvent, DrizzleTransaction, IEventBus } from './event-bus.protocol';\nimport type { DrizzleClient } from '../../types/drizzle';\nimport { domainEvents } from './domain-events.schema';\nimport { DRIZZLE } from '../../constants/tokens';\n\n/** How long to wait between polling cycles (ms). */\nconst POLL_INTERVAL_MS = 1_000;\n/** Max events claimed per polling cycle to bound memory usage. */\nconst POLL_BATCH_SIZE = 50;\n/** Max processing attempts before marking an event failed. */\nconst MAX_RETRIES = 3;\n\n@Injectable()\nexport class DrizzleEventBus implements IEventBus, OnModuleInit, OnModuleDestroy {\n private readonly logger = new Logger(DrizzleEventBus.name);\n private polling = false;\n private pollTimer: ReturnType<typeof setTimeout> | null = null;\n private readonly handlers = new Map<string, Set<(event: DomainEvent) => Promise<void>>>();\n\n constructor(@Inject(DRIZZLE) private readonly db: DrizzleClient) {}\n\n // ============================================================================\n // Lifecycle\n // ============================================================================\n\n async onModuleInit(): Promise<void> {\n this.polling = true;\n this.schedulePoll();\n }\n\n async onModuleDestroy(): Promise<void> {\n this.polling = false;\n if (this.pollTimer) {\n clearTimeout(this.pollTimer);\n this.pollTimer = null;\n }\n }\n\n // ============================================================================\n // IEventBus\n // ============================================================================\n\n async publish(event: DomainEvent, tx?: DrizzleTransaction): Promise<void> {\n const client = (tx ?? this.db) as DrizzleClient;\n await client.insert(domainEvents).values({\n id: event.id,\n type: event.type,\n aggregateId: event.aggregateId,\n aggregateType: event.aggregateType,\n payload: event.payload,\n occurredAt: event.occurredAt,\n processedAt: null,\n status: 'pending',\n metadata: event.metadata,\n });\n }\n\n async publishMany(events: DomainEvent[], tx?: DrizzleTransaction): Promise<void> {\n if (events.length === 0) return;\n const client = (tx ?? this.db) as DrizzleClient;\n await client.insert(domainEvents).values(\n events.map((e) => ({\n id: e.id,\n type: e.type,\n aggregateId: e.aggregateId,\n aggregateType: e.aggregateType,\n payload: e.payload,\n occurredAt: e.occurredAt,\n processedAt: null,\n status: 'pending' as const,\n metadata: e.metadata,\n })),\n );\n }\n\n subscribe<T extends DomainEvent = DomainEvent>(\n eventType: string,\n handler: (event: T) => Promise<void>,\n ): () => void {\n if (!this.handlers.has(eventType)) {\n this.handlers.set(eventType, new Set());\n }\n const set = this.handlers.get(eventType)!;\n const h = handler as (event: DomainEvent) => Promise<void>;\n set.add(h);\n return () => {\n set.delete(h);\n };\n }\n\n // ============================================================================\n // Polling\n // ============================================================================\n\n private schedulePoll(): void {\n if (!this.polling) return;\n this.pollTimer = setTimeout(async () => {\n try {\n await this.processBatch();\n } catch (err) {\n this.logger.error(`Poll cycle error: ${err}`);\n } finally {\n this.schedulePoll();\n }\n }, POLL_INTERVAL_MS);\n }\n\n private async processBatch(): Promise<void> {\n // Fetch a batch of pending events with FOR UPDATE SKIP LOCKED to prevent\n // double-processing when multiple instances are polling concurrently.\n const rows = await this.db.transaction(async (tx) => {\n return tx.execute(\n sql`SELECT * FROM domain_events WHERE status = 'pending' ORDER BY occurred_at ASC LIMIT ${POLL_BATCH_SIZE} FOR UPDATE SKIP LOCKED`,\n ) as Promise<{ rows: Record<string, unknown>[] }>;\n }).then((result) => (result as unknown as { rows: Record<string, unknown>[] }).rows ?? result as unknown as Record<string, unknown>[]);\n\n for (const row of rows) {\n const event: DomainEvent = {\n id: row['id'] as string,\n type: row['type'] as string,\n aggregateId: row['aggregate_id'] as string,\n aggregateType: row['aggregate_type'] as string,\n payload: row['payload'] as Record<string, unknown>,\n occurredAt: new Date(row['occurred_at'] as string),\n metadata: row['metadata'] as Record<string, unknown> | undefined,\n };\n\n let attempt = 0;\n let lastError: unknown;\n while (attempt < MAX_RETRIES) {\n try {\n await this.dispatch(event);\n // Mark processed\n await this.db\n .update(domainEvents)\n .set({ status: 'processed', processedAt: new Date() })\n .where(eq(domainEvents.id, event.id));\n lastError = undefined;\n break;\n } catch (err) {\n lastError = err;\n attempt++;\n }\n }\n\n if (lastError !== undefined) {\n const errorMessage = lastError instanceof Error ? lastError.message : String(lastError);\n await this.db\n .update(domainEvents)\n .set({ status: 'failed', error: errorMessage })\n .where(and(eq(domainEvents.id, event.id), eq(domainEvents.status, 'pending')));\n }\n }\n }\n\n private async dispatch(event: DomainEvent): Promise<void> {\n const set = this.handlers.get(event.type);\n if (!set) return;\n\n let firstError: unknown;\n for (const handler of set) {\n try {\n await handler(event);\n } catch (err) {\n this.logger.error(\n `Handler error for event type \"${event.type}\" (id: ${event.id}): ${err}`,\n );\n if (firstError === undefined) {\n firstError = err;\n }\n }\n }\n\n if (firstError !== undefined) {\n throw firstError;\n }\n }\n}\n","/**\n * Drizzle schema for the domain_events outbox table.\n *\n * This table backs the DrizzleEventBus. Events are inserted within the\n * same database transaction as the domain write (outbox pattern). A\n * polling process reads unprocessed rows and dispatches to subscribers.\n *\n * Indexes:\n * - (status, occurredAt) — polling query filter\n * - (aggregateId, aggregateType) — event replay per aggregate\n */\nimport {\n jsonb,\n pgTable,\n text,\n timestamp,\n uuid,\n} from 'drizzle-orm/pg-core';\nimport type { InferSelectModel } from 'drizzle-orm';\n\nexport const domainEvents = pgTable(\n 'domain_events',\n {\n id: uuid('id').primaryKey(),\n type: text('type').notNull(),\n aggregateId: text('aggregate_id').notNull(),\n aggregateType: text('aggregate_type').notNull(),\n payload: jsonb('payload').notNull().$type<Record<string, unknown>>(),\n occurredAt: timestamp('occurred_at', { withTimezone: true }).notNull(),\n processedAt: timestamp('processed_at', { withTimezone: true }),\n /** Lifecycle status: pending | processed | failed */\n status: text('status').notNull().default('pending'),\n /** Error message from the last failed dispatch attempt. */\n error: text('error'),\n metadata: jsonb('metadata').$type<Record<string, unknown>>(),\n },\n // Indexes: add via migration when deploying\n // - (status, occurred_at) for polling\n // - (aggregate_id, aggregate_type) for replay\n);\n\nexport type DomainEventRecord = InferSelectModel<typeof domainEvents>;\n","/**\n * NestJS injection tokens\n *\n * Used with @Inject() decorator in concrete repository constructors.\n */\n\n/**\n * Injection token for the Drizzle ORM database client.\n *\n * Usage in concrete repositories:\n * ```typescript\n * constructor(@Inject(DRIZZLE) db: DrizzleClient) { super(db); }\n * ```\n */\nexport const DRIZZLE = 'DRIZZLE' as const;\n\n/**\n * Injection token for the event bus (IEventBus).\n *\n * Optional — only resolved when EventsModule.forRoot() is registered.\n * BaseService uses this with @Optional() to emit lifecycle events\n * without requiring the events subsystem to be installed.\n *\n * Usage in services/use cases:\n * ```typescript\n * @Optional() @Inject(EVENT_BUS) eventBus?: IEventBus\n * ```\n */\nexport const EVENT_BUS = 'EVENT_BUS' as const;\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { IEventBus, DomainEvent } from './event-bus.protocol.js';
|
|
2
|
+
import '../../types/drizzle.js';
|
|
3
|
+
import 'drizzle-orm/node-postgres';
|
|
4
|
+
|
|
5
|
+
declare class MemoryEventBus implements IEventBus {
|
|
6
|
+
private readonly logger;
|
|
7
|
+
/** All events published since construction (or last clear). */
|
|
8
|
+
readonly publishedEvents: DomainEvent[];
|
|
9
|
+
private readonly handlers;
|
|
10
|
+
publish(event: DomainEvent): Promise<void>;
|
|
11
|
+
publishMany(events: DomainEvent[]): Promise<void>;
|
|
12
|
+
subscribe<T extends DomainEvent = DomainEvent>(eventType: string, handler: (event: T) => Promise<void>): () => void;
|
|
13
|
+
/** Remove all published events and subscriptions. Useful in beforeEach. */
|
|
14
|
+
clear(): void;
|
|
15
|
+
private dispatch;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export { MemoryEventBus };
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
3
|
+
var __decorateClass = (decorators, target, key, kind) => {
|
|
4
|
+
var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc(target, key) : target;
|
|
5
|
+
for (var i = decorators.length - 1, decorator; i >= 0; i--)
|
|
6
|
+
if (decorator = decorators[i])
|
|
7
|
+
result = (kind ? decorator(target, key, result) : decorator(result)) || result;
|
|
8
|
+
if (kind && result) __defProp(target, key, result);
|
|
9
|
+
return result;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
// runtime/subsystems/events/event-bus.memory-backend.ts
|
|
13
|
+
import { Injectable, Logger } from "@nestjs/common";
|
|
14
|
+
var MemoryEventBus = class {
|
|
15
|
+
logger = new Logger(MemoryEventBus.name);
|
|
16
|
+
/** All events published since construction (or last clear). */
|
|
17
|
+
publishedEvents = [];
|
|
18
|
+
handlers = /* @__PURE__ */ new Map();
|
|
19
|
+
async publish(event) {
|
|
20
|
+
this.publishedEvents.push(event);
|
|
21
|
+
await this.dispatch(event);
|
|
22
|
+
}
|
|
23
|
+
async publishMany(events) {
|
|
24
|
+
for (const event of events) {
|
|
25
|
+
await this.publish(event);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
subscribe(eventType, handler) {
|
|
29
|
+
if (!this.handlers.has(eventType)) {
|
|
30
|
+
this.handlers.set(eventType, /* @__PURE__ */ new Set());
|
|
31
|
+
}
|
|
32
|
+
const set = this.handlers.get(eventType);
|
|
33
|
+
const h = handler;
|
|
34
|
+
set.add(h);
|
|
35
|
+
return () => {
|
|
36
|
+
set.delete(h);
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
/** Remove all published events and subscriptions. Useful in beforeEach. */
|
|
40
|
+
clear() {
|
|
41
|
+
this.publishedEvents.length = 0;
|
|
42
|
+
this.handlers.clear();
|
|
43
|
+
}
|
|
44
|
+
async dispatch(event) {
|
|
45
|
+
const set = this.handlers.get(event.type);
|
|
46
|
+
if (!set) return;
|
|
47
|
+
let firstError;
|
|
48
|
+
for (const handler of set) {
|
|
49
|
+
try {
|
|
50
|
+
await handler(event);
|
|
51
|
+
} catch (err) {
|
|
52
|
+
this.logger.error(
|
|
53
|
+
`Handler error for event type "${event.type}" (id: ${event.id}): ${err}`
|
|
54
|
+
);
|
|
55
|
+
if (firstError === void 0) {
|
|
56
|
+
firstError = err;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
if (firstError !== void 0) {
|
|
61
|
+
throw firstError;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
MemoryEventBus = __decorateClass([
|
|
66
|
+
Injectable()
|
|
67
|
+
], MemoryEventBus);
|
|
68
|
+
export {
|
|
69
|
+
MemoryEventBus
|
|
70
|
+
};
|
|
71
|
+
//# sourceMappingURL=event-bus.memory-backend.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../runtime/subsystems/events/event-bus.memory-backend.ts"],"sourcesContent":["/**\n * MemoryEventBus — in-memory backend for the event bus.\n *\n * Dispatches events synchronously to registered subscribers. The `tx`\n * parameter is ignored — all events are dispatched immediately.\n *\n * Use this backend in tests to assert event publication without a database.\n * Swap via EventsModule.forRoot({ backend: 'memory' }).\n */\nimport { Injectable, Logger } from '@nestjs/common';\nimport type { DomainEvent, IEventBus } from './event-bus.protocol';\n\n@Injectable()\nexport class MemoryEventBus implements IEventBus {\n private readonly logger = new Logger(MemoryEventBus.name);\n\n /** All events published since construction (or last clear). */\n readonly publishedEvents: DomainEvent[] = [];\n\n private readonly handlers = new Map<string, Set<(event: DomainEvent) => Promise<void>>>();\n\n async publish(event: DomainEvent): Promise<void> {\n this.publishedEvents.push(event);\n await this.dispatch(event);\n }\n\n async publishMany(events: DomainEvent[]): Promise<void> {\n for (const event of events) {\n await this.publish(event);\n }\n }\n\n subscribe<T extends DomainEvent = DomainEvent>(\n eventType: string,\n handler: (event: T) => Promise<void>,\n ): () => void {\n if (!this.handlers.has(eventType)) {\n this.handlers.set(eventType, new Set());\n }\n // Cast is safe — callers pass a typed handler; we store as the base type\n const set = this.handlers.get(eventType)!;\n const h = handler as (event: DomainEvent) => Promise<void>;\n set.add(h);\n\n return () => {\n set.delete(h);\n };\n }\n\n /** Remove all published events and subscriptions. Useful in beforeEach. */\n clear(): void {\n this.publishedEvents.length = 0;\n this.handlers.clear();\n }\n\n private async dispatch(event: DomainEvent): Promise<void> {\n const set = this.handlers.get(event.type);\n if (!set) return;\n\n let firstError: unknown;\n for (const handler of set) {\n try {\n await handler(event);\n } catch (err) {\n this.logger.error(\n `Handler error for event type \"${event.type}\" (id: ${event.id}): ${err}`,\n );\n if (firstError === undefined) {\n firstError = err;\n }\n }\n }\n\n if (firstError !== undefined) {\n throw firstError;\n }\n }\n}\n"],"mappings":";;;;;;;;;;;;AASA,SAAS,YAAY,cAAc;AAI5B,IAAM,iBAAN,MAA0C;AAAA,EAC9B,SAAS,IAAI,OAAO,eAAe,IAAI;AAAA;AAAA,EAG/C,kBAAiC,CAAC;AAAA,EAE1B,WAAW,oBAAI,IAAwD;AAAA,EAExF,MAAM,QAAQ,OAAmC;AAC/C,SAAK,gBAAgB,KAAK,KAAK;AAC/B,UAAM,KAAK,SAAS,KAAK;AAAA,EAC3B;AAAA,EAEA,MAAM,YAAY,QAAsC;AACtD,eAAW,SAAS,QAAQ;AAC1B,YAAM,KAAK,QAAQ,KAAK;AAAA,IAC1B;AAAA,EACF;AAAA,EAEA,UACE,WACA,SACY;AACZ,QAAI,CAAC,KAAK,SAAS,IAAI,SAAS,GAAG;AACjC,WAAK,SAAS,IAAI,WAAW,oBAAI,IAAI,CAAC;AAAA,IACxC;AAEA,UAAM,MAAM,KAAK,SAAS,IAAI,SAAS;AACvC,UAAM,IAAI;AACV,QAAI,IAAI,CAAC;AAET,WAAO,MAAM;AACX,UAAI,OAAO,CAAC;AAAA,IACd;AAAA,EACF;AAAA;AAAA,EAGA,QAAc;AACZ,SAAK,gBAAgB,SAAS;AAC9B,SAAK,SAAS,MAAM;AAAA,EACtB;AAAA,EAEA,MAAc,SAAS,OAAmC;AACxD,UAAM,MAAM,KAAK,SAAS,IAAI,MAAM,IAAI;AACxC,QAAI,CAAC,IAAK;AAEV,QAAI;AACJ,eAAW,WAAW,KAAK;AACzB,UAAI;AACF,cAAM,QAAQ,KAAK;AAAA,MACrB,SAAS,KAAK;AACZ,aAAK,OAAO;AAAA,UACV,iCAAiC,MAAM,IAAI,UAAU,MAAM,EAAE,MAAM,GAAG;AAAA,QACxE;AACA,YAAI,eAAe,QAAW;AAC5B,uBAAa;AAAA,QACf;AAAA,MACF;AAAA,IACF;AAEA,QAAI,eAAe,QAAW;AAC5B,YAAM;AAAA,IACR;AAAA,EACF;AACF;AAhEa,iBAAN;AAAA,EADN,WAAW;AAAA,GACC;","names":[]}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { DrizzleClient } from '../../types/drizzle.js';
|
|
2
|
+
import 'drizzle-orm/node-postgres';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Events subsystem — protocol (port)
|
|
6
|
+
*
|
|
7
|
+
* IEventBus is the hexagonal port. Use cases inject this interface via
|
|
8
|
+
* EVENT_BUS token. They never depend on a specific backend implementation.
|
|
9
|
+
*
|
|
10
|
+
* The DrizzleTransaction type mirrors what the Drizzle client exposes
|
|
11
|
+
* so callers can pass a transaction for the outbox pattern.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
type DrizzleTransaction = Parameters<Parameters<DrizzleClient['transaction']>[0]>[0];
|
|
15
|
+
interface DomainEvent {
|
|
16
|
+
/** UUID — used for deduplication and idempotency. */
|
|
17
|
+
readonly id: string;
|
|
18
|
+
/** Event type discriminator, e.g. 'contact_created'. */
|
|
19
|
+
readonly type: string;
|
|
20
|
+
/** ID of the aggregate that produced this event. */
|
|
21
|
+
readonly aggregateId: string;
|
|
22
|
+
/** Aggregate type name, e.g. 'contact'. */
|
|
23
|
+
readonly aggregateType: string;
|
|
24
|
+
/** Event-specific payload. */
|
|
25
|
+
readonly payload: Record<string, unknown>;
|
|
26
|
+
/** Wall-clock time the event occurred. */
|
|
27
|
+
readonly occurredAt: Date;
|
|
28
|
+
/** Optional routing / audit metadata. */
|
|
29
|
+
readonly metadata?: Record<string, unknown>;
|
|
30
|
+
}
|
|
31
|
+
interface IEventBus {
|
|
32
|
+
/**
|
|
33
|
+
* Publish a single domain event.
|
|
34
|
+
*
|
|
35
|
+
* Pass `tx` to include the event in an ongoing Drizzle transaction
|
|
36
|
+
* (transactional outbox pattern). If the transaction rolls back, the
|
|
37
|
+
* event is never persisted.
|
|
38
|
+
*/
|
|
39
|
+
publish(event: DomainEvent, tx?: DrizzleTransaction): Promise<void>;
|
|
40
|
+
/**
|
|
41
|
+
* Publish multiple domain events atomically.
|
|
42
|
+
* Same transactional semantics as `publish`.
|
|
43
|
+
*/
|
|
44
|
+
publishMany(events: DomainEvent[], tx?: DrizzleTransaction): Promise<void>;
|
|
45
|
+
/**
|
|
46
|
+
* Subscribe to events of the given type.
|
|
47
|
+
* Returns an unsubscribe function — call it to remove the handler.
|
|
48
|
+
*/
|
|
49
|
+
subscribe<T extends DomainEvent = DomainEvent>(eventType: string, handler: (event: T) => Promise<void>): () => void;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export type { DomainEvent, DrizzleTransaction, IEventBus };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
//# sourceMappingURL=event-bus.protocol.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|