@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.
- package/CHANGELOG.md +37 -0
- package/LICENSE +21 -0
- package/README.md +846 -0
- package/dist/cjs/adapters/memory/index.js +134 -0
- package/dist/cjs/adapters/memory/index.js.map +1 -0
- package/dist/cjs/adapters/mongodb/index.js +189 -0
- package/dist/cjs/adapters/mongodb/index.js.map +1 -0
- package/dist/cjs/adapters/mongoose/index.js +199 -0
- package/dist/cjs/adapters/mongoose/index.js.map +1 -0
- package/dist/cjs/adapters/postgres/index.js +202 -0
- package/dist/cjs/adapters/postgres/index.js.map +1 -0
- package/dist/cjs/adapters/prisma/index.js +176 -0
- package/dist/cjs/adapters/prisma/index.js.map +1 -0
- package/dist/cjs/adapters/redis/index.js +178 -0
- package/dist/cjs/adapters/redis/index.js.map +1 -0
- package/dist/cjs/cleanup/engine.js +100 -0
- package/dist/cjs/cleanup/engine.js.map +1 -0
- package/dist/cjs/core/concurrencyGuard.js +50 -0
- package/dist/cjs/core/concurrencyGuard.js.map +1 -0
- package/dist/cjs/core/metrics.js +39 -0
- package/dist/cjs/core/metrics.js.map +1 -0
- package/dist/cjs/core/stateMachine.js +46 -0
- package/dist/cjs/core/stateMachine.js.map +1 -0
- package/dist/cjs/errors/index.js +127 -0
- package/dist/cjs/errors/index.js.map +1 -0
- package/dist/cjs/http/express.js +84 -0
- package/dist/cjs/http/express.js.map +1 -0
- package/dist/cjs/http/fastify.js +70 -0
- package/dist/cjs/http/fastify.js.map +1 -0
- package/dist/cjs/idempotency/engine.js +266 -0
- package/dist/cjs/idempotency/engine.js.map +1 -0
- package/dist/cjs/index.js +19 -0
- package/dist/cjs/index.js.map +1 -0
- package/dist/cjs/lock/engine.js +187 -0
- package/dist/cjs/lock/engine.js.map +1 -0
- package/dist/cjs/observability/metrics.js +92 -0
- package/dist/cjs/observability/metrics.js.map +1 -0
- package/dist/cjs/resilience/circuitBreaker.js +129 -0
- package/dist/cjs/resilience/circuitBreaker.js.map +1 -0
- package/dist/cjs/types/index.js +13 -0
- package/dist/cjs/types/index.js.map +1 -0
- package/dist/cjs/utils/crypto.js +64 -0
- package/dist/cjs/utils/crypto.js.map +1 -0
- package/dist/cjs/utils/keys.js +40 -0
- package/dist/cjs/utils/keys.js.map +1 -0
- package/dist/cjs/utils/sleep.js +25 -0
- package/dist/cjs/utils/sleep.js.map +1 -0
- package/dist/esm/adapters/memory/index.js +129 -0
- package/dist/esm/adapters/memory/index.js.map +1 -0
- package/dist/esm/adapters/mongodb/index.js +184 -0
- package/dist/esm/adapters/mongodb/index.js.map +1 -0
- package/dist/esm/adapters/mongoose/index.js +193 -0
- package/dist/esm/adapters/mongoose/index.js.map +1 -0
- package/dist/esm/adapters/postgres/index.js +197 -0
- package/dist/esm/adapters/postgres/index.js.map +1 -0
- package/dist/esm/adapters/prisma/index.js +171 -0
- package/dist/esm/adapters/prisma/index.js.map +1 -0
- package/dist/esm/adapters/redis/index.js +173 -0
- package/dist/esm/adapters/redis/index.js.map +1 -0
- package/dist/esm/cleanup/engine.js +95 -0
- package/dist/esm/cleanup/engine.js.map +1 -0
- package/dist/esm/core/concurrencyGuard.js +46 -0
- package/dist/esm/core/concurrencyGuard.js.map +1 -0
- package/dist/esm/core/metrics.js +35 -0
- package/dist/esm/core/metrics.js.map +1 -0
- package/dist/esm/core/stateMachine.js +40 -0
- package/dist/esm/core/stateMachine.js.map +1 -0
- package/dist/esm/errors/index.js +114 -0
- package/dist/esm/errors/index.js.map +1 -0
- package/dist/esm/http/express.js +81 -0
- package/dist/esm/http/express.js.map +1 -0
- package/dist/esm/http/fastify.js +67 -0
- package/dist/esm/http/fastify.js.map +1 -0
- package/dist/esm/idempotency/engine.js +261 -0
- package/dist/esm/idempotency/engine.js.map +1 -0
- package/dist/esm/index.js +9 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/lock/engine.js +182 -0
- package/dist/esm/lock/engine.js.map +1 -0
- package/dist/esm/observability/metrics.js +89 -0
- package/dist/esm/observability/metrics.js.map +1 -0
- package/dist/esm/resilience/circuitBreaker.js +124 -0
- package/dist/esm/resilience/circuitBreaker.js.map +1 -0
- package/dist/esm/types/index.js +10 -0
- package/dist/esm/types/index.js.map +1 -0
- package/dist/esm/utils/crypto.js +58 -0
- package/dist/esm/utils/crypto.js.map +1 -0
- package/dist/esm/utils/keys.js +35 -0
- package/dist/esm/utils/keys.js.map +1 -0
- package/dist/esm/utils/sleep.js +20 -0
- package/dist/esm/utils/sleep.js.map +1 -0
- package/dist/types/adapters/memory/index.d.ts +49 -0
- package/dist/types/adapters/memory/index.d.ts.map +1 -0
- package/dist/types/adapters/mongodb/index.d.ts +97 -0
- package/dist/types/adapters/mongodb/index.d.ts.map +1 -0
- package/dist/types/adapters/mongoose/index.d.ts +107 -0
- package/dist/types/adapters/mongoose/index.d.ts.map +1 -0
- package/dist/types/adapters/postgres/index.d.ts +85 -0
- package/dist/types/adapters/postgres/index.d.ts.map +1 -0
- package/dist/types/adapters/prisma/index.d.ts +73 -0
- package/dist/types/adapters/prisma/index.d.ts.map +1 -0
- package/dist/types/adapters/redis/index.d.ts +77 -0
- package/dist/types/adapters/redis/index.d.ts.map +1 -0
- package/dist/types/cleanup/engine.d.ts +41 -0
- package/dist/types/cleanup/engine.d.ts.map +1 -0
- package/dist/types/core/concurrencyGuard.d.ts +28 -0
- package/dist/types/core/concurrencyGuard.d.ts.map +1 -0
- package/dist/types/core/metrics.d.ts +13 -0
- package/dist/types/core/metrics.d.ts.map +1 -0
- package/dist/types/core/stateMachine.d.ts +20 -0
- package/dist/types/core/stateMachine.d.ts.map +1 -0
- package/dist/types/errors/index.d.ts +32 -0
- package/dist/types/errors/index.d.ts.map +1 -0
- package/dist/types/http/express.d.ts +50 -0
- package/dist/types/http/express.d.ts.map +1 -0
- package/dist/types/http/fastify.d.ts +48 -0
- package/dist/types/http/fastify.d.ts.map +1 -0
- package/dist/types/idempotency/engine.d.ts +24 -0
- package/dist/types/idempotency/engine.d.ts.map +1 -0
- package/dist/types/index.d.ts +8 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/lock/engine.d.ts +28 -0
- package/dist/types/lock/engine.d.ts.map +1 -0
- package/dist/types/observability/metrics.d.ts +45 -0
- package/dist/types/observability/metrics.d.ts.map +1 -0
- package/dist/types/resilience/circuitBreaker.d.ts +48 -0
- package/dist/types/resilience/circuitBreaker.d.ts.map +1 -0
- package/dist/types/types/index.d.ts +170 -0
- package/dist/types/types/index.d.ts.map +1 -0
- package/dist/types/utils/crypto.d.ts +20 -0
- package/dist/types/utils/crypto.d.ts.map +1 -0
- package/dist/types/utils/keys.d.ts +15 -0
- package/dist/types/utils/keys.d.ts.map +1 -0
- package/dist/types/utils/sleep.d.ts +13 -0
- package/dist/types/utils/sleep.d.ts.map +1 -0
- package/package.json +140 -0
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.CircuitBreakerAdapter = void 0;
|
|
4
|
+
exports.createCircuitBreaker = createCircuitBreaker;
|
|
5
|
+
const index_js_1 = require("../errors/index.js");
|
|
6
|
+
const sleep_js_1 = require("../utils/sleep.js");
|
|
7
|
+
const DEFAULT_FAILURE_THRESHOLD = 5;
|
|
8
|
+
const DEFAULT_RESET_TIMEOUT_MS = 30000;
|
|
9
|
+
const DEFAULT_HALF_OPEN_MAX_CALLS = 1;
|
|
10
|
+
// ─── Circuit Breaker ──────────────────────────────────────────────────────────
|
|
11
|
+
/**
|
|
12
|
+
* Wraps a StorageAdapter with a circuit breaker pattern.
|
|
13
|
+
* Protects the application from repeated storage failures.
|
|
14
|
+
*
|
|
15
|
+
* States:
|
|
16
|
+
* - CLOSED: Normal operation. Failures counted.
|
|
17
|
+
* - OPEN: Storage blocked. All calls throw immediately.
|
|
18
|
+
* - HALF_OPEN: Probe mode. Limited calls allowed to test recovery.
|
|
19
|
+
*/
|
|
20
|
+
class CircuitBreakerAdapter {
|
|
21
|
+
constructor(adapter, options = {}, clock = Date.now) {
|
|
22
|
+
this.state = 'CLOSED';
|
|
23
|
+
this.failureCount = 0;
|
|
24
|
+
this.successCount = 0;
|
|
25
|
+
this.halfOpenCalls = 0;
|
|
26
|
+
this.adapter = adapter;
|
|
27
|
+
this.name = `circuit-breaker(${adapter.name})`;
|
|
28
|
+
this.failureThreshold = options.failureThreshold ?? DEFAULT_FAILURE_THRESHOLD;
|
|
29
|
+
this.resetTimeoutMs = options.resetTimeoutMs ?? DEFAULT_RESET_TIMEOUT_MS;
|
|
30
|
+
this.halfOpenMaxCalls = options.halfOpenMaxCalls ?? DEFAULT_HALF_OPEN_MAX_CALLS;
|
|
31
|
+
this.clock = clock;
|
|
32
|
+
this.lastFailureAt = clock();
|
|
33
|
+
}
|
|
34
|
+
// ── StorageAdapter implementation ─────────────────────────────────────────
|
|
35
|
+
async get(key) {
|
|
36
|
+
return this._execute(() => this.adapter.get(key), key);
|
|
37
|
+
}
|
|
38
|
+
async set(key, value, ttlMs) {
|
|
39
|
+
return this._execute(() => this.adapter.set(key, value, ttlMs), key);
|
|
40
|
+
}
|
|
41
|
+
async delete(key) {
|
|
42
|
+
return this._execute(() => this.adapter.delete(key), key);
|
|
43
|
+
}
|
|
44
|
+
async compareAndSet(key, expectedOwnerToken, newValue, ttlMs) {
|
|
45
|
+
if (!this.adapter.compareAndSet) {
|
|
46
|
+
throw (0, index_js_1.createStorageError)(key, this.name, new Error('Adapter does not support compareAndSet'), this.clock);
|
|
47
|
+
}
|
|
48
|
+
return this._execute(() => this.adapter.compareAndSet(key, expectedOwnerToken, newValue, ttlMs), key);
|
|
49
|
+
}
|
|
50
|
+
// ── State Inspection ──────────────────────────────────────────────────────
|
|
51
|
+
getState() {
|
|
52
|
+
this._checkReset();
|
|
53
|
+
return this.state;
|
|
54
|
+
}
|
|
55
|
+
reset() {
|
|
56
|
+
this.state = 'CLOSED';
|
|
57
|
+
this.failureCount = 0;
|
|
58
|
+
this.successCount = 0;
|
|
59
|
+
this.halfOpenCalls = 0;
|
|
60
|
+
}
|
|
61
|
+
// ── Private ───────────────────────────────────────────────────────────────
|
|
62
|
+
async _execute(fn, key) {
|
|
63
|
+
this._checkReset();
|
|
64
|
+
if (this.state === 'OPEN') {
|
|
65
|
+
throw (0, index_js_1.createStorageError)(key, this.name, new Error(`Circuit breaker is OPEN for adapter "${this.adapter.name}". Storage calls blocked.`), this.clock);
|
|
66
|
+
}
|
|
67
|
+
if (this.state === 'HALF_OPEN' && this.halfOpenCalls >= this.halfOpenMaxCalls) {
|
|
68
|
+
await (0, sleep_js_1.sleep)(100); // brief backoff
|
|
69
|
+
throw (0, index_js_1.createStorageError)(key, this.name, new Error('Circuit breaker is HALF_OPEN. Probe call limit reached.'), this.clock);
|
|
70
|
+
}
|
|
71
|
+
if (this.state === 'HALF_OPEN') {
|
|
72
|
+
this.halfOpenCalls++;
|
|
73
|
+
}
|
|
74
|
+
try {
|
|
75
|
+
const result = await fn();
|
|
76
|
+
this._onSuccess();
|
|
77
|
+
return result;
|
|
78
|
+
}
|
|
79
|
+
catch (err) {
|
|
80
|
+
this._onFailure();
|
|
81
|
+
throw err;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
_onSuccess() {
|
|
85
|
+
this.failureCount = 0;
|
|
86
|
+
this.successCount++;
|
|
87
|
+
if (this.state === 'HALF_OPEN') {
|
|
88
|
+
this.state = 'CLOSED';
|
|
89
|
+
this.halfOpenCalls = 0;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
_onFailure() {
|
|
93
|
+
this.failureCount++;
|
|
94
|
+
this.lastFailureAt = this.clock();
|
|
95
|
+
if (this.state === 'HALF_OPEN') {
|
|
96
|
+
this.state = 'OPEN';
|
|
97
|
+
this.halfOpenCalls = 0;
|
|
98
|
+
}
|
|
99
|
+
else if (this.failureCount >= this.failureThreshold) {
|
|
100
|
+
this.state = 'OPEN';
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
_checkReset() {
|
|
104
|
+
if (this.state === 'OPEN') {
|
|
105
|
+
const elapsed = this.clock() - this.lastFailureAt;
|
|
106
|
+
if (elapsed >= this.resetTimeoutMs) {
|
|
107
|
+
this.state = 'HALF_OPEN';
|
|
108
|
+
this.halfOpenCalls = 0;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
exports.CircuitBreakerAdapter = CircuitBreakerAdapter;
|
|
114
|
+
// ─── Factory ──────────────────────────────────────────────────────────────────
|
|
115
|
+
/**
|
|
116
|
+
* Wrap any StorageAdapter with a circuit breaker.
|
|
117
|
+
*
|
|
118
|
+
* @example
|
|
119
|
+
* ```ts
|
|
120
|
+
* const protectedAdapter = createCircuitBreaker(redisAdapter, {
|
|
121
|
+
* failureThreshold: 3,
|
|
122
|
+
* resetTimeoutMs: 15_000,
|
|
123
|
+
* });
|
|
124
|
+
* ```
|
|
125
|
+
*/
|
|
126
|
+
function createCircuitBreaker(adapter, options, clock) {
|
|
127
|
+
return new CircuitBreakerAdapter(adapter, options, clock);
|
|
128
|
+
}
|
|
129
|
+
//# sourceMappingURL=circuitBreaker.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"circuitBreaker.js","sourceRoot":"","sources":["../../../src/resilience/circuitBreaker.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* @periodic/vanadium — Core Type Definitions
|
|
4
|
+
* Deterministic idempotency and distributed lock engine
|
|
5
|
+
*/
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.VALID_TRANSITIONS = void 0;
|
|
8
|
+
exports.VALID_TRANSITIONS = [
|
|
9
|
+
'IN_PROGRESS -> COMPLETED',
|
|
10
|
+
'IN_PROGRESS -> FAILED',
|
|
11
|
+
'IN_PROGRESS -> IN_PROGRESS', // expired takeover
|
|
12
|
+
];
|
|
13
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/types/index.ts"],"names":[],"mappings":";AAAA;;;GAGG;;;AA2OU,QAAA,iBAAiB,GAA+B;IAC3D,0BAA0B;IAC1B,uBAAuB;IACvB,4BAA4B,EAAE,mBAAmB;CACzC,CAAC"}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.hashPayload = hashPayload;
|
|
4
|
+
exports.generateOwnerToken = generateOwnerToken;
|
|
5
|
+
exports.safeJsonStringify = safeJsonStringify;
|
|
6
|
+
exports.safeJsonParse = safeJsonParse;
|
|
7
|
+
const node_crypto_1 = require("node:crypto");
|
|
8
|
+
/**
|
|
9
|
+
* Deterministically hash an arbitrary payload using SHA-256.
|
|
10
|
+
* JSON.stringify order is normalized using sorted keys for determinism.
|
|
11
|
+
*/
|
|
12
|
+
function hashPayload(payload) {
|
|
13
|
+
const normalized = JSON.stringify(sortKeys(payload));
|
|
14
|
+
return (0, node_crypto_1.createHash)('sha256').update(normalized).digest('hex');
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Recursively sort object keys for deterministic JSON serialization.
|
|
18
|
+
*/
|
|
19
|
+
function sortKeys(value) {
|
|
20
|
+
if (value === null || typeof value !== 'object') {
|
|
21
|
+
return value;
|
|
22
|
+
}
|
|
23
|
+
if (Array.isArray(value)) {
|
|
24
|
+
return value.map(sortKeys);
|
|
25
|
+
}
|
|
26
|
+
const sorted = {};
|
|
27
|
+
for (const key of Object.keys(value).sort()) {
|
|
28
|
+
sorted[key] = sortKeys(value[key]);
|
|
29
|
+
}
|
|
30
|
+
return sorted;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Generate a cryptographically secure owner token (UUID v4).
|
|
34
|
+
*/
|
|
35
|
+
function generateOwnerToken() {
|
|
36
|
+
return (0, node_crypto_1.randomUUID)();
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Safely serialize a value to JSON string.
|
|
40
|
+
* Throws STORAGE_ERROR compatible message on failure.
|
|
41
|
+
*/
|
|
42
|
+
function safeJsonStringify(value) {
|
|
43
|
+
try {
|
|
44
|
+
return JSON.stringify(value);
|
|
45
|
+
}
|
|
46
|
+
catch (err) {
|
|
47
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
48
|
+
throw new Error(`JSON serialization failed: ${msg}`);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Safely parse a JSON string.
|
|
53
|
+
* Throws STORAGE_ERROR compatible message on failure.
|
|
54
|
+
*/
|
|
55
|
+
function safeJsonParse(raw, context = 'record') {
|
|
56
|
+
try {
|
|
57
|
+
return JSON.parse(raw);
|
|
58
|
+
}
|
|
59
|
+
catch (err) {
|
|
60
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
61
|
+
throw new Error(`JSON parse failure in ${context}: ${msg}`);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
//# sourceMappingURL=crypto.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"crypto.js","sourceRoot":"","sources":["../../../src/utils/crypto.ts"],"names":[],"mappings":";;AAMA,kCAGC;AAsBD,gDAEC;AAMD,8CAOC;AAMD,sCAOC;AA3DD,6CAAqD;AAErD;;;GAGG;AACH,SAAgB,WAAW,CAAC,OAAgB;IAC1C,MAAM,UAAU,GAAG,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,CAAC;IACrD,OAAO,IAAA,wBAAU,EAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;AAC/D,CAAC;AAED;;GAEG;AACH,SAAS,QAAQ,CAAC,KAAc;IAC9B,IAAI,KAAK,KAAK,IAAI,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;QAChD,OAAO,KAAK,CAAC;IACf,CAAC;IACD,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;QACzB,OAAO,KAAK,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;IAC7B,CAAC;IACD,MAAM,MAAM,GAA4B,EAAE,CAAC;IAC3C,KAAK,MAAM,GAAG,IAAI,MAAM,CAAC,IAAI,CAAC,KAAgC,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC;QACvE,MAAM,CAAC,GAAG,CAAC,GAAG,QAAQ,CAAE,KAAiC,CAAC,GAAG,CAAC,CAAC,CAAC;IAClE,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC;AAED;;GAEG;AACH,SAAgB,kBAAkB;IAChC,OAAO,IAAA,wBAAU,GAAE,CAAC;AACtB,CAAC;AAED;;;GAGG;AACH,SAAgB,iBAAiB,CAAC,KAAc;IAC9C,IAAI,CAAC;QACH,OAAO,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;IAC/B,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,GAAG,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QAC7D,MAAM,IAAI,KAAK,CAAC,8BAA8B,GAAG,EAAE,CAAC,CAAC;IACvD,CAAC;AACH,CAAC;AAED;;;GAGG;AACH,SAAgB,aAAa,CAAc,GAAW,EAAE,OAAO,GAAG,QAAQ;IACxE,IAAI,CAAC;QACH,OAAO,IAAI,CAAC,KAAK,CAAC,GAAG,CAAM,CAAC;IAC9B,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,GAAG,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QAC7D,MAAM,IAAI,KAAK,CAAC,yBAAyB,OAAO,KAAK,GAAG,EAAE,CAAC,CAAC;IAC9D,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.validateKey = validateKey;
|
|
4
|
+
exports.buildNamespacedKey = buildNamespacedKey;
|
|
5
|
+
exports.assertValidKey = assertValidKey;
|
|
6
|
+
const index_js_1 = require("../errors/index.js");
|
|
7
|
+
const MAX_KEY_LENGTH = 512;
|
|
8
|
+
const VALID_KEY_PATTERN = /^[\w\-:.@/]+$/;
|
|
9
|
+
/**
|
|
10
|
+
* Validate an idempotency key for safety and format correctness.
|
|
11
|
+
* Throws a VanadiumError (CONFIGURATION_ERROR) on invalid input.
|
|
12
|
+
*/
|
|
13
|
+
function validateKey(key, adapterName) {
|
|
14
|
+
if (!key || key.trim().length === 0) {
|
|
15
|
+
throw (0, index_js_1.createConfigurationError)(key, adapterName, 'Idempotency key must be a non-empty string.');
|
|
16
|
+
}
|
|
17
|
+
if (key.length > MAX_KEY_LENGTH) {
|
|
18
|
+
throw (0, index_js_1.createConfigurationError)(key, adapterName, `Idempotency key exceeds maximum length of ${MAX_KEY_LENGTH} characters. Got ${key.length}.`);
|
|
19
|
+
}
|
|
20
|
+
if (!VALID_KEY_PATTERN.test(key)) {
|
|
21
|
+
throw (0, index_js_1.createConfigurationError)(key, adapterName, `Idempotency key "${key}" contains invalid characters. ` +
|
|
22
|
+
`Allowed: alphanumeric, hyphens, underscores, colons, dots, @, and forward slashes.`);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Build a namespaced key: prefix + namespace + key.
|
|
27
|
+
* Example: "vanadium:payments:order_123"
|
|
28
|
+
*/
|
|
29
|
+
function buildNamespacedKey(key, prefix = '', namespace = '') {
|
|
30
|
+
const parts = [prefix, namespace, key].filter(Boolean);
|
|
31
|
+
return parts.join(':');
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Assert the key is valid and return it unchanged (for chaining).
|
|
35
|
+
*/
|
|
36
|
+
function assertValidKey(key, adapterName) {
|
|
37
|
+
validateKey(key, adapterName);
|
|
38
|
+
return key;
|
|
39
|
+
}
|
|
40
|
+
//# sourceMappingURL=keys.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"keys.js","sourceRoot":"","sources":["../../../src/utils/keys.ts"],"names":[],"mappings":";;AASA,kCAmBC;AAMD,gDAGC;AAKD,wCAGC;AA7CD,iDAA8D;AAE9D,MAAM,cAAc,GAAG,GAAG,CAAC;AAC3B,MAAM,iBAAiB,GAAG,eAAe,CAAC;AAE1C;;;GAGG;AACH,SAAgB,WAAW,CAAC,GAAW,EAAE,WAAmB;IAC1D,IAAI,CAAC,GAAG,IAAI,GAAG,CAAC,IAAI,EAAE,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACpC,MAAM,IAAA,mCAAwB,EAAC,GAAG,EAAE,WAAW,EAAE,6CAA6C,CAAC,CAAC;IAClG,CAAC;IACD,IAAI,GAAG,CAAC,MAAM,GAAG,cAAc,EAAE,CAAC;QAChC,MAAM,IAAA,mCAAwB,EAC5B,GAAG,EACH,WAAW,EACX,6CAA6C,cAAc,oBAAoB,GAAG,CAAC,MAAM,GAAG,CAC7F,CAAC;IACJ,CAAC;IACD,IAAI,CAAC,iBAAiB,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC;QACjC,MAAM,IAAA,mCAAwB,EAC5B,GAAG,EACH,WAAW,EACX,oBAAoB,GAAG,iCAAiC;YACtD,oFAAoF,CACvF,CAAC;IACJ,CAAC;AACH,CAAC;AAED;;;GAGG;AACH,SAAgB,kBAAkB,CAAC,GAAW,EAAE,MAAM,GAAG,EAAE,EAAE,SAAS,GAAG,EAAE;IACzE,MAAM,KAAK,GAAG,CAAC,MAAM,EAAE,SAAS,EAAE,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;IACvD,OAAO,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AACzB,CAAC;AAED;;GAEG;AACH,SAAgB,cAAc,CAAC,GAAW,EAAE,WAAmB;IAC7D,WAAW,CAAC,GAAG,EAAE,WAAW,CAAC,CAAC;IAC9B,OAAO,GAAG,CAAC;AACb,CAAC"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.sleep = sleep;
|
|
4
|
+
exports.jitter = jitter;
|
|
5
|
+
exports.sleepWithJitter = sleepWithJitter;
|
|
6
|
+
/**
|
|
7
|
+
* Non-blocking async sleep.
|
|
8
|
+
*/
|
|
9
|
+
function sleep(ms) {
|
|
10
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Add ±jitter to a base delay to prevent thundering herd.
|
|
14
|
+
*/
|
|
15
|
+
function jitter(baseMs, factor = 0.2) {
|
|
16
|
+
const delta = baseMs * factor;
|
|
17
|
+
return baseMs + (Math.random() * 2 - 1) * delta;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Sleep with jitter applied.
|
|
21
|
+
*/
|
|
22
|
+
function sleepWithJitter(baseMs, factor = 0.2) {
|
|
23
|
+
return sleep(jitter(baseMs, factor));
|
|
24
|
+
}
|
|
25
|
+
//# sourceMappingURL=sleep.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sleep.js","sourceRoot":"","sources":["../../../src/utils/sleep.ts"],"names":[],"mappings":";;AAGA,sBAEC;AAKD,wBAGC;AAKD,0CAEC;AApBD;;GAEG;AACH,SAAgB,KAAK,CAAC,EAAU;IAC9B,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,CAAC;AAC3D,CAAC;AAED;;GAEG;AACH,SAAgB,MAAM,CAAC,MAAc,EAAE,MAAM,GAAG,GAAG;IACjD,MAAM,KAAK,GAAG,MAAM,GAAG,MAAM,CAAC;IAC9B,OAAO,MAAM,GAAG,CAAC,IAAI,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,CAAC,CAAC,GAAG,KAAK,CAAC;AAClD,CAAC;AAED;;GAEG;AACH,SAAgB,eAAe,CAAC,MAAc,EAAE,MAAM,GAAG,GAAG;IAC1D,OAAO,KAAK,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;AACvC,CAAC"}
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { createStorageError } from '../../errors/index.js';
|
|
2
|
+
// ─── Memory Storage Adapter ───────────────────────────────────────────────────
|
|
3
|
+
/**
|
|
4
|
+
* Built-in in-memory storage adapter.
|
|
5
|
+
*
|
|
6
|
+
* Features:
|
|
7
|
+
* - O(1) key lookup via Map
|
|
8
|
+
* - Automatic TTL eviction on read
|
|
9
|
+
* - Optional maxKeys with LRU eviction to prevent unbounded growth
|
|
10
|
+
* - No global state — each instance is isolated
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* ```ts
|
|
14
|
+
* import { createMemoryAdapter } from '@periodic/vanadium';
|
|
15
|
+
* const adapter = createMemoryAdapter({ maxKeys: 10_000 });
|
|
16
|
+
* ```
|
|
17
|
+
*/
|
|
18
|
+
export class MemoryAdapter {
|
|
19
|
+
constructor(options = {}) {
|
|
20
|
+
this.name = 'memory';
|
|
21
|
+
this.store = new Map();
|
|
22
|
+
this.maxKeys = options.maxKeys ?? Infinity;
|
|
23
|
+
this.clock = options.clock ?? Date.now;
|
|
24
|
+
}
|
|
25
|
+
async get(key) {
|
|
26
|
+
const entry = this.store.get(key);
|
|
27
|
+
if (!entry)
|
|
28
|
+
return null;
|
|
29
|
+
const now = this.clock();
|
|
30
|
+
// Return expired IN_PROGRESS records so callers can attempt takeover
|
|
31
|
+
if (entry.expiresAt !== undefined && now >= entry.expiresAt) {
|
|
32
|
+
if (entry.record.status === 'IN_PROGRESS') {
|
|
33
|
+
entry.lastAccessed = now;
|
|
34
|
+
return { ...entry.record };
|
|
35
|
+
}
|
|
36
|
+
this.store.delete(key);
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
entry.lastAccessed = now;
|
|
40
|
+
return { ...entry.record };
|
|
41
|
+
}
|
|
42
|
+
async set(key, value, ttlMs) {
|
|
43
|
+
const now = this.clock();
|
|
44
|
+
const expiresAt = ttlMs !== undefined ? now + ttlMs : undefined;
|
|
45
|
+
// Evict LRU entries if at capacity
|
|
46
|
+
if (!this.store.has(key) && this.store.size >= this.maxKeys) {
|
|
47
|
+
this._evictLRU();
|
|
48
|
+
}
|
|
49
|
+
this.store.set(key, {
|
|
50
|
+
record: { ...value },
|
|
51
|
+
expiresAt,
|
|
52
|
+
lastAccessed: now,
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
async delete(key) {
|
|
56
|
+
this.store.delete(key);
|
|
57
|
+
}
|
|
58
|
+
async compareAndSet(key, expectedOwnerToken, newValue, ttlMs) {
|
|
59
|
+
const entry = this.store.get(key);
|
|
60
|
+
if (!entry)
|
|
61
|
+
return false;
|
|
62
|
+
if (entry.record.ownerToken !== expectedOwnerToken)
|
|
63
|
+
return false;
|
|
64
|
+
const now = this.clock();
|
|
65
|
+
const expiresAt = ttlMs !== undefined ? now + ttlMs : entry.expiresAt;
|
|
66
|
+
this.store.set(key, {
|
|
67
|
+
record: { ...newValue },
|
|
68
|
+
expiresAt,
|
|
69
|
+
lastAccessed: now,
|
|
70
|
+
});
|
|
71
|
+
return true;
|
|
72
|
+
}
|
|
73
|
+
// ── Maintenance ───────────────────────────────────────────────────────────
|
|
74
|
+
async setIfAbsent(key, value, ttlMs) {
|
|
75
|
+
const now = this.clock();
|
|
76
|
+
const existing = this.store.get(key);
|
|
77
|
+
if (existing !== undefined) {
|
|
78
|
+
if (existing.expiresAt === undefined || now < existing.expiresAt) {
|
|
79
|
+
return false;
|
|
80
|
+
}
|
|
81
|
+
this.store.delete(key);
|
|
82
|
+
}
|
|
83
|
+
const expiresAt = ttlMs !== undefined ? now + ttlMs : undefined;
|
|
84
|
+
this.store.set(key, {
|
|
85
|
+
record: { ...value },
|
|
86
|
+
expiresAt,
|
|
87
|
+
lastAccessed: now,
|
|
88
|
+
});
|
|
89
|
+
return true;
|
|
90
|
+
}
|
|
91
|
+
/** Evict the single least-recently-accessed entry. */
|
|
92
|
+
_evictLRU() {
|
|
93
|
+
let oldestKey;
|
|
94
|
+
let oldestTime = Infinity;
|
|
95
|
+
for (const [key, entry] of this.store) {
|
|
96
|
+
if (entry.lastAccessed < oldestTime) {
|
|
97
|
+
oldestTime = entry.lastAccessed;
|
|
98
|
+
oldestKey = key;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
if (oldestKey !== undefined) {
|
|
102
|
+
this.store.delete(oldestKey);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
/** Return current number of stored keys. */
|
|
106
|
+
size() {
|
|
107
|
+
return this.store.size;
|
|
108
|
+
}
|
|
109
|
+
/** Clear all stored entries. */
|
|
110
|
+
clear() {
|
|
111
|
+
this.store.clear();
|
|
112
|
+
}
|
|
113
|
+
/** Expose adapter capabilities. */
|
|
114
|
+
capabilities() {
|
|
115
|
+
return {
|
|
116
|
+
transactions: false,
|
|
117
|
+
cas: true,
|
|
118
|
+
ttl: true,
|
|
119
|
+
advisoryLocks: false,
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
// ─── Factory ──────────────────────────────────────────────────────────────────
|
|
124
|
+
export function createMemoryAdapter(options) {
|
|
125
|
+
return new MemoryAdapter(options);
|
|
126
|
+
}
|
|
127
|
+
// Suppress unused import warning — this file intentionally imports for type safety
|
|
128
|
+
void createStorageError;
|
|
129
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../src/adapters/memory/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MongoDB Storage Adapter for @periodic/vanadium
|
|
3
|
+
*
|
|
4
|
+
* Peer dependency: "mongodb" >= 5.0.0
|
|
5
|
+
* Import: import { createMongoAdapter } from '@periodic/vanadium/adapters/mongodb'
|
|
6
|
+
*
|
|
7
|
+
* Uses the official MongoDB driver (NOT Mongoose).
|
|
8
|
+
* The caller is responsible for providing a connected MongoClient.
|
|
9
|
+
*
|
|
10
|
+
* Required indexes (run once):
|
|
11
|
+
* ```js
|
|
12
|
+
* db.collection('vanadium_records').createIndex({ expiresAt: 1 });
|
|
13
|
+
* db.collection('vanadium_records').createIndex({ status: 1, expiresAt: 1 });
|
|
14
|
+
* ```
|
|
15
|
+
*/
|
|
16
|
+
import { createStorageError, createConfigurationError } from '../../errors/index.js';
|
|
17
|
+
/* eslint-enable @typescript-eslint/no-explicit-any */
|
|
18
|
+
const MONGO_DUPLICATE_KEY_CODE = 11000;
|
|
19
|
+
// ─── MongoDB Adapter ──────────────────────────────────────────────────────────
|
|
20
|
+
export class MongoAdapter {
|
|
21
|
+
constructor(options) {
|
|
22
|
+
this.name = 'mongodb';
|
|
23
|
+
if (!options.client || !options.dbName) {
|
|
24
|
+
throw createConfigurationError('', 'mongodb', 'MongoDB adapter requires a client and dbName.');
|
|
25
|
+
}
|
|
26
|
+
this.db = options.client.db(options.dbName);
|
|
27
|
+
this.collectionName = options.collectionName ?? 'vanadium_records';
|
|
28
|
+
this.useTransactions = options.useTransactions ?? true;
|
|
29
|
+
this.clock = options.clock ?? Date.now;
|
|
30
|
+
}
|
|
31
|
+
get collection() {
|
|
32
|
+
return this.db.collection(this.collectionName);
|
|
33
|
+
}
|
|
34
|
+
// ── Storage Interface ─────────────────────────────────────────────────────
|
|
35
|
+
async get(key) {
|
|
36
|
+
try {
|
|
37
|
+
const doc = (await this.collection.findOne({ _id: key }));
|
|
38
|
+
if (!doc)
|
|
39
|
+
return null;
|
|
40
|
+
return this._docToRecord(doc);
|
|
41
|
+
}
|
|
42
|
+
catch (err) {
|
|
43
|
+
throw createStorageError(key, this.name, err, this.clock);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
async set(key, value, ttlMs) {
|
|
47
|
+
const now = this.clock();
|
|
48
|
+
const expiresAt = ttlMs !== undefined ? now + ttlMs : value.expiresAt;
|
|
49
|
+
const doc = {
|
|
50
|
+
_id: key,
|
|
51
|
+
status: value.status,
|
|
52
|
+
result: value.result,
|
|
53
|
+
payloadHash: value.payloadHash,
|
|
54
|
+
ownerToken: value.ownerToken,
|
|
55
|
+
attempts: value.attempts,
|
|
56
|
+
createdAt: value.createdAt,
|
|
57
|
+
updatedAt: value.updatedAt,
|
|
58
|
+
expiresAt,
|
|
59
|
+
};
|
|
60
|
+
try {
|
|
61
|
+
// Try atomic insert first (first-writer-wins)
|
|
62
|
+
await this.collection.insertOne(doc);
|
|
63
|
+
}
|
|
64
|
+
catch (err) {
|
|
65
|
+
const code = err.code;
|
|
66
|
+
if (code !== MONGO_DUPLICATE_KEY_CODE) {
|
|
67
|
+
throw createStorageError(key, this.name, err, this.clock);
|
|
68
|
+
}
|
|
69
|
+
// Key exists — update it
|
|
70
|
+
try {
|
|
71
|
+
await this.collection.findOneAndUpdate({ _id: key }, {
|
|
72
|
+
$set: {
|
|
73
|
+
status: value.status,
|
|
74
|
+
result: value.result,
|
|
75
|
+
payloadHash: value.payloadHash,
|
|
76
|
+
ownerToken: value.ownerToken,
|
|
77
|
+
attempts: value.attempts,
|
|
78
|
+
updatedAt: value.updatedAt,
|
|
79
|
+
expiresAt,
|
|
80
|
+
},
|
|
81
|
+
}, { returnDocument: 'after' });
|
|
82
|
+
}
|
|
83
|
+
catch (updateErr) {
|
|
84
|
+
throw createStorageError(key, this.name, updateErr, this.clock);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
async delete(key) {
|
|
89
|
+
try {
|
|
90
|
+
await this.collection.deleteOne({ _id: key });
|
|
91
|
+
}
|
|
92
|
+
catch (err) {
|
|
93
|
+
throw createStorageError(key, this.name, err, this.clock);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
async compareAndSet(key, expectedOwnerToken, newValue, ttlMs) {
|
|
97
|
+
const now = this.clock();
|
|
98
|
+
const expiresAt = ttlMs !== undefined ? now + ttlMs : newValue.expiresAt;
|
|
99
|
+
try {
|
|
100
|
+
const result = await this.collection.findOneAndUpdate({ _id: key, ownerToken: expectedOwnerToken }, {
|
|
101
|
+
$set: {
|
|
102
|
+
status: newValue.status,
|
|
103
|
+
result: newValue.result,
|
|
104
|
+
payloadHash: newValue.payloadHash,
|
|
105
|
+
ownerToken: newValue.ownerToken,
|
|
106
|
+
attempts: newValue.attempts,
|
|
107
|
+
updatedAt: newValue.updatedAt,
|
|
108
|
+
expiresAt,
|
|
109
|
+
},
|
|
110
|
+
}, { returnDocument: 'after' });
|
|
111
|
+
return result !== null;
|
|
112
|
+
}
|
|
113
|
+
catch (err) {
|
|
114
|
+
throw createStorageError(key, this.name, err, this.clock);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Atomic expired takeover using conditional findOneAndUpdate.
|
|
119
|
+
* Guarantees only one concurrent caller succeeds.
|
|
120
|
+
*/
|
|
121
|
+
async takeoverExpired(key, newOwnerToken, ttlMs) {
|
|
122
|
+
const now = this.clock();
|
|
123
|
+
try {
|
|
124
|
+
const result = (await this.collection.findOneAndUpdate({
|
|
125
|
+
_id: key,
|
|
126
|
+
status: 'IN_PROGRESS',
|
|
127
|
+
expiresAt: { $lt: now },
|
|
128
|
+
}, {
|
|
129
|
+
$set: {
|
|
130
|
+
ownerToken: newOwnerToken,
|
|
131
|
+
updatedAt: now,
|
|
132
|
+
expiresAt: now + ttlMs,
|
|
133
|
+
},
|
|
134
|
+
$inc: { attempts: 1 },
|
|
135
|
+
}, { returnDocument: 'after' }));
|
|
136
|
+
return result;
|
|
137
|
+
}
|
|
138
|
+
catch (err) {
|
|
139
|
+
throw createStorageError(key, this.name, err, this.clock);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
// ── Capabilities ──────────────────────────────────────────────────────────
|
|
143
|
+
capabilities() {
|
|
144
|
+
return {
|
|
145
|
+
transactions: this.useTransactions,
|
|
146
|
+
cas: true,
|
|
147
|
+
ttl: true, // via TTL index
|
|
148
|
+
advisoryLocks: false,
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
// ── Private ───────────────────────────────────────────────────────────────
|
|
152
|
+
_docToRecord(doc) {
|
|
153
|
+
return {
|
|
154
|
+
key: doc._id,
|
|
155
|
+
status: doc.status,
|
|
156
|
+
result: doc.result,
|
|
157
|
+
payloadHash: doc.payloadHash,
|
|
158
|
+
ownerToken: doc.ownerToken,
|
|
159
|
+
attempts: doc.attempts,
|
|
160
|
+
createdAt: doc.createdAt,
|
|
161
|
+
updatedAt: doc.updatedAt,
|
|
162
|
+
expiresAt: doc.expiresAt,
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
// ─── Factory ──────────────────────────────────────────────────────────────────
|
|
167
|
+
/**
|
|
168
|
+
* Create a MongoDB storage adapter.
|
|
169
|
+
*
|
|
170
|
+
* @example
|
|
171
|
+
* ```ts
|
|
172
|
+
* import { MongoClient } from 'mongodb';
|
|
173
|
+
* import { createMongoAdapter } from '@periodic/vanadium/adapters/mongodb';
|
|
174
|
+
*
|
|
175
|
+
* const client = new MongoClient(process.env.MONGO_URL!);
|
|
176
|
+
* await client.connect();
|
|
177
|
+
*
|
|
178
|
+
* const adapter = createMongoAdapter({ client, dbName: 'myapp' });
|
|
179
|
+
* ```
|
|
180
|
+
*/
|
|
181
|
+
export function createMongoAdapter(options) {
|
|
182
|
+
return new MongoAdapter(options);
|
|
183
|
+
}
|
|
184
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../src/adapters/mongodb/index.ts"],"names":[],"mappings":"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"}
|