@vinaverse/server 0.0.0-stage → 0.1.0-alpha.1

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 (64) hide show
  1. package/README.md +165 -2
  2. package/dist/api.cjs +528 -0
  3. package/dist/api.cjs.map +1 -0
  4. package/dist/api.d.cts +453 -0
  5. package/dist/api.d.ts +453 -0
  6. package/dist/api.js +516 -0
  7. package/dist/api.js.map +1 -0
  8. package/dist/chunk-FEEJNQXV.js +64 -0
  9. package/dist/chunk-FEEJNQXV.js.map +1 -0
  10. package/dist/chunk-GTPMQKGX.js +277 -0
  11. package/dist/chunk-GTPMQKGX.js.map +1 -0
  12. package/dist/chunk-NPXO6SMZ.js +84 -0
  13. package/dist/chunk-NPXO6SMZ.js.map +1 -0
  14. package/dist/chunk-SDJFZPHJ.js +141 -0
  15. package/dist/chunk-SDJFZPHJ.js.map +1 -0
  16. package/dist/express.cjs +397 -0
  17. package/dist/express.cjs.map +1 -0
  18. package/dist/express.d.cts +43 -0
  19. package/dist/express.d.ts +43 -0
  20. package/dist/express.js +87 -0
  21. package/dist/express.js.map +1 -0
  22. package/dist/fastify.cjs +363 -0
  23. package/dist/fastify.cjs.map +1 -0
  24. package/dist/fastify.d.cts +62 -0
  25. package/dist/fastify.d.ts +62 -0
  26. package/dist/fastify.js +54 -0
  27. package/dist/fastify.js.map +1 -0
  28. package/dist/hono.cjs +421 -0
  29. package/dist/hono.cjs.map +1 -0
  30. package/dist/hono.d.cts +21 -0
  31. package/dist/hono.d.ts +21 -0
  32. package/dist/hono.js +34 -0
  33. package/dist/hono.js.map +1 -0
  34. package/dist/index.cjs +1481 -0
  35. package/dist/index.cjs.map +1 -0
  36. package/dist/index.d.cts +196 -0
  37. package/dist/index.d.ts +196 -0
  38. package/dist/index.js +920 -0
  39. package/dist/index.js.map +1 -0
  40. package/dist/lambda.cjs +405 -0
  41. package/dist/lambda.cjs.map +1 -0
  42. package/dist/lambda.d.cts +53 -0
  43. package/dist/lambda.d.ts +53 -0
  44. package/dist/lambda.js +86 -0
  45. package/dist/lambda.js.map +1 -0
  46. package/dist/nonces-scwljZwl.d.cts +92 -0
  47. package/dist/nonces-scwljZwl.d.ts +92 -0
  48. package/dist/provider-BqfTvF3g.d.ts +2016 -0
  49. package/dist/provider-Miaa9V7I.d.cts +2016 -0
  50. package/dist/stores.cjs +315 -0
  51. package/dist/stores.cjs.map +1 -0
  52. package/dist/stores.d.cts +52 -0
  53. package/dist/stores.d.ts +52 -0
  54. package/dist/stores.js +306 -0
  55. package/dist/stores.js.map +1 -0
  56. package/dist/testing.cjs +135 -0
  57. package/dist/testing.cjs.map +1 -0
  58. package/dist/testing.d.cts +29 -0
  59. package/dist/testing.d.ts +29 -0
  60. package/dist/testing.js +49 -0
  61. package/dist/testing.js.map +1 -0
  62. package/dist/vinaverse-v1.d-BVc4uyUd.d.cts +3460 -0
  63. package/dist/vinaverse-v1.d-BVc4uyUd.d.ts +3460 -0
  64. package/package.json +226 -4
@@ -0,0 +1,315 @@
1
+ 'use strict';
2
+
3
+ // src/stores/dynamodb.ts
4
+ var ABSENT_OR_EXPIRED = "attribute_not_exists(pk) OR expires_at < :now";
5
+ function conditionFailed(error) {
6
+ return error?.name === "ConditionalCheckFailedException";
7
+ }
8
+ function dynamoNonceStore(options) {
9
+ const now = options.now ?? (() => Math.floor(Date.now() / 1e3));
10
+ return {
11
+ async claim(nonce, keepUntil) {
12
+ try {
13
+ await options.send("PutItem", {
14
+ TableName: options.table,
15
+ Item: {
16
+ pk: { S: `${options.prefix ?? ""}nonce#${nonce}` },
17
+ expires_at: { N: String(Math.ceil(keepUntil)) }
18
+ },
19
+ ConditionExpression: ABSENT_OR_EXPIRED,
20
+ ExpressionAttributeValues: { ":now": { N: String(now()) } }
21
+ });
22
+ return true;
23
+ } catch (error) {
24
+ if (conditionFailed(error)) return false;
25
+ throw error;
26
+ }
27
+ }
28
+ };
29
+ }
30
+ var FREE_OR_OWNED = `${ABSENT_OR_EXPIRED} OR (#state = :pending AND #owner = :owner)`;
31
+ function dynamoIdempotencyStore(options) {
32
+ const now = options.now ?? (() => Math.floor(Date.now() / 1e3));
33
+ const pk = (key) => ({
34
+ S: `${options.prefix ?? ""}idempotency#${key}`
35
+ });
36
+ const owned = (owner) => ({
37
+ ExpressionAttributeNames: { "#state": "state", "#owner": "owner" },
38
+ ":pending": { S: "pending" },
39
+ ":owner": { S: owner }
40
+ });
41
+ return {
42
+ async start(key, fingerprint, pendingSeconds, owner) {
43
+ for (; ; ) {
44
+ try {
45
+ await options.send("PutItem", {
46
+ TableName: options.table,
47
+ Item: {
48
+ pk: pk(key),
49
+ fingerprint: { S: fingerprint },
50
+ state: { S: "pending" },
51
+ owner: { S: owner },
52
+ expires_at: { N: String(now() + Math.ceil(pendingSeconds)) }
53
+ },
54
+ ConditionExpression: ABSENT_OR_EXPIRED,
55
+ ExpressionAttributeValues: { ":now": { N: String(now()) } }
56
+ });
57
+ return null;
58
+ } catch (error) {
59
+ if (!conditionFailed(error)) throw error;
60
+ }
61
+ const held = await options.send("GetItem", {
62
+ TableName: options.table,
63
+ Key: { pk: pk(key) },
64
+ ConsistentRead: true
65
+ });
66
+ const item = held?.Item;
67
+ if (item && Number(item.expires_at?.N) >= now()) {
68
+ return {
69
+ fingerprint: item.fingerprint?.S ?? "",
70
+ state: item.state?.S === "done" ? "done" : "pending",
71
+ ...item.status?.N === void 0 ? {} : { status: Number(item.status.N) },
72
+ ...item.body?.S === void 0 ? {} : { body: item.body.S }
73
+ };
74
+ }
75
+ }
76
+ },
77
+ async finish(key, record, keepSeconds, owner) {
78
+ const { ExpressionAttributeNames, ...values } = owned(owner);
79
+ try {
80
+ await options.send("PutItem", {
81
+ TableName: options.table,
82
+ Item: {
83
+ pk: pk(key),
84
+ fingerprint: { S: record.fingerprint },
85
+ state: { S: "done" },
86
+ status: { N: String(record.status) },
87
+ body: { S: record.body },
88
+ expires_at: { N: String(now() + Math.ceil(keepSeconds)) }
89
+ },
90
+ ConditionExpression: FREE_OR_OWNED,
91
+ ExpressionAttributeNames,
92
+ ExpressionAttributeValues: {
93
+ ...values,
94
+ ":now": { N: String(now()) }
95
+ }
96
+ });
97
+ return true;
98
+ } catch (error) {
99
+ if (conditionFailed(error)) return false;
100
+ throw error;
101
+ }
102
+ },
103
+ async release(key, owner) {
104
+ const { ExpressionAttributeNames, ...values } = owned(owner);
105
+ try {
106
+ await options.send("DeleteItem", {
107
+ TableName: options.table,
108
+ Key: { pk: pk(key) },
109
+ ConditionExpression: "#state = :pending AND #owner = :owner",
110
+ ExpressionAttributeNames,
111
+ ExpressionAttributeValues: values
112
+ });
113
+ } catch (error) {
114
+ if (!conditionFailed(error)) throw error;
115
+ }
116
+ }
117
+ };
118
+ }
119
+
120
+ // src/stores/postgres.ts
121
+ var PREFIX = /^[a-z_][a-z0-9_]{0,40}$/;
122
+ function postgresTables(tablePrefix = "vinaverse_") {
123
+ const t = tables(tablePrefix);
124
+ return `
125
+ CREATE TABLE IF NOT EXISTS ${t.nonces} (
126
+ nonce text PRIMARY KEY,
127
+ keep_until timestamptz NOT NULL
128
+ );
129
+ CREATE TABLE IF NOT EXISTS ${t.idempotency} (
130
+ key text PRIMARY KEY,
131
+ fingerprint text NOT NULL,
132
+ state text NOT NULL CHECK (state IN ('pending', 'done')),
133
+ owner text,
134
+ status integer,
135
+ body text,
136
+ keep_until timestamptz NOT NULL
137
+ );`;
138
+ }
139
+ var POSTGRES_TABLES = postgresTables();
140
+ function tables(prefix) {
141
+ if (!PREFIX.test(prefix)) {
142
+ throw new TypeError(
143
+ `tablePrefix must be lower-case letters, digits and _, starting with a letter or _, not ${JSON.stringify(prefix)}`
144
+ );
145
+ }
146
+ return { nonces: `${prefix}nonces`, idempotency: `${prefix}idempotency` };
147
+ }
148
+ function pruner(options, sql) {
149
+ const every = options.pruneEvery ?? 1e3;
150
+ let count = 0;
151
+ return async () => {
152
+ count = (count + 1) % every;
153
+ if (count === 0) await options.query(sql, []);
154
+ };
155
+ }
156
+ function postgresNonceStore(options) {
157
+ const { nonces } = tables(options.tablePrefix ?? "vinaverse_");
158
+ const claim = `INSERT INTO ${nonces} (nonce, keep_until) VALUES ($1, to_timestamp($2))
159
+ ON CONFLICT (nonce) DO UPDATE SET keep_until = EXCLUDED.keep_until
160
+ WHERE ${nonces}.keep_until < now()
161
+ RETURNING nonce`;
162
+ const prune = pruner(
163
+ options,
164
+ `DELETE FROM ${nonces} WHERE keep_until < now()`
165
+ );
166
+ return {
167
+ async claim(nonce, keepUntil) {
168
+ await prune();
169
+ const { rows } = await options.query(claim, [nonce, keepUntil]);
170
+ return rows.length === 1;
171
+ }
172
+ };
173
+ }
174
+ function postgresIdempotencyStore(options) {
175
+ const { idempotency: t } = tables(options.tablePrefix ?? "vinaverse_");
176
+ const start = `INSERT INTO ${t} (key, fingerprint, state, owner, keep_until)
177
+ VALUES ($1, $2, 'pending', $4, now() + make_interval(secs => $3))
178
+ ON CONFLICT (key) DO UPDATE SET fingerprint = EXCLUDED.fingerprint, state = 'pending', owner = EXCLUDED.owner,
179
+ status = NULL, body = NULL, keep_until = EXCLUDED.keep_until
180
+ WHERE ${t}.keep_until < now()
181
+ RETURNING key`;
182
+ const held = `SELECT fingerprint, state, status, body FROM ${t}
183
+ WHERE key = $1 AND keep_until >= now()`;
184
+ const finish = `INSERT INTO ${t} (key, fingerprint, state, owner, status, body, keep_until)
185
+ VALUES ($1, $2, 'done', NULL, $3, $4, now() + make_interval(secs => $5))
186
+ ON CONFLICT (key) DO UPDATE SET fingerprint = EXCLUDED.fingerprint, state = 'done', owner = NULL,
187
+ status = EXCLUDED.status, body = EXCLUDED.body, keep_until = EXCLUDED.keep_until
188
+ WHERE ${t}.keep_until < now() OR (${t}.state = 'pending' AND ${t}.owner = $6)
189
+ RETURNING key`;
190
+ const release = `DELETE FROM ${t} WHERE key = $1 AND state = 'pending' AND owner = $2`;
191
+ const prune = pruner(options, `DELETE FROM ${t} WHERE keep_until < now()`);
192
+ return {
193
+ async start(key, fingerprint, pendingSeconds, owner) {
194
+ await prune();
195
+ for (; ; ) {
196
+ const started = await options.query(start, [
197
+ key,
198
+ fingerprint,
199
+ pendingSeconds,
200
+ owner
201
+ ]);
202
+ if (started.rows.length === 1) return null;
203
+ const [row] = (await options.query(held, [key])).rows;
204
+ if (row) {
205
+ return {
206
+ fingerprint: String(row.fingerprint),
207
+ state: row.state === "done" ? "done" : "pending",
208
+ ...row.status === null || row.status === void 0 ? {} : { status: Number(row.status) },
209
+ ...row.body === null || row.body === void 0 ? {} : { body: String(row.body) }
210
+ };
211
+ }
212
+ }
213
+ },
214
+ async finish(key, record, keepSeconds, owner) {
215
+ const { rows } = await options.query(finish, [
216
+ key,
217
+ record.fingerprint,
218
+ record.status,
219
+ record.body,
220
+ keepSeconds,
221
+ owner
222
+ ]);
223
+ return rows.length === 1;
224
+ },
225
+ async release(key, owner) {
226
+ await options.query(release, [key, owner]);
227
+ }
228
+ };
229
+ }
230
+
231
+ // src/stores/redis.ts
232
+ function redisNonceStore(options) {
233
+ const prefix = options.prefix ?? "vinaverse:";
234
+ return {
235
+ async claim(nonce, keepUntil) {
236
+ const reply = await options.command([
237
+ "SET",
238
+ `${prefix}nonce:${nonce}`,
239
+ "1",
240
+ "NX",
241
+ "EXAT",
242
+ String(Math.ceil(keepUntil))
243
+ ]);
244
+ return reply === "OK";
245
+ }
246
+ };
247
+ }
248
+ var RELEASE_IF_OWNED = `local held = redis.call('GET', KEYS[1])
249
+ if held then
250
+ local r = cjson.decode(held)
251
+ if r.state == 'pending' and r.owner == ARGV[1] then return redis.call('DEL', KEYS[1]) end
252
+ end
253
+ return 0`;
254
+ var FINISH_IF_OWNED = `local held = redis.call('GET', KEYS[1])
255
+ if held then
256
+ local r = cjson.decode(held)
257
+ if r.state ~= 'pending' or r.owner ~= ARGV[1] then return 0 end
258
+ end
259
+ redis.call('SET', KEYS[1], ARGV[2], 'EX', ARGV[3])
260
+ return 1`;
261
+ function redisIdempotencyStore(options) {
262
+ const prefix = options.prefix ?? "vinaverse:";
263
+ const key = (k) => `${prefix}idempotency:${k}`;
264
+ return {
265
+ async start(k, fingerprint, pendingSeconds, owner) {
266
+ for (; ; ) {
267
+ const set = await options.command([
268
+ "SET",
269
+ key(k),
270
+ JSON.stringify({ fingerprint, state: "pending", owner }),
271
+ "NX",
272
+ "EX",
273
+ seconds(pendingSeconds)
274
+ ]);
275
+ if (set === "OK") return null;
276
+ const held = await options.command(["GET", key(k)]);
277
+ if (typeof held === "string") {
278
+ const { owner: _, ...record } = JSON.parse(
279
+ held
280
+ );
281
+ return record;
282
+ }
283
+ }
284
+ },
285
+ async finish(k, record, keepSeconds, owner) {
286
+ const stored = await options.command([
287
+ "EVAL",
288
+ FINISH_IF_OWNED,
289
+ "1",
290
+ key(k),
291
+ owner,
292
+ JSON.stringify({ ...record, state: "done" }),
293
+ seconds(keepSeconds)
294
+ ]);
295
+ return Number(stored) === 1;
296
+ },
297
+ async release(k, owner) {
298
+ await options.command(["EVAL", RELEASE_IF_OWNED, "1", key(k), owner]);
299
+ }
300
+ };
301
+ }
302
+ function seconds(value) {
303
+ return String(Math.max(1, Math.ceil(value)));
304
+ }
305
+
306
+ exports.POSTGRES_TABLES = POSTGRES_TABLES;
307
+ exports.dynamoIdempotencyStore = dynamoIdempotencyStore;
308
+ exports.dynamoNonceStore = dynamoNonceStore;
309
+ exports.postgresIdempotencyStore = postgresIdempotencyStore;
310
+ exports.postgresNonceStore = postgresNonceStore;
311
+ exports.postgresTables = postgresTables;
312
+ exports.redisIdempotencyStore = redisIdempotencyStore;
313
+ exports.redisNonceStore = redisNonceStore;
314
+ //# sourceMappingURL=stores.cjs.map
315
+ //# sourceMappingURL=stores.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/stores/dynamodb.ts","../src/stores/postgres.ts","../src/stores/redis.ts"],"names":[],"mappings":"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","file":"stores.cjs","sourcesContent":["// Nonces and idempotent answers in DynamoDB, shared by every Lambda instance.\n// One table with a string partition key `pk` and DynamoDB TTL on the number\n// attribute `expires_at` (DynamoDB deletes expired items lazily, so every\n// condition here also checks the time). Works with the AWS SDK v3 through one\n// function that sends a command by name:\n// const commands = { PutItem: PutItemCommand, GetItem: GetItemCommand, DeleteItem: DeleteItemCommand };\n// dynamoNonceStore({ table, send: (name, input) => client.send(new commands[name](input)) })\nimport type {\n IdempotencyRecord,\n IdempotencyStore,\n} from '../tools/idempotency.js';\nimport type { NonceStore } from '../tools/nonces.js';\n\nexport type DynamoCommand = 'PutItem' | 'GetItem' | 'DeleteItem';\n\nexport type DynamoSend = (\n command: DynamoCommand,\n input: Record<string, unknown>,\n) => Promise<{ Item?: Record<string, { S?: string; N?: string }> } | undefined>;\n\nexport interface DynamoStoreOptions {\n table: string;\n send: DynamoSend;\n /** The clock, in Unix seconds; default the system clock. */\n now?: () => number;\n /** Prefix for every partition key, default none: one per environment or signing secret sharing a table. */\n prefix?: string;\n}\n\nconst ABSENT_OR_EXPIRED = 'attribute_not_exists(pk) OR expires_at < :now';\n\nfunction conditionFailed(error: unknown): boolean {\n return (\n (error as { name?: string } | null)?.name ===\n 'ConditionalCheckFailedException'\n );\n}\n\nexport function dynamoNonceStore(options: DynamoStoreOptions): NonceStore {\n const now = options.now ?? (() => Math.floor(Date.now() / 1000));\n return {\n async claim(nonce, keepUntil) {\n try {\n await options.send('PutItem', {\n TableName: options.table,\n Item: {\n pk: { S: `${options.prefix ?? ''}nonce#${nonce}` },\n expires_at: { N: String(Math.ceil(keepUntil)) },\n },\n ConditionExpression: ABSENT_OR_EXPIRED,\n ExpressionAttributeValues: { ':now': { N: String(now()) } },\n });\n return true;\n } catch (error) {\n if (conditionFailed(error)) return false;\n throw error;\n }\n },\n };\n}\n\n// An answer is stored only when the key is free or still this call's pending lease.\nconst FREE_OR_OWNED = `${ABSENT_OR_EXPIRED} OR (#state = :pending AND #owner = :owner)`;\n\nexport function dynamoIdempotencyStore(\n options: DynamoStoreOptions,\n): IdempotencyStore {\n const now = options.now ?? (() => Math.floor(Date.now() / 1000));\n const pk = (key: string) => ({\n S: `${options.prefix ?? ''}idempotency#${key}`,\n });\n const owned = (owner: string) => ({\n ExpressionAttributeNames: { '#state': 'state', '#owner': 'owner' },\n ':pending': { S: 'pending' },\n ':owner': { S: owner },\n });\n return {\n async start(key, fingerprint, pendingSeconds, owner) {\n for (;;) {\n try {\n await options.send('PutItem', {\n TableName: options.table,\n Item: {\n pk: pk(key),\n fingerprint: { S: fingerprint },\n state: { S: 'pending' },\n owner: { S: owner },\n expires_at: { N: String(now() + Math.ceil(pendingSeconds)) },\n },\n ConditionExpression: ABSENT_OR_EXPIRED,\n ExpressionAttributeValues: { ':now': { N: String(now()) } },\n });\n return null;\n } catch (error) {\n if (!conditionFailed(error)) throw error;\n }\n const held = await options.send('GetItem', {\n TableName: options.table,\n Key: { pk: pk(key) },\n ConsistentRead: true,\n });\n const item = held?.Item;\n // Lapsed between the two calls: try to take it again.\n if (item && Number(item.expires_at?.N) >= now()) {\n return {\n fingerprint: item.fingerprint?.S ?? '',\n state: item.state?.S === 'done' ? 'done' : 'pending',\n ...(item.status?.N === undefined\n ? {}\n : { status: Number(item.status.N) }),\n ...(item.body?.S === undefined ? {} : { body: item.body.S }),\n } satisfies IdempotencyRecord;\n }\n }\n },\n async finish(key, record, keepSeconds, owner) {\n const { ExpressionAttributeNames, ...values } = owned(owner);\n try {\n await options.send('PutItem', {\n TableName: options.table,\n Item: {\n pk: pk(key),\n fingerprint: { S: record.fingerprint },\n state: { S: 'done' },\n status: { N: String(record.status) },\n body: { S: record.body },\n expires_at: { N: String(now() + Math.ceil(keepSeconds)) },\n },\n ConditionExpression: FREE_OR_OWNED,\n ExpressionAttributeNames,\n ExpressionAttributeValues: {\n ...values,\n ':now': { N: String(now()) },\n },\n });\n return true;\n } catch (error) {\n if (conditionFailed(error)) return false;\n throw error;\n }\n },\n async release(key, owner) {\n const { ExpressionAttributeNames, ...values } = owned(owner);\n try {\n await options.send('DeleteItem', {\n TableName: options.table,\n Key: { pk: pk(key) },\n ConditionExpression: '#state = :pending AND #owner = :owner',\n ExpressionAttributeNames,\n ExpressionAttributeValues: values,\n });\n } catch (error) {\n if (!conditionFailed(error)) throw error;\n }\n },\n };\n}\n","// Nonces and idempotent answers in PostgreSQL, shared by every instance.\n// Works with any client through one function that runs a parameterised\n// statement and returns its rows: node-postgres `(sql, params) => pool.query(sql, params)`,\n// postgres.js `(sql, params) => sql.unsafe(text, params).then((rows) => ({ rows }))`.\n// Create the tables once (`POSTGRES_TABLES`, `postgresTables(prefix)`, or your\n// migrations); expired rows are reused in place and pruned now and then.\nimport type {\n IdempotencyRecord,\n IdempotencyStore,\n} from '../tools/idempotency.js';\nimport type { NonceStore } from '../tools/nonces.js';\n\nexport type PostgresQuery = (\n sql: string,\n params: readonly unknown[],\n) => Promise<{ rows: readonly Record<string, unknown>[] }>;\n\nexport interface PostgresStoreOptions {\n query: PostgresQuery;\n /** Prune expired rows on one call in this many; default 1000. */\n pruneEvery?: number;\n /**\n * The tables' name prefix, default `vinaverse_` (`vinaverse_nonces`,\n * `vinaverse_idempotency`): a separate prefix per environment or signing\n * secret when they share a database. Lower-case letters, digits and `_`.\n */\n tablePrefix?: string;\n}\n\nconst PREFIX = /^[a-z_][a-z0-9_]{0,40}$/;\n\n/** The tables the stores use, under a prefix (default `vinaverse_`). */\nexport function postgresTables(tablePrefix = 'vinaverse_'): string {\n const t = tables(tablePrefix);\n return `\nCREATE TABLE IF NOT EXISTS ${t.nonces} (\n nonce text PRIMARY KEY,\n keep_until timestamptz NOT NULL\n);\nCREATE TABLE IF NOT EXISTS ${t.idempotency} (\n key text PRIMARY KEY,\n fingerprint text NOT NULL,\n state text NOT NULL CHECK (state IN ('pending', 'done')),\n owner text,\n status integer,\n body text,\n keep_until timestamptz NOT NULL\n);`;\n}\n\n/** The tables the stores use, with the default prefix. */\nexport const POSTGRES_TABLES = postgresTables();\n\nfunction tables(prefix: string): { nonces: string; idempotency: string } {\n if (!PREFIX.test(prefix)) {\n throw new TypeError(\n `tablePrefix must be lower-case letters, digits and _, starting with a letter or _, not ${JSON.stringify(prefix)}`,\n );\n }\n return { nonces: `${prefix}nonces`, idempotency: `${prefix}idempotency` };\n}\n\nfunction pruner(\n options: PostgresStoreOptions,\n sql: string,\n): () => Promise<void> {\n const every = options.pruneEvery ?? 1000;\n let count = 0;\n return async () => {\n count = (count + 1) % every;\n if (count === 0) await options.query(sql, []);\n };\n}\n\nexport function postgresNonceStore(options: PostgresStoreOptions): NonceStore {\n const { nonces } = tables(options.tablePrefix ?? 'vinaverse_');\n const claim = `INSERT INTO ${nonces} (nonce, keep_until) VALUES ($1, to_timestamp($2))\nON CONFLICT (nonce) DO UPDATE SET keep_until = EXCLUDED.keep_until\nWHERE ${nonces}.keep_until < now()\nRETURNING nonce`;\n const prune = pruner(\n options,\n `DELETE FROM ${nonces} WHERE keep_until < now()`,\n );\n return {\n async claim(nonce, keepUntil) {\n await prune();\n const { rows } = await options.query(claim, [nonce, keepUntil]);\n return rows.length === 1;\n },\n };\n}\n\nexport function postgresIdempotencyStore(\n options: PostgresStoreOptions,\n): IdempotencyStore {\n const { idempotency: t } = tables(options.tablePrefix ?? 'vinaverse_');\n const start = `INSERT INTO ${t} (key, fingerprint, state, owner, keep_until)\nVALUES ($1, $2, 'pending', $4, now() + make_interval(secs => $3))\nON CONFLICT (key) DO UPDATE SET fingerprint = EXCLUDED.fingerprint, state = 'pending', owner = EXCLUDED.owner,\n status = NULL, body = NULL, keep_until = EXCLUDED.keep_until\nWHERE ${t}.keep_until < now()\nRETURNING key`;\n const held = `SELECT fingerprint, state, status, body FROM ${t}\nWHERE key = $1 AND keep_until >= now()`;\n // Stored only when the key is free (absent or expired) or still this call's pending lease.\n const finish = `INSERT INTO ${t} (key, fingerprint, state, owner, status, body, keep_until)\nVALUES ($1, $2, 'done', NULL, $3, $4, now() + make_interval(secs => $5))\nON CONFLICT (key) DO UPDATE SET fingerprint = EXCLUDED.fingerprint, state = 'done', owner = NULL,\n status = EXCLUDED.status, body = EXCLUDED.body, keep_until = EXCLUDED.keep_until\nWHERE ${t}.keep_until < now() OR (${t}.state = 'pending' AND ${t}.owner = $6)\nRETURNING key`;\n const release = `DELETE FROM ${t} WHERE key = $1 AND state = 'pending' AND owner = $2`;\n const prune = pruner(options, `DELETE FROM ${t} WHERE keep_until < now()`);\n return {\n async start(key, fingerprint, pendingSeconds, owner) {\n await prune();\n for (;;) {\n const started = await options.query(start, [\n key,\n fingerprint,\n pendingSeconds,\n owner,\n ]);\n if (started.rows.length === 1) return null;\n const [row] = (await options.query(held, [key])).rows;\n // Lapsed between the two statements: try to take it again.\n if (row) {\n return {\n fingerprint: String(row.fingerprint),\n state: row.state === 'done' ? 'done' : 'pending',\n ...(row.status === null || row.status === undefined\n ? {}\n : { status: Number(row.status) }),\n ...(row.body === null || row.body === undefined\n ? {}\n : { body: String(row.body) }),\n } satisfies IdempotencyRecord;\n }\n }\n },\n async finish(key, record, keepSeconds, owner) {\n const { rows } = await options.query(finish, [\n key,\n record.fingerprint,\n record.status,\n record.body,\n keepSeconds,\n owner,\n ]);\n return rows.length === 1;\n },\n async release(key, owner) {\n await options.query(release, [key, owner]);\n },\n };\n}\n","// Nonces and idempotent answers in Redis, shared by every instance. Works with\n// any client through one function that sends a raw command:\n// node-redis: (args) => client.sendCommand(args)\n// ioredis: (args) => client.call(args[0], ...args.slice(1))\nimport type {\n IdempotencyRecord,\n IdempotencyStore,\n} from '../tools/idempotency.js';\nimport type { NonceStore } from '../tools/nonces.js';\n\n/** Sends one Redis command (`['SET', key, value, 'NX', 'EX', '60']`) and returns its reply. */\nexport type RedisCommand = (args: string[]) => Promise<unknown>;\n\nexport interface RedisStoreOptions {\n command: RedisCommand;\n /** Prefix for every key; default `vinaverse:`. */\n prefix?: string;\n}\n\nexport function redisNonceStore(options: RedisStoreOptions): NonceStore {\n const prefix = options.prefix ?? 'vinaverse:';\n return {\n async claim(nonce, keepUntil) {\n const reply = await options.command([\n 'SET',\n `${prefix}nonce:${nonce}`,\n '1',\n 'NX',\n 'EXAT',\n String(Math.ceil(keepUntil)),\n ]);\n return reply === 'OK';\n },\n };\n}\n\n// Atomically, on the record a call holds: a pending record goes only when this\n// call's `owner` holds it; an answer is stored only when the key is free or\n// still this call's pending lease (never over another call's, after this\n// call's lease lapsed).\nconst RELEASE_IF_OWNED = `local held = redis.call('GET', KEYS[1])\nif held then\n local r = cjson.decode(held)\n if r.state == 'pending' and r.owner == ARGV[1] then return redis.call('DEL', KEYS[1]) end\nend\nreturn 0`;\nconst FINISH_IF_OWNED = `local held = redis.call('GET', KEYS[1])\nif held then\n local r = cjson.decode(held)\n if r.state ~= 'pending' or r.owner ~= ARGV[1] then return 0 end\nend\nredis.call('SET', KEYS[1], ARGV[2], 'EX', ARGV[3])\nreturn 1`;\n\nexport function redisIdempotencyStore(\n options: RedisStoreOptions,\n): IdempotencyStore {\n const prefix = options.prefix ?? 'vinaverse:';\n const key = (k: string) => `${prefix}idempotency:${k}`;\n return {\n async start(k, fingerprint, pendingSeconds, owner) {\n for (;;) {\n const set = await options.command([\n 'SET',\n key(k),\n JSON.stringify({ fingerprint, state: 'pending', owner }),\n 'NX',\n 'EX',\n seconds(pendingSeconds),\n ]);\n if (set === 'OK') return null;\n const held = await options.command(['GET', key(k)]);\n // Gone between the two commands (it lapsed): try to take it again.\n if (typeof held === 'string') {\n const { owner: _, ...record } = JSON.parse(\n held,\n ) as IdempotencyRecord & {\n owner?: string;\n };\n return record;\n }\n }\n },\n async finish(k, record, keepSeconds, owner) {\n const stored = await options.command([\n 'EVAL',\n FINISH_IF_OWNED,\n '1',\n key(k),\n owner,\n JSON.stringify({ ...record, state: 'done' }),\n seconds(keepSeconds),\n ]);\n return Number(stored) === 1;\n },\n async release(k, owner) {\n await options.command(['EVAL', RELEASE_IF_OWNED, '1', key(k), owner]);\n },\n };\n}\n\nfunction seconds(value: number): string {\n return String(Math.max(1, Math.ceil(value)));\n}\n"]}
@@ -0,0 +1,52 @@
1
+ import { a as IdempotencyStore, c as NonceStore } from './nonces-scwljZwl.cjs';
2
+
3
+ type DynamoCommand = 'PutItem' | 'GetItem' | 'DeleteItem';
4
+ type DynamoSend = (command: DynamoCommand, input: Record<string, unknown>) => Promise<{
5
+ Item?: Record<string, {
6
+ S?: string;
7
+ N?: string;
8
+ }>;
9
+ } | undefined>;
10
+ interface DynamoStoreOptions {
11
+ table: string;
12
+ send: DynamoSend;
13
+ /** The clock, in Unix seconds; default the system clock. */
14
+ now?: () => number;
15
+ /** Prefix for every partition key, default none: one per environment or signing secret sharing a table. */
16
+ prefix?: string;
17
+ }
18
+ declare function dynamoNonceStore(options: DynamoStoreOptions): NonceStore;
19
+ declare function dynamoIdempotencyStore(options: DynamoStoreOptions): IdempotencyStore;
20
+
21
+ type PostgresQuery = (sql: string, params: readonly unknown[]) => Promise<{
22
+ rows: readonly Record<string, unknown>[];
23
+ }>;
24
+ interface PostgresStoreOptions {
25
+ query: PostgresQuery;
26
+ /** Prune expired rows on one call in this many; default 1000. */
27
+ pruneEvery?: number;
28
+ /**
29
+ * The tables' name prefix, default `vinaverse_` (`vinaverse_nonces`,
30
+ * `vinaverse_idempotency`): a separate prefix per environment or signing
31
+ * secret when they share a database. Lower-case letters, digits and `_`.
32
+ */
33
+ tablePrefix?: string;
34
+ }
35
+ /** The tables the stores use, under a prefix (default `vinaverse_`). */
36
+ declare function postgresTables(tablePrefix?: string): string;
37
+ /** The tables the stores use, with the default prefix. */
38
+ declare const POSTGRES_TABLES: string;
39
+ declare function postgresNonceStore(options: PostgresStoreOptions): NonceStore;
40
+ declare function postgresIdempotencyStore(options: PostgresStoreOptions): IdempotencyStore;
41
+
42
+ /** Sends one Redis command (`['SET', key, value, 'NX', 'EX', '60']`) and returns its reply. */
43
+ type RedisCommand = (args: string[]) => Promise<unknown>;
44
+ interface RedisStoreOptions {
45
+ command: RedisCommand;
46
+ /** Prefix for every key; default `vinaverse:`. */
47
+ prefix?: string;
48
+ }
49
+ declare function redisNonceStore(options: RedisStoreOptions): NonceStore;
50
+ declare function redisIdempotencyStore(options: RedisStoreOptions): IdempotencyStore;
51
+
52
+ export { type DynamoCommand, type DynamoSend, type DynamoStoreOptions, POSTGRES_TABLES, type PostgresQuery, type PostgresStoreOptions, type RedisCommand, type RedisStoreOptions, dynamoIdempotencyStore, dynamoNonceStore, postgresIdempotencyStore, postgresNonceStore, postgresTables, redisIdempotencyStore, redisNonceStore };
@@ -0,0 +1,52 @@
1
+ import { a as IdempotencyStore, c as NonceStore } from './nonces-scwljZwl.js';
2
+
3
+ type DynamoCommand = 'PutItem' | 'GetItem' | 'DeleteItem';
4
+ type DynamoSend = (command: DynamoCommand, input: Record<string, unknown>) => Promise<{
5
+ Item?: Record<string, {
6
+ S?: string;
7
+ N?: string;
8
+ }>;
9
+ } | undefined>;
10
+ interface DynamoStoreOptions {
11
+ table: string;
12
+ send: DynamoSend;
13
+ /** The clock, in Unix seconds; default the system clock. */
14
+ now?: () => number;
15
+ /** Prefix for every partition key, default none: one per environment or signing secret sharing a table. */
16
+ prefix?: string;
17
+ }
18
+ declare function dynamoNonceStore(options: DynamoStoreOptions): NonceStore;
19
+ declare function dynamoIdempotencyStore(options: DynamoStoreOptions): IdempotencyStore;
20
+
21
+ type PostgresQuery = (sql: string, params: readonly unknown[]) => Promise<{
22
+ rows: readonly Record<string, unknown>[];
23
+ }>;
24
+ interface PostgresStoreOptions {
25
+ query: PostgresQuery;
26
+ /** Prune expired rows on one call in this many; default 1000. */
27
+ pruneEvery?: number;
28
+ /**
29
+ * The tables' name prefix, default `vinaverse_` (`vinaverse_nonces`,
30
+ * `vinaverse_idempotency`): a separate prefix per environment or signing
31
+ * secret when they share a database. Lower-case letters, digits and `_`.
32
+ */
33
+ tablePrefix?: string;
34
+ }
35
+ /** The tables the stores use, under a prefix (default `vinaverse_`). */
36
+ declare function postgresTables(tablePrefix?: string): string;
37
+ /** The tables the stores use, with the default prefix. */
38
+ declare const POSTGRES_TABLES: string;
39
+ declare function postgresNonceStore(options: PostgresStoreOptions): NonceStore;
40
+ declare function postgresIdempotencyStore(options: PostgresStoreOptions): IdempotencyStore;
41
+
42
+ /** Sends one Redis command (`['SET', key, value, 'NX', 'EX', '60']`) and returns its reply. */
43
+ type RedisCommand = (args: string[]) => Promise<unknown>;
44
+ interface RedisStoreOptions {
45
+ command: RedisCommand;
46
+ /** Prefix for every key; default `vinaverse:`. */
47
+ prefix?: string;
48
+ }
49
+ declare function redisNonceStore(options: RedisStoreOptions): NonceStore;
50
+ declare function redisIdempotencyStore(options: RedisStoreOptions): IdempotencyStore;
51
+
52
+ export { type DynamoCommand, type DynamoSend, type DynamoStoreOptions, POSTGRES_TABLES, type PostgresQuery, type PostgresStoreOptions, type RedisCommand, type RedisStoreOptions, dynamoIdempotencyStore, dynamoNonceStore, postgresIdempotencyStore, postgresNonceStore, postgresTables, redisIdempotencyStore, redisNonceStore };