@superfunctions/queue 0.0.1 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,29 @@
1
+ import type { EnqueueOptions, FailOptions, LeaseFence } from './types.js';
2
+ export declare class QueueError extends Error {
3
+ readonly code: string;
4
+ constructor(code: string);
5
+ }
6
+ export declare function normalizeQueueName(queueName: string): string;
7
+ export interface NormalizedEnqueueOptions {
8
+ delayMs: number;
9
+ dedupeKey: string | null;
10
+ }
11
+ export declare function normalizeEnqueueOptions(options: EnqueueOptions | undefined): NormalizedEnqueueOptions;
12
+ export declare function normalizeLeaseMs(leaseMs: unknown): number;
13
+ export declare function normalizeLimit(limit: unknown): number;
14
+ /** Job ids are positive decimal integers rendered as strings. */
15
+ export declare function normalizeJobId(id: unknown): number;
16
+ export declare function normalizeFence(fence: LeaseFence | undefined): number | null;
17
+ export type NormalizedFailOptions = {
18
+ kind: 'retry';
19
+ retryAt: number;
20
+ reason: string | null;
21
+ attempt: number | null;
22
+ } | {
23
+ kind: 'dead';
24
+ reason: string;
25
+ attempt: number | null;
26
+ };
27
+ export declare function normalizeFailOptions(options: FailOptions): NormalizedFailOptions;
28
+ export declare function readClock(now: () => number): number;
29
+ //# sourceMappingURL=validation.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validation.d.ts","sourceRoot":"","sources":["../src/validation.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAE1E,qBAAa,UAAW,SAAQ,KAAK;IACnC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;gBAEV,IAAI,EAAE,MAAM;CAKzB;AAQD,wBAAgB,kBAAkB,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAM5D;AAED,MAAM,WAAW,wBAAwB;IACvC,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;CAC1B;AAED,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,cAAc,GAAG,SAAS,GAAG,wBAAwB,CAarG;AAED,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,OAAO,GAAG,MAAM,CAKzD;AAED,wBAAgB,cAAc,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAKrD;AAED,iEAAiE;AACjE,wBAAgB,cAAc,CAAC,EAAE,EAAE,OAAO,GAAG,MAAM,CASlD;AAED,wBAAgB,cAAc,CAAC,KAAK,EAAE,UAAU,GAAG,SAAS,GAAG,MAAM,GAAG,IAAI,CAS3E;AAED,MAAM,MAAM,qBAAqB,GAC7B;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,GACjF;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,CAAC;AAE7D,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,WAAW,GAAG,qBAAqB,CAyBhF;AAED,wBAAgB,SAAS,CAAC,GAAG,EAAE,MAAM,MAAM,GAAG,MAAM,CAMnD"}
@@ -0,0 +1,99 @@
1
+ export class QueueError extends Error {
2
+ code;
3
+ constructor(code) {
4
+ super(code);
5
+ this.name = 'QueueError';
6
+ this.code = code;
7
+ }
8
+ }
9
+ const MAX_DEDUPE_KEY_LENGTH = 512;
10
+ /** Upper bound for `delayMs` and `leaseMs` (100 years), so `now + duration` stays a valid Date. */
11
+ const MAX_DURATION_MS = 100 * 365 * 24 * 60 * 60 * 1000;
12
+ /** The largest epoch-millisecond value a Date can hold. */
13
+ const MAX_TIMESTAMP_MS = 8.64e15;
14
+ export function normalizeQueueName(queueName) {
15
+ const normalized = typeof queueName === 'string' ? queueName.trim() : '';
16
+ if (!normalized || normalized.includes('/') || normalized.includes('\\') || normalized.includes('..')) {
17
+ throw new QueueError('FLOWFN_QUEUE_NAME_INVALID');
18
+ }
19
+ return normalized;
20
+ }
21
+ export function normalizeEnqueueOptions(options) {
22
+ const delayMs = options?.delayMs ?? 0;
23
+ if (!Number.isSafeInteger(delayMs) || delayMs < 0 || delayMs > MAX_DURATION_MS) {
24
+ throw new QueueError('FLOWFN_QUEUE_DELAY_INVALID');
25
+ }
26
+ const dedupeKey = options?.dedupeKey;
27
+ if (dedupeKey !== undefined &&
28
+ (typeof dedupeKey !== 'string' || dedupeKey.length === 0 || dedupeKey.length > MAX_DEDUPE_KEY_LENGTH)) {
29
+ throw new QueueError('FLOWFN_QUEUE_DEDUPE_KEY_INVALID');
30
+ }
31
+ return { delayMs, dedupeKey: dedupeKey ?? null };
32
+ }
33
+ export function normalizeLeaseMs(leaseMs) {
34
+ if (typeof leaseMs !== 'number' || !Number.isSafeInteger(leaseMs) || leaseMs <= 0 || leaseMs > MAX_DURATION_MS) {
35
+ throw new QueueError('FLOWFN_QUEUE_LEASE_INVALID');
36
+ }
37
+ return leaseMs;
38
+ }
39
+ export function normalizeLimit(limit) {
40
+ if (typeof limit !== 'number' || !Number.isSafeInteger(limit) || limit < 0) {
41
+ throw new QueueError('FLOWFN_QUEUE_LIMIT_INVALID');
42
+ }
43
+ return limit;
44
+ }
45
+ /** Job ids are positive decimal integers rendered as strings. */
46
+ export function normalizeJobId(id) {
47
+ if (typeof id !== 'string' || !/^[1-9]\d*$/.test(id)) {
48
+ throw new QueueError('FLOWFN_QUEUE_JOB_ID_INVALID');
49
+ }
50
+ const value = Number(id);
51
+ if (!Number.isSafeInteger(value)) {
52
+ throw new QueueError('FLOWFN_QUEUE_JOB_ID_INVALID');
53
+ }
54
+ return value;
55
+ }
56
+ export function normalizeFence(fence) {
57
+ const attempt = fence?.attempt;
58
+ if (attempt === undefined) {
59
+ return null;
60
+ }
61
+ if (typeof attempt !== 'number' || !Number.isSafeInteger(attempt) || attempt < 1) {
62
+ throw new QueueError('FLOWFN_QUEUE_ATTEMPT_INVALID');
63
+ }
64
+ return attempt;
65
+ }
66
+ export function normalizeFailOptions(options) {
67
+ if (!options || typeof options !== 'object') {
68
+ throw new QueueError('FLOWFN_QUEUE_FAIL_OPTIONS_INVALID');
69
+ }
70
+ const attempt = normalizeFence(options);
71
+ if ('deadLetter' in options && 'retryAt' in options) {
72
+ throw new QueueError('FLOWFN_QUEUE_FAIL_OPTIONS_INVALID');
73
+ }
74
+ if ('deadLetter' in options) {
75
+ if (options.deadLetter !== true || typeof options.reason !== 'string' || options.reason.length === 0) {
76
+ throw new QueueError('FLOWFN_QUEUE_FAIL_OPTIONS_INVALID');
77
+ }
78
+ return { kind: 'dead', reason: options.reason, attempt };
79
+ }
80
+ if (!('retryAt' in options)) {
81
+ throw new QueueError('FLOWFN_QUEUE_FAIL_OPTIONS_INVALID');
82
+ }
83
+ const retryAt = options.retryAt instanceof Date ? options.retryAt.getTime() : options.retryAt;
84
+ if (typeof retryAt !== 'number' || !Number.isSafeInteger(retryAt) || retryAt < 0 || retryAt > MAX_TIMESTAMP_MS) {
85
+ throw new QueueError('FLOWFN_QUEUE_RETRY_AT_INVALID');
86
+ }
87
+ if (options.reason !== undefined && typeof options.reason !== 'string') {
88
+ throw new QueueError('FLOWFN_QUEUE_FAIL_OPTIONS_INVALID');
89
+ }
90
+ return { kind: 'retry', retryAt, reason: options.reason ?? null, attempt };
91
+ }
92
+ export function readClock(now) {
93
+ const value = now();
94
+ if (!Number.isSafeInteger(value) || value < 0 || value > MAX_TIMESTAMP_MS - MAX_DURATION_MS) {
95
+ throw new QueueError('FLOWFN_QUEUE_CLOCK_INVALID');
96
+ }
97
+ return value;
98
+ }
99
+ //# sourceMappingURL=validation.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validation.js","sourceRoot":"","sources":["../src/validation.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,19 @@
1
+ CREATE TABLE IF NOT EXISTS "queue_jobs" (
2
+ id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
3
+ queue TEXT NOT NULL,
4
+ payload JSON NOT NULL,
5
+ state TEXT NOT NULL DEFAULT 'pending' CHECK (state IN ('pending', 'leased', 'dead')),
6
+ attempt INTEGER NOT NULL DEFAULT 0,
7
+ available_at BIGINT NOT NULL,
8
+ lease_until BIGINT,
9
+ dedupe_key TEXT,
10
+ last_error TEXT,
11
+ created_at BIGINT NOT NULL,
12
+ updated_at BIGINT NOT NULL
13
+ );
14
+
15
+ CREATE INDEX IF NOT EXISTS "queue_jobs_available_idx" ON "queue_jobs" (queue, state, available_at, id);
16
+
17
+ CREATE INDEX IF NOT EXISTS "queue_jobs_lease_idx" ON "queue_jobs" (queue, state, lease_until);
18
+
19
+ CREATE UNIQUE INDEX IF NOT EXISTS "queue_jobs_dedupe_idx" ON "queue_jobs" (queue, dedupe_key) WHERE dedupe_key IS NOT NULL AND state IN ('pending', 'leased');
@@ -0,0 +1,19 @@
1
+ CREATE TABLE IF NOT EXISTS "queue_jobs" (
2
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
3
+ queue TEXT NOT NULL,
4
+ payload TEXT NOT NULL,
5
+ state TEXT NOT NULL DEFAULT 'pending' CHECK (state IN ('pending', 'leased', 'dead')),
6
+ attempt INTEGER NOT NULL DEFAULT 0,
7
+ available_at INTEGER NOT NULL,
8
+ lease_until INTEGER,
9
+ dedupe_key TEXT,
10
+ last_error TEXT,
11
+ created_at INTEGER NOT NULL,
12
+ updated_at INTEGER NOT NULL
13
+ );
14
+
15
+ CREATE INDEX IF NOT EXISTS "queue_jobs_available_idx" ON "queue_jobs" (queue, state, available_at, id);
16
+
17
+ CREATE INDEX IF NOT EXISTS "queue_jobs_lease_idx" ON "queue_jobs" (queue, state, lease_until);
18
+
19
+ CREATE UNIQUE INDEX IF NOT EXISTS "queue_jobs_dedupe_idx" ON "queue_jobs" (queue, dedupe_key) WHERE dedupe_key IS NOT NULL AND state IN ('pending', 'leased');
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@superfunctions/queue",
3
- "version": "0.0.1",
4
- "description": "Shared queue adapters for Superfunctions",
3
+ "version": "0.2.0",
4
+ "description": "Queue adapters for Superfunctions: in-memory and durable SQL (SQLite, D1, Postgres)",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
7
7
  "types": "./dist/index.d.ts",
@@ -9,14 +9,17 @@
9
9
  ".": {
10
10
  "types": "./dist/index.d.ts",
11
11
  "default": "./dist/index.js"
12
- }
12
+ },
13
+ "./migrations/*": "./migrations/*"
13
14
  },
14
15
  "files": [
15
- "dist"
16
+ "dist",
17
+ "migrations",
18
+ "CHANGELOG.md"
16
19
  ],
17
20
  "scripts": {
18
21
  "build": "tsc",
19
- "test": "vitest",
22
+ "test": "vitest run",
20
23
  "test:watch": "vitest --watch",
21
24
  "lint": "echo 'lint not configured'",
22
25
  "typecheck": "tsc --noEmit",
@@ -24,6 +27,10 @@
24
27
  },
25
28
  "keywords": [
26
29
  "queue",
30
+ "jobs",
31
+ "sqlite",
32
+ "postgres",
33
+ "d1",
27
34
  "flowfn",
28
35
  "superfunctions"
29
36
  ],
@@ -31,15 +38,34 @@
31
38
  "license": "MIT",
32
39
  "repository": {
33
40
  "type": "git",
34
- "url": "git+https://github.com/21nCo/super-functions.git",
41
+ "url": "git+https://github.com/21nCo/superfunctions.git",
35
42
  "directory": "packages/queue"
36
43
  },
37
44
  "publishConfig": {
38
45
  "access": "public"
39
46
  },
47
+ "peerDependencies": {
48
+ "@superfunctions/db": "^0.3.1"
49
+ },
50
+ "peerDependenciesMeta": {
51
+ "@superfunctions/db": {
52
+ "optional": true
53
+ }
54
+ },
40
55
  "devDependencies": {
56
+ "@electric-sql/pglite": "^0.3.0",
57
+ "@superfunctions/db": "0.3.2",
58
+ "@types/better-sqlite3": "^7.6.13",
41
59
  "@types/node": "^22.0.0",
60
+ "@types/pg": "^8.20.0",
61
+ "better-sqlite3": "^12.5.0",
62
+ "drizzle-orm": "^0.35.0",
63
+ "esbuild": "^0.21.5",
64
+ "kysely": "^0.28.0",
65
+ "miniflare": "4.20251113.0",
66
+ "pg": "^8.20.0",
42
67
  "typescript": "^5.6.0",
43
68
  "vitest": "^3.2.4"
44
- }
69
+ },
70
+ "gitHead": "699d9d09932199d660333c57fe7bc646492ea3bf"
45
71
  }