@classytic/arc 2.25.0 → 2.30.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.
Files changed (92) hide show
  1. package/README.md +2 -0
  2. package/dist/{BaseController--M8EMzt0.mjs → BaseController-B5DwufbB.mjs} +15 -11
  3. package/dist/EventTransport-C6YA8xY9.mjs +103 -0
  4. package/dist/{HookSystem-C1vB_Gc9.mjs → HookSystem-WkIougo6.mjs} +15 -35
  5. package/dist/applyPermissionResult-Cf6zUCvb.mjs +257 -0
  6. package/dist/audit/index.d.mts +2 -2
  7. package/dist/audit/index.mjs +1 -1
  8. package/dist/auth/index.d.mts +2 -2
  9. package/dist/auth/index.mjs +1 -1
  10. package/dist/{buildHandler-Dtwd8fdz.mjs → buildHandler-BYS3Vkfz.mjs} +32 -6
  11. package/dist/cleanup/index.d.mts +313 -0
  12. package/dist/cleanup/index.mjs +1267 -0
  13. package/dist/cli/commands/describe.d.mts +1 -1
  14. package/dist/cli/commands/docs.mjs +1 -1
  15. package/dist/core/index.d.mts +2 -2
  16. package/dist/core/index.mjs +5 -3
  17. package/dist/core-poalJLvp.mjs +175 -0
  18. package/dist/{createActionRouter-B6GvAuAX.mjs → createActionRouter-BxuywVRB.mjs} +2 -2
  19. package/dist/{createAggregationRouter-wzwFa_dW.mjs → createAggregationRouter-BiY-lGdY.mjs} +7 -20
  20. package/dist/{preloadResources-DfyngIN9.mjs → createApp-CXrKFciH.mjs} +25 -76
  21. package/dist/defineAggregation-ufWjUwml.mjs +47 -0
  22. package/dist/{core-EUNu0u07.mjs → defineResource-CxWqDCAy.mjs} +36 -261
  23. package/dist/docs/index.d.mts +1 -1
  24. package/dist/docs/index.mjs +1 -1
  25. package/dist/{eventPlugin-DrQ_FesK.mjs → eventPlugin-D2NNR4py.mjs} +2 -103
  26. package/dist/events/index.d.mts +28 -2
  27. package/dist/events/index.mjs +24 -343
  28. package/dist/factory/index.d.mts +4 -3
  29. package/dist/factory/index.mjs +3 -1
  30. package/dist/{fastify-augmentation-dPNBVeIj.d.mts → fastify-augmentation-0WhPnUH7.d.mts} +3 -2
  31. package/dist/{applyPermissionResult-Tw4iXtlf.mjs → fields-CQ_7KQWw.mjs} +1 -97
  32. package/dist/hooks/index.d.mts +1 -1
  33. package/dist/hooks/index.mjs +1 -1
  34. package/dist/idempotency/index.mjs +1 -1
  35. package/dist/index-CCzVR-N3.d.mts +107 -0
  36. package/dist/{index-BMnbzvtO.d.mts → index-Ch16aAah.d.mts} +201 -21
  37. package/dist/{index-f4PTrk3B.d.mts → index-Cx7bro4R.d.mts} +4 -4
  38. package/dist/{index-DPRH0Cme.d.mts → index-Q5gzEIWY.d.mts} +97 -66
  39. package/dist/index.d.mts +6 -6
  40. package/dist/index.mjs +8 -6
  41. package/dist/integrations/index.d.mts +1 -1
  42. package/dist/integrations/mcp/index.d.mts +6 -6
  43. package/dist/integrations/mcp/index.mjs +1 -1
  44. package/dist/integrations/mcp/testing.d.mts +1 -1
  45. package/dist/integrations/mcp/testing.mjs +1 -1
  46. package/dist/{mcpPlugin-Cb7olw0j.mjs → mcpPlugin-nRzm5g2s.mjs} +36 -43
  47. package/dist/middleware/index.d.mts +1 -1
  48. package/dist/migrations/index.d.mts +0 -3
  49. package/dist/migrations/index.mjs +0 -3
  50. package/dist/module-_-M7C5ES.mjs +29 -0
  51. package/dist/{openapi-DrLtog_B.mjs → openapi-B8Vkbxbn.mjs} +1 -1
  52. package/dist/permissions/index.d.mts +3 -3
  53. package/dist/permissions/index.mjs +4 -3
  54. package/dist/{permissions-BrtiBKGs.mjs → permissions-Bfu_8y1U.mjs} +318 -289
  55. package/dist/pipeline/index.d.mts +1 -1
  56. package/dist/{pipeline-BN5G05rd.mjs → pipeline-BJnpkY9i.mjs} +4 -18
  57. package/dist/plugins/index.d.mts +4 -4
  58. package/dist/plugins/index.mjs +2 -2
  59. package/dist/plugins/tracing-entry.mjs +1 -1
  60. package/dist/{preloadResources-0oe-JSSS.d.mts → preloadResources-DCfa2egz.d.mts} +3 -2
  61. package/dist/preloadResources-DSY5HGpS.mjs +45 -0
  62. package/dist/presets/filesUpload.d.mts +2 -2
  63. package/dist/presets/filesUpload.mjs +1 -1
  64. package/dist/presets/index.d.mts +14 -2
  65. package/dist/presets/index.mjs +1 -1
  66. package/dist/presets/multiTenant.d.mts +1 -1
  67. package/dist/presets/search.d.mts +2 -2
  68. package/dist/presets/search.mjs +1 -1
  69. package/dist/{presets-DwFDVKP0.mjs → presets-CJOEXLuk.mjs} +25 -9
  70. package/dist/projection-UBDgfMwu.mjs +17 -0
  71. package/dist/{realtime-Cuw_deA-.mjs → realtime-B9nmIY5V.mjs} +2 -1
  72. package/dist/{registerRoute-v0Wb6Plt.mjs → registerRoute-JkxIEBJq.mjs} +3 -1
  73. package/dist/registry/index.d.mts +2 -2
  74. package/dist/registry/index.mjs +1 -1
  75. package/dist/{registry-vcOCtPij.mjs → registry-BYXUUg21.mjs} +16 -1
  76. package/dist/repositoryFilter-Dz8k3S4P.mjs +44 -0
  77. package/dist/subscribe-helpers-DfzsivM6.mjs +339 -0
  78. package/dist/testing/cleanupStoreContract.d.mts +10 -0
  79. package/dist/testing/cleanupStoreContract.mjs +375 -0
  80. package/dist/testing/index.d.mts +34 -5
  81. package/dist/testing/index.mjs +316 -211
  82. package/dist/testing/outboxStoreContract.d.mts +13 -0
  83. package/dist/testing/outboxStoreContract.mjs +247 -0
  84. package/dist/types/index.d.mts +3 -3
  85. package/dist/{types-QFGc3Cjv.d.mts → types-Bmmn-fO0.d.mts} +1 -1
  86. package/dist/types-C_ZZRcD6.d.mts +450 -0
  87. package/dist/{types-Dmw-6Qeo.d.mts → types-ChvvVNP8.d.mts} +93 -88
  88. package/dist/{index-KUoxjIq1.d.mts → types-D_n8L6VY.d.mts} +33 -106
  89. package/dist/usage/index.d.mts +1 -1
  90. package/dist/usage/index.mjs +0 -0
  91. package/dist/utils/index.d.mts +2 -5
  92. package/package.json +539 -524
@@ -1,5 +1,10 @@
1
1
  import { t as CRUD_OPERATIONS } from "../constants-DNre0wRg.mjs";
2
- import { i as createApp, n as preloadResourcesAsync, p as getModuleExports, t as preloadResources } from "../preloadResources-DfyngIN9.mjs";
2
+ import { t as BaseController } from "../BaseController-B5DwufbB.mjs";
3
+ import { t as defineResource } from "../defineResource-CxWqDCAy.mjs";
4
+ import { t as defineAggregation } from "../defineAggregation-ufWjUwml.mjs";
5
+ import { n as createApp } from "../createApp-CXrKFciH.mjs";
6
+ import { n as getModuleExports } from "../module-_-M7C5ES.mjs";
7
+ import { n as preloadResourcesAsync, t as preloadResources } from "../preloadResources-DSY5HGpS.mjs";
3
8
  import { runStorageContract } from "./storageContract.mjs";
4
9
  import Fastify from "fastify";
5
10
  import { afterAll, describe, expect, it, vi } from "vitest";
@@ -123,6 +128,314 @@ function expectArc(response) {
123
128
  return assertion;
124
129
  }
125
130
  //#endregion
131
+ //#region src/testing/mocks.ts
132
+ /**
133
+ * Create a mock repository for testing
134
+ *
135
+ * @example
136
+ * const mockRepo = createMockRepository<Product>({
137
+ * getById: vi.fn().mockResolvedValue({ id: '1', name: 'Test' }),
138
+ * create: vi.fn().mockImplementation(data => Promise.resolve({ id: '1', ...data })),
139
+ * });
140
+ *
141
+ * await mockRepo.getById('1'); // Returns mocked product
142
+ */
143
+ function createMockRepository(overrides = {}) {
144
+ return {
145
+ getAll: vi.fn().mockResolvedValue({
146
+ method: "offset",
147
+ data: [],
148
+ total: 0,
149
+ page: 1,
150
+ limit: 20,
151
+ pages: 0,
152
+ hasNext: false,
153
+ hasPrev: false
154
+ }),
155
+ getById: vi.fn().mockResolvedValue(null),
156
+ create: vi.fn().mockImplementation((data) => Promise.resolve({
157
+ _id: "mock-id",
158
+ ...data
159
+ })),
160
+ update: vi.fn().mockImplementation((_id, data) => Promise.resolve({
161
+ _id: "mock-id",
162
+ ...data
163
+ })),
164
+ delete: vi.fn().mockResolvedValue({
165
+ success: true,
166
+ message: "Deleted"
167
+ }),
168
+ updateMany: vi.fn().mockResolvedValue({
169
+ acknowledged: true,
170
+ matchedCount: 0,
171
+ modifiedCount: 0
172
+ }),
173
+ deleteMany: vi.fn().mockResolvedValue({
174
+ acknowledged: true,
175
+ deletedCount: 0
176
+ }),
177
+ claim: vi.fn().mockResolvedValue(null),
178
+ claimVersion: vi.fn().mockResolvedValue(null),
179
+ capabilities: {
180
+ transactions: false,
181
+ nestedTransactions: false,
182
+ upsert: false,
183
+ duplicateKeyError: false,
184
+ distinct: false,
185
+ aggregate: false,
186
+ getOrCreate: false,
187
+ countAndExists: false
188
+ },
189
+ getBySlug: vi.fn().mockResolvedValue(null),
190
+ getDeleted: vi.fn().mockResolvedValue([]),
191
+ restore: vi.fn().mockResolvedValue(null),
192
+ getTree: vi.fn().mockResolvedValue([]),
193
+ getChildren: vi.fn().mockResolvedValue([]),
194
+ ...overrides
195
+ };
196
+ }
197
+ /**
198
+ * Create a mock user for authentication testing
199
+ */
200
+ function createMockUser(overrides = {}) {
201
+ return {
202
+ _id: "mock-user-id",
203
+ id: "mock-user-id",
204
+ email: "test@example.com",
205
+ roles: ["user"],
206
+ organizationId: null,
207
+ ...overrides
208
+ };
209
+ }
210
+ /**
211
+ * Create a mock Fastify request
212
+ */
213
+ function createMockRequest(overrides = {}) {
214
+ return {
215
+ body: {},
216
+ params: {},
217
+ query: {},
218
+ headers: {},
219
+ user: createMockUser(),
220
+ context: {},
221
+ log: {
222
+ info: vi.fn(),
223
+ warn: vi.fn(),
224
+ error: vi.fn(),
225
+ debug: vi.fn()
226
+ },
227
+ ...overrides
228
+ };
229
+ }
230
+ /**
231
+ * Create a mock Fastify reply
232
+ */
233
+ function createMockReply() {
234
+ return {
235
+ code: vi.fn().mockReturnThis(),
236
+ send: vi.fn().mockReturnThis(),
237
+ header: vi.fn().mockReturnThis(),
238
+ headers: vi.fn().mockReturnThis(),
239
+ status: vi.fn().mockReturnThis(),
240
+ type: vi.fn().mockReturnThis(),
241
+ redirect: vi.fn().mockReturnThis(),
242
+ callNotFound: vi.fn().mockReturnThis(),
243
+ sent: false
244
+ };
245
+ }
246
+ /**
247
+ * Create a mock controller for testing
248
+ */
249
+ function createMockController(repository) {
250
+ return {
251
+ repository,
252
+ list: vi.fn(),
253
+ get: vi.fn(),
254
+ create: vi.fn(),
255
+ update: vi.fn(),
256
+ delete: vi.fn()
257
+ };
258
+ }
259
+ /**
260
+ * Create mock data factory
261
+ *
262
+ * @example
263
+ * const productFactory = createDataFactory<Product>({
264
+ * name: () => faker.commerce.productName(),
265
+ * price: () => faker.number.int({ min: 10, max: 1000 }),
266
+ * sku: (i) => `SKU-${i}`,
267
+ * });
268
+ *
269
+ * const product = productFactory.build();
270
+ * const products = productFactory.buildMany(10);
271
+ */
272
+ function createDataFactory(template) {
273
+ let counter = 0;
274
+ return {
275
+ build(overrides = {}) {
276
+ const index = counter++;
277
+ const data = {};
278
+ for (const [key, generator] of Object.entries(template)) data[key] = generator(index);
279
+ return {
280
+ ...data,
281
+ ...overrides
282
+ };
283
+ },
284
+ buildMany(count, overrides = {}) {
285
+ return Array.from({ length: count }, () => this.build(overrides));
286
+ },
287
+ reset() {
288
+ counter = 0;
289
+ }
290
+ };
291
+ }
292
+ /**
293
+ * Create a spy that tracks function calls
294
+ *
295
+ * Useful for testing side effects without full mocking
296
+ */
297
+ function createSpy(_name = "spy") {
298
+ const calls = [];
299
+ const spy = vi.fn((...args) => {
300
+ calls.push(args);
301
+ });
302
+ spy.getCalls = () => calls;
303
+ spy.getLastCall = () => calls[calls.length - 1] || [];
304
+ return spy;
305
+ }
306
+ /**
307
+ * Wait for a condition to be true
308
+ *
309
+ * Useful for async testing
310
+ */
311
+ async function waitFor(condition, options = {}) {
312
+ const { timeout = 5e3, interval = 100 } = options;
313
+ const startTime = Date.now();
314
+ while (Date.now() - startTime < timeout) {
315
+ if (await condition()) return;
316
+ await new Promise((resolve) => setTimeout(resolve, interval));
317
+ }
318
+ throw new Error(`Timeout waiting for condition after ${timeout}ms`);
319
+ }
320
+ /**
321
+ * Create a test timer that can be controlled
322
+ */
323
+ function createTestTimer() {
324
+ let time = Date.now();
325
+ return {
326
+ now: () => time,
327
+ advance: (ms) => {
328
+ time += ms;
329
+ },
330
+ set: (timestamp) => {
331
+ time = timestamp;
332
+ },
333
+ reset: () => {
334
+ time = Date.now();
335
+ }
336
+ };
337
+ }
338
+ //#endregion
339
+ //#region src/testing/authorizationConformance.ts
340
+ /**
341
+ * Cross-surface authorization conformance (arc 2.30).
342
+ *
343
+ * The regression wall that keeps "partial enforcement" from ever coming back:
344
+ * run the SAME permission through more than one arc surface and prove it enforces
345
+ * identically. Reviews kept finding aggregation (and MCP) drifting from CRUD —
346
+ * this makes that a one-call assertion for arc AND for host resources.
347
+ *
348
+ * `runAuthorizationConformance` boots a throwaway Fastify app with one resource
349
+ * whose `list` op AND an `aggregation` share the given permission, backed by a
350
+ * capturing mock repo (no DB). It returns, per surface, the HTTP status and the
351
+ * exact filter arc handed the repository — so the caller asserts:
352
+ * - deny → both surfaces fail closed with the SAME status;
353
+ * - allow + `policy` → the row policy reaches BOTH the list query and the
354
+ * aggregation `$match`, identically.
355
+ *
356
+ * Runner-agnostic: it returns data, it does not assert. Pair with your test
357
+ * framework's expectations (see `tests/core/authorization-conformance.test.ts`).
358
+ */
359
+ /**
360
+ * Boot a one-resource app gated by `permission` on both list + an aggregation,
361
+ * hit both surfaces anonymously, and report what arc enforced on each.
362
+ */
363
+ async function runAuthorizationConformance(opts) {
364
+ const rows = opts.rows ?? [{ _id: "1" }];
365
+ let listFilter;
366
+ let aggFilter;
367
+ const repo = createMockRepository({
368
+ getAll: (async (params = {}) => {
369
+ listFilter = params.filters;
370
+ return {
371
+ method: "offset",
372
+ data: rows,
373
+ total: rows.length,
374
+ page: 1,
375
+ limit: 20,
376
+ pages: 1,
377
+ hasNext: false,
378
+ hasPrev: false
379
+ };
380
+ }),
381
+ aggregate: (async (req = {}) => {
382
+ aggFilter = req.filter;
383
+ return { rows: [{ tally: rows.length }] };
384
+ }),
385
+ capabilities: {
386
+ count: false,
387
+ exists: false,
388
+ distinct: false,
389
+ aggregate: true,
390
+ transactions: false,
391
+ lookup: false,
392
+ softDelete: false
393
+ }
394
+ });
395
+ const resource = defineResource({
396
+ name: "conf",
397
+ prefix: "/confs",
398
+ adapter: { repository: repo },
399
+ controller: new BaseController(repo, {
400
+ resourceName: "conf",
401
+ tenantField: false
402
+ }),
403
+ permissions: {
404
+ list: opts.permission,
405
+ get: opts.permission,
406
+ create: opts.permission,
407
+ update: opts.permission,
408
+ delete: opts.permission
409
+ },
410
+ aggregations: { tally: defineAggregation({
411
+ measures: { tally: { op: "count" } },
412
+ permissions: opts.permission
413
+ }) }
414
+ });
415
+ const app = Fastify({ logger: false });
416
+ await app.register(resource.toPlugin());
417
+ await app.ready();
418
+ const list = await app.inject({
419
+ method: "GET",
420
+ url: "/confs"
421
+ });
422
+ const aggregation = await app.inject({
423
+ method: "GET",
424
+ url: "/confs/aggregations/tally"
425
+ });
426
+ return {
427
+ list: {
428
+ status: list.statusCode,
429
+ filter: listFilter
430
+ },
431
+ aggregation: {
432
+ status: aggregation.statusCode,
433
+ filter: aggFilter
434
+ },
435
+ close: () => app.close()
436
+ };
437
+ }
438
+ //#endregion
126
439
  //#region src/testing/authSession.ts
127
440
  function buildHeaders(token, orgId, extra) {
128
441
  const headers = { authorization: `Bearer ${token}` };
@@ -843,214 +1156,6 @@ function createHttpTestHarness(resource, optionsOrGetter) {
843
1156
  return new HttpTestHarness(resource, optionsOrGetter);
844
1157
  }
845
1158
  //#endregion
846
- //#region src/testing/mocks.ts
847
- /**
848
- * Create a mock repository for testing
849
- *
850
- * @example
851
- * const mockRepo = createMockRepository<Product>({
852
- * getById: vi.fn().mockResolvedValue({ id: '1', name: 'Test' }),
853
- * create: vi.fn().mockImplementation(data => Promise.resolve({ id: '1', ...data })),
854
- * });
855
- *
856
- * await mockRepo.getById('1'); // Returns mocked product
857
- */
858
- function createMockRepository(overrides = {}) {
859
- return {
860
- getAll: vi.fn().mockResolvedValue({
861
- method: "offset",
862
- data: [],
863
- total: 0,
864
- page: 1,
865
- limit: 20,
866
- pages: 0,
867
- hasNext: false,
868
- hasPrev: false
869
- }),
870
- getById: vi.fn().mockResolvedValue(null),
871
- create: vi.fn().mockImplementation((data) => Promise.resolve({
872
- _id: "mock-id",
873
- ...data
874
- })),
875
- update: vi.fn().mockImplementation((_id, data) => Promise.resolve({
876
- _id: "mock-id",
877
- ...data
878
- })),
879
- delete: vi.fn().mockResolvedValue({
880
- success: true,
881
- message: "Deleted"
882
- }),
883
- updateMany: vi.fn().mockResolvedValue({
884
- acknowledged: true,
885
- matchedCount: 0,
886
- modifiedCount: 0
887
- }),
888
- deleteMany: vi.fn().mockResolvedValue({
889
- acknowledged: true,
890
- deletedCount: 0
891
- }),
892
- claim: vi.fn().mockResolvedValue(null),
893
- claimVersion: vi.fn().mockResolvedValue(null),
894
- capabilities: {
895
- transactions: false,
896
- nestedTransactions: false,
897
- upsert: false,
898
- duplicateKeyError: false,
899
- distinct: false,
900
- aggregate: false,
901
- getOrCreate: false,
902
- countAndExists: false
903
- },
904
- getBySlug: vi.fn().mockResolvedValue(null),
905
- getDeleted: vi.fn().mockResolvedValue([]),
906
- restore: vi.fn().mockResolvedValue(null),
907
- getTree: vi.fn().mockResolvedValue([]),
908
- getChildren: vi.fn().mockResolvedValue([]),
909
- ...overrides
910
- };
911
- }
912
- /**
913
- * Create a mock user for authentication testing
914
- */
915
- function createMockUser(overrides = {}) {
916
- return {
917
- _id: "mock-user-id",
918
- id: "mock-user-id",
919
- email: "test@example.com",
920
- roles: ["user"],
921
- organizationId: null,
922
- ...overrides
923
- };
924
- }
925
- /**
926
- * Create a mock Fastify request
927
- */
928
- function createMockRequest(overrides = {}) {
929
- return {
930
- body: {},
931
- params: {},
932
- query: {},
933
- headers: {},
934
- user: createMockUser(),
935
- context: {},
936
- log: {
937
- info: vi.fn(),
938
- warn: vi.fn(),
939
- error: vi.fn(),
940
- debug: vi.fn()
941
- },
942
- ...overrides
943
- };
944
- }
945
- /**
946
- * Create a mock Fastify reply
947
- */
948
- function createMockReply() {
949
- return {
950
- code: vi.fn().mockReturnThis(),
951
- send: vi.fn().mockReturnThis(),
952
- header: vi.fn().mockReturnThis(),
953
- headers: vi.fn().mockReturnThis(),
954
- status: vi.fn().mockReturnThis(),
955
- type: vi.fn().mockReturnThis(),
956
- redirect: vi.fn().mockReturnThis(),
957
- callNotFound: vi.fn().mockReturnThis(),
958
- sent: false
959
- };
960
- }
961
- /**
962
- * Create a mock controller for testing
963
- */
964
- function createMockController(repository) {
965
- return {
966
- repository,
967
- list: vi.fn(),
968
- get: vi.fn(),
969
- create: vi.fn(),
970
- update: vi.fn(),
971
- delete: vi.fn()
972
- };
973
- }
974
- /**
975
- * Create mock data factory
976
- *
977
- * @example
978
- * const productFactory = createDataFactory<Product>({
979
- * name: () => faker.commerce.productName(),
980
- * price: () => faker.number.int({ min: 10, max: 1000 }),
981
- * sku: (i) => `SKU-${i}`,
982
- * });
983
- *
984
- * const product = productFactory.build();
985
- * const products = productFactory.buildMany(10);
986
- */
987
- function createDataFactory(template) {
988
- let counter = 0;
989
- return {
990
- build(overrides = {}) {
991
- const index = counter++;
992
- const data = {};
993
- for (const [key, generator] of Object.entries(template)) data[key] = generator(index);
994
- return {
995
- ...data,
996
- ...overrides
997
- };
998
- },
999
- buildMany(count, overrides = {}) {
1000
- return Array.from({ length: count }, () => this.build(overrides));
1001
- },
1002
- reset() {
1003
- counter = 0;
1004
- }
1005
- };
1006
- }
1007
- /**
1008
- * Create a spy that tracks function calls
1009
- *
1010
- * Useful for testing side effects without full mocking
1011
- */
1012
- function createSpy(_name = "spy") {
1013
- const calls = [];
1014
- const spy = vi.fn((...args) => {
1015
- calls.push(args);
1016
- });
1017
- spy.getCalls = () => calls;
1018
- spy.getLastCall = () => calls[calls.length - 1] || [];
1019
- return spy;
1020
- }
1021
- /**
1022
- * Wait for a condition to be true
1023
- *
1024
- * Useful for async testing
1025
- */
1026
- async function waitFor(condition, options = {}) {
1027
- const { timeout = 5e3, interval = 100 } = options;
1028
- const startTime = Date.now();
1029
- while (Date.now() - startTime < timeout) {
1030
- if (await condition()) return;
1031
- await new Promise((resolve) => setTimeout(resolve, interval));
1032
- }
1033
- throw new Error(`Timeout waiting for condition after ${timeout}ms`);
1034
- }
1035
- /**
1036
- * Create a test timer that can be controlled
1037
- */
1038
- function createTestTimer() {
1039
- let time = Date.now();
1040
- return {
1041
- now: () => time,
1042
- advance: (ms) => {
1043
- time += ms;
1044
- },
1045
- set: (timestamp) => {
1046
- time = timestamp;
1047
- },
1048
- reset: () => {
1049
- time = Date.now();
1050
- }
1051
- };
1052
- }
1053
- //#endregion
1054
1159
  //#region src/testing/testApp.ts
1055
1160
  /**
1056
1161
  * createTestApp — test app factory for arc
@@ -1116,7 +1221,7 @@ function pickDefaultAuth(authMode, callerAuth) {
1116
1221
  };
1117
1222
  }
1118
1223
  async function createTestApp(options = {}) {
1119
- const { createApp } = await import("../preloadResources-DfyngIN9.mjs").then((n) => n.a);
1224
+ const { createApp } = await import("../createApp-CXrKFciH.mjs").then((n) => n.r);
1120
1225
  const { resources = [], db = "in-memory", connectMongoose = false, authMode = "jwt", defaultOrgId, plugins, auth: callerAuth, ...appOptions } = options;
1121
1226
  let dbHandle;
1122
1227
  let dbUri;
@@ -1186,4 +1291,4 @@ function createMinimalTestApp(options = {}) {
1186
1291
  });
1187
1292
  }
1188
1293
  //#endregion
1189
- export { HttpTestHarness, bootModuleApp, createBetterAuthProvider, createBetterAuthTestHelpers, createCustomAuthProvider, createDataFactory, createHttpTestHarness, createJwtAuthProvider, createMinimalTestApp, createMockController, createMockReply, createMockRepository, createMockRequest, createMockUser, createSpy, createTestApp, createTestFixtures, createTestTimer, expectArc, mongoMemoryDatabase, preloadResources, preloadResourcesAsync, runStorageContract, safeParseBody, setupBetterAuthTestApp, waitFor };
1294
+ export { HttpTestHarness, bootModuleApp, createBetterAuthProvider, createBetterAuthTestHelpers, createCustomAuthProvider, createDataFactory, createHttpTestHarness, createJwtAuthProvider, createMinimalTestApp, createMockController, createMockReply, createMockRepository, createMockRequest, createMockUser, createSpy, createTestApp, createTestFixtures, createTestTimer, expectArc, mongoMemoryDatabase, preloadResources, preloadResourcesAsync, runAuthorizationConformance, runStorageContract, safeParseBody, setupBetterAuthTestApp, waitFor };
@@ -0,0 +1,13 @@
1
+ import { OutboxStore } from "@classytic/primitives/outbox";
2
+ //#region src/testing/outboxStoreContract.d.ts
3
+ interface OutboxStoreContractSetupResult {
4
+ store: OutboxStore;
5
+ /** Called once after the suite. */
6
+ teardown?: () => Promise<void>;
7
+ /** Empty the store between tests. Strongly recommended for isolation. */
8
+ reset?: () => Promise<void>;
9
+ }
10
+ type OutboxStoreContractSetup = () => Promise<OutboxStoreContractSetupResult>;
11
+ declare function runOutboxStoreContract(name: string, setup: OutboxStoreContractSetup): void;
12
+ //#endregion
13
+ export { OutboxStoreContractSetup, OutboxStoreContractSetupResult, runOutboxStoreContract };