@lunora/testing 1.0.0-alpha.37 → 1.0.0-alpha.39

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/dist/index.mjs CHANGED
@@ -1,2 +1,2 @@
1
- export { lunoraTest } from './packem_shared/lunoraTest-DO9gV_fa.mjs';
1
+ export { lunoraTest } from './packem_shared/lunoraTest-C0DZz2W9.mjs';
2
2
  export { extractLink, listCapturedMail, waitForMail } from '@lunora/mail/testing';
@@ -2,8 +2,8 @@ import { runShardMigrations, createShardCtxDb } from '@lunora/do';
2
2
  import { LunoraError } from '@lunora/errors';
3
3
  import { DatabaseSync } from 'node:sqlite';
4
4
 
5
- const createFakeScheduler = (getDispatch, getMutationContext, getFunctionRegistry) => {
6
- let nowMs = Date.now();
5
+ const createFakeScheduler = (getDispatch, getMutationContext, getFunctionRegistry, now) => {
6
+ let nowMs = now;
7
7
  let nextId = 1;
8
8
  const pending = /* @__PURE__ */ new Map();
9
9
  const recordedFailures = [];
@@ -58,7 +58,12 @@ const createFakeScheduler = (getDispatch, getMutationContext, getFunctionRegistr
58
58
  const executeDue = async (cutoff) => {
59
59
  const due = [...pending.values()].filter((j) => j.scheduledFor <= cutoff).toSorted((a, b) => a.scheduledFor - b.scheduledFor);
60
60
  const failed = [];
61
+ let executed = 0;
61
62
  for (const job of due) {
63
+ if (!pending.has(job.id)) {
64
+ continue;
65
+ }
66
+ executed += 1;
62
67
  try {
63
68
  await dispatchJob(job);
64
69
  } catch (error) {
@@ -67,7 +72,7 @@ const createFakeScheduler = (getDispatch, getMutationContext, getFunctionRegistr
67
72
  recordedFailures.push(failure);
68
73
  }
69
74
  }
70
- return { executed: due.length, failed };
75
+ return { executed, failed };
71
76
  };
72
77
  const runSweep = async (cutoff, options) => {
73
78
  const { executed, failed } = await executeDue(cutoff);
@@ -155,8 +160,9 @@ const noopLog = {
155
160
  const buildSubscribe = (runRegistered, queryContext, mutationListeners) => {
156
161
  const factory = (referenceOrInline, args) => {
157
162
  let done = false;
158
- let pendingResolve;
163
+ const pendingWaiters = [];
159
164
  let pendingResult;
165
+ let pendingError;
160
166
  let latestSeq = 0;
161
167
  let appliedSeq = 0;
162
168
  const runQuery = () => {
@@ -171,25 +177,46 @@ const buildSubscribe = (runRegistered, queryContext, mutationListeners) => {
171
177
  }
172
178
  appliedSeq = seq;
173
179
  const iterResult = { done: false, value };
174
- if (pendingResolve === void 0) {
180
+ if (pendingWaiters.length === 0) {
175
181
  pendingResult = iterResult;
182
+ pendingError = void 0;
176
183
  } else {
177
- const resolve = pendingResolve;
178
- pendingResolve = void 0;
179
184
  pendingResult = void 0;
180
- resolve(iterResult);
185
+ pendingError = void 0;
186
+ for (const waiter of pendingWaiters.splice(0)) {
187
+ waiter.resolve(iterResult);
188
+ }
189
+ }
190
+ };
191
+ const emitError = (seq, error) => {
192
+ if (seq < appliedSeq) {
193
+ return;
194
+ }
195
+ appliedSeq = seq;
196
+ if (pendingWaiters.length === 0) {
197
+ pendingError = { error };
198
+ pendingResult = void 0;
199
+ } else {
200
+ pendingResult = void 0;
201
+ pendingError = void 0;
202
+ for (const waiter of pendingWaiters.splice(0)) {
203
+ waiter.reject(error);
204
+ }
181
205
  }
182
206
  };
183
207
  const emitAt = (seq) => (value) => {
184
208
  emit(seq, value);
185
209
  };
210
+ const emitErrorAt = (seq) => (error) => {
211
+ emitError(seq, error);
212
+ };
186
213
  const listener = () => {
187
214
  if (done) {
188
215
  return;
189
216
  }
190
217
  latestSeq += 1;
191
218
  const seq = latestSeq;
192
- runQuery().then(emitAt(seq)).catch(() => void 0);
219
+ runQuery().then(emitAt(seq)).catch(emitErrorAt(seq));
193
220
  };
194
221
  mutationListeners.add(listener);
195
222
  const iterator = {
@@ -200,17 +227,29 @@ const buildSubscribe = (runRegistered, queryContext, mutationListeners) => {
200
227
  if (done) {
201
228
  return Promise.resolve({ done: true, value: void 0 });
202
229
  }
203
- if (pendingResult !== void 0 && appliedSeq === latestSeq) {
204
- const result = pendingResult;
205
- pendingResult = void 0;
206
- return Promise.resolve(result);
230
+ if (appliedSeq === latestSeq) {
231
+ if (pendingError !== void 0) {
232
+ const { error } = pendingError;
233
+ pendingError = void 0;
234
+ return Promise.reject(error);
235
+ }
236
+ if (pendingResult !== void 0) {
237
+ const result = pendingResult;
238
+ pendingResult = void 0;
239
+ return Promise.resolve(result);
240
+ }
207
241
  }
208
242
  if (appliedSeq < latestSeq) {
209
- return new Promise((resolve) => {
210
- pendingResolve = resolve;
243
+ return new Promise((resolve, reject) => {
244
+ pendingWaiters.push({ reject, resolve });
211
245
  });
212
246
  }
213
247
  return runQuery().then((value) => {
248
+ if (pendingError !== void 0) {
249
+ const { error } = pendingError;
250
+ pendingError = void 0;
251
+ throw error;
252
+ }
214
253
  if (pendingResult !== void 0) {
215
254
  const result = pendingResult;
216
255
  pendingResult = void 0;
@@ -222,15 +261,13 @@ const buildSubscribe = (runRegistered, queryContext, mutationListeners) => {
222
261
  return: () => {
223
262
  done = true;
224
263
  mutationListeners.delete(listener);
225
- if (pendingResolve !== void 0) {
226
- const resolve = pendingResolve;
227
- pendingResolve = void 0;
228
- resolve({ done: true, value: void 0 });
264
+ for (const waiter of pendingWaiters.splice(0)) {
265
+ waiter.resolve({ done: true, value: void 0 });
229
266
  }
230
267
  return Promise.resolve({ done: true, value: void 0 });
231
268
  }
232
269
  };
233
- runQuery().then(emitAt(0)).catch(() => void 0);
270
+ runQuery().then(emitAt(0)).catch(emitErrorAt(0));
234
271
  return iterator;
235
272
  };
236
273
  return factory;
@@ -244,26 +281,28 @@ const lunoraTest = (schema, options) => {
244
281
  const runner = sql.exec;
245
282
  runner.call(sql, statement);
246
283
  };
247
- let transactionDepth = 0;
248
- const runInMutationTransaction = async (function_) => {
249
- if (transactionDepth > 0) {
250
- return function_();
251
- }
252
- transactionDepth = 1;
253
- execStatement("BEGIN");
254
- try {
255
- const result = await function_();
256
- execStatement("COMMIT");
257
- return result;
258
- } catch (error) {
284
+ let mutationQueue = Promise.resolve();
285
+ const runInMutationTransaction = (function_) => {
286
+ const runTransaction = async () => {
287
+ execStatement("BEGIN");
259
288
  try {
260
- execStatement("ROLLBACK");
261
- } catch {
289
+ const result2 = await function_();
290
+ execStatement("COMMIT");
291
+ return result2;
292
+ } catch (error) {
293
+ try {
294
+ execStatement("ROLLBACK");
295
+ } catch {
296
+ }
297
+ throw error;
262
298
  }
263
- throw error;
264
- } finally {
265
- transactionDepth = 0;
266
- }
299
+ };
300
+ const result = mutationQueue.then(runTransaction);
301
+ mutationQueue = result.then(
302
+ () => void 0,
303
+ () => void 0
304
+ );
305
+ return result;
267
306
  };
268
307
  let closed = false;
269
308
  const closeDatabase = () => {
@@ -284,6 +323,7 @@ const lunoraTest = (schema, options) => {
284
323
  };
285
324
  let scheduledDispatchRef;
286
325
  let mutationContextRef;
326
+ const harnessNow = options?.now ?? Date.now();
287
327
  const { controls: schedulerControls, scheduler: fakeScheduler } = createFakeScheduler(
288
328
  () => {
289
329
  if (scheduledDispatchRef === void 0) {
@@ -303,9 +343,9 @@ const lunoraTest = (schema, options) => {
303
343
  }
304
344
  return mutationContextRef;
305
345
  },
306
- () => functionRegistryMap
346
+ () => functionRegistryMap,
347
+ harnessNow
307
348
  );
308
- const harnessNow = options?.now ?? Date.now();
309
349
  const makeHarness = (identity) => {
310
350
  const auth = {
311
351
  // eslint-disable-next-line unicorn/no-null -- AuthState.getIdentity's anonymous sentinel is `null` (mirrors a decoded JWT being absent)
@@ -370,7 +410,7 @@ const lunoraTest = (schema, options) => {
370
410
  if (!allowInternal && registeredFunctionVisibility(reference) === "internal") {
371
411
  throw new LunoraError(
372
412
  "INTERNAL",
373
- `"${expected}" is an internal function — it is unreachable from the external RPC boundary in production. Call it through ctx.run${expected.charAt(0).toUpperCase()}${expected.slice(1)} from another function instead.`
413
+ `This ${expected} is an internal function — it is unreachable from the external RPC boundary in production. Call it through ctx.run${expected.charAt(0).toUpperCase()}${expected.slice(1)} from another function instead.`
374
414
  );
375
415
  }
376
416
  return Promise.resolve(reference.handler(context, args ?? {}));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lunora/testing",
3
- "version": "1.0.0-alpha.37",
3
+ "version": "1.0.0-alpha.39",
4
4
  "description": "Testing toolkit for Lunora: an in-memory harness for queries, mutations, and actions",
5
5
  "keywords": [
6
6
  "cloudflare",
@@ -46,10 +46,10 @@
46
46
  "access": "public"
47
47
  },
48
48
  "dependencies": {
49
- "@lunora/do": "1.0.0-alpha.26",
50
- "@lunora/errors": "1.0.0-alpha.3",
51
- "@lunora/mail": "1.0.0-alpha.13",
52
- "@lunora/server": "1.0.0-alpha.21"
49
+ "@lunora/do": "1.0.0-alpha.27",
50
+ "@lunora/errors": "1.0.0-alpha.4",
51
+ "@lunora/mail": "1.0.0-alpha.14",
52
+ "@lunora/server": "1.0.0-alpha.23"
53
53
  },
54
54
  "engines": {
55
55
  "node": "^22.15.0 || >=24.11.0"