@classytic/arc 2.31.0 → 2.33.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.
- package/README.md +1 -1
- package/bin/arc.js +58 -17
- package/dist/{BaseController-De7GqHck.mjs → BaseController-CaQC5EXX.mjs} +298 -35
- package/dist/{EventTransport-2uVV34WU.d.mts → EventTransport-BEVP50ka.d.mts} +16 -0
- package/dist/{EventTransport-9mlTgqzE.mjs → EventTransport-tq4CEB-q.mjs} +39 -2
- package/dist/{HookSystem-BcmPyq5I.mjs → HookSystem-DqXkMXkB.mjs} +2 -2
- package/dist/{ResourceRegistry-qq0mn2s9.mjs → ResourceRegistry-DoALUlOO.mjs} +18 -15
- package/dist/{actionPermissions-BE5NwaQk.mjs → actionPermissions-CUeCmpUd.mjs} +1 -1
- package/dist/audit/index.d.mts +2 -2
- package/dist/audit/index.mjs +2 -2
- package/dist/auth/audit.mjs +1 -1
- package/dist/auth/index.d.mts +2 -2
- package/dist/auth/index.mjs +44 -11
- package/dist/auth/redis-session.mjs +1 -1
- package/dist/{authorizationDecision-ChvUQGG1.mjs → authorizationDecision-B3mP_QTy.mjs} +2 -2
- package/dist/{types-B7XeLEgK.d.mts → base-Csjc2oF-.d.mts} +74 -74
- package/dist/{buildHandler-CDrCJzmB.mjs → buildHandler-DLE9xX-V.mjs} +6 -5
- package/dist/cache/index.mjs +4 -4
- package/dist/{caching-BFHA6Scf.mjs → caching-BcGmvnHh.mjs} +2 -2
- package/dist/cleanup/index.d.mts +52 -18
- package/dist/cleanup/index.mjs +281 -32
- package/dist/cli/commands/describe.d.mts +1 -1
- package/dist/cli/commands/describe.mjs +4 -6
- package/dist/cli/commands/docs.mjs +3 -3
- package/dist/cli/commands/doctor.mjs +1 -1
- package/dist/cli/commands/generate.mjs +2 -2
- package/dist/cli/commands/init.mjs +2 -2
- package/dist/cli/commands/introspect.mjs +2 -2
- package/dist/cli/index.mjs +3 -3
- package/dist/{constants-D3Dv4LAV.mjs → constants-BxCoxmiN.mjs} +1 -1
- package/dist/context/index.mjs +3 -3
- package/dist/core/index.d.mts +2 -2
- package/dist/core/index.mjs +7 -7
- package/dist/{core-BGpIxINN.mjs → core-dxhcHhUD.mjs} +3 -3
- package/dist/{createActionRouter-CDXEdasl.mjs → createActionRouter-DVpoA0do.mjs} +14 -11
- package/dist/{createAggregationRouter-KrQDzx-k.mjs → createAggregationRouter-B_B48JZ-.mjs} +10 -8
- package/dist/{createApp-Df89BEni.mjs → createApp-CLGegqiG.mjs} +287 -62
- package/dist/{defineAggregation-DbPPS8RI.mjs → defineAggregation-Nv-DwfOf.mjs} +1 -1
- package/dist/{defineResource-Bv6EtN1x.mjs → defineResource-DR9vsUYy.mjs} +318 -35
- package/dist/discovery/index.mjs +1 -1
- package/dist/docs/index.d.mts +1 -1
- package/dist/docs/index.mjs +3 -3
- package/dist/{elevation-D2l1mh4g.mjs → elevation-B0tlY8y1.mjs} +4 -4
- package/dist/encryption/index.mjs +5 -4
- package/dist/{errorHandler-C5NeD6bT.mjs → errorHandler-C7xBqHsI.mjs} +42 -5
- package/dist/{errorHandler-CpYUJmPA.d.mts → errorHandler-DI5AN2b5.d.mts} +14 -3
- package/dist/{errors-Cj42AntO.mjs → errors-Cd_yy7aW.mjs} +1 -1
- package/dist/{eventPlugin-GF5nnBIq.d.mts → eventPlugin-DSNEZWfV.d.mts} +29 -1
- package/dist/{eventPlugin-CkffEihm.mjs → eventPlugin-Dar8qRaj.mjs} +68 -11
- package/dist/events/index.d.mts +18 -297
- package/dist/events/index.mjs +7 -758
- package/dist/events/transports/redis-stream-entry.d.mts +1 -1
- package/dist/events/transports/redis-stream-entry.mjs +14 -10
- package/dist/events/transports/redis.d.mts +1 -1
- package/dist/events/transports/redis.mjs +1 -1
- package/dist/factory/index.d.mts +20 -5
- package/dist/factory/index.mjs +131 -8
- package/dist/fastify-augmentation-DVsTAy6X.d.mts +179 -0
- package/dist/{fields-CB5Cuzk9.mjs → fields-CojeUkS4.mjs} +6 -11
- package/dist/{generate-EcA6ui4i.mjs → generate-DiB0nHPR.mjs} +2 -2
- package/dist/{headers-3zvE0rM3.mjs → headers-CveNO9gR.mjs} +2 -2
- package/dist/{health-DjZYjM7H.mjs → health-CasG2A28.mjs} +1 -1
- package/dist/hooks/index.d.mts +1 -1
- package/dist/hooks/index.mjs +2 -2
- package/dist/idempotency/index.d.mts +45 -1
- package/dist/idempotency/index.mjs +95 -11
- package/dist/idempotency/redis.mjs +1 -1
- package/dist/{index-CEJNZFtX.d.mts → index-B-DvUzsr.d.mts} +82 -3
- package/dist/{index-CpzxqePp.d.mts → index-BcR2bFZQ.d.mts} +707 -373
- package/dist/{index-Bc2ox8_U.d.mts → index-Brin0ODU.d.mts} +4 -4
- package/dist/{index-CFThn1K9.d.mts → index-C5NnMEKF.d.mts} +70 -4
- package/dist/{index-C-481Zf2.d.mts → index-ugr1tQlW.d.mts} +17 -1
- package/dist/index.d.mts +6 -6
- package/dist/index.mjs +11 -11
- package/dist/{init-BEiNZBsJ.mjs → init-mdJ-v4Xp.mjs} +24 -16
- package/dist/integrations/event-gateway.d.mts +1 -1
- package/dist/integrations/event-gateway.mjs +2 -2
- package/dist/integrations/index.d.mts +2 -2
- package/dist/integrations/index.mjs +1 -1
- package/dist/integrations/jobs/index.d.mts +1 -1
- package/dist/integrations/jobs/index.mjs +100 -98
- package/dist/integrations/mcp/index.d.mts +12 -3
- package/dist/integrations/mcp/index.mjs +2 -2
- package/dist/integrations/mcp/testing.d.mts +1 -1
- package/dist/integrations/mcp/testing.mjs +2 -2
- package/dist/integrations/streamline.mjs +3 -3
- package/dist/integrations/webhooks.d.mts +124 -1
- package/dist/integrations/webhooks.mjs +180 -2
- package/dist/integrations/websocket-pushref-redis.mjs +1 -1
- package/dist/integrations/websocket-redis.mjs +1 -1
- package/dist/integrations/websocket.mjs +38 -35
- package/dist/{interface-DYzM0q-u.mjs → interface-DznJrm9R.mjs} +1 -1
- package/dist/{jsonBody-BYbLDi5D.mjs → jsonBody-CfGH-GxO.mjs} +2 -2
- package/dist/{keys-C0_pa1tY.mjs → keys-WeKvY1dE.mjs} +1 -1
- package/dist/{loadResources-yawi9tAE.mjs → loadResources-Bj-aSsXi.mjs} +4 -3
- package/dist/{loadResourcesFromEntry-BAjYZonx.mjs → loadResourcesFromEntry-DwaxNcE2.mjs} +2 -2
- package/dist/logger/index.mjs +1 -1
- package/dist/{mcpPlugin-B_es0hiF.mjs → mcpPlugin-aJg5TqHi.mjs} +76 -36
- package/dist/{memory-DR6wpa9F.mjs → memory-qYSB7rMQ.mjs} +5 -5
- package/dist/{metrics-CUdw_wLB.mjs → metrics-D7uchLjN.mjs} +2 -2
- package/dist/middleware/index.d.mts +1 -1
- package/dist/middleware/index.mjs +3 -3
- package/dist/migrations/index.mjs +3 -3
- package/dist/{module-0pFJA6YW.mjs → module-CwKsgZXp.mjs} +1 -1
- package/dist/{multipartBody-wOgj_fnj.mjs → multipartBody-4w-S3-PU.mjs} +3 -3
- package/dist/{openapi-B2L4js1v.mjs → openapi-BN5cQaO2.mjs} +6 -6
- package/dist/outbox-admin/index.d.mts +85 -0
- package/dist/outbox-admin/index.mjs +115 -0
- package/dist/outbox-module-CXB79bvt.mjs +839 -0
- package/dist/outbox-module-D-VFq37T.d.mts +368 -0
- package/dist/permissions/index.d.mts +3 -3
- package/dist/permissions/index.mjs +6 -6
- package/dist/{permissions-L--QDAir.mjs → permissions-BAfwgBxc.mjs} +120 -8
- package/dist/{pipe-DRDNsI2T.mjs → pipe-BzzoEhHj.mjs} +3 -5
- package/dist/pipeline/index.d.mts +1 -1
- package/dist/pipeline/index.mjs +2 -2
- package/dist/{pipeline-UPQFatit.mjs → pipeline-q69DZzgG.mjs} +17 -15
- package/dist/plugins/index.d.mts +6 -6
- package/dist/plugins/index.mjs +16 -16
- package/dist/plugins/response-cache.mjs +2 -2
- package/dist/plugins/tracing-entry.mjs +2 -2
- package/dist/{pluralize-O0FZgESl.mjs → pluralize-B7xf8qZF.mjs} +1 -1
- package/dist/{preloadResources-Dz6BjzM6.mjs → preloadResources-BkyqeTDz.mjs} +1 -1
- package/dist/{preloadResources-CyoJMSiO.d.mts → preloadResources-g07ldtzh.d.mts} +30 -7
- package/dist/presets/filesUpload.d.mts +2 -2
- package/dist/presets/filesUpload.mjs +6 -6
- package/dist/presets/index.d.mts +2 -2
- package/dist/presets/index.mjs +2 -2
- package/dist/presets/multiTenant.d.mts +1 -1
- package/dist/presets/multiTenant.mjs +3 -3
- package/dist/presets/search.d.mts +2 -2
- package/dist/presets/search.mjs +2 -2
- package/dist/{presets-C4-g4_4P.mjs → presets-D-h0uWtP.mjs} +6 -5
- package/dist/projection-BKM0Ui-s.mjs +42 -0
- package/dist/{queryCachePlugin-BZr8v46E.mjs → queryCachePlugin-DhsZ0ZIc.mjs} +50 -8
- package/dist/{rateLimit-CMPY5Qms.mjs → rateLimit-D4-ASwsI.mjs} +2 -2
- package/dist/{rateLimitKey-Dn5JNmkj.mjs → rateLimitKey-XngqVNEV.mjs} +1 -1
- package/dist/{realtime-C5NoxWFY.mjs → realtime-CTkv3O59.mjs} +10 -10
- package/dist/{redis-stream-qN6A-RFv.d.mts → redis-stream-mwLWlfHG.d.mts} +1 -1
- package/dist/{registerRoute-CBN84fc7.mjs → registerRoute-DNnd3vkz.mjs} +10 -8
- package/dist/registry/index.d.mts +3 -3
- package/dist/registry/index.mjs +3 -3
- package/dist/{registry-4213U4zb.mjs → registry-CLxCR5LF.mjs} +2 -2
- package/dist/{renewingLease-Dll8mGgc.mjs → renewingLease-BnWtbA2U.mjs} +1 -1
- package/dist/{replyHelpers-DIiZKGUt.mjs → replyHelpers-BSBVCwL3.mjs} +2 -2
- package/dist/{repositoryFilter-f9eAfq_l.mjs → repositoryFilter-DKny7PDn.mjs} +1 -1
- package/dist/{requestContext-2TxXOJ7T.mjs → requestContext-uYE35JxI.mjs} +1 -1
- package/dist/{rolldown-runtime-CUsjNX9R.mjs → rolldown-runtime-DNg-KNCk.mjs} +1 -1
- package/dist/runtimeCapabilities-Dm88DaTW.mjs +49 -0
- package/dist/{schedules-DdtlICTf.mjs → schedules-CAcZgia0.mjs} +3 -3
- package/dist/{schemaConverter-D--wdJM-.mjs → schemaConverter-XH1RZRyu.mjs} +1 -1
- package/dist/{schemaIR-Bm2enbnh.mjs → schemaIR-CDvclAew.mjs} +2 -2
- package/dist/schemas/index.mjs +1 -1
- package/dist/scim/index.mjs +2 -2
- package/dist/scope/index.mjs +5 -5
- package/dist/{scope-DHx5Fous.mjs → scope-BaWcqBvr.mjs} +4 -4
- package/dist/{simpleEqualityMatcher-DjQJ7FKA.mjs → simpleEqualityMatcher-Se8VrsaP.mjs} +1 -1
- package/dist/{sse-CUTo9y0X.mjs → sse-Cf0jUHsh.mjs} +7 -6
- package/dist/{sseStream-B_5XcVVd.mjs → sseStream-Bvpi-UXQ.mjs} +2 -2
- package/dist/{store-helpers-D1MP_gRG.mjs → store-helpers-B88p99Z2.mjs} +1 -1
- package/dist/{streaming-PSZwi8JV.mjs → streaming-DsT1B5zJ.mjs} +1 -1
- package/dist/{subscribe-helpers-C0Tb2aUp.mjs → subscribe-helpers-CLIOHsOK.mjs} +2 -2
- package/dist/sync/changelog.mjs +1 -1
- package/dist/testing/cleanupStoreContract.d.mts +1 -1
- package/dist/testing/cleanupStoreContract.mjs +12 -6
- package/dist/testing/index.d.mts +52 -13
- package/dist/testing/index.mjs +110 -40
- package/dist/testing/outboxStoreContract.d.mts +11 -2
- package/dist/testing/outboxStoreContract.mjs +110 -21
- package/dist/testing/storageContract.mjs +11 -6
- package/dist/{transactionContext-dAfBVUrk.mjs → transactionContext-BYtWnwAS.mjs} +1 -1
- package/dist/{typeGuards-B0ryPo3I.mjs → typeGuards-jjKJDN13.mjs} +1 -1
- package/dist/types/index.d.mts +3 -3
- package/dist/types/index.mjs +1 -1
- package/dist/types/storage.mjs +1 -1
- package/dist/{types-CBgvkp8O.d.mts → types-BcTumAz_.d.mts} +128 -18
- package/dist/{types-CwcUQ610.d.mts → types-BfC83KsP.d.mts} +15 -1
- package/dist/{types-Cys0ko9Z.mjs → types-CoqIRrkY.mjs} +4 -4
- package/dist/{types-C4QE8qnm.d.mts → types-DVLddu2q.d.mts} +144 -1
- package/dist/usage/index.d.mts +1 -1
- package/dist/usage/index.mjs +0 -0
- package/dist/{userHelpers-ChYJ6CdK.mjs → userHelpers-CWF5pbZD.mjs} +1 -1
- package/dist/utils/index.d.mts +57 -5
- package/dist/utils/index.mjs +12 -11
- package/dist/{utils-vL8BrDE2.mjs → utils-h1V_sT-t.mjs} +38 -7
- package/dist/{validate-_KbifpHF.mjs → validate-CdQlz52F.mjs} +2 -2
- package/dist/{versioning-D_rhHfZm.d.mts → versioning-DEs-_hFi.d.mts} +1 -1
- package/dist/{versioning-CHDTor3P.mjs → versioning-rZbyLVD-.mjs} +4 -4
- package/package.json +31 -20
- package/skills/arc/SKILL.md +96 -4
- package/skills/arc/references/api-reference.md +19 -0
- package/skills/arc/references/production.md +2 -2
- package/dist/fastify-augmentation-zh0_AYHa.d.mts +0 -85
- package/dist/projection-BHw6xlOQ.mjs +0 -18
package/dist/testing/index.mjs
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
/*! @classytic/arc v2.
|
|
2
|
-
import { t as CRUD_OPERATIONS } from "../constants-
|
|
3
|
-
import { g as getUserId } from "../types-
|
|
4
|
-
import { t as BaseController } from "../BaseController-
|
|
5
|
-
import { t as defineResource } from "../defineResource-
|
|
6
|
-
import { t as defineAggregation } from "../defineAggregation-
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import { n as getModuleExports } from "../module-
|
|
10
|
-
import { n as preloadResourcesAsync, t as preloadResources } from "../preloadResources-
|
|
1
|
+
/*! @classytic/arc v2.34.0 | MIT | https://classytic.com */
|
|
2
|
+
import { t as CRUD_OPERATIONS } from "../constants-BxCoxmiN.mjs";
|
|
3
|
+
import { g as getUserId } from "../types-CoqIRrkY.mjs";
|
|
4
|
+
import { t as BaseController } from "../BaseController-CaQC5EXX.mjs";
|
|
5
|
+
import { t as defineResource } from "../defineResource-DR9vsUYy.mjs";
|
|
6
|
+
import { t as defineAggregation } from "../defineAggregation-Nv-DwfOf.mjs";
|
|
7
|
+
import { E as requireOrgRole } from "../permissions-BAfwgBxc.mjs";
|
|
8
|
+
import { r as createApp } from "../createApp-CLGegqiG.mjs";
|
|
9
|
+
import { n as getModuleExports } from "../module-CwKsgZXp.mjs";
|
|
10
|
+
import { n as preloadResourcesAsync, t as preloadResources } from "../preloadResources-BkyqeTDz.mjs";
|
|
11
11
|
import { runStorageContract } from "./storageContract.mjs";
|
|
12
12
|
import Fastify from "fastify";
|
|
13
13
|
import { afterAll, describe, expect, it, vi } from "vitest";
|
|
@@ -64,7 +64,8 @@ function expectArc(response) {
|
|
|
64
64
|
failed(status) {
|
|
65
65
|
if (status !== void 0) expect(response.statusCode).toBe(status);
|
|
66
66
|
else expect(response.statusCode).toBeGreaterThanOrEqual(400);
|
|
67
|
-
|
|
67
|
+
const body = getBody();
|
|
68
|
+
expect(body.code, "expected ErrorContract.code on failed response").toBeDefined();
|
|
68
69
|
return assertion;
|
|
69
70
|
},
|
|
70
71
|
unauthorized() {
|
|
@@ -84,7 +85,8 @@ function expectArc(response) {
|
|
|
84
85
|
},
|
|
85
86
|
hasData() {
|
|
86
87
|
const body = getBody();
|
|
87
|
-
|
|
88
|
+
const hasContent = Object.keys(body).length > 0;
|
|
89
|
+
expect(hasContent, "expected response body to be defined").toBe(true);
|
|
88
90
|
return assertion;
|
|
89
91
|
},
|
|
90
92
|
hasStatus(status) {
|
|
@@ -92,11 +94,13 @@ function expectArc(response) {
|
|
|
92
94
|
return assertion;
|
|
93
95
|
},
|
|
94
96
|
hidesField(field) {
|
|
95
|
-
|
|
97
|
+
const body = getBody();
|
|
98
|
+
expect(body, `expected field '${field}' to be hidden from response`).not.toHaveProperty(field);
|
|
96
99
|
return assertion;
|
|
97
100
|
},
|
|
98
101
|
showsField(field) {
|
|
99
|
-
|
|
102
|
+
const body = getBody();
|
|
103
|
+
expect(body, `expected field '${field}' on response`).toHaveProperty(field);
|
|
100
104
|
return assertion;
|
|
101
105
|
},
|
|
102
106
|
paginated(expected) {
|
|
@@ -247,7 +251,19 @@ function createMockReply() {
|
|
|
247
251
|
};
|
|
248
252
|
}
|
|
249
253
|
/**
|
|
250
|
-
* Create a mock controller for testing
|
|
254
|
+
* Create a mock controller for testing.
|
|
255
|
+
*
|
|
256
|
+
* The return type is ANNOTATED rather than inferred, and writing it out IS the
|
|
257
|
+
* fix: inference expands `Mock`'s default type argument to `Procedure`, which
|
|
258
|
+
* vitest declares in an internal sub-package arc does not depend on. The
|
|
259
|
+
* emitted declaration then referenced that package by name — resolvable under
|
|
260
|
+
* npm's hoisting because `vitest` (a real peer) drags it in, and broken under
|
|
261
|
+
* pnpm's strict layout, where a transitive dep is not reachable from a
|
|
262
|
+
* consumer's root.
|
|
263
|
+
*
|
|
264
|
+
* Bare `Mock` keeps the permissive ergonomics (`.mockResolvedValue(...)`, any
|
|
265
|
+
* arguments) while naming only `vitest`, which arc does declare. Same shape
|
|
266
|
+
* `MockRepository` above uses.
|
|
251
267
|
*/
|
|
252
268
|
function createMockController(repository) {
|
|
253
269
|
return {
|
|
@@ -398,6 +414,21 @@ async function runAuthorizationConformance(opts) {
|
|
|
398
414
|
getFilter = filter;
|
|
399
415
|
return rows[0] ?? null;
|
|
400
416
|
}),
|
|
417
|
+
/**
|
|
418
|
+
* The single-record read has TWO paths, and the harness must be able to observe both.
|
|
419
|
+
*
|
|
420
|
+
* `fetchDetailed` only builds a compound filter and calls `getOne` when there is something to
|
|
421
|
+
* compound — a row policy, a tenant field, or a non-default id field. A permission that simply
|
|
422
|
+
* ALLOWS (no policy) leaves `{ _id }` alone and takes the plain `getById` path instead. The base
|
|
423
|
+
* mock stubs that as `null`, so before this stub the `get` surface reported **404 on every
|
|
424
|
+
* policy-free allow** while `list` and `aggregation` reported 200.
|
|
425
|
+
*
|
|
426
|
+
* That reads as partial enforcement and it is not: it is the harness failing to answer. A host
|
|
427
|
+
* asserting `get.status === 200` for its own allow-gate — the obvious falsification case to pair
|
|
428
|
+
* with a deny-parity suite — was told its gate broke the single-record surface. Found from
|
|
429
|
+
* be-prod, whose conformance suite does exactly that.
|
|
430
|
+
*/
|
|
431
|
+
getById: (async () => rows[0] ?? null),
|
|
401
432
|
aggregate: (async (req = {}) => {
|
|
402
433
|
aggFilter = req.filter;
|
|
403
434
|
return { rows: [{ tally: rows.length }] };
|
|
@@ -1166,12 +1197,13 @@ var HttpTestHarness = class {
|
|
|
1166
1197
|
it(`${verb} /:id with non-existent ID should return 404`, async () => {
|
|
1167
1198
|
const { app, fixtures } = this.getOptions();
|
|
1168
1199
|
const payload = fixtures.update ?? fixtures.valid;
|
|
1169
|
-
|
|
1200
|
+
const res = await app.inject({
|
|
1170
1201
|
method: verb,
|
|
1171
1202
|
url: `${this.getBaseUrl()}/000000000000000000000000`,
|
|
1172
1203
|
headers: this.adminHeaders(),
|
|
1173
1204
|
payload
|
|
1174
|
-
})
|
|
1205
|
+
});
|
|
1206
|
+
expect(res.statusCode).toBe(404);
|
|
1175
1207
|
});
|
|
1176
1208
|
}
|
|
1177
1209
|
if (enabledRoutes.has("delete")) {
|
|
@@ -1188,24 +1220,29 @@ var HttpTestHarness = class {
|
|
|
1188
1220
|
deleteId = JSON.parse(createRes.body)._id;
|
|
1189
1221
|
}
|
|
1190
1222
|
if (!deleteId) return;
|
|
1191
|
-
|
|
1223
|
+
const res = await app.inject({
|
|
1192
1224
|
method: "DELETE",
|
|
1193
1225
|
url: `${this.getBaseUrl()}/${deleteId}`,
|
|
1194
1226
|
headers: this.adminHeaders()
|
|
1195
|
-
})
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1227
|
+
});
|
|
1228
|
+
expect(res.statusCode).toBe(200);
|
|
1229
|
+
if (enabledRoutes.has("get")) {
|
|
1230
|
+
const getRes = await app.inject({
|
|
1231
|
+
method: "GET",
|
|
1232
|
+
url: `${this.getBaseUrl()}/${deleteId}`,
|
|
1233
|
+
headers: this.adminHeaders()
|
|
1234
|
+
});
|
|
1235
|
+
expect(getRes.statusCode).toBe(404);
|
|
1236
|
+
}
|
|
1201
1237
|
});
|
|
1202
1238
|
it("DELETE /:id with non-existent ID should return 404", async () => {
|
|
1203
1239
|
const { app } = this.getOptions();
|
|
1204
|
-
|
|
1240
|
+
const res = await app.inject({
|
|
1205
1241
|
method: "DELETE",
|
|
1206
1242
|
url: `${this.getBaseUrl()}/000000000000000000000000`,
|
|
1207
1243
|
headers: this.adminHeaders()
|
|
1208
|
-
})
|
|
1244
|
+
});
|
|
1245
|
+
expect(res.statusCode).toBe(404);
|
|
1209
1246
|
});
|
|
1210
1247
|
}
|
|
1211
1248
|
});
|
|
@@ -1224,49 +1261,55 @@ var HttpTestHarness = class {
|
|
|
1224
1261
|
describe(`${resource.displayName} HTTP Permissions`, () => {
|
|
1225
1262
|
if (enabledRoutes.has("list") && protectedOps.list) it("GET list without auth should return 401", async () => {
|
|
1226
1263
|
const { app } = this.getOptions();
|
|
1227
|
-
|
|
1264
|
+
const res = await app.inject({
|
|
1228
1265
|
method: "GET",
|
|
1229
1266
|
url: this.getBaseUrl()
|
|
1230
|
-
})
|
|
1267
|
+
});
|
|
1268
|
+
expect(res.statusCode).toBe(401);
|
|
1231
1269
|
});
|
|
1232
1270
|
if (enabledRoutes.has("get") && protectedOps.get) it("GET /:id without auth should return 401", async () => {
|
|
1233
1271
|
const { app } = this.getOptions();
|
|
1234
|
-
|
|
1272
|
+
const res = await app.inject({
|
|
1235
1273
|
method: "GET",
|
|
1236
1274
|
url: `${this.getBaseUrl()}/000000000000000000000000`
|
|
1237
|
-
})
|
|
1275
|
+
});
|
|
1276
|
+
expect(res.statusCode).toBe(401);
|
|
1238
1277
|
});
|
|
1239
1278
|
if (enabledRoutes.has("create") && protectedOps.create) it("POST without auth should return 401", async () => {
|
|
1240
1279
|
const { app, fixtures } = this.getOptions();
|
|
1241
|
-
|
|
1280
|
+
const res = await app.inject({
|
|
1242
1281
|
method: "POST",
|
|
1243
1282
|
url: this.getBaseUrl(),
|
|
1244
1283
|
payload: fixtures.valid
|
|
1245
|
-
})
|
|
1284
|
+
});
|
|
1285
|
+
expect(res.statusCode).toBe(401);
|
|
1246
1286
|
});
|
|
1247
1287
|
if (enabledRoutes.has("update") && protectedOps.update) for (const verb of updateMethods) it(`${verb} without auth should return 401`, async () => {
|
|
1248
1288
|
const { app, fixtures } = this.getOptions();
|
|
1249
1289
|
const payload = fixtures.update ?? fixtures.valid;
|
|
1250
|
-
|
|
1290
|
+
const res = await app.inject({
|
|
1251
1291
|
method: verb,
|
|
1252
1292
|
url: `${this.getBaseUrl()}/000000000000000000000000`,
|
|
1253
1293
|
payload
|
|
1254
|
-
})
|
|
1294
|
+
});
|
|
1295
|
+
expect(res.statusCode).toBe(401);
|
|
1255
1296
|
});
|
|
1256
1297
|
if (enabledRoutes.has("delete") && protectedOps.delete) it("DELETE without auth should return 401", async () => {
|
|
1257
1298
|
const { app } = this.getOptions();
|
|
1258
|
-
|
|
1299
|
+
const res = await app.inject({
|
|
1259
1300
|
method: "DELETE",
|
|
1260
1301
|
url: `${this.getBaseUrl()}/000000000000000000000000`
|
|
1261
|
-
})
|
|
1302
|
+
});
|
|
1303
|
+
expect(res.statusCode).toBe(401);
|
|
1262
1304
|
});
|
|
1263
1305
|
if (enabledRoutes.has("list")) it("admin should access list endpoint", async () => {
|
|
1264
1306
|
const { app } = this.getOptions();
|
|
1265
|
-
|
|
1307
|
+
const res = await app.inject({
|
|
1266
1308
|
method: "GET",
|
|
1267
1309
|
url: this.getBaseUrl(),
|
|
1268
1310
|
headers: this.adminHeaders()
|
|
1269
|
-
})
|
|
1311
|
+
});
|
|
1312
|
+
expect(res.statusCode).toBeLessThan(400);
|
|
1270
1313
|
});
|
|
1271
1314
|
if (enabledRoutes.has("create")) it("admin should access create endpoint", async () => {
|
|
1272
1315
|
const { app, fixtures } = this.getOptions();
|
|
@@ -1313,6 +1356,33 @@ function createHttpTestHarness(resource, optionsOrGetter) {
|
|
|
1313
1356
|
return new HttpTestHarness(resource, optionsOrGetter);
|
|
1314
1357
|
}
|
|
1315
1358
|
//#endregion
|
|
1359
|
+
//#region src/testing/moduleSetup.ts
|
|
1360
|
+
function createTestModuleSetup() {
|
|
1361
|
+
const deferred = [];
|
|
1362
|
+
return {
|
|
1363
|
+
context: { defer(disposer) {
|
|
1364
|
+
if (typeof disposer !== "function") throw new TypeError(`[arc/testing] defer() expected a function, received ${typeof disposer}.`);
|
|
1365
|
+
deferred.push(disposer);
|
|
1366
|
+
} },
|
|
1367
|
+
deferred,
|
|
1368
|
+
async dispose() {
|
|
1369
|
+
const errors = [];
|
|
1370
|
+
for (let i = deferred.length - 1; i >= 0; i--) {
|
|
1371
|
+
const dispose = deferred[i];
|
|
1372
|
+
if (!dispose) continue;
|
|
1373
|
+
try {
|
|
1374
|
+
await dispose();
|
|
1375
|
+
} catch (err) {
|
|
1376
|
+
errors.push(err);
|
|
1377
|
+
}
|
|
1378
|
+
}
|
|
1379
|
+
deferred.length = 0;
|
|
1380
|
+
if (errors.length === 1) throw errors[0];
|
|
1381
|
+
if (errors.length > 1) throw new AggregateError(errors, `[arc/testing] ${errors.length} disposers threw`);
|
|
1382
|
+
}
|
|
1383
|
+
};
|
|
1384
|
+
}
|
|
1385
|
+
//#endregion
|
|
1316
1386
|
//#region src/testing/scopeRoleGate.ts
|
|
1317
1387
|
/**
|
|
1318
1388
|
* `scopeRoleGate` — the ONE role gate for package tests.
|
|
@@ -1435,7 +1505,7 @@ function pickDefaultAuth(authMode, callerAuth) {
|
|
|
1435
1505
|
};
|
|
1436
1506
|
}
|
|
1437
1507
|
async function createTestApp(options = {}) {
|
|
1438
|
-
const { createApp } = await import("../createApp-
|
|
1508
|
+
const { createApp } = await import("../createApp-CLGegqiG.mjs").then((n) => n.i);
|
|
1439
1509
|
const { resources = [], db = "in-memory", connectMongoose = false, authMode = "jwt", defaultOrgId, plugins, auth: callerAuth, ...appOptions } = options;
|
|
1440
1510
|
let dbHandle;
|
|
1441
1511
|
let dbUri;
|
|
@@ -1505,4 +1575,4 @@ function createMinimalTestApp(options = {}) {
|
|
|
1505
1575
|
});
|
|
1506
1576
|
}
|
|
1507
1577
|
//#endregion
|
|
1508
|
-
export { HttpTestHarness, TEST_ACTOR_HEADER, bootModuleApp, createBetterAuthProvider, createBetterAuthTestHelpers, createCustomAuthProvider, createDataFactory, createHttpTestHarness, createJwtAuthProvider, createMinimalTestApp, createMockController, createMockReply, createMockRepository, createMockRequest, createMockUser, createSpy, createTestApp, createTestFixtures, createTestTimer, expectArc, mongoMemoryDatabase, preloadResources, preloadResourcesAsync, runAuthorizationConformance, runStorageContract, safeParseBody, scopeFromTestActor, scopeRoleGate, setupBetterAuthTestApp, testActorAuth, testActorHeaders, waitFor };
|
|
1578
|
+
export { HttpTestHarness, TEST_ACTOR_HEADER, bootModuleApp, createBetterAuthProvider, createBetterAuthTestHelpers, createCustomAuthProvider, createDataFactory, createHttpTestHarness, createJwtAuthProvider, createMinimalTestApp, createMockController, createMockReply, createMockRepository, createMockRequest, createMockUser, createSpy, createTestApp, createTestFixtures, createTestModuleSetup, createTestTimer, expectArc, mongoMemoryDatabase, preloadResources, preloadResourcesAsync, runAuthorizationConformance, runStorageContract, safeParseBody, scopeFromTestActor, scopeRoleGate, setupBetterAuthTestApp, testActorAuth, testActorHeaders, waitFor };
|
|
@@ -5,8 +5,17 @@ interface OutboxStoreContractSetupResult {
|
|
|
5
5
|
store: OutboxStore;
|
|
6
6
|
/** Called once after the suite. */
|
|
7
7
|
teardown?: () => Promise<void>;
|
|
8
|
-
/**
|
|
9
|
-
|
|
8
|
+
/**
|
|
9
|
+
* Empty the store. REQUIRED, and run before every test.
|
|
10
|
+
*
|
|
11
|
+
* Not a recommendation: several assertions are absolute rather than relative —
|
|
12
|
+
* "exactly zero pending", "exactly two", "`oldestPendingAgeMs()` is null on an
|
|
13
|
+
* empty queue". Without a reset those read whatever the previous test left, so the
|
|
14
|
+
* suite passes on ORDER rather than on behaviour, and inserting one unrelated case
|
|
15
|
+
* turns a conforming store red. A store that cannot empty itself cannot be
|
|
16
|
+
* conformance-tested.
|
|
17
|
+
*/
|
|
18
|
+
reset: () => Promise<void>;
|
|
10
19
|
}
|
|
11
20
|
type OutboxStoreContractSetup = () => Promise<OutboxStoreContractSetupResult>;
|
|
12
21
|
declare function runOutboxStoreContract(name: string, setup: OutboxStoreContractSetup): void;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
/*! @classytic/arc v2.
|
|
1
|
+
/*! @classytic/arc v2.34.0 | MIT | https://classytic.com */
|
|
2
2
|
import { OutboxOwnershipError } from "@classytic/primitives/outbox";
|
|
3
|
-
import { afterAll, beforeAll, describe, expect, it } from "vitest";
|
|
3
|
+
import { afterAll, beforeAll, beforeEach, describe, expect, it } from "vitest";
|
|
4
4
|
//#region src/testing/outboxStoreContract.ts
|
|
5
5
|
let seq = 0;
|
|
6
6
|
/** A minimal well-formed event. Ids are unique per call. */
|
|
@@ -30,9 +30,16 @@ function runOutboxStoreContract(name, setup) {
|
|
|
30
30
|
afterAll(async () => {
|
|
31
31
|
if (teardown) await teardown();
|
|
32
32
|
});
|
|
33
|
+
/**
|
|
34
|
+
* Isolation for EVERY test, not just the ones that call `seed()`. Resetting inside
|
|
35
|
+
* `seed()` left any test that asserts on an empty store — or that only reads —
|
|
36
|
+
* running against its predecessor's rows.
|
|
37
|
+
*/
|
|
38
|
+
beforeEach(async () => {
|
|
39
|
+
await reset();
|
|
40
|
+
});
|
|
33
41
|
/** Save n events, oldest first. */
|
|
34
42
|
async function seed(n, type) {
|
|
35
|
-
if (reset) await reset();
|
|
36
43
|
const events = [];
|
|
37
44
|
for (let i = 0; i < n; i++) {
|
|
38
45
|
const e = event(type);
|
|
@@ -44,10 +51,10 @@ function runOutboxStoreContract(name, setup) {
|
|
|
44
51
|
describe("save — rejects malformed events (invariant 1)", () => {
|
|
45
52
|
it("persists a well-formed event", async () => {
|
|
46
53
|
const [e] = await seed(1);
|
|
47
|
-
|
|
54
|
+
const pending = await store.getPending(10);
|
|
55
|
+
expect(pending.map(idOf)).toContain(idOf(e));
|
|
48
56
|
});
|
|
49
57
|
it("THROWS rather than persisting an event with no `type`", async () => {
|
|
50
|
-
if (reset) await reset();
|
|
51
58
|
await expect(store.save({
|
|
52
59
|
payload: {},
|
|
53
60
|
meta: { id: "no-type" }
|
|
@@ -55,7 +62,6 @@ function runOutboxStoreContract(name, setup) {
|
|
|
55
62
|
expect((await store.getPending(10)).length).toBe(0);
|
|
56
63
|
});
|
|
57
64
|
it("THROWS rather than persisting an event with no `meta.id`", async () => {
|
|
58
|
-
if (reset) await reset();
|
|
59
65
|
await expect(store.save({
|
|
60
66
|
type: "x",
|
|
61
67
|
payload: {},
|
|
@@ -67,7 +73,8 @@ function runOutboxStoreContract(name, setup) {
|
|
|
67
73
|
describe("getPending", () => {
|
|
68
74
|
it("returns events FIFO (oldest first)", async () => {
|
|
69
75
|
const saved = await seed(3);
|
|
70
|
-
|
|
76
|
+
const pending = await store.getPending(10);
|
|
77
|
+
expect(pending.slice(0, 3).map(idOf)).toEqual(saved.map(idOf));
|
|
71
78
|
});
|
|
72
79
|
it("honours the limit", async () => {
|
|
73
80
|
await seed(3);
|
|
@@ -86,10 +93,11 @@ function runOutboxStoreContract(name, setup) {
|
|
|
86
93
|
it("claims pending events", async ({ skip }) => {
|
|
87
94
|
if (!store.claimPending) return skip();
|
|
88
95
|
await seed(2);
|
|
89
|
-
|
|
96
|
+
const claimed = await store.claimPending({
|
|
90
97
|
consumerId: "c1",
|
|
91
98
|
limit: 10
|
|
92
|
-
})
|
|
99
|
+
});
|
|
100
|
+
expect(claimed.length).toBe(2);
|
|
93
101
|
});
|
|
94
102
|
it("two concurrent claimers NEVER receive the same event", async ({ skip }) => {
|
|
95
103
|
if (!store.claimPending) return skip();
|
|
@@ -103,7 +111,8 @@ function runOutboxStoreContract(name, setup) {
|
|
|
103
111
|
})]);
|
|
104
112
|
const idsA = a.map(idOf);
|
|
105
113
|
const idsB = b.map(idOf);
|
|
106
|
-
|
|
114
|
+
const overlap = idsA.filter((id) => idsB.includes(id));
|
|
115
|
+
expect(overlap).toEqual([]);
|
|
107
116
|
expect(idsA.length + idsB.length).toBeLessThanOrEqual(6);
|
|
108
117
|
});
|
|
109
118
|
it("does not re-claim an event whose lease is still live", async ({ skip }) => {
|
|
@@ -114,10 +123,11 @@ function runOutboxStoreContract(name, setup) {
|
|
|
114
123
|
limit: 10,
|
|
115
124
|
leaseMs: 6e4
|
|
116
125
|
});
|
|
117
|
-
|
|
126
|
+
const second = await store.claimPending({
|
|
118
127
|
consumerId: "c2",
|
|
119
128
|
limit: 10
|
|
120
|
-
})
|
|
129
|
+
});
|
|
130
|
+
expect(second).toEqual([]);
|
|
121
131
|
});
|
|
122
132
|
it("RECLAIMS an event whose lease expired (crashed worker)", async ({ skip }) => {
|
|
123
133
|
if (!store.claimPending) return skip();
|
|
@@ -127,18 +137,20 @@ function runOutboxStoreContract(name, setup) {
|
|
|
127
137
|
limit: 10,
|
|
128
138
|
leaseMs: 0
|
|
129
139
|
});
|
|
130
|
-
|
|
140
|
+
const reclaimed = await store.claimPending({
|
|
131
141
|
consumerId: "c2",
|
|
132
142
|
limit: 10
|
|
133
|
-
})
|
|
143
|
+
});
|
|
144
|
+
expect(reclaimed.length).toBe(1);
|
|
134
145
|
});
|
|
135
146
|
it("claims FIFO", async ({ skip }) => {
|
|
136
147
|
if (!store.claimPending) return skip();
|
|
137
148
|
const saved = await seed(3);
|
|
138
|
-
|
|
149
|
+
const claimed = await store.claimPending({
|
|
139
150
|
consumerId: "c1",
|
|
140
151
|
limit: 2
|
|
141
|
-
})
|
|
152
|
+
});
|
|
153
|
+
expect(claimed.map(idOf)).toEqual(saved.slice(0, 2).map(idOf));
|
|
142
154
|
});
|
|
143
155
|
});
|
|
144
156
|
describe("acknowledge", () => {
|
|
@@ -148,7 +160,6 @@ function runOutboxStoreContract(name, setup) {
|
|
|
148
160
|
expect((await store.getPending(10)).map(idOf)).not.toContain(idOf(e));
|
|
149
161
|
});
|
|
150
162
|
it("is a NO-OP for an unknown id — never an ownership error (invariant 4)", async () => {
|
|
151
|
-
if (reset) await reset();
|
|
152
163
|
await expect(store.acknowledge("does-not-exist")).resolves.not.toThrow();
|
|
153
164
|
});
|
|
154
165
|
it("THROWS OutboxOwnershipError when consumerId is not the lease owner (invariant 3)", async ({ skip }) => {
|
|
@@ -185,10 +196,11 @@ function runOutboxStoreContract(name, setup) {
|
|
|
185
196
|
});
|
|
186
197
|
if (!claimed) return skip();
|
|
187
198
|
await store.fail(idOf(claimed), { message: "boom" });
|
|
188
|
-
|
|
199
|
+
const again = await store.claimPending({
|
|
189
200
|
consumerId: "c2",
|
|
190
201
|
limit: 10
|
|
191
|
-
})
|
|
202
|
+
});
|
|
203
|
+
expect(again.map(idOf)).toContain(idOf(claimed));
|
|
192
204
|
});
|
|
193
205
|
it("THROWS OutboxOwnershipError on a consumerId mismatch (invariant 3)", async ({ skip }) => {
|
|
194
206
|
if (!store.fail || !store.claimPending) return skip();
|
|
@@ -211,10 +223,11 @@ function runOutboxStoreContract(name, setup) {
|
|
|
211
223
|
});
|
|
212
224
|
if (!claimed) return skip();
|
|
213
225
|
await store.fail(idOf(claimed), { message: "boom" }, { retryAt: new Date(Date.now() + 6e4) });
|
|
214
|
-
|
|
226
|
+
const again = await store.claimPending({
|
|
215
227
|
consumerId: "c2",
|
|
216
228
|
limit: 10
|
|
217
|
-
})
|
|
229
|
+
});
|
|
230
|
+
expect(again.map(idOf)).not.toContain(idOf(claimed));
|
|
218
231
|
});
|
|
219
232
|
it("`deadLetter: true` removes it from the pending stream", async ({ skip }) => {
|
|
220
233
|
if (!store.fail) return skip();
|
|
@@ -242,6 +255,82 @@ function runOutboxStoreContract(name, setup) {
|
|
|
242
255
|
expect((await store.getPending(10)).length).toBe(2);
|
|
243
256
|
});
|
|
244
257
|
});
|
|
258
|
+
/**
|
|
259
|
+
* `requeue`, `countByStatus` and `oldestPendingAgeMs` were a HOST's three extra
|
|
260
|
+
* methods before they were contract members — which is why they are pinned here.
|
|
261
|
+
* A dead-letter row an operator cannot re-drive, or a health number that lies, fails
|
|
262
|
+
* silently: the operator concludes the queue is fine, and the whole point of the
|
|
263
|
+
* table is that somebody eventually looks.
|
|
264
|
+
*/
|
|
265
|
+
describe("requeue", () => {
|
|
266
|
+
it("returns a dead-lettered event to pending and clears the attempt count", async ({ skip }) => {
|
|
267
|
+
if (!store.fail || !store.requeue) return skip();
|
|
268
|
+
const [e] = await seed(1);
|
|
269
|
+
const id = idOf(e);
|
|
270
|
+
await store.fail(id, { message: "fatal" }, { deadLetter: true });
|
|
271
|
+
expect((await store.getPending(10)).length).toBe(0);
|
|
272
|
+
expect(await store.requeue(id)).toBe(true);
|
|
273
|
+
const pending = await store.getPending(10);
|
|
274
|
+
expect(pending.length).toBe(1);
|
|
275
|
+
if (store.countByStatus) expect(await store.countByStatus("dead_letter")).toBe(0);
|
|
276
|
+
});
|
|
277
|
+
it("REFUSES a pending event — requeue is dead-letter-scoped", async ({ skip }) => {
|
|
278
|
+
if (!store.requeue) return skip();
|
|
279
|
+
const [e] = await seed(1);
|
|
280
|
+
expect(await store.requeue(idOf(e))).toBe(false);
|
|
281
|
+
});
|
|
282
|
+
it("is FALSE, not an error, for an unknown id", async ({ skip }) => {
|
|
283
|
+
if (!store.requeue) return skip();
|
|
284
|
+
expect(await store.requeue("no-such-event")).toBe(false);
|
|
285
|
+
});
|
|
286
|
+
});
|
|
287
|
+
describe("countByStatus", () => {
|
|
288
|
+
it("counts pending, and moves the count as events are delivered", async ({ skip }) => {
|
|
289
|
+
if (!store.countByStatus) return skip();
|
|
290
|
+
await seed(3);
|
|
291
|
+
expect(await store.countByStatus("pending")).toBe(3);
|
|
292
|
+
if (!store.claimPending) return skip();
|
|
293
|
+
const [claimed] = await store.claimPending({
|
|
294
|
+
consumerId: "c",
|
|
295
|
+
limit: 1,
|
|
296
|
+
leaseMs: 6e4
|
|
297
|
+
});
|
|
298
|
+
if (!claimed) return skip();
|
|
299
|
+
await store.acknowledge(idOf(claimed), { consumerId: "c" });
|
|
300
|
+
expect(await store.countByStatus("pending")).toBe(2);
|
|
301
|
+
expect(await store.countByStatus("delivered")).toBe(1);
|
|
302
|
+
});
|
|
303
|
+
it("counts dead-lettered rows — the number an alert fires on", async ({ skip }) => {
|
|
304
|
+
if (!store.countByStatus || !store.fail) return skip();
|
|
305
|
+
expect(await store.countByStatus("dead_letter")).toBe(0);
|
|
306
|
+
const [e] = await seed(1);
|
|
307
|
+
await store.fail(idOf(e), { message: "fatal" }, { deadLetter: true });
|
|
308
|
+
expect(await store.countByStatus("dead_letter")).toBe(1);
|
|
309
|
+
});
|
|
310
|
+
});
|
|
311
|
+
describe("oldestPendingAgeMs", () => {
|
|
312
|
+
it("is null on an empty queue, and a non-negative age once something pends", async ({ skip }) => {
|
|
313
|
+
if (!store.oldestPendingAgeMs) return skip();
|
|
314
|
+
expect(await store.oldestPendingAgeMs()).toBeNull();
|
|
315
|
+
await seed(1);
|
|
316
|
+
const age = await store.oldestPendingAgeMs();
|
|
317
|
+
expect(age).not.toBeNull();
|
|
318
|
+
expect(age ?? -1).toBeGreaterThanOrEqual(0);
|
|
319
|
+
});
|
|
320
|
+
it("ignores DELIVERED rows — it answers 'how long undelivered', not 'how old'", async ({ skip }) => {
|
|
321
|
+
if (!store.oldestPendingAgeMs) return skip();
|
|
322
|
+
if (!store.claimPending) return skip();
|
|
323
|
+
await seed(1);
|
|
324
|
+
const [claimed] = await store.claimPending({
|
|
325
|
+
consumerId: "c",
|
|
326
|
+
limit: 1,
|
|
327
|
+
leaseMs: 6e4
|
|
328
|
+
});
|
|
329
|
+
if (!claimed) return skip();
|
|
330
|
+
await store.acknowledge(idOf(claimed), { consumerId: "c" });
|
|
331
|
+
expect(await store.oldestPendingAgeMs()).toBeNull();
|
|
332
|
+
});
|
|
333
|
+
});
|
|
245
334
|
});
|
|
246
335
|
}
|
|
247
336
|
//#endregion
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/*! @classytic/arc v2.
|
|
1
|
+
/*! @classytic/arc v2.34.0 | MIT | https://classytic.com */
|
|
2
2
|
import { afterAll, beforeAll, describe, expect, it } from "vitest";
|
|
3
3
|
//#region src/testing/storageContract.ts
|
|
4
4
|
/**
|
|
@@ -89,7 +89,8 @@ function runStorageContract(name, setup) {
|
|
|
89
89
|
size: bytes.length
|
|
90
90
|
}, EMPTY_CTX);
|
|
91
91
|
const read = await storage.read(file.id, EMPTY_CTX);
|
|
92
|
-
|
|
92
|
+
const actual = await readAll(read);
|
|
93
|
+
expect(actual.equals(bytes)).toBe(true);
|
|
93
94
|
expect(read.contentType).toBe("application/octet-stream");
|
|
94
95
|
await storage.delete(file.id, EMPTY_CTX);
|
|
95
96
|
});
|
|
@@ -101,7 +102,8 @@ function runStorageContract(name, setup) {
|
|
|
101
102
|
mimeType: "application/octet-stream",
|
|
102
103
|
size: bytes.length
|
|
103
104
|
}, EMPTY_CTX);
|
|
104
|
-
|
|
105
|
+
const first = await storage.delete(file.id, EMPTY_CTX);
|
|
106
|
+
expect(first).toBe(true);
|
|
105
107
|
let second = "threw";
|
|
106
108
|
try {
|
|
107
109
|
second = await storage.delete(file.id, EMPTY_CTX);
|
|
@@ -169,8 +171,10 @@ function runStorageContract(name, setup) {
|
|
|
169
171
|
mimeType: "application/octet-stream",
|
|
170
172
|
size: bytes.length
|
|
171
173
|
}, EMPTY_CTX);
|
|
172
|
-
|
|
173
|
-
expect(
|
|
174
|
+
const before = await readAll(await storage.read(file.id, EMPTY_CTX));
|
|
175
|
+
expect(before.equals(bytes)).toBe(true);
|
|
176
|
+
const removed = await storage.delete(file.id, EMPTY_CTX);
|
|
177
|
+
expect(removed).toBe(true);
|
|
174
178
|
let rejected = false;
|
|
175
179
|
try {
|
|
176
180
|
if (!(await readAll(await storage.read(file.id, EMPTY_CTX))).equals(bytes)) rejected = true;
|
|
@@ -189,7 +193,8 @@ function runStorageContract(name, setup) {
|
|
|
189
193
|
}, EMPTY_CTX);
|
|
190
194
|
const result = await storage.read(file.id, EMPTY_CTX);
|
|
191
195
|
expect(result.kind === "stream" || result.kind === "buffer").toBe(true);
|
|
192
|
-
|
|
196
|
+
const actual = await readAll(result);
|
|
197
|
+
expect(actual.equals(bytes)).toBe(true);
|
|
193
198
|
await storage.delete(file.id, EMPTY_CTX);
|
|
194
199
|
});
|
|
195
200
|
it("read() with a mid-object range slices correctly (when adapter supports ranges)", async () => {
|
package/dist/types/index.d.mts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
|
|
2
|
-
import { $ as OpenApiSchemas, A as RequestIdOptions, An as BaseControllerOptions, At as RouteDefinition, Bt as RawRouteHandler, C as RequestContext, D as HealthCheck, Dt as ActionEntry, E as GracefulShutdownOptions, Et as ActionDefinition, F as FastifyWithDecorators, Ft as ControllerLike, G as OnTenantDeleteConfig, Gt as Authenticator, H as CrudController, I as MiddlewareHandler, It as FastifyHandler, J as PresetHook, Jt as TokenPair, K as ResolvedTenantPurge, Kt as AuthenticatorContext, L as RequestWithExtras, Lt as IController, M as EventsDecorator, Mt as RouteMethod, N as FastifyRequestExtras, Nt as RateLimitConfig, O as HealthOptions, Ot as ActionHandlerFn, P as FastifyWithAuth, Pt as ControllerHandler, Q as MiddlewareConfig, Rt as IControllerResponse, S as QueryParserInterface, T as CrudRouterOptions, Tt as ResourceCacheConfig, U as ResourceConfig, Ut as AuthHelpers, Vt as RouteHandler, W as ResourcePermissions, Wt as AuthPluginOptions, X as CrudRouteKey, Y as PresetResult, Z as CrudSchemas, _ as ControllerQueryOptions, a as InferAdapterDoc, at as ResourceExtensions, b as ParsedQuery, c as TypedController, d as PaginationResult, et as RouteSchemaOptions, f as IntrospectionData, g as ArcInternalMetadata, h as ResourceMetadata, i as ValidationResult, it as ResourceHooks, j as ArcDecorator, jt as RouteMcpConfig, k as IntrospectionPluginOptions, kt as ActionsMap, l as TypedRepository, m as RegistryStats, n as ConfigError, nt as FieldRule, o as InferDocType, ot as EventDefinition, p as RegistryEntry, q as PresetFunction, qt as JwtContext, r as ValidateOptions, rt as ResourceHookContext, s as InferResourceDoc, t as RouteHandlerMethod, tt as ArcFieldRule, u as TypedResourceConfig, v as LookupOption, w as ServiceContext, x as PopulateOption, y as OwnershipCheck, zt as IRequestContext } from "../index-CpzxqePp.mjs";
|
|
3
2
|
import { i as RequestScope } from "../types-ChuShpCP.mjs";
|
|
4
|
-
import { a as
|
|
3
|
+
import { a as UserLike, c as DataPolicy, d as UserBase, i as ObjectId, l as PermissionCheck, n as ArcRequest, o as UserOrganization, r as JWTPayload, s as AuthorizationDecision, t as AnyRecord, u as PermissionContext } from "../base-Csjc2oF-.mjs";
|
|
4
|
+
import { $ as OpenApiSchemas, A as RequestIdOptions, An as WriteVerbKey, At as RouteDefinition, C as RequestContext, D as HealthCheck, Dn as MutationWriteContext, Dt as ActionEntry, E as GracefulShutdownOptions, Et as ActionDefinition, F as FastifyWithDecorators, Fn as IControllerResponse, Ft as AuthPluginOptions, G as OnTenantDeleteConfig, H as CrudController, I as MiddlewareHandler, In as IRequestContext, It as Authenticator, J as PresetHook, K as ResolvedTenantPurge, L as RequestWithExtras, Ln as RawRouteHandler, Lt as AuthenticatorContext, M as EventsDecorator, Mn as ControllerLike, Mt as RouteMethod, N as FastifyRequestExtras, Nn as FastifyHandler, Nt as RateLimitConfig, O as HealthOptions, On as ResourceWrites, Ot as ActionHandlerFn, P as FastifyWithAuth, Pn as IController, Pt as AuthHelpers, Q as MiddlewareConfig, Rn as RouteHandler, Rt as JwtContext, S as QueryParserInterface, T as CrudRouterOptions, Tt as ResourceCacheConfig, U as ResourceConfig, W as ResourcePermissions, X as CrudRouteKey, Y as PresetResult, Z as CrudSchemas, _ as ControllerQueryOptions, a as InferAdapterDoc, at as ResourceExtensions, b as ParsedQuery, c as TypedController, d as PaginationResult, et as RouteSchemaOptions, f as IntrospectionData, g as ArcInternalMetadata, h as ResourceMetadata, i as ValidationResult, it as ResourceHooks, j as ArcDecorator, jn as ControllerHandler, jt as RouteMcpConfig, k as IntrospectionPluginOptions, kn as WriteContext, kt as ActionsMap, l as TypedRepository, m as RegistryStats, n as ConfigError, nt as FieldRule, o as InferDocType, ot as EventDefinition, p as RegistryEntry, q as PresetFunction, r as ValidateOptions, rt as ResourceHookContext, s as InferResourceDoc, t as RouteHandlerMethod, tt as ArcFieldRule, u as TypedResourceConfig, v as LookupOption, w as ServiceContext, x as PopulateOption, xn as BaseControllerOptions, y as OwnershipCheck, zt as TokenPair } from "../index-BcR2bFZQ.mjs";
|
|
5
5
|
import { n as ElevationOptions, t as ElevationEvent } from "../elevation-BXCfVcNG.mjs";
|
|
6
|
-
export type { ActionDefinition, ActionEntry, ActionHandlerFn, ActionsMap, AnyRecord, ArcDecorator, ArcFieldRule, ArcInternalMetadata, ArcRequest, AuthHelpers, AuthPluginOptions, Authenticator, AuthenticatorContext, AuthorizationDecision, BaseControllerOptions, ConfigError, ControllerHandler, ControllerLike, ControllerQueryOptions, CrudController, CrudRouteKey, CrudRouterOptions, CrudSchemas, DataPolicy, ElevationEvent, ElevationOptions, EventDefinition, EventsDecorator, FastifyHandler, FastifyRequestExtras, FastifyWithAuth, FastifyWithDecorators, FieldRule, GracefulShutdownOptions, HealthCheck, HealthOptions, IController, IControllerResponse, IRequestContext, InferAdapterDoc, InferDocType, InferResourceDoc, IntrospectionData, IntrospectionPluginOptions, JWTPayload, JwtContext, LookupOption, MiddlewareConfig, MiddlewareHandler, ObjectId, OnTenantDeleteConfig, OpenApiSchemas, OwnershipCheck, PaginationResult, ParsedQuery, PermissionCheck, PermissionContext, PopulateOption, PresetFunction, PresetHook, PresetResult, QueryParserInterface, RateLimitConfig, RawRouteHandler, RegistryEntry, RegistryStats, RequestContext, RequestIdOptions, RequestScope, RequestWithExtras, ResolvedTenantPurge, ResourceCacheConfig, ResourceConfig, ResourceExtensions, ResourceHookContext, ResourceHooks, ResourceMetadata, ResourcePermissions, RouteDefinition, RouteHandler, RouteHandlerMethod, RouteMcpConfig, RouteMethod, RouteSchemaOptions, ServiceContext, TokenPair, TypedController, TypedRepository, TypedResourceConfig, UserBase, UserLike, UserOrganization, ValidateOptions, ValidationResult };
|
|
6
|
+
export type { ActionDefinition, ActionEntry, ActionHandlerFn, ActionsMap, AnyRecord, ArcDecorator, ArcFieldRule, ArcInternalMetadata, ArcRequest, AuthHelpers, AuthPluginOptions, Authenticator, AuthenticatorContext, AuthorizationDecision, BaseControllerOptions, ConfigError, ControllerHandler, ControllerLike, ControllerQueryOptions, CrudController, CrudRouteKey, CrudRouterOptions, CrudSchemas, DataPolicy, ElevationEvent, ElevationOptions, EventDefinition, EventsDecorator, FastifyHandler, FastifyRequestExtras, FastifyWithAuth, FastifyWithDecorators, FieldRule, GracefulShutdownOptions, HealthCheck, HealthOptions, IController, IControllerResponse, IRequestContext, InferAdapterDoc, InferDocType, InferResourceDoc, IntrospectionData, IntrospectionPluginOptions, JWTPayload, JwtContext, LookupOption, MiddlewareConfig, MiddlewareHandler, MutationWriteContext, ObjectId, OnTenantDeleteConfig, OpenApiSchemas, OwnershipCheck, PaginationResult, ParsedQuery, PermissionCheck, PermissionContext, PopulateOption, PresetFunction, PresetHook, PresetResult, QueryParserInterface, RateLimitConfig, RawRouteHandler, RegistryEntry, RegistryStats, RequestContext, RequestIdOptions, RequestScope, RequestWithExtras, ResolvedTenantPurge, ResourceCacheConfig, ResourceConfig, ResourceExtensions, ResourceHookContext, ResourceHooks, ResourceMetadata, ResourcePermissions, ResourceWrites, RouteDefinition, RouteHandler, RouteHandlerMethod, RouteMcpConfig, RouteMethod, RouteSchemaOptions, ServiceContext, TokenPair, TypedController, TypedRepository, TypedResourceConfig, UserBase, UserLike, UserOrganization, ValidateOptions, ValidationResult, WriteContext, WriteVerbKey };
|
package/dist/types/index.mjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
/*! @classytic/arc v2.
|
|
1
|
+
/*! @classytic/arc v2.34.0 | MIT | https://classytic.com */
|
|
2
2
|
export {};
|
package/dist/types/storage.mjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
/*! @classytic/arc v2.
|
|
1
|
+
/*! @classytic/arc v2.34.0 | MIT | https://classytic.com */
|
|
2
2
|
export {};
|