@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-
|
|
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 =
|
|
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
|
|
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
|
-
|
|
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 (
|
|
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
|
-
|
|
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(()
|
|
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 (
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
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
|
-
|
|
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
|
-
|
|
226
|
-
|
|
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((
|
|
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
|
|
248
|
-
const runInMutationTransaction =
|
|
249
|
-
|
|
250
|
-
|
|
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
|
-
|
|
261
|
-
|
|
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
|
-
|
|
264
|
-
|
|
265
|
-
|
|
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
|
-
`
|
|
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.
|
|
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.
|
|
50
|
-
"@lunora/errors": "1.0.0-alpha.
|
|
51
|
-
"@lunora/mail": "1.0.0-alpha.
|
|
52
|
-
"@lunora/server": "1.0.0-alpha.
|
|
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"
|