@periodic/vanadium 1.0.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 (136) hide show
  1. package/CHANGELOG.md +37 -0
  2. package/LICENSE +21 -0
  3. package/README.md +846 -0
  4. package/dist/cjs/adapters/memory/index.js +134 -0
  5. package/dist/cjs/adapters/memory/index.js.map +1 -0
  6. package/dist/cjs/adapters/mongodb/index.js +189 -0
  7. package/dist/cjs/adapters/mongodb/index.js.map +1 -0
  8. package/dist/cjs/adapters/mongoose/index.js +199 -0
  9. package/dist/cjs/adapters/mongoose/index.js.map +1 -0
  10. package/dist/cjs/adapters/postgres/index.js +202 -0
  11. package/dist/cjs/adapters/postgres/index.js.map +1 -0
  12. package/dist/cjs/adapters/prisma/index.js +176 -0
  13. package/dist/cjs/adapters/prisma/index.js.map +1 -0
  14. package/dist/cjs/adapters/redis/index.js +178 -0
  15. package/dist/cjs/adapters/redis/index.js.map +1 -0
  16. package/dist/cjs/cleanup/engine.js +100 -0
  17. package/dist/cjs/cleanup/engine.js.map +1 -0
  18. package/dist/cjs/core/concurrencyGuard.js +50 -0
  19. package/dist/cjs/core/concurrencyGuard.js.map +1 -0
  20. package/dist/cjs/core/metrics.js +39 -0
  21. package/dist/cjs/core/metrics.js.map +1 -0
  22. package/dist/cjs/core/stateMachine.js +46 -0
  23. package/dist/cjs/core/stateMachine.js.map +1 -0
  24. package/dist/cjs/errors/index.js +127 -0
  25. package/dist/cjs/errors/index.js.map +1 -0
  26. package/dist/cjs/http/express.js +84 -0
  27. package/dist/cjs/http/express.js.map +1 -0
  28. package/dist/cjs/http/fastify.js +70 -0
  29. package/dist/cjs/http/fastify.js.map +1 -0
  30. package/dist/cjs/idempotency/engine.js +266 -0
  31. package/dist/cjs/idempotency/engine.js.map +1 -0
  32. package/dist/cjs/index.js +19 -0
  33. package/dist/cjs/index.js.map +1 -0
  34. package/dist/cjs/lock/engine.js +187 -0
  35. package/dist/cjs/lock/engine.js.map +1 -0
  36. package/dist/cjs/observability/metrics.js +92 -0
  37. package/dist/cjs/observability/metrics.js.map +1 -0
  38. package/dist/cjs/resilience/circuitBreaker.js +129 -0
  39. package/dist/cjs/resilience/circuitBreaker.js.map +1 -0
  40. package/dist/cjs/types/index.js +13 -0
  41. package/dist/cjs/types/index.js.map +1 -0
  42. package/dist/cjs/utils/crypto.js +64 -0
  43. package/dist/cjs/utils/crypto.js.map +1 -0
  44. package/dist/cjs/utils/keys.js +40 -0
  45. package/dist/cjs/utils/keys.js.map +1 -0
  46. package/dist/cjs/utils/sleep.js +25 -0
  47. package/dist/cjs/utils/sleep.js.map +1 -0
  48. package/dist/esm/adapters/memory/index.js +129 -0
  49. package/dist/esm/adapters/memory/index.js.map +1 -0
  50. package/dist/esm/adapters/mongodb/index.js +184 -0
  51. package/dist/esm/adapters/mongodb/index.js.map +1 -0
  52. package/dist/esm/adapters/mongoose/index.js +193 -0
  53. package/dist/esm/adapters/mongoose/index.js.map +1 -0
  54. package/dist/esm/adapters/postgres/index.js +197 -0
  55. package/dist/esm/adapters/postgres/index.js.map +1 -0
  56. package/dist/esm/adapters/prisma/index.js +171 -0
  57. package/dist/esm/adapters/prisma/index.js.map +1 -0
  58. package/dist/esm/adapters/redis/index.js +173 -0
  59. package/dist/esm/adapters/redis/index.js.map +1 -0
  60. package/dist/esm/cleanup/engine.js +95 -0
  61. package/dist/esm/cleanup/engine.js.map +1 -0
  62. package/dist/esm/core/concurrencyGuard.js +46 -0
  63. package/dist/esm/core/concurrencyGuard.js.map +1 -0
  64. package/dist/esm/core/metrics.js +35 -0
  65. package/dist/esm/core/metrics.js.map +1 -0
  66. package/dist/esm/core/stateMachine.js +40 -0
  67. package/dist/esm/core/stateMachine.js.map +1 -0
  68. package/dist/esm/errors/index.js +114 -0
  69. package/dist/esm/errors/index.js.map +1 -0
  70. package/dist/esm/http/express.js +81 -0
  71. package/dist/esm/http/express.js.map +1 -0
  72. package/dist/esm/http/fastify.js +67 -0
  73. package/dist/esm/http/fastify.js.map +1 -0
  74. package/dist/esm/idempotency/engine.js +261 -0
  75. package/dist/esm/idempotency/engine.js.map +1 -0
  76. package/dist/esm/index.js +9 -0
  77. package/dist/esm/index.js.map +1 -0
  78. package/dist/esm/lock/engine.js +182 -0
  79. package/dist/esm/lock/engine.js.map +1 -0
  80. package/dist/esm/observability/metrics.js +89 -0
  81. package/dist/esm/observability/metrics.js.map +1 -0
  82. package/dist/esm/resilience/circuitBreaker.js +124 -0
  83. package/dist/esm/resilience/circuitBreaker.js.map +1 -0
  84. package/dist/esm/types/index.js +10 -0
  85. package/dist/esm/types/index.js.map +1 -0
  86. package/dist/esm/utils/crypto.js +58 -0
  87. package/dist/esm/utils/crypto.js.map +1 -0
  88. package/dist/esm/utils/keys.js +35 -0
  89. package/dist/esm/utils/keys.js.map +1 -0
  90. package/dist/esm/utils/sleep.js +20 -0
  91. package/dist/esm/utils/sleep.js.map +1 -0
  92. package/dist/types/adapters/memory/index.d.ts +49 -0
  93. package/dist/types/adapters/memory/index.d.ts.map +1 -0
  94. package/dist/types/adapters/mongodb/index.d.ts +97 -0
  95. package/dist/types/adapters/mongodb/index.d.ts.map +1 -0
  96. package/dist/types/adapters/mongoose/index.d.ts +107 -0
  97. package/dist/types/adapters/mongoose/index.d.ts.map +1 -0
  98. package/dist/types/adapters/postgres/index.d.ts +85 -0
  99. package/dist/types/adapters/postgres/index.d.ts.map +1 -0
  100. package/dist/types/adapters/prisma/index.d.ts +73 -0
  101. package/dist/types/adapters/prisma/index.d.ts.map +1 -0
  102. package/dist/types/adapters/redis/index.d.ts +77 -0
  103. package/dist/types/adapters/redis/index.d.ts.map +1 -0
  104. package/dist/types/cleanup/engine.d.ts +41 -0
  105. package/dist/types/cleanup/engine.d.ts.map +1 -0
  106. package/dist/types/core/concurrencyGuard.d.ts +28 -0
  107. package/dist/types/core/concurrencyGuard.d.ts.map +1 -0
  108. package/dist/types/core/metrics.d.ts +13 -0
  109. package/dist/types/core/metrics.d.ts.map +1 -0
  110. package/dist/types/core/stateMachine.d.ts +20 -0
  111. package/dist/types/core/stateMachine.d.ts.map +1 -0
  112. package/dist/types/errors/index.d.ts +32 -0
  113. package/dist/types/errors/index.d.ts.map +1 -0
  114. package/dist/types/http/express.d.ts +50 -0
  115. package/dist/types/http/express.d.ts.map +1 -0
  116. package/dist/types/http/fastify.d.ts +48 -0
  117. package/dist/types/http/fastify.d.ts.map +1 -0
  118. package/dist/types/idempotency/engine.d.ts +24 -0
  119. package/dist/types/idempotency/engine.d.ts.map +1 -0
  120. package/dist/types/index.d.ts +8 -0
  121. package/dist/types/index.d.ts.map +1 -0
  122. package/dist/types/lock/engine.d.ts +28 -0
  123. package/dist/types/lock/engine.d.ts.map +1 -0
  124. package/dist/types/observability/metrics.d.ts +45 -0
  125. package/dist/types/observability/metrics.d.ts.map +1 -0
  126. package/dist/types/resilience/circuitBreaker.d.ts +48 -0
  127. package/dist/types/resilience/circuitBreaker.d.ts.map +1 -0
  128. package/dist/types/types/index.d.ts +170 -0
  129. package/dist/types/types/index.d.ts.map +1 -0
  130. package/dist/types/utils/crypto.d.ts +20 -0
  131. package/dist/types/utils/crypto.d.ts.map +1 -0
  132. package/dist/types/utils/keys.d.ts +15 -0
  133. package/dist/types/utils/keys.d.ts.map +1 -0
  134. package/dist/types/utils/sleep.d.ts +13 -0
  135. package/dist/types/utils/sleep.d.ts.map +1 -0
  136. package/package.json +140 -0
@@ -0,0 +1,266 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.IdempotencyEngineImpl = void 0;
4
+ exports.createIdempotency = createIdempotency;
5
+ const index_js_1 = require("../errors/index.js");
6
+ const metrics_js_1 = require("../core/metrics.js");
7
+ const concurrencyGuard_js_1 = require("../core/concurrencyGuard.js");
8
+ const stateMachine_js_1 = require("../core/stateMachine.js");
9
+ const crypto_js_1 = require("../utils/crypto.js");
10
+ const keys_js_1 = require("../utils/keys.js");
11
+ const DEFAULT_TTL_MS = 24 * 60 * 60 * 1000; // 24 hours
12
+ const DEFAULT_IN_PROGRESS_EXPIRY_MS = 5 * 60 * 1000; // 5 minutes
13
+ // ─── Idempotency Engine ───────────────────────────────────────────────────────
14
+ class IdempotencyEngineImpl {
15
+ constructor(options) {
16
+ this.metrics = new metrics_js_1.MetricsStore();
17
+ this.guard = new concurrencyGuard_js_1.ConcurrencyGuard();
18
+ this.opts = {
19
+ adapter: options.adapter,
20
+ ttlMs: options.ttlMs ?? DEFAULT_TTL_MS,
21
+ inProgressExpiryMs: options.inProgressExpiryMs ?? DEFAULT_IN_PROGRESS_EXPIRY_MS,
22
+ hashPayload: options.hashPayload ?? false,
23
+ cacheFailures: options.cacheFailures ?? false,
24
+ clock: options.clock ?? Date.now,
25
+ onDuplicate: options.onDuplicate,
26
+ hooks: options.hooks,
27
+ };
28
+ }
29
+ // ── Public API ────────────────────────────────────────────────────────────
30
+ async execute(key, fn, payload) {
31
+ (0, keys_js_1.validateKey)(key, this.opts.adapter.name);
32
+ return this.guard.wrap(key, () => this._executeInternal(key, fn, payload));
33
+ }
34
+ getMetrics() {
35
+ return this.metrics.get();
36
+ }
37
+ resetMetrics() {
38
+ this.metrics.reset();
39
+ }
40
+ // ── Core Execution Flow ───────────────────────────────────────────────────
41
+ async _executeInternal(key, fn, payload) {
42
+ const { adapter, clock,
43
+ // ttlMs,
44
+ inProgressExpiryMs, hashPayload: shouldHash, cacheFailures, } = this.opts;
45
+ const now = clock();
46
+ const ownerToken = (0, crypto_js_1.generateOwnerToken)();
47
+ const incomingHash = shouldHash && payload !== undefined ? (0, crypto_js_1.hashPayload)(payload) : undefined;
48
+ // ── Step 1: Check existing record ────────────────────────────────────────
49
+ let existing = null;
50
+ try {
51
+ existing = await adapter.get(key);
52
+ }
53
+ catch (err) {
54
+ this.metrics.increment('totalStorageErrors');
55
+ await this._runHook('onStorageError', err, key);
56
+ throw (0, index_js_1.createStorageError)(key, adapter.name, err, clock);
57
+ }
58
+ // ── Step 2: Handle existing record ────────────────────────────────────────
59
+ if (existing !== null) {
60
+ // Payload mismatch detection
61
+ if (incomingHash !== undefined && existing.payloadHash !== undefined) {
62
+ if (existing.payloadHash !== incomingHash) {
63
+ this.metrics.increment('totalPayloadMismatches');
64
+ throw (0, index_js_1.createPayloadMismatchError)(key, adapter.name, existing.payloadHash, incomingHash, clock);
65
+ }
66
+ }
67
+ // Completed record — return cached result
68
+ if (existing.status === 'COMPLETED') {
69
+ this.metrics.increment('totalDuplicates');
70
+ this.opts.onDuplicate?.({
71
+ key,
72
+ status: existing.status,
73
+ attempts: existing.attempts,
74
+ adapterName: adapter.name,
75
+ });
76
+ await this._runHook('onDuplicateHit', {
77
+ key,
78
+ status: existing.status,
79
+ attempts: existing.attempts,
80
+ adapterName: adapter.name,
81
+ });
82
+ return existing.result;
83
+ }
84
+ // Failed record with cacheFailures enabled — rethrow cached error
85
+ if (existing.status === 'FAILED' && cacheFailures) {
86
+ this.metrics.increment('totalDuplicates');
87
+ throw (0, index_js_1.createDuplicateExecutionError)(key, adapter.name, clock);
88
+ }
89
+ // IN_PROGRESS: check for expired takeover
90
+ if (existing.status === 'IN_PROGRESS') {
91
+ if ((0, stateMachine_js_1.canTakeover)(existing.status, existing.expiresAt, clock)) {
92
+ // Takeover: increment attempts, assign new owner
93
+ const takeoverRecord = {
94
+ ...existing,
95
+ ownerToken,
96
+ attempts: existing.attempts + 1,
97
+ updatedAt: now,
98
+ expiresAt: now + inProgressExpiryMs,
99
+ };
100
+ (0, stateMachine_js_1.assertValidTransition)('IN_PROGRESS', 'IN_PROGRESS', key, adapter.name, clock);
101
+ try {
102
+ if (adapter.compareAndSet) {
103
+ const success = await adapter.compareAndSet(key, existing.ownerToken ?? '', takeoverRecord, inProgressExpiryMs);
104
+ if (!success) {
105
+ // Another node beat us to the takeover
106
+ throw (0, index_js_1.createInProgressError)(key, adapter.name, existing.attempts, clock);
107
+ }
108
+ }
109
+ else {
110
+ await adapter.set(key, takeoverRecord, inProgressExpiryMs);
111
+ }
112
+ }
113
+ catch (err) {
114
+ if (err.type === 'IN_PROGRESS')
115
+ throw err;
116
+ this.metrics.increment('totalStorageErrors');
117
+ throw (0, index_js_1.createStorageError)(key, adapter.name, err, clock);
118
+ }
119
+ this.metrics.increment('totalTakeovers');
120
+ await this._runHook('onTakeover', {
121
+ key,
122
+ previousOwnerToken: existing.ownerToken,
123
+ newOwnerToken: ownerToken,
124
+ attempts: takeoverRecord.attempts,
125
+ adapterName: adapter.name,
126
+ });
127
+ return this._runFunction(key, fn, ownerToken, takeoverRecord.attempts, incomingHash);
128
+ }
129
+ // Truly in-progress — reject
130
+ this.metrics.increment('totalDuplicates');
131
+ throw (0, index_js_1.createInProgressError)(key, adapter.name, existing.attempts, clock);
132
+ }
133
+ }
134
+ // ── Step 3: Claim ownership — write IN_PROGRESS ───────────────────────────
135
+ const progressRecord = {
136
+ key,
137
+ status: 'IN_PROGRESS',
138
+ ownerToken,
139
+ attempts: 1,
140
+ payloadHash: incomingHash,
141
+ createdAt: now,
142
+ updatedAt: now,
143
+ expiresAt: now + inProgressExpiryMs,
144
+ };
145
+ try {
146
+ await adapter.set(key, progressRecord, inProgressExpiryMs);
147
+ }
148
+ catch (err) {
149
+ this.metrics.increment('totalStorageErrors');
150
+ await this._runHook('onStorageError', err, key);
151
+ throw (0, index_js_1.createStorageError)(key, adapter.name, err, clock);
152
+ }
153
+ this.metrics.increment('inProgressCount');
154
+ this.metrics.increment('totalExecutions');
155
+ return this._runFunction(key, fn, ownerToken, 1, incomingHash);
156
+ }
157
+ // ── Function Execution ────────────────────────────────────────────────────
158
+ async _runFunction(key, fn, ownerToken, attempt, incomingHash) {
159
+ const { adapter, clock, ttlMs, cacheFailures } = this.opts;
160
+ const startedAt = clock();
161
+ await this._runHook('onBeforeExecute', {
162
+ key,
163
+ attempt,
164
+ adapterName: adapter.name,
165
+ startedAt,
166
+ });
167
+ try {
168
+ const result = await fn();
169
+ const completedAt = clock();
170
+ // Write COMPLETED record
171
+ (0, stateMachine_js_1.assertValidTransition)('IN_PROGRESS', 'COMPLETED', key, adapter.name, clock);
172
+ const completedRecord = {
173
+ key,
174
+ status: 'COMPLETED',
175
+ result,
176
+ ownerToken,
177
+ attempts: attempt,
178
+ payloadHash: incomingHash,
179
+ createdAt: startedAt,
180
+ updatedAt: completedAt,
181
+ expiresAt: completedAt + ttlMs,
182
+ };
183
+ try {
184
+ await adapter.set(key, completedRecord, ttlMs);
185
+ }
186
+ catch (err) {
187
+ this.metrics.increment('totalStorageErrors');
188
+ await this._runHook('onStorageError', err, key);
189
+ throw (0, index_js_1.createStorageError)(key, adapter.name, err, clock);
190
+ }
191
+ this.metrics.decrement('inProgressCount');
192
+ await this._runHook('onAfterExecute', {
193
+ key,
194
+ attempt,
195
+ adapterName: adapter.name,
196
+ startedAt,
197
+ durationMs: completedAt - startedAt,
198
+ });
199
+ return result;
200
+ }
201
+ catch (err) {
202
+ // If it's already a VanadiumError from storage, re-throw
203
+ if (err.type !== undefined)
204
+ throw err;
205
+ const failedAt = clock();
206
+ (0, stateMachine_js_1.assertValidTransition)('IN_PROGRESS', 'FAILED', key, adapter.name, clock);
207
+ if (cacheFailures) {
208
+ const failedRecord = {
209
+ key,
210
+ status: 'FAILED',
211
+ result: undefined,
212
+ ownerToken,
213
+ attempts: attempt,
214
+ createdAt: startedAt,
215
+ updatedAt: failedAt,
216
+ };
217
+ try {
218
+ await adapter.set(key, failedRecord);
219
+ }
220
+ catch {
221
+ // Best effort — don't mask original error
222
+ }
223
+ this.metrics.increment('totalFailuresCached');
224
+ }
225
+ else {
226
+ try {
227
+ await adapter.delete(key);
228
+ }
229
+ catch {
230
+ // Best effort
231
+ }
232
+ }
233
+ this.metrics.decrement('inProgressCount');
234
+ throw err;
235
+ }
236
+ }
237
+ // ── Hook Runner ───────────────────────────────────────────────────────────
238
+ async _runHook(hookName,
239
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
240
+ ...args) {
241
+ const hook = this.opts.hooks?.[hookName];
242
+ if (hook) {
243
+ try {
244
+ await hook(...args);
245
+ }
246
+ catch {
247
+ // Hooks must never break execution flow
248
+ }
249
+ }
250
+ }
251
+ }
252
+ exports.IdempotencyEngineImpl = IdempotencyEngineImpl;
253
+ // ─── Factory ──────────────────────────────────────────────────────────────────
254
+ /**
255
+ * Create a new idempotency engine instance.
256
+ *
257
+ * @example
258
+ * ```ts
259
+ * const idempotency = createIdempotency({ adapter, ttlMs: 60_000 });
260
+ * const result = await idempotency.execute('payment:123', () => chargeCard());
261
+ * ```
262
+ */
263
+ function createIdempotency(options) {
264
+ return new IdempotencyEngineImpl(options);
265
+ }
266
+ //# sourceMappingURL=engine.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"engine.js","sourceRoot":"","sources":["../../../src/idempotency/engine.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,19 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.isVanadiumError = exports.VanadiumError = exports.createVanadiumMetrics = exports.createCircuitBreaker = exports.createMemoryAdapter = exports.createLock = exports.createIdempotency = void 0;
4
+ // ─── Core ─────────────────────────────────────────────────────────────────────
5
+ var engine_js_1 = require("./idempotency/engine.js");
6
+ Object.defineProperty(exports, "createIdempotency", { enumerable: true, get: function () { return engine_js_1.createIdempotency; } });
7
+ var engine_js_2 = require("./lock/engine.js");
8
+ Object.defineProperty(exports, "createLock", { enumerable: true, get: function () { return engine_js_2.createLock; } });
9
+ var index_js_1 = require("./adapters/memory/index.js");
10
+ Object.defineProperty(exports, "createMemoryAdapter", { enumerable: true, get: function () { return index_js_1.createMemoryAdapter; } });
11
+ var circuitBreaker_js_1 = require("./resilience/circuitBreaker.js");
12
+ Object.defineProperty(exports, "createCircuitBreaker", { enumerable: true, get: function () { return circuitBreaker_js_1.createCircuitBreaker; } });
13
+ var metrics_js_1 = require("./observability/metrics.js");
14
+ Object.defineProperty(exports, "createVanadiumMetrics", { enumerable: true, get: function () { return metrics_js_1.createVanadiumMetrics; } });
15
+ // ─── Errors ───────────────────────────────────────────────────────────────────
16
+ var index_js_2 = require("./errors/index.js");
17
+ Object.defineProperty(exports, "VanadiumError", { enumerable: true, get: function () { return index_js_2.VanadiumError; } });
18
+ Object.defineProperty(exports, "isVanadiumError", { enumerable: true, get: function () { return index_js_2.isVanadiumError; } });
19
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":";;;AAAA,iFAAiF;AACjF,qDAA4D;AAAnD,8GAAA,iBAAiB,OAAA;AAC1B,8CAA8C;AAArC,uGAAA,UAAU,OAAA;AACnB,uDAAiE;AAAxD,+GAAA,mBAAmB,OAAA;AAC5B,oEAAsE;AAA7D,yHAAA,oBAAoB,OAAA;AAC7B,yDAAmE;AAA1D,mHAAA,qBAAqB,OAAA;AAE9B,iFAAiF;AACjF,8CAAmE;AAA1D,yGAAA,aAAa,OAAA;AAAE,2GAAA,eAAe,OAAA"}
@@ -0,0 +1,187 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.LockEngineImpl = void 0;
4
+ exports.createLock = createLock;
5
+ const index_js_1 = require("../errors/index.js");
6
+ const metrics_js_1 = require("../core/metrics.js");
7
+ const crypto_js_1 = require("../utils/crypto.js");
8
+ const keys_js_1 = require("../utils/keys.js");
9
+ const sleep_js_1 = require("../utils/sleep.js");
10
+ const DEFAULT_RETRY_INTERVAL_MS = 50;
11
+ const DEFAULT_MAX_WAIT_MS = 0; // 0 = no wait, fail immediately
12
+ // ─── Lock Engine ──────────────────────────────────────────────────────────────
13
+ class LockEngineImpl {
14
+ constructor(options) {
15
+ this.metrics = new metrics_js_1.MetricsStore();
16
+ this.opts = {
17
+ adapter: options.adapter,
18
+ ttlMs: options.ttlMs,
19
+ retryIntervalMs: options.retryIntervalMs ?? DEFAULT_RETRY_INTERVAL_MS,
20
+ maxWaitMs: options.maxWaitMs ?? DEFAULT_MAX_WAIT_MS,
21
+ clock: options.clock ?? Date.now,
22
+ hooks: options.hooks,
23
+ };
24
+ }
25
+ // ── Public API ────────────────────────────────────────────────────────────
26
+ /**
27
+ * Acquire an exclusive lock for `key`, execute `fn`, then release the lock.
28
+ * Guarantees only one concurrent execution per key across all nodes.
29
+ *
30
+ * @throws {VanadiumError} LOCK_ACQUISITION_FAILED | LOCK_TIMEOUT
31
+ */
32
+ async acquire(key, fn) {
33
+ (0, keys_js_1.validateKey)(key, this.opts.adapter.name);
34
+ const ownerToken = (0, crypto_js_1.generateOwnerToken)();
35
+ const { adapter, ttlMs, retryIntervalMs, maxWaitMs, clock } = this.opts;
36
+ const startedAt = clock();
37
+ const deadline = maxWaitMs > 0 ? startedAt + maxWaitMs : null;
38
+ // ── Acquire Phase ─────────────────────────────────────────────────────────
39
+ while (true) {
40
+ const acquired = await this._tryAcquire(key, ownerToken, ttlMs);
41
+ if (acquired) {
42
+ break;
43
+ }
44
+ const now = clock();
45
+ if (deadline !== null && now >= deadline) {
46
+ this.metrics.increment('totalLockFailures');
47
+ await this._runHook('onLockFailed', key, 'timeout');
48
+ throw (0, index_js_1.createLockTimeoutError)(key, adapter.name, maxWaitMs, clock);
49
+ }
50
+ if (deadline === null) {
51
+ // No wait — fail immediately
52
+ this.metrics.increment('totalLockFailures');
53
+ await this._runHook('onLockFailed', key, 'immediate_fail');
54
+ throw (0, index_js_1.createLockAcquisitionFailedError)(key, adapter.name, clock);
55
+ }
56
+ await (0, sleep_js_1.sleep)(retryIntervalMs);
57
+ }
58
+ const acquiredAt = clock();
59
+ this.metrics.increment('totalLocksAcquired');
60
+ await this._runHook('onLockAcquired', {
61
+ key,
62
+ ownerToken,
63
+ adapterName: adapter.name,
64
+ acquiredAt,
65
+ ttlMs,
66
+ });
67
+ // ── Execution Phase ───────────────────────────────────────────────────────
68
+ try {
69
+ return await fn();
70
+ }
71
+ finally {
72
+ // ── Release Phase (always) ────────────────────────────────────────────
73
+ await this._safeRelease(key, ownerToken);
74
+ const releasedAt = clock();
75
+ await this._runHook('onLockReleased', {
76
+ key,
77
+ ownerToken,
78
+ adapterName: adapter.name,
79
+ acquiredAt,
80
+ durationMs: releasedAt - acquiredAt,
81
+ ttlMs,
82
+ });
83
+ }
84
+ }
85
+ getMetrics() {
86
+ const m = this.metrics.get();
87
+ return {
88
+ totalLocksAcquired: m.totalLocksAcquired,
89
+ totalLockFailures: m.totalLockFailures,
90
+ };
91
+ }
92
+ // ── Private ───────────────────────────────────────────────────────────────
93
+ async _tryAcquire(key, ownerToken, ttlMs) {
94
+ const { adapter, clock } = this.opts;
95
+ const now = clock();
96
+ try {
97
+ const existing = await adapter.get(key);
98
+ if (existing !== null) {
99
+ // Lock is held — check if TTL has expired (deadlock protection)
100
+ if (existing.expiresAt !== undefined && now >= existing.expiresAt) {
101
+ // Expired lock — attempt CAS takeover
102
+ if (adapter.compareAndSet) {
103
+ const lockRecord = {
104
+ key,
105
+ status: 'IN_PROGRESS',
106
+ ownerToken,
107
+ attempts: (existing.attempts ?? 0) + 1,
108
+ createdAt: existing.createdAt,
109
+ updatedAt: now,
110
+ expiresAt: now + ttlMs,
111
+ };
112
+ const taken = await adapter.compareAndSet(key, existing.ownerToken ?? '', lockRecord, ttlMs);
113
+ return taken;
114
+ }
115
+ // No CAS support — overwrite (less safe, but acceptable for memory adapter)
116
+ }
117
+ else {
118
+ return false;
119
+ }
120
+ }
121
+ const lockRecord = {
122
+ key,
123
+ status: 'IN_PROGRESS',
124
+ ownerToken,
125
+ attempts: 1,
126
+ createdAt: now,
127
+ updatedAt: now,
128
+ expiresAt: now + ttlMs,
129
+ };
130
+ // Use setIfAbsent if available for atomic initial acquisition
131
+ if (adapter.setIfAbsent) {
132
+ return await adapter.setIfAbsent(key, lockRecord, ttlMs);
133
+ }
134
+ await adapter.set(key, lockRecord, ttlMs);
135
+ return true;
136
+ return true;
137
+ }
138
+ catch (err) {
139
+ if (err.type !== undefined)
140
+ throw err;
141
+ throw (0, index_js_1.createStorageError)(key, adapter.name, err, clock);
142
+ }
143
+ }
144
+ async _safeRelease(key, ownerToken) {
145
+ const { adapter, clock } = this.opts;
146
+ try {
147
+ const existing = await adapter.get(key);
148
+ // Only release if we still own the lock
149
+ if (existing?.ownerToken === ownerToken) {
150
+ await adapter.delete(key);
151
+ }
152
+ }
153
+ catch {
154
+ // Best-effort release — TTL will handle cleanup
155
+ void clock; // reference to avoid unused var
156
+ }
157
+ }
158
+ // ── Hook Runner ───────────────────────────────────────────────────────────
159
+ async _runHook(hookName,
160
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
161
+ ...args) {
162
+ const hook = this.opts.hooks?.[hookName];
163
+ if (hook) {
164
+ try {
165
+ await hook(...args);
166
+ }
167
+ catch {
168
+ // Hooks must never break execution
169
+ }
170
+ }
171
+ }
172
+ }
173
+ exports.LockEngineImpl = LockEngineImpl;
174
+ // ─── Factory ──────────────────────────────────────────────────────────────────
175
+ /**
176
+ * Create a new distributed lock engine.
177
+ *
178
+ * @example
179
+ * ```ts
180
+ * const lock = createLock({ adapter, ttlMs: 10_000, maxWaitMs: 5_000 });
181
+ * const result = await lock.acquire('order:123', () => processOrder());
182
+ * ```
183
+ */
184
+ function createLock(options) {
185
+ return new LockEngineImpl(options);
186
+ }
187
+ //# sourceMappingURL=engine.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"engine.js","sourceRoot":"","sources":["../../../src/lock/engine.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,92 @@
1
+ "use strict";
2
+ /**
3
+ * OpenTelemetry-compatible metrics for @periodic/vanadium
4
+ *
5
+ * Peer dependency (optional): "@opentelemetry/api" >= 1.0.0
6
+ *
7
+ * If OpenTelemetry is not configured, all metrics gracefully no-op.
8
+ *
9
+ * @example
10
+ * ```ts
11
+ * import { VanadiumInstrumentation } from '@periodic/vanadium';
12
+ * // or in your setup:
13
+ * import { createVanadiumMetrics } from '@periodic/vanadium/observability';
14
+ *
15
+ * const metrics = createVanadiumMetrics('my-service');
16
+ * metrics.recordExecution('payment:123', 'payments', 42);
17
+ * ```
18
+ */
19
+ Object.defineProperty(exports, "__esModule", { value: true });
20
+ exports.createVanadiumMetrics = createVanadiumMetrics;
21
+ /* eslint-enable @typescript-eslint/no-explicit-any */
22
+ // ─── No-op instruments ────────────────────────────────────────────────────────
23
+ const noopCounter = { add: () => undefined };
24
+ const noopHistogram = { record: () => undefined };
25
+ const noopUpDownCounter = { add: () => undefined };
26
+ /**
27
+ * Create metrics instruments. Pass an OTel Meter if you have one,
28
+ * otherwise all instruments are graceful no-ops.
29
+ */
30
+ function createVanadiumMetrics(meter, scopeName = 'periodic.vanadium') {
31
+ const executionTotal = meter?.createCounter(`${scopeName}.execution_total`, {
32
+ description: 'Total idempotent executions',
33
+ }) ?? noopCounter;
34
+ const duplicateTotal = meter?.createCounter(`${scopeName}.duplicate_hit_total`, {
35
+ description: 'Total duplicate execution attempts deflected',
36
+ }) ?? noopCounter;
37
+ const takeoverTotal = meter?.createCounter(`${scopeName}.takeover_total`, {
38
+ description: 'Total expired in-progress record takeovers',
39
+ }) ?? noopCounter;
40
+ const storageErrorTotal = meter?.createCounter(`${scopeName}.storage_error_total`, {
41
+ description: 'Total storage adapter errors',
42
+ }) ?? noopCounter;
43
+ const payloadMismatchTotal = meter?.createCounter(`${scopeName}.payload_mismatch_total`, {
44
+ description: 'Total payload hash mismatch rejections',
45
+ }) ?? noopCounter;
46
+ const lockAcquiredTotal = meter?.createCounter(`${scopeName}.lock_acquired_total`, {
47
+ description: 'Total distributed locks acquired',
48
+ }) ?? noopCounter;
49
+ const lockFailedTotal = meter?.createCounter(`${scopeName}.lock_failed_total`, {
50
+ description: 'Total lock acquisition failures',
51
+ }) ?? noopCounter;
52
+ const executionDuration = meter?.createHistogram(`${scopeName}.execution_duration_ms`, {
53
+ description: 'Duration of idempotent function executions in milliseconds',
54
+ unit: 'ms',
55
+ }) ?? noopHistogram;
56
+ const lockDuration = meter?.createHistogram(`${scopeName}.lock_duration_ms`, {
57
+ description: 'Duration locks were held in milliseconds',
58
+ unit: 'ms',
59
+ }) ?? noopHistogram;
60
+ const inProgressGauge = meter?.createUpDownCounter(`${scopeName}.in_progress_count`, {
61
+ description: 'Current number of in-progress executions',
62
+ }) ?? noopUpDownCounter;
63
+ return {
64
+ recordExecution(key, adapterName, durationMs) {
65
+ executionTotal.add(1, { 'idempotency.key': key, 'adapter.name': adapterName });
66
+ executionDuration.record(durationMs, { 'adapter.name': adapterName });
67
+ },
68
+ recordDuplicate(key, adapterName) {
69
+ duplicateTotal.add(1, { 'idempotency.key': key, 'adapter.name': adapterName });
70
+ },
71
+ recordTakeover(key, adapterName) {
72
+ takeoverTotal.add(1, { 'idempotency.key': key, 'adapter.name': adapterName });
73
+ },
74
+ recordStorageError(key, adapterName) {
75
+ storageErrorTotal.add(1, { 'idempotency.key': key, 'adapter.name': adapterName });
76
+ },
77
+ recordPayloadMismatch(key, adapterName) {
78
+ payloadMismatchTotal.add(1, { 'idempotency.key': key, 'adapter.name': adapterName });
79
+ },
80
+ recordLockAcquired(key, adapterName, durationMs) {
81
+ lockAcquiredTotal.add(1, { 'lock.key': key, 'adapter.name': adapterName });
82
+ lockDuration.record(durationMs, { 'adapter.name': adapterName });
83
+ },
84
+ recordLockFailed(key, adapterName, reason) {
85
+ lockFailedTotal.add(1, { 'lock.key': key, 'adapter.name': adapterName, reason });
86
+ },
87
+ setInProgressCount(count, adapterName) {
88
+ inProgressGauge.add(count, { 'adapter.name': adapterName });
89
+ },
90
+ };
91
+ }
92
+ //# sourceMappingURL=metrics.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"metrics.js","sourceRoot":"","sources":["../../../src/observability/metrics.ts"],"names":[],"mappings":"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"}