@nestarc/inbox 0.0.0-stage → 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.
Files changed (98) hide show
  1. package/CHANGELOG.md +72 -0
  2. package/LICENSE +21 -0
  3. package/README.md +309 -2
  4. package/dist/contracts/errors.d.ts +34 -0
  5. package/dist/contracts/errors.js +73 -0
  6. package/dist/contracts/errors.js.map +1 -0
  7. package/dist/contracts/types.d.ts +174 -0
  8. package/dist/contracts/types.js +3 -0
  9. package/dist/contracts/types.js.map +1 -0
  10. package/dist/inbox-admin.service.d.ts +44 -0
  11. package/dist/inbox-admin.service.js +102 -0
  12. package/dist/inbox-admin.service.js.map +1 -0
  13. package/dist/inbox.module.d.ts +13 -0
  14. package/dist/inbox.module.js +59 -0
  15. package/dist/inbox.module.js.map +1 -0
  16. package/dist/inbox.service.d.ts +22 -0
  17. package/dist/inbox.service.js +74 -0
  18. package/dist/inbox.service.js.map +1 -0
  19. package/dist/index.d.ts +9 -0
  20. package/dist/index.js +28 -0
  21. package/dist/index.js.map +1 -0
  22. package/dist/integrations/tenancy.d.ts +25 -0
  23. package/dist/integrations/tenancy.js +56 -0
  24. package/dist/integrations/tenancy.js.map +1 -0
  25. package/dist/internal/handler-context.d.ts +3 -0
  26. package/dist/internal/handler-context.js +39 -0
  27. package/dist/internal/handler-context.js.map +1 -0
  28. package/dist/internal/options.d.ts +5 -0
  29. package/dist/internal/options.js +86 -0
  30. package/dist/internal/options.js.map +1 -0
  31. package/dist/internal/reliability-core/abort.d.ts +2 -0
  32. package/dist/internal/reliability-core/abort.js +21 -0
  33. package/dist/internal/reliability-core/abort.js.map +1 -0
  34. package/dist/internal/reliability-core/index.d.ts +3 -0
  35. package/dist/internal/reliability-core/index.js +14 -0
  36. package/dist/internal/reliability-core/index.js.map +1 -0
  37. package/dist/internal/reliability-core/json.d.ts +13 -0
  38. package/dist/internal/reliability-core/json.js +125 -0
  39. package/dist/internal/reliability-core/json.js.map +1 -0
  40. package/dist/internal/reliability-core/retry.d.ts +8 -0
  41. package/dist/internal/reliability-core/retry.js +26 -0
  42. package/dist/internal/reliability-core/retry.js.map +1 -0
  43. package/dist/internal/validation.d.ts +6 -0
  44. package/dist/internal/validation.js +123 -0
  45. package/dist/internal/validation.js.map +1 -0
  46. package/dist/storage/prisma/prisma-inbox.storage.d.ts +30 -0
  47. package/dist/storage/prisma/prisma-inbox.storage.js +369 -0
  48. package/dist/storage/prisma/prisma-inbox.storage.js.map +1 -0
  49. package/dist/testing/index.d.ts +40 -0
  50. package/dist/testing/index.js +120 -0
  51. package/dist/testing/index.js.map +1 -0
  52. package/dist/worker/inbox-worker.d.ts +31 -0
  53. package/dist/worker/inbox-worker.js +343 -0
  54. package/dist/worker/inbox-worker.js.map +1 -0
  55. package/docs/adr/0001-durable-inbox.md +40 -0
  56. package/docs/benchmarks/2026-10-27-local.md +31 -0
  57. package/docs/benchmarks/2026-10-28-preview1-local.md +87 -0
  58. package/docs/benchmarks/2026-10-28-preview1-run1.json +120 -0
  59. package/docs/benchmarks/2026-10-28-preview1-run2.json +120 -0
  60. package/docs/benchmarks/2026-10-28-preview1-run3.json +120 -0
  61. package/docs/benchmarks/2026-10-28-sustained-local.md +104 -0
  62. package/docs/benchmarks/2026-10-28-sustained-run1.json +2553 -0
  63. package/docs/benchmarks/2026-10-28-sustained-run2.json +2553 -0
  64. package/docs/broker-recovery-results.json +52 -0
  65. package/docs/broker-recovery.md +30 -0
  66. package/docs/db-operations.md +41 -0
  67. package/docs/implementation-roadmap.md +129 -0
  68. package/docs/operations/2026-10-28-database.json +34 -0
  69. package/docs/package-plan.md +250 -0
  70. package/docs/releasing.md +91 -0
  71. package/docs/testing.md +86 -0
  72. package/docs/usage.md +266 -0
  73. package/docs/validation.md +174 -0
  74. package/examples/README.md +124 -0
  75. package/examples/compose.yaml +26 -0
  76. package/examples/package.json +42 -0
  77. package/examples/prisma/schema.prisma +29 -0
  78. package/examples/prisma.config.ts +10 -0
  79. package/examples/scripts/prepare-db.cjs +45 -0
  80. package/examples/scripts/smoke.cjs +37 -0
  81. package/examples/scripts/with-brokers.cjs +249 -0
  82. package/examples/scripts/with-rabbitmq.cjs +131 -0
  83. package/examples/sql/application.sql +14 -0
  84. package/examples/sql/tenancy.sql +26 -0
  85. package/examples/src/broker-common.ts +79 -0
  86. package/examples/src/bullmq-ingress/main.ts +165 -0
  87. package/examples/src/common.ts +112 -0
  88. package/examples/src/idempotency-ingress/main.ts +211 -0
  89. package/examples/src/kafka-ingress/main.ts +209 -0
  90. package/examples/src/outbox-to-inbox/main.ts +198 -0
  91. package/examples/src/rabbitmq-ingress/main.ts +203 -0
  92. package/examples/src/sqs-ingress/main.ts +273 -0
  93. package/examples/src/tenancy/main.ts +210 -0
  94. package/examples/src/webhook-ingress/main.ts +172 -0
  95. package/examples/tsconfig.json +16 -0
  96. package/package.json +99 -4
  97. package/src/sql/create-inbox-table.sql +100 -0
  98. package/src/sql/migrate-v1-to-v2.sql +53 -0
@@ -0,0 +1,174 @@
1
+ export type JsonValue = null | boolean | number | string | JsonValue[] | {
2
+ [key: string]: JsonValue;
3
+ };
4
+ export type InboxScope = {
5
+ kind: "tenant";
6
+ id: string;
7
+ } | {
8
+ kind: "global";
9
+ };
10
+ export type InboxStatus = "PENDING" | "PROCESSING" | "RETRYABLE" | "COMPLETED" | "DEAD";
11
+ /** The subset of a Prisma interactive transaction used by the inbox. */
12
+ export interface InboxTransaction {
13
+ $queryRawUnsafe<T = unknown>(query: string, ...values: any[]): Promise<T>;
14
+ $executeRawUnsafe(query: string, ...values: any[]): Promise<number>;
15
+ }
16
+ export interface InboxPrismaClient extends InboxTransaction {
17
+ $transaction<T>(fn: (tx: InboxTransaction) => Promise<T>, options?: {
18
+ maxWait?: number;
19
+ timeout?: number;
20
+ isolationLevel?: "ReadCommitted";
21
+ }): Promise<T>;
22
+ }
23
+ export interface InboxEnvelope<T extends JsonValue = JsonValue> {
24
+ scope: InboxScope;
25
+ source: string;
26
+ consumer: string;
27
+ messageId: string;
28
+ eventType: string;
29
+ schemaVersion: number;
30
+ payload: T;
31
+ correlationId?: string;
32
+ causationId?: string;
33
+ headers?: {
34
+ traceparent?: string;
35
+ tracestate?: string;
36
+ };
37
+ }
38
+ export interface InboxRecord {
39
+ id: string;
40
+ scope: InboxScope;
41
+ source: string;
42
+ consumer: string;
43
+ messageId: string;
44
+ eventType: string;
45
+ schemaVersion: number;
46
+ payload: JsonValue;
47
+ payloadPurged: boolean;
48
+ fingerprint: string;
49
+ fingerprintVersion: number;
50
+ status: InboxStatus;
51
+ claimToken: string;
52
+ rowVersion: string;
53
+ attemptCount: number;
54
+ cycleAttemptCount: number;
55
+ requeueCount: number;
56
+ receivedAt: Date;
57
+ completedAt: Date | null;
58
+ nextAttemptAt: Date;
59
+ leaseExpiresAt: Date | null;
60
+ workerId: string | null;
61
+ lastErrorCode: string | null;
62
+ lastErrorMessage: string | null;
63
+ lastRequeueReason: string | null;
64
+ lastRequeueActor: string | null;
65
+ lastRequeueAt: Date | null;
66
+ correlationId?: string;
67
+ causationId?: string;
68
+ headers?: InboxEnvelope["headers"];
69
+ /** Present only for an explicitly requested replay; the original receipt is retained. */
70
+ replay?: {
71
+ originalInboxId: string;
72
+ requestId: string;
73
+ sourceVersion: string;
74
+ actor: string;
75
+ reason: string;
76
+ requestedAt: Date;
77
+ };
78
+ }
79
+ export interface InboxReceipt {
80
+ kind: "accepted" | "duplicate";
81
+ id: string;
82
+ status: InboxStatus;
83
+ }
84
+ export interface InboxContext<TTx extends InboxTransaction = InboxTransaction> {
85
+ tx: TTx;
86
+ scope: InboxScope;
87
+ messageId: string;
88
+ source: string;
89
+ consumer: string;
90
+ inboxId: string;
91
+ attempt: number;
92
+ signal: AbortSignal;
93
+ replay?: InboxRecord["replay"];
94
+ correlationId?: string;
95
+ causationId?: string;
96
+ }
97
+ export interface InboxHandler<TPayload = JsonValue, TTx extends InboxTransaction = InboxTransaction> {
98
+ consumer: string;
99
+ eventType: string;
100
+ schemaVersion: number;
101
+ /** Return false or throw to quarantine invalid payloads. Must be synchronous and side-effect free. */
102
+ validate?: (payload: JsonValue) => boolean | void;
103
+ handle(payload: TPayload, context: InboxContext<TTx>): Promise<void>;
104
+ }
105
+ /** Wrap handler execution inside its existing transaction, e.g. with tenant context. */
106
+ export type InboxHandlerContextRunner = (context: InboxContext, next: () => Promise<void>) => Promise<void>;
107
+ export interface InboxListOptions {
108
+ status?: InboxStatus;
109
+ consumer?: string;
110
+ cursor?: string;
111
+ limit?: number;
112
+ }
113
+ export interface InboxPage {
114
+ records: InboxRecord[];
115
+ nextCursor: string | null;
116
+ }
117
+ export interface InboxStats {
118
+ counts: Record<InboxStatus, number>;
119
+ oldestPendingAt: Date | null;
120
+ }
121
+ /** No payloads, tenant IDs, or raw exceptions are passed to observation hooks. */
122
+ export interface InboxObservation {
123
+ type: "accepted" | "duplicate" | "completed" | "retry" | "dead" | "lease_lost" | "error";
124
+ consumer?: string;
125
+ code?: string;
126
+ }
127
+ export interface InboxWorkerOptions {
128
+ enabled?: boolean;
129
+ consumers?: string[];
130
+ concurrency?: number;
131
+ pollIntervalMs?: number;
132
+ leaseMs?: number;
133
+ heartbeatIntervalMs?: number;
134
+ transactionTimeoutMs?: number;
135
+ transactionMaxWaitMs?: number;
136
+ lockTimeoutMs?: number;
137
+ shutdownTimeoutMs?: number;
138
+ maxAttempts?: number;
139
+ retryBaseMs?: number;
140
+ retryMaxMs?: number;
141
+ }
142
+ export interface InboxOptions {
143
+ prisma: InboxPrismaClient;
144
+ handlers?: InboxHandler<any, any>[];
145
+ /** Accepted consumer names; defaults to registered handler names. Required for ingress-only apps. */
146
+ consumers?: string[];
147
+ worker?: InboxWorkerOptions;
148
+ maxPayloadBytes?: number;
149
+ /** Optional approximate admission limit; concurrently accepted messages may exceed it. */
150
+ maxPendingMessages?: number;
151
+ observer?: (event: InboxObservation) => void | Promise<void>;
152
+ handlerContext?: InboxHandlerContextRunner;
153
+ privilegedAdmin?: boolean;
154
+ }
155
+ export interface NormalizedEnvelope extends InboxEnvelope {
156
+ payloadJson: string;
157
+ fingerprint: string;
158
+ fingerprintVersion: 1;
159
+ }
160
+ export interface ResolvedWorkerOptions {
161
+ enabled: boolean;
162
+ consumers: string[];
163
+ concurrency: number;
164
+ pollIntervalMs: number;
165
+ leaseMs: number;
166
+ heartbeatIntervalMs: number;
167
+ transactionTimeoutMs: number;
168
+ transactionMaxWaitMs: number;
169
+ lockTimeoutMs: number;
170
+ shutdownTimeoutMs: number;
171
+ maxAttempts: number;
172
+ retryBaseMs: number;
173
+ retryMaxMs: number;
174
+ }
@@ -0,0 +1,3 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/contracts/types.ts"],"names":[],"mappings":""}
@@ -0,0 +1,44 @@
1
+ import type { InboxListOptions, InboxRecord, InboxScope } from "./contracts/types";
2
+ import { PrismaInboxStorage } from "./storage/prisma/prisma-inbox.storage";
3
+ export interface InboxRequeueOptions {
4
+ id: string;
5
+ expectedVersion: string;
6
+ reason: string;
7
+ actor: string;
8
+ }
9
+ export interface InboxReplayOptions extends InboxRequeueOptions {
10
+ /** Stable UUID identifying one operator request within the original record's scope. */
11
+ requestId: string;
12
+ /** May name the original consumer or a separately registered projection consumer. */
13
+ targetConsumer: string;
14
+ }
15
+ export interface InboxReplayResult {
16
+ kind: "accepted" | "duplicate";
17
+ record: InboxRecord;
18
+ }
19
+ /** A fixed scope is captured when this view is constructed. It is never read from request payloads. */
20
+ export declare class InboxAdminView {
21
+ private readonly storage;
22
+ private readonly scope;
23
+ constructor(storage: PrismaInboxStorage, scope: InboxScope | undefined);
24
+ get(recordId: string): Promise<InboxRecord | null>;
25
+ list(options?: InboxListOptions): Promise<import("./contracts/types").InboxPage>;
26
+ stats(): Promise<import("./contracts/types").InboxStats>;
27
+ requeue(options: InboxRequeueOptions): Promise<InboxRecord>;
28
+ /** An intentional new delivery; the original COMPLETED receipt stays immutable. */
29
+ replay(options: InboxReplayOptions): Promise<InboxReplayResult>;
30
+ purgeCompletedPayload(options: {
31
+ before: Date;
32
+ limit?: number;
33
+ }): Promise<number>;
34
+ }
35
+ export declare class InboxAdminService {
36
+ private readonly storage;
37
+ constructor(storage: PrismaInboxStorage);
38
+ forTenant(tenantId: string): InboxAdminView;
39
+ forGlobal(): InboxAdminView;
40
+ }
41
+ /** Injected only when the module explicitly enables privilegedAdmin. Protect access in your app. */
42
+ export declare class InboxPrivilegedAdminService extends InboxAdminView {
43
+ constructor(storage: PrismaInboxStorage);
44
+ }
@@ -0,0 +1,102 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.InboxPrivilegedAdminService = exports.InboxAdminService = exports.InboxAdminView = void 0;
4
+ const errors_1 = require("./contracts/errors");
5
+ const validation_1 = require("./internal/validation");
6
+ function id(value) {
7
+ if (typeof value !== "string" ||
8
+ !/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(value))
9
+ throw new errors_1.InboxValidationError("Invalid inbox ID.");
10
+ return value;
11
+ }
12
+ function version(value) {
13
+ if (typeof value !== "string" ||
14
+ !/^[1-9][0-9]{0,18}$/.test(value) ||
15
+ BigInt(value) > 9223372036854775807n) {
16
+ throw new errors_1.InboxValidationError("expectedVersion must be a positive bigint string.");
17
+ }
18
+ return value;
19
+ }
20
+ /** A fixed scope is captured when this view is constructed. It is never read from request payloads. */
21
+ class InboxAdminView {
22
+ storage;
23
+ scope;
24
+ constructor(storage, scope) {
25
+ this.storage = storage;
26
+ this.scope = scope;
27
+ }
28
+ get(recordId) {
29
+ return this.storage.get(id(recordId), this.scope);
30
+ }
31
+ list(options = {}) {
32
+ if (options.consumer !== undefined)
33
+ (0, validation_1.boundedString)(options.consumer, "consumer", 128);
34
+ return this.storage.list(options, this.scope);
35
+ }
36
+ stats() {
37
+ return this.storage.stats(this.scope);
38
+ }
39
+ requeue(options) {
40
+ id(options.id);
41
+ return this.storage.requeue(options.id, this.scope, version(options.expectedVersion), (0, validation_1.boundedString)(options.reason, "reason", 1024), (0, validation_1.boundedString)(options.actor, "actor", 256));
42
+ }
43
+ /** An intentional new delivery; the original COMPLETED receipt stays immutable. */
44
+ replay(options) {
45
+ if (!options ||
46
+ typeof options !== "object" ||
47
+ Array.isArray(options) ||
48
+ ![Object.prototype, null].includes(Object.getPrototypeOf(options)))
49
+ throw new errors_1.InboxValidationError("Replay options must be a plain object.");
50
+ const fields = [
51
+ "id",
52
+ "expectedVersion",
53
+ "requestId",
54
+ "targetConsumer",
55
+ "reason",
56
+ "actor",
57
+ ];
58
+ for (const key of Reflect.ownKeys(options)) {
59
+ const descriptor = Object.getOwnPropertyDescriptor(options, key);
60
+ if (typeof key !== "string" ||
61
+ !fields.includes(key) ||
62
+ !descriptor.enumerable ||
63
+ !("value" in descriptor))
64
+ throw new errors_1.InboxValidationError("Replay options contain an unsupported field.");
65
+ }
66
+ return this.storage.replay({
67
+ id: id(options.id).toLowerCase(),
68
+ expectedVersion: version(options.expectedVersion),
69
+ requestId: id(options.requestId).toLowerCase(),
70
+ targetConsumer: (0, validation_1.boundedString)(options.targetConsumer, "targetConsumer", 128),
71
+ reason: (0, validation_1.boundedString)(options.reason, "reason", 1024),
72
+ actor: (0, validation_1.boundedString)(options.actor, "actor", 256),
73
+ }, this.scope);
74
+ }
75
+ purgeCompletedPayload(options) {
76
+ if (!(options.before instanceof Date))
77
+ throw new errors_1.InboxValidationError("before must be a Date.");
78
+ return this.storage.purgeCompletedPayload(options.before, options.limit ?? 100, this.scope);
79
+ }
80
+ }
81
+ exports.InboxAdminView = InboxAdminView;
82
+ class InboxAdminService {
83
+ storage;
84
+ constructor(storage) {
85
+ this.storage = storage;
86
+ }
87
+ forTenant(tenantId) {
88
+ return new InboxAdminView(this.storage, (0, validation_1.validateScope)({ kind: "tenant", id: tenantId }));
89
+ }
90
+ forGlobal() {
91
+ return new InboxAdminView(this.storage, { kind: "global" });
92
+ }
93
+ }
94
+ exports.InboxAdminService = InboxAdminService;
95
+ /** Injected only when the module explicitly enables privilegedAdmin. Protect access in your app. */
96
+ class InboxPrivilegedAdminService extends InboxAdminView {
97
+ constructor(storage) {
98
+ super(storage, undefined);
99
+ }
100
+ }
101
+ exports.InboxPrivilegedAdminService = InboxPrivilegedAdminService;
102
+ //# sourceMappingURL=inbox-admin.service.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"inbox-admin.service.js","sourceRoot":"","sources":["../src/inbox-admin.service.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,13 @@
1
+ import type { DynamicModule, FactoryProvider, ModuleMetadata } from "@nestjs/common";
2
+ import type { InboxOptions } from "./contracts/types";
3
+ export interface InboxModuleAsyncOptions extends Pick<ModuleMetadata, "imports"> {
4
+ inject?: FactoryProvider["inject"];
5
+ useFactory: (...dependencies: any[]) => InboxOptions | Promise<InboxOptions>;
6
+ /** Explicit module-level opt-in is required to register the cross-tenant admin provider. */
7
+ privilegedAdmin?: boolean;
8
+ }
9
+ export declare class InboxModule {
10
+ static forRoot(options: InboxOptions): DynamicModule;
11
+ static forRootAsync(options: InboxModuleAsyncOptions): DynamicModule;
12
+ private static configure;
13
+ }
@@ -0,0 +1,59 @@
1
+ "use strict";
2
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
3
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
4
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
5
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
6
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
7
+ };
8
+ var InboxModule_1;
9
+ Object.defineProperty(exports, "__esModule", { value: true });
10
+ exports.InboxModule = void 0;
11
+ const common_1 = require("@nestjs/common");
12
+ const inbox_admin_service_1 = require("./inbox-admin.service");
13
+ const inbox_service_1 = require("./inbox.service");
14
+ const INBOX_OPTIONS = Symbol("NESTARC_INBOX_OPTIONS");
15
+ let InboxModule = InboxModule_1 = class InboxModule {
16
+ static forRoot(options) {
17
+ return this.configure({ provide: INBOX_OPTIONS, useValue: options }, [], options.privilegedAdmin === true);
18
+ }
19
+ static forRootAsync(options) {
20
+ return this.configure({
21
+ provide: INBOX_OPTIONS,
22
+ inject: options.inject ?? [],
23
+ useFactory: async (...dependencies) => ({
24
+ ...(await options.useFactory(...dependencies)),
25
+ privilegedAdmin: options.privilegedAdmin === true,
26
+ }),
27
+ }, options.imports ?? [], options.privilegedAdmin === true);
28
+ }
29
+ static configure(options, imports, privileged) {
30
+ const providers = [
31
+ options,
32
+ {
33
+ provide: inbox_service_1.InboxService,
34
+ inject: [INBOX_OPTIONS],
35
+ useFactory: (value) => new inbox_service_1.InboxService(value),
36
+ },
37
+ {
38
+ provide: inbox_admin_service_1.InboxAdminService,
39
+ inject: [inbox_service_1.InboxService],
40
+ useFactory: (inbox) => inbox.admin,
41
+ },
42
+ ];
43
+ const exports = [inbox_service_1.InboxService, inbox_admin_service_1.InboxAdminService];
44
+ if (privileged) {
45
+ providers.push({
46
+ provide: inbox_admin_service_1.InboxPrivilegedAdminService,
47
+ inject: [inbox_service_1.InboxService],
48
+ useFactory: (inbox) => inbox.privilegedAdmin,
49
+ });
50
+ exports.push(inbox_admin_service_1.InboxPrivilegedAdminService);
51
+ }
52
+ return { module: InboxModule_1, imports, providers, exports };
53
+ }
54
+ };
55
+ exports.InboxModule = InboxModule;
56
+ exports.InboxModule = InboxModule = InboxModule_1 = __decorate([
57
+ (0, common_1.Module)({})
58
+ ], InboxModule);
59
+ //# sourceMappingURL=inbox.module.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"inbox.module.js","sourceRoot":"","sources":["../src/inbox.module.ts"],"names":[],"mappings":";;;;;;;;;;AAAA,2CAAwC;AAQxC,+DAG+B;AAC/B,mDAA+C;AAE/C,MAAM,aAAa,GAAG,MAAM,CAAC,uBAAuB,CAAC,CAAC;AAW/C,IAAM,WAAW,mBAAjB,MAAM,WAAW;IACtB,MAAM,CAAC,OAAO,CAAC,OAAqB;QAClC,OAAO,IAAI,CAAC,SAAS,CACnB,EAAE,OAAO,EAAE,aAAa,EAAE,QAAQ,EAAE,OAAO,EAAE,EAC7C,EAAE,EACF,OAAO,CAAC,eAAe,KAAK,IAAI,CACjC,CAAC;IACJ,CAAC;IACD,MAAM,CAAC,YAAY,CAAC,OAAgC;QAClD,OAAO,IAAI,CAAC,SAAS,CACnB;YACE,OAAO,EAAE,aAAa;YACtB,MAAM,EAAE,OAAO,CAAC,MAAM,IAAI,EAAE;YAC5B,UAAU,EAAE,KAAK,EAAE,GAAG,YAAmB,EAAE,EAAE,CAAC,CAAC;gBAC7C,GAAG,CAAC,MAAM,OAAO,CAAC,UAAU,CAAC,GAAG,YAAY,CAAC,CAAC;gBAC9C,eAAe,EAAE,OAAO,CAAC,eAAe,KAAK,IAAI;aAClD,CAAC;SACH,EACD,OAAO,CAAC,OAAO,IAAI,EAAE,EACrB,OAAO,CAAC,eAAe,KAAK,IAAI,CACjC,CAAC;IACJ,CAAC;IACO,MAAM,CAAC,SAAS,CACtB,OAAiB,EACjB,OAA+C,EAC/C,UAAmB;QAEnB,MAAM,SAAS,GAAe;YAC5B,OAAO;YACP;gBACE,OAAO,EAAE,4BAAY;gBACrB,MAAM,EAAE,CAAC,aAAa,CAAC;gBACvB,UAAU,EAAE,CAAC,KAAmB,EAAE,EAAE,CAAC,IAAI,4BAAY,CAAC,KAAK,CAAC;aAC7D;YACD;gBACE,OAAO,EAAE,uCAAiB;gBAC1B,MAAM,EAAE,CAAC,4BAAY,CAAC;gBACtB,UAAU,EAAE,CAAC,KAAmB,EAAE,EAAE,CAAC,KAAK,CAAC,KAAK;aACjD;SACF,CAAC;QACF,MAAM,OAAO,GAIT,CAAC,4BAAY,EAAE,uCAAiB,CAAC,CAAC;QACtC,IAAI,UAAU,EAAE,CAAC;YACf,SAAS,CAAC,IAAI,CAAC;gBACb,OAAO,EAAE,iDAA2B;gBACpC,MAAM,EAAE,CAAC,4BAAY,CAAC;gBACtB,UAAU,EAAE,CAAC,KAAmB,EAAE,EAAE,CAAC,KAAK,CAAC,eAAgB;aAC5D,CAAC,CAAC;YACH,OAAO,CAAC,IAAI,CAAC,iDAA2B,CAAC,CAAC;QAC5C,CAAC;QACD,OAAO,EAAE,MAAM,EAAE,aAAW,EAAE,OAAO,EAAE,SAAS,EAAE,OAAO,EAAE,CAAC;IAC9D,CAAC;CACF,CAAA;AAvDY,kCAAW;sBAAX,WAAW;IADvB,IAAA,eAAM,EAAC,EAAE,CAAC;GACE,WAAW,CAuDvB"}
@@ -0,0 +1,22 @@
1
+ import type { OnModuleInit, OnModuleDestroy } from "@nestjs/common";
2
+ import type { InboxEnvelope, InboxOptions } from "./contracts/types";
3
+ import { InboxAdminService, InboxPrivilegedAdminService } from "./inbox-admin.service";
4
+ export declare class InboxService implements OnModuleInit, OnModuleDestroy {
5
+ private readonly options;
6
+ private readonly storage;
7
+ private readonly worker;
8
+ private readonly consumers;
9
+ private initialization?;
10
+ private closed;
11
+ readonly admin: InboxAdminService;
12
+ readonly privilegedAdmin?: InboxPrivilegedAdminService;
13
+ constructor(options: InboxOptions);
14
+ onModuleInit(): Promise<void>;
15
+ onModuleDestroy(): Promise<void>;
16
+ initialize(): Promise<void>;
17
+ accept(envelope: InboxEnvelope): Promise<import("./contracts/types").InboxReceipt>;
18
+ /** Explicit bounded processing for managed workers and tests; automatic polling is optional. */
19
+ runOnce(): Promise<number>;
20
+ close(): Promise<void>;
21
+ private observe;
22
+ }
@@ -0,0 +1,74 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.InboxService = void 0;
4
+ const errors_1 = require("./contracts/errors");
5
+ const inbox_admin_service_1 = require("./inbox-admin.service");
6
+ const options_1 = require("./internal/options");
7
+ const validation_1 = require("./internal/validation");
8
+ const prisma_inbox_storage_1 = require("./storage/prisma/prisma-inbox.storage");
9
+ const inbox_worker_1 = require("./worker/inbox-worker");
10
+ class InboxService {
11
+ options;
12
+ storage;
13
+ worker;
14
+ consumers;
15
+ initialization;
16
+ closed = false;
17
+ admin;
18
+ privilegedAdmin;
19
+ constructor(options) {
20
+ this.options = options;
21
+ const resolved = (0, options_1.resolveOptions)(options);
22
+ this.consumers = resolved.consumers;
23
+ this.storage = new prisma_inbox_storage_1.PrismaInboxStorage(options.prisma, {
24
+ maxPendingMessages: options.maxPendingMessages,
25
+ });
26
+ this.worker = new inbox_worker_1.InboxWorker(this.storage, options.handlers ?? [], resolved.worker, options.observer, options.handlerContext);
27
+ this.admin = new inbox_admin_service_1.InboxAdminService(this.storage);
28
+ if (options.privilegedAdmin)
29
+ this.privilegedAdmin = new inbox_admin_service_1.InboxPrivilegedAdminService(this.storage);
30
+ }
31
+ async onModuleInit() {
32
+ await this.initialize();
33
+ }
34
+ async onModuleDestroy() {
35
+ await this.close();
36
+ }
37
+ initialize() {
38
+ if (this.closed)
39
+ return Promise.reject(new errors_1.InboxError("CLOSED", "The inbox service is closed."));
40
+ this.initialization ??= this.storage.assertSchema().then(() => {
41
+ if (!this.closed)
42
+ this.worker.start();
43
+ });
44
+ return this.initialization;
45
+ }
46
+ async accept(envelope) {
47
+ const normalized = (0, validation_1.normalizeEnvelope)(envelope, this.consumers, this.options.maxPayloadBytes);
48
+ await this.initialize();
49
+ if (this.closed)
50
+ throw new errors_1.InboxError("CLOSED", "The inbox service is closed.");
51
+ const receipt = await this.storage.accept(normalized);
52
+ this.observe({ type: receipt.kind, consumer: normalized.consumer });
53
+ return receipt;
54
+ }
55
+ /** Explicit bounded processing for managed workers and tests; automatic polling is optional. */
56
+ async runOnce() {
57
+ await this.initialize();
58
+ return this.worker.tick();
59
+ }
60
+ async close() {
61
+ this.closed = true;
62
+ await this.worker.stop();
63
+ }
64
+ observe(event) {
65
+ try {
66
+ void Promise.resolve(this.options.observer?.(event)).catch(() => undefined);
67
+ }
68
+ catch {
69
+ /* Observation cannot change committed data. */
70
+ }
71
+ }
72
+ }
73
+ exports.InboxService = InboxService;
74
+ //# sourceMappingURL=inbox.service.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"inbox.service.js","sourceRoot":"","sources":["../src/inbox.service.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,9 @@
1
+ export { InboxModule } from "./inbox.module";
2
+ export type { InboxModuleAsyncOptions } from "./inbox.module";
3
+ export { InboxService } from "./inbox.service";
4
+ export { createInboxTenancyContext } from "./integrations/tenancy";
5
+ export type { InboxTenancyOptions } from "./integrations/tenancy";
6
+ export { InboxAdminService, InboxPrivilegedAdminService, } from "./inbox-admin.service";
7
+ export type { InboxAdminView, InboxRequeueOptions, InboxReplayOptions, InboxReplayResult, } from "./inbox-admin.service";
8
+ export * from "./contracts/errors";
9
+ export type { JsonValue, InboxScope, InboxStatus, InboxTransaction, InboxPrismaClient, InboxEnvelope, InboxRecord, InboxReceipt, InboxContext, InboxHandler, InboxListOptions, InboxPage, InboxStats, InboxObservation, InboxWorkerOptions, InboxOptions, InboxHandlerContextRunner, } from "./contracts/types";
package/dist/index.js ADDED
@@ -0,0 +1,28 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ exports.InboxPrivilegedAdminService = exports.InboxAdminService = exports.createInboxTenancyContext = exports.InboxService = exports.InboxModule = void 0;
18
+ var inbox_module_1 = require("./inbox.module");
19
+ Object.defineProperty(exports, "InboxModule", { enumerable: true, get: function () { return inbox_module_1.InboxModule; } });
20
+ var inbox_service_1 = require("./inbox.service");
21
+ Object.defineProperty(exports, "InboxService", { enumerable: true, get: function () { return inbox_service_1.InboxService; } });
22
+ var tenancy_1 = require("./integrations/tenancy");
23
+ Object.defineProperty(exports, "createInboxTenancyContext", { enumerable: true, get: function () { return tenancy_1.createInboxTenancyContext; } });
24
+ var inbox_admin_service_1 = require("./inbox-admin.service");
25
+ Object.defineProperty(exports, "InboxAdminService", { enumerable: true, get: function () { return inbox_admin_service_1.InboxAdminService; } });
26
+ Object.defineProperty(exports, "InboxPrivilegedAdminService", { enumerable: true, get: function () { return inbox_admin_service_1.InboxPrivilegedAdminService; } });
27
+ __exportStar(require("./contracts/errors"), exports);
28
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;AAAA,+CAA6C;AAApC,2GAAA,WAAW,OAAA;AAEpB,iDAA+C;AAAtC,6GAAA,YAAY,OAAA;AACrB,kDAAmE;AAA1D,oHAAA,yBAAyB,OAAA;AAElC,6DAG+B;AAF7B,wHAAA,iBAAiB,OAAA;AACjB,kIAAA,2BAA2B,OAAA;AAQ7B,qDAAmC"}
@@ -0,0 +1,25 @@
1
+ import type { InboxContext } from "../contracts/types";
2
+ export interface InboxTenancyOptions {
3
+ /** Public TenancyContext methods; structural so the integration stays optional. */
4
+ context: {
5
+ run<T>(tenantId: string, callback: () => Promise<T>): Promise<T>;
6
+ };
7
+ /** Public TenancyService methods, supplied by the application's TenancyModule. */
8
+ tenancy: {
9
+ getDbSettingKey(): string;
10
+ getCurrentTenantOrThrow(): string;
11
+ withoutTenant<T>(callback: () => Promise<T>): Promise<T>;
12
+ };
13
+ /** Reject global messages by default. Shared work still obeys database RLS. */
14
+ global?: "reject" | "shared";
15
+ }
16
+ /**
17
+ * Bind @nestarc/tenancy context to the durable message scope and set the canonical
18
+ * PostgreSQL setting on Inbox's existing transaction before running the handler.
19
+ * Use the application's raw Prisma client for Inbox, not a tenancy extension.
20
+ *
21
+ * The application authenticates the scope before accept(), provisions RLS policies
22
+ * using the same setting key, and runs handlers with a non-owner, non-BYPASSRLS
23
+ * database role. This adapter neither installs policies nor grants authorization.
24
+ */
25
+ export declare function createInboxTenancyContext(options: InboxTenancyOptions): (context: InboxContext, next: () => Promise<void>) => Promise<void>;
@@ -0,0 +1,56 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createInboxTenancyContext = createInboxTenancyContext;
4
+ const errors_1 = require("../contracts/errors");
5
+ const validation_1 = require("../internal/validation");
6
+ /**
7
+ * Bind @nestarc/tenancy context to the durable message scope and set the canonical
8
+ * PostgreSQL setting on Inbox's existing transaction before running the handler.
9
+ * Use the application's raw Prisma client for Inbox, not a tenancy extension.
10
+ *
11
+ * The application authenticates the scope before accept(), provisions RLS policies
12
+ * using the same setting key, and runs handlers with a non-owner, non-BYPASSRLS
13
+ * database role. This adapter neither installs policies nor grants authorization.
14
+ */
15
+ function createInboxTenancyContext(options) {
16
+ if (!options ||
17
+ typeof options.context?.run !== "function" ||
18
+ typeof options.tenancy?.getDbSettingKey !== "function" ||
19
+ typeof options.tenancy?.getCurrentTenantOrThrow !== "function" ||
20
+ typeof options.tenancy?.withoutTenant !== "function" ||
21
+ (options.global !== undefined &&
22
+ options.global !== "reject" &&
23
+ options.global !== "shared")) {
24
+ throw new errors_1.InboxConfigurationError("Provide valid tenancy context and service options.");
25
+ }
26
+ const settingKey = options.tenancy.getDbSettingKey();
27
+ // A canonical custom GUC is required; do not accept built-in session settings.
28
+ if (!/^[A-Za-z_][A-Za-z0-9_]*(?:\.[A-Za-z_][A-Za-z0-9_]*)+$/.test(settingKey)) {
29
+ throw new errors_1.InboxConfigurationError("Tenancy requires a valid PostgreSQL custom setting key.");
30
+ }
31
+ const global = options.global ?? "reject";
32
+ return async (context, next) => {
33
+ const scope = (0, validation_1.validateScope)(context.scope);
34
+ if (scope.kind === "global") {
35
+ if (global !== "shared") {
36
+ throw new errors_1.InboxPermanentError("This handler context requires a tenant-scoped message.", "TENANT_SCOPE_REQUIRED");
37
+ }
38
+ await options.tenancy.withoutTenant(async () => {
39
+ // An explicit shared path clears any inherited database tenant setting.
40
+ // Empty context cannot satisfy the tenant RLS policy; this is no bypass.
41
+ await context.tx.$queryRawUnsafe("SELECT set_config($1, $2, true)", settingKey, "");
42
+ await next();
43
+ });
44
+ return;
45
+ }
46
+ await options.context.run(scope.id, async () => {
47
+ const tenantId = options.tenancy.getCurrentTenantOrThrow();
48
+ if (tenantId !== scope.id) {
49
+ throw new errors_1.InboxPermanentError("The active tenant context does not match the durable message scope.", "TENANT_CONTEXT_MISMATCH");
50
+ }
51
+ await context.tx.$queryRawUnsafe("SELECT set_config($1, $2, true)", settingKey, tenantId);
52
+ await next();
53
+ });
54
+ };
55
+ }
56
+ //# sourceMappingURL=tenancy.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tenancy.js","sourceRoot":"","sources":["../../src/integrations/tenancy.ts"],"names":[],"mappings":";;AA+BA,8DAkEC;AAjGD,gDAG6B;AAE7B,uDAAuD;AAiBvD;;;;;;;;GAQG;AACH,SAAgB,yBAAyB,CACvC,OAA4B;IAE5B,IACE,CAAC,OAAO;QACR,OAAO,OAAO,CAAC,OAAO,EAAE,GAAG,KAAK,UAAU;QAC1C,OAAO,OAAO,CAAC,OAAO,EAAE,eAAe,KAAK,UAAU;QACtD,OAAO,OAAO,CAAC,OAAO,EAAE,uBAAuB,KAAK,UAAU;QAC9D,OAAO,OAAO,CAAC,OAAO,EAAE,aAAa,KAAK,UAAU;QACpD,CAAC,OAAO,CAAC,MAAM,KAAK,SAAS;YAC3B,OAAO,CAAC,MAAM,KAAK,QAAQ;YAC3B,OAAO,CAAC,MAAM,KAAK,QAAQ,CAAC,EAC9B,CAAC;QACD,MAAM,IAAI,gCAAuB,CAC/B,oDAAoD,CACrD,CAAC;IACJ,CAAC;IACD,MAAM,UAAU,GAAG,OAAO,CAAC,OAAO,CAAC,eAAe,EAAE,CAAC;IACrD,+EAA+E;IAC/E,IACE,CAAC,uDAAuD,CAAC,IAAI,CAAC,UAAU,CAAC,EACzE,CAAC;QACD,MAAM,IAAI,gCAAuB,CAC/B,yDAAyD,CAC1D,CAAC;IACJ,CAAC;IACD,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,IAAI,QAAQ,CAAC;IAE1C,OAAO,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,EAAE;QAC7B,MAAM,KAAK,GAAG,IAAA,0BAAa,EAAC,OAAO,CAAC,KAAK,CAAC,CAAC;QAC3C,IAAI,KAAK,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;YAC5B,IAAI,MAAM,KAAK,QAAQ,EAAE,CAAC;gBACxB,MAAM,IAAI,4BAAmB,CAC3B,wDAAwD,EACxD,uBAAuB,CACxB,CAAC;YACJ,CAAC;YACD,MAAM,OAAO,CAAC,OAAO,CAAC,aAAa,CAAC,KAAK,IAAI,EAAE;gBAC7C,wEAAwE;gBACxE,yEAAyE;gBACzE,MAAM,OAAO,CAAC,EAAE,CAAC,eAAe,CAC9B,iCAAiC,EACjC,UAAU,EACV,EAAE,CACH,CAAC;gBACF,MAAM,IAAI,EAAE,CAAC;YACf,CAAC,CAAC,CAAC;YACH,OAAO;QACT,CAAC;QAED,MAAM,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,EAAE,KAAK,IAAI,EAAE;YAC7C,MAAM,QAAQ,GAAG,OAAO,CAAC,OAAO,CAAC,uBAAuB,EAAE,CAAC;YAC3D,IAAI,QAAQ,KAAK,KAAK,CAAC,EAAE,EAAE,CAAC;gBAC1B,MAAM,IAAI,4BAAmB,CAC3B,qEAAqE,EACrE,yBAAyB,CAC1B,CAAC;YACJ,CAAC;YACD,MAAM,OAAO,CAAC,EAAE,CAAC,eAAe,CAC9B,iCAAiC,EACjC,UAAU,EACV,QAAQ,CACT,CAAC;YACF,MAAM,IAAI,EAAE,CAAC;QACf,CAAC,CAAC,CAAC;IACL,CAAC,CAAC;AACJ,CAAC"}