@absurd-sqlite/bun-worker 0.3.0-alpha.0 → 0.3.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.
@@ -1 +1 @@
1
- {"version":3,"file":"sqlite.d.ts","sourceRoot":"","sources":["../src/sqlite.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAEtC,OAAO,KAAK,EAGV,uBAAuB,EAIxB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAEtD,qBAAa,mBAAoB,SAAQ,gBAAgB;gBAC3C,EAAE,EAAE,QAAQ,EAAE,OAAO,GAAE,uBAA4B;CAIhE"}
1
+ {"version":3,"file":"sqlite.d.ts","sourceRoot":"","sources":["../src/sqlite.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAEtC,OAAO,KAAK,EAGV,uBAAuB,EAIxB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAE,gBAAgB,EAAY,MAAM,oBAAoB,CAAC;AAEhE,qBAAa,mBAAoB,SAAQ,gBAAgB;gBAC3C,EAAE,EAAE,QAAQ,EAAE,OAAO,GAAE,uBAA4B;CAIhE"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@absurd-sqlite/bun-worker",
3
- "version": "0.3.0-alpha.0",
3
+ "version": "0.3.0-alpha.1",
4
4
  "description": "Bun worker utilities for Absurd-SQLite",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -39,7 +39,8 @@
39
39
  "homepage": "https://github.com/b4fun/absurd-sqlite#readme",
40
40
  "dependencies": {
41
41
  "@absurd-sqlite/sdk": "next",
42
- "cac": "^6.7.14"
42
+ "cac": "^6.7.14",
43
+ "temporal-polyfill": "^0.3.0"
43
44
  },
44
45
  "devDependencies": {
45
46
  "bun-types": "^1.3.6",
package/src/index.ts CHANGED
@@ -14,6 +14,7 @@ export type { WorkerOptions } from "@absurd-sqlite/sdk";
14
14
  export {
15
15
  downloadExtension,
16
16
  type DownloadExtensionOptions,
17
+ Temporal,
17
18
  } from "@absurd-sqlite/sdk";
18
19
 
19
20
  /**
package/src/sqlite.ts CHANGED
@@ -8,7 +8,7 @@ import type {
8
8
  SQLiteStatement,
9
9
  SQLiteValueCodec,
10
10
  } from "@absurd-sqlite/sdk";
11
- import { SQLiteConnection } from "@absurd-sqlite/sdk";
11
+ import { SQLiteConnection, Temporal } from "@absurd-sqlite/sdk";
12
12
 
13
13
  export class BunSqliteConnection extends SQLiteConnection {
14
14
  constructor(db: Database, options: SQLiteConnectionOptions = {}) {
@@ -115,6 +115,19 @@ function decodeRowValues<R extends object = any>(args: {
115
115
  }) => unknown;
116
116
  }): R {
117
117
  const decodedRow: any = {};
118
+ if (args.columns && args.decodeColumn) {
119
+ for (const column of args.columns) {
120
+ const columnName = column.name;
121
+ const rawValue = args.row[columnName];
122
+ decodedRow[columnName] = args.decodeColumn({
123
+ value: rawValue,
124
+ columnName,
125
+ columnType: column.type,
126
+ });
127
+ }
128
+ return decodedRow as R;
129
+ }
130
+
118
131
  for (const [columnName, rawValue] of Object.entries(args.row)) {
119
132
  decodedRow[columnName] = decodeColumnValue({
120
133
  value: rawValue,
@@ -132,21 +145,22 @@ function decodeColumnValue<V = any>(args: {
132
145
  columnType: string | null;
133
146
  verbose?: (...args: any[]) => void;
134
147
  }): V | null {
135
- const { value, columnName } = args;
148
+ const { value, columnName, columnType } = args;
136
149
  if (value === null || value === undefined) {
137
150
  return null;
138
151
  }
139
152
 
140
- if (isTimestampColumn(columnName)) {
141
- if (typeof value === "number") {
142
- return new Date(value) as V;
143
- }
153
+ const isDateTime = columnType === "datetime" || isTimestampColumn(columnName);
154
+ if (isDateTime) {
144
155
  if (typeof value === "string") {
145
- const parsed = Date.parse(value);
146
- if (!Number.isNaN(parsed)) {
147
- return new Date(parsed) as V;
148
- }
156
+ return Temporal.Instant.from(value) as V;
157
+ }
158
+ if (typeof value === "number") {
159
+ return Temporal.Instant.fromEpochMilliseconds(value) as V;
149
160
  }
161
+ throw new Error(
162
+ `Expected datetime column ${columnName} to be a string or number, got ${typeof value}`
163
+ );
150
164
  }
151
165
 
152
166
  if (typeof value === "string") {
@@ -171,6 +185,12 @@ function tryDecodeJson<V = any>(value: string): V | null {
171
185
  }
172
186
 
173
187
  function encodeColumnValue(value: SQLiteBindValue): SQLiteBindValue {
188
+ if (value instanceof Temporal.Instant) {
189
+ return value.toString();
190
+ }
191
+ if (value instanceof Temporal.Duration) {
192
+ return value.toString();
193
+ }
174
194
  if (value instanceof Date) {
175
195
  return value.toISOString();
176
196
  }
@@ -7,7 +7,7 @@ import {
7
7
  jest,
8
8
  } from "bun:test";
9
9
  import assert from "node:assert/strict";
10
- import type { Absurd } from "@absurd-sqlite/sdk";
10
+ import { Temporal, type Absurd } from "@absurd-sqlite/sdk";
11
11
  import { createTestAbsurd, randomName, type TestContext } from "./setup";
12
12
  import { EventEmitter, once } from "events";
13
13
  import { waitFor } from "./wait-for";
@@ -171,7 +171,7 @@ describe("Basic SDK Operations", () => {
171
171
  const scheduledRun = await ctx.getRun(runID);
172
172
  expect(scheduledRun).toMatchObject({
173
173
  state: "sleeping",
174
- available_at: wakeAt,
174
+ available_at: Temporal.Instant.fromEpochMilliseconds(wakeAt.getTime()),
175
175
  wake_event: null,
176
176
  });
177
177
 
@@ -189,7 +189,7 @@ describe("Basic SDK Operations", () => {
189
189
  const resumedRun = await ctx.getRun(runID);
190
190
  expect(resumedRun).toMatchObject({
191
191
  state: "running",
192
- started_at: wakeAt,
192
+ started_at: Temporal.Instant.fromEpochMilliseconds(wakeAt.getTime()),
193
193
  });
194
194
  });
195
195
 
@@ -216,7 +216,9 @@ describe("Basic SDK Operations", () => {
216
216
  expect(running).toMatchObject({
217
217
  state: "running",
218
218
  claimed_by: "worker-a",
219
- claim_expires_at: new Date(baseTime.getTime() + 30 * 1000),
219
+ claim_expires_at: Temporal.Instant.fromEpochMilliseconds(
220
+ baseTime.getTime() + 30 * 1000,
221
+ ),
220
222
  });
221
223
 
222
224
  await ctx.setFakeNow(new Date(baseTime.getTime() + 5 * 60 * 1000));
@@ -275,7 +277,9 @@ describe("Basic SDK Operations", () => {
275
277
  const runRow = await ctx.getRun(runID);
276
278
  expect(runRow).toMatchObject({
277
279
  claimed_by: "worker-clean",
278
- claim_expires_at: new Date(base.getTime() + 60 * 1000),
280
+ claim_expires_at: Temporal.Instant.fromEpochMilliseconds(
281
+ base.getTime() + 60 * 1000,
282
+ ),
279
283
  });
280
284
 
281
285
  const beforeTTL = new Date(finishTime.getTime() + 30 * 60 * 1000);
@@ -482,7 +486,9 @@ describe("Basic SDK Operations", () => {
482
486
 
483
487
  const getExpiresAt = async (runID: string) => {
484
488
  const run = await ctx.getRun(runID);
485
- return run?.claim_expires_at ? run.claim_expires_at.getTime() : 0;
489
+ return run?.claim_expires_at
490
+ ? run.claim_expires_at.epochMilliseconds
491
+ : 0;
486
492
  };
487
493
 
488
494
  absurd.workBatch("test-worker", claimTimeout);
@@ -1,5 +1,5 @@
1
1
  import { describe, test, expect, beforeAll, afterEach } from "bun:test";
2
- import type { Absurd } from "@absurd-sqlite/sdk";
2
+ import { Temporal, type Absurd } from "@absurd-sqlite/sdk";
3
3
  import { createTestAbsurd, randomName, type TestContext } from "./setup";
4
4
  import { TimeoutError } from "@absurd-sqlite/sdk";
5
5
 
@@ -21,7 +21,9 @@ describe("Event system", () => {
21
21
  const eventName = randomName("test_event");
22
22
 
23
23
  absurd.registerTask({ name: "waiter" }, async (params, ctx) => {
24
- const payload = await ctx.awaitEvent(eventName, { timeout: 60 });
24
+ const payload = await ctx.awaitEvent(eventName, {
25
+ timeout: Temporal.Duration.from({ seconds: 60 }),
26
+ });
25
27
  return { received: payload };
26
28
  });
27
29
 
@@ -86,7 +88,7 @@ describe("Event system", () => {
86
88
  absurd.registerTask({ name: "timeout-waiter" }, async (_params, ctx) => {
87
89
  try {
88
90
  const payload = await ctx.awaitEvent(eventName, {
89
- timeout: timeoutSeconds,
91
+ timeout: Temporal.Duration.from({ seconds: timeoutSeconds }),
90
92
  });
91
93
  return { timedOut: false, result: payload };
92
94
  } catch (err) {
@@ -109,7 +111,9 @@ describe("Event system", () => {
109
111
  wake_event: eventName,
110
112
  });
111
113
  const expectedWake = new Date(baseTime.getTime() + timeoutSeconds * 1000);
112
- expect(sleepingRun?.available_at?.getTime()).toBe(expectedWake.getTime());
114
+ expect(sleepingRun?.available_at?.epochMilliseconds).toBe(
115
+ expectedWake.getTime(),
116
+ );
113
117
 
114
118
  await ctx.setFakeNow(new Date(expectedWake.getTime() + 1000));
115
119
  await absurd.workBatch("worker1", 120, 1);
@@ -170,13 +174,16 @@ describe("Event system", () => {
170
174
 
171
175
  absurd.registerTask({ name: "timeout-no-loop" }, async (_params, ctx) => {
172
176
  try {
173
- await ctx.awaitEvent(eventName, { stepName: "wait", timeout: 10 });
177
+ await ctx.awaitEvent(eventName, {
178
+ stepName: "wait",
179
+ timeout: Temporal.Duration.from({ seconds: 10 }),
180
+ });
174
181
  return { stage: "unexpected" };
175
182
  } catch (err) {
176
183
  if (err instanceof TimeoutError) {
177
184
  const payload = await ctx.awaitEvent(eventName, {
178
185
  stepName: "wait",
179
- timeout: 10,
186
+ timeout: Temporal.Duration.from({ seconds: 10 }),
180
187
  });
181
188
  return { stage: "resumed", payload };
182
189
  }
@@ -1,5 +1,5 @@
1
1
  import { describe, test, expect, beforeAll, afterEach } from "bun:test";
2
- import type { Absurd } from "@absurd-sqlite/sdk";
2
+ import { Temporal, type Absurd } from "@absurd-sqlite/sdk";
3
3
  import { createTestAbsurd, randomName, type TestContext } from "./setup";
4
4
 
5
5
  describe("Retry and cancellation", () => {
@@ -159,7 +159,7 @@ describe("Retry and cancellation", () => {
159
159
  const { taskID } = await absurd.spawn("duration-cancel", undefined, {
160
160
  maxAttempts: 4,
161
161
  retryStrategy: { kind: "fixed", baseSeconds: 30 },
162
- cancellation: { maxDuration: 90 },
162
+ cancellation: { maxDuration: Temporal.Duration.from({ seconds: 90 }) },
163
163
  });
164
164
 
165
165
  await absurd.workBatch("worker1", 60, 1);
@@ -185,7 +185,7 @@ describe("Retry and cancellation", () => {
185
185
  });
186
186
 
187
187
  const { taskID } = await absurd.spawn("delay-cancel", undefined, {
188
- cancellation: { maxDelay: 60 },
188
+ cancellation: { maxDelay: Temporal.Duration.from({ seconds: 60 }) },
189
189
  });
190
190
 
191
191
  await ctx.setFakeNow(new Date(baseTime.getTime() + 61 * 1000));
@@ -312,8 +312,8 @@ describe("Retry and cancellation", () => {
312
312
 
313
313
  await absurd.cancelTask(taskID);
314
314
  const second = await ctx.getTask(taskID);
315
- expect(second?.cancelled_at?.getTime()).toBe(
316
- first?.cancelled_at?.getTime(),
315
+ expect(second?.cancelled_at?.epochMilliseconds).toBe(
316
+ first?.cancelled_at?.epochMilliseconds,
317
317
  );
318
318
  });
319
319
 
package/test/setup.ts CHANGED
@@ -6,6 +6,7 @@ import { join } from "node:path";
6
6
  import { fileURLToPath } from "node:url";
7
7
  import {
8
8
  Absurd,
9
+ Temporal,
9
10
  type AbsurdHooks,
10
11
  type JsonValue,
11
12
  } from "@absurd-sqlite/sdk";
@@ -23,8 +24,8 @@ export interface TaskRow {
23
24
  retry_strategy: JsonValue | null;
24
25
  max_attempts: number | null;
25
26
  cancellation: JsonValue | null;
26
- enqueue_at: Date;
27
- first_started_at: Date | null;
27
+ enqueue_at: Temporal.Instant;
28
+ first_started_at: Temporal.Instant | null;
28
29
  state:
29
30
  | "pending"
30
31
  | "running"
@@ -35,7 +36,7 @@ export interface TaskRow {
35
36
  attempts: number;
36
37
  last_attempt_run: string | null;
37
38
  completed_payload: JsonValue | null;
38
- cancelled_at: Date | null;
39
+ cancelled_at: Temporal.Instant | null;
39
40
  }
40
41
 
41
42
  export interface RunRow {
@@ -50,16 +51,16 @@ export interface RunRow {
50
51
  | "failed"
51
52
  | "cancelled";
52
53
  claimed_by: string | null;
53
- claim_expires_at: Date | null;
54
- available_at: Date;
54
+ claim_expires_at: Temporal.Instant | null;
55
+ available_at: Temporal.Instant;
55
56
  wake_event: string | null;
56
57
  event_payload: JsonValue | null;
57
- started_at: Date | null;
58
- completed_at: Date | null;
59
- failed_at: Date | null;
58
+ started_at: Temporal.Instant | null;
59
+ completed_at: Temporal.Instant | null;
60
+ failed_at: Temporal.Instant | null;
60
61
  result: JsonValue | null;
61
62
  failure_reason: JsonValue | null;
62
- created_at: Date;
63
+ created_at: Temporal.Instant;
63
64
  }
64
65
 
65
66
  interface SqliteFixture {
@@ -5,6 +5,7 @@ import { join } from "node:path";
5
5
  import { tmpdir } from "node:os";
6
6
 
7
7
  import { BunSqliteConnection } from "../src/sqlite";
8
+ import { Temporal } from "@absurd-sqlite/sdk";
8
9
 
9
10
  describe("BunSqliteConnection", () => {
10
11
  it("rewrites postgres-style params and absurd schema names", async () => {
@@ -75,7 +76,7 @@ describe("BunSqliteConnection", () => {
75
76
  db.close();
76
77
  });
77
78
 
78
- it("decodes datetime columns into Date objects", async () => {
79
+ it("decodes datetime columns into Temporal.Instant objects", async () => {
79
80
  const db = new Database(":memory:");
80
81
  const conn = new BunSqliteConnection(db);
81
82
  const now = Date.now();
@@ -83,12 +84,12 @@ describe("BunSqliteConnection", () => {
83
84
  await conn.exec("CREATE TABLE t_date (created_at DATETIME)");
84
85
  await conn.exec("INSERT INTO t_date (created_at) VALUES ($1)", [now]);
85
86
 
86
- const { rows } = await conn.query<{ created_at: Date }>(
87
+ const { rows } = await conn.query<{ created_at: Temporal.Instant }>(
87
88
  "SELECT created_at FROM t_date"
88
89
  );
89
90
 
90
- expect(rows[0]?.created_at).toBeInstanceOf(Date);
91
- expect(rows[0]?.created_at.getTime()).toBe(now);
91
+ expect(rows[0]?.created_at).toBeInstanceOf(Temporal.Instant);
92
+ expect(rows[0]?.created_at.epochMilliseconds).toBe(now);
92
93
  db.close();
93
94
  });
94
95
 
package/test/step.test.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { describe, test, expect, beforeAll, afterEach, jest } from "bun:test";
2
- import type { Absurd } from "@absurd-sqlite/sdk";
2
+ import { Temporal, type Absurd } from "@absurd-sqlite/sdk";
3
3
  import { createTestAbsurd, randomName, type TestContext } from "./setup";
4
4
 
5
5
  describe("Step functionality", () => {
@@ -193,7 +193,10 @@ describe("Step functionality", () => {
193
193
 
194
194
  const durationSeconds = 60;
195
195
  absurd.registerTask({ name: "sleep-for" }, async (_params, ctx) => {
196
- await ctx.sleepFor("wait-for", durationSeconds);
196
+ await ctx.sleepFor(
197
+ "wait-for",
198
+ Temporal.Duration.from({ seconds: durationSeconds }),
199
+ );
197
200
  return { resumed: true };
198
201
  });
199
202
 
@@ -205,7 +208,9 @@ describe("Step functionality", () => {
205
208
  state: "sleeping",
206
209
  });
207
210
  const wakeTime = new Date(base.getTime() + durationSeconds * 1000);
208
- expect(sleepingRun?.available_at?.getTime()).toBe(wakeTime.getTime());
211
+ expect(sleepingRun?.available_at?.epochMilliseconds).toBe(
212
+ wakeTime.getTime(),
213
+ );
209
214
 
210
215
  const resumeTime = new Date(wakeTime.getTime() + 5 * 1000);
211
216
  jest.setSystemTime(resumeTime);
@@ -225,11 +230,14 @@ describe("Step functionality", () => {
225
230
  await ctx.setFakeNow(base);
226
231
 
227
232
  const wakeTime = new Date(base.getTime() + 5 * 60 * 1000);
233
+ const wakeInstant = Temporal.Instant.fromEpochMilliseconds(
234
+ wakeTime.getTime(),
235
+ );
228
236
  let executions = 0;
229
237
 
230
238
  absurd.registerTask({ name: "sleep-until" }, async (_params, ctx) => {
231
239
  executions++;
232
- await ctx.sleepUntil("sleep-step", wakeTime);
240
+ await ctx.sleepUntil("sleep-step", wakeInstant);
233
241
  return { executions };
234
242
  });
235
243
 
@@ -240,7 +248,7 @@ describe("Step functionality", () => {
240
248
  expect(checkpointRow).toMatchObject({
241
249
  checkpoint_name: "sleep-step",
242
250
  owner_run_id: runID,
243
- state: wakeTime.toISOString(),
251
+ state: wakeInstant.toString(),
244
252
  });
245
253
 
246
254
  const sleepingRun = await ctx.getRun(runID);