@classytic/arc 2.41.0 → 2.43.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/dist/{EventTransport-CbUKDzZZ.d.mts → EventTransport-CiicKz2Z.d.mts} +1 -1
- package/dist/{EventTransport-n7qO5Cz3.mjs → EventTransport-tzA7CAB4.mjs} +22 -17
- package/dist/{HookSystem-CxvZBo77.mjs → HookSystem-B6UaKRbF.mjs} +2 -2
- package/dist/{QueryCache-DFA-QrU9.d.mts → QueryCache--5uAuYPW.d.mts} +7 -3
- package/dist/{ResourceRegistry-Cu7uTJp9.mjs → ResourceRegistry-CuCARu2B.mjs} +44 -13
- package/dist/actionPermissions-BotX1YOa.mjs +776 -0
- package/dist/audit/index.d.mts +43 -5
- package/dist/audit/index.mjs +48 -26
- package/dist/auth/audit.mjs +1 -1
- package/dist/auth/index.d.mts +43 -13
- package/dist/auth/index.mjs +130 -155
- package/dist/auth/redis-session.mjs +1 -1
- package/dist/{authorizationDecision-2YY_Kaj1.mjs → authorizationDecision-DQYrkl5f.mjs} +7 -102
- package/dist/{base-CxrV1LmG.d.mts → base-CZyixEQR.d.mts} +27 -5
- package/dist/boundedMap-BULzfXYU.mjs +57 -0
- package/dist/{buildHandler-CUhjGHGZ.mjs → buildHandler-CRQ8EcX5.mjs} +4 -4
- package/dist/cache/index.d.mts +22 -34
- package/dist/cache/index.mjs +4 -4
- package/dist/{caching-DsWDjIxx.mjs → caching-Cc2M1DwO.mjs} +3 -3
- package/dist/claimed-B10RTfkM.mjs +8 -0
- package/dist/cleanup/index.d.mts +6 -10
- package/dist/cleanup/index.mjs +27 -73
- package/dist/cli/commands/describe.d.mts +4 -2
- package/dist/cli/commands/describe.mjs +7 -4
- 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-BxLgNFF7.mjs → constants-Cu7Mx7_U.mjs} +4 -2
- package/dist/context/index.d.mts +2 -5
- package/dist/context/index.mjs +18 -38
- package/dist/core/index.d.mts +3 -3
- package/dist/core/index.mjs +8 -8
- package/dist/{core-iNGqYerO.mjs → core-Cb0LjccA.mjs} +5 -4
- package/dist/{createActionRouter-BBlbPs3k.mjs → createActionRouter-xjqonMRO.mjs} +87 -39
- package/dist/{createAggregationRouter-Dv5SuK2x.mjs → createAggregationRouter-zRYWw_5D.mjs} +11 -8
- package/dist/{preloadResources-Bbp4QJ8V.mjs → createApp-D-953ZAq.mjs} +299 -390
- package/dist/{createMcpServer-CbDWkZ1T.d.mts → createMcpServer-C9cxS8zt.d.mts} +1 -1
- package/dist/{defineAggregation-UyeurGkp.mjs → defineAggregation-Bm8RjMbq.mjs} +1 -1
- package/dist/{defineResource-DzGT-2LP.mjs → defineResource-DIC7JZGU.mjs} +118 -1024
- package/dist/discovery/index.mjs +1 -1
- package/dist/docs/index.d.mts +10 -4
- package/dist/docs/index.mjs +4 -4
- package/dist/{elevation-BXCfVcNG.d.mts → elevation-BW2J3tNj.d.mts} +1 -2
- package/dist/{elevation-BF-vhGxY.mjs → elevation-DG8SrLN-.mjs} +41 -12
- package/dist/encryption/index.mjs +3 -3
- package/dist/{errorHandler-CIMkCOWm.mjs → errorHandler-BnfDviL1.mjs} +26 -5
- package/dist/{errors-C_YhELhe.mjs → errors-CtrywMHP.mjs} +58 -23
- package/dist/{errors-BGdVwP25.d.mts → errors-JYc9vwqE.d.mts} +9 -0
- package/dist/{eventPlugin-B52abJ8W.d.mts → eventPlugin-BtmA4-3P.d.mts} +65 -9
- package/dist/{eventPlugin-CmXaPWLv.mjs → eventPlugin-Q6BhzHzs.mjs} +82 -21
- package/dist/{subscribe-helpers-AVYPgqTl.mjs → eventTypes-Cdhm6ZlV.mjs} +151 -17
- package/dist/events/index.d.mts +33 -8
- package/dist/events/index.mjs +49 -53
- package/dist/events/transports/redis-stream-entry.d.mts +1 -1
- package/dist/events/transports/redis-stream-entry.mjs +139 -157
- package/dist/events/transports/redis.d.mts +1 -1
- package/dist/events/transports/redis.mjs +2 -2
- package/dist/factory/index.d.mts +32 -17
- package/dist/factory/index.mjs +213 -32
- package/dist/{fastify-augmentation-Cq2nHVaW.d.mts → fastify-augmentation-DhUJ-mvo.d.mts} +11 -25
- package/dist/{fields-I_EeRWND.d.mts → fields-BBSpf8pS.d.mts} +1 -1
- package/dist/{fields-BTyPLHK0.mjs → fields-ToPYpMqS.mjs} +1 -1
- package/dist/filter-merge-IpGywvym.mjs +99 -0
- package/dist/{generate-D_YmiCNf.mjs → generate-DRTkLlIl.mjs} +2 -2
- package/dist/{headers-CNECDE6q.mjs → headers-BbkIF6vA.mjs} +2 -2
- package/dist/health-D-Ft6JM6.mjs +22 -0
- package/dist/{health-CxQzbJmr.mjs → health-DqbsG-_w.mjs} +115 -55
- package/dist/hooks/index.d.mts +2 -2
- package/dist/hooks/index.mjs +2 -2
- package/dist/idempotency/index.d.mts +31 -7
- package/dist/idempotency/index.mjs +155 -98
- package/dist/idempotency/redis.d.mts +1 -1
- package/dist/idempotency/redis.mjs +48 -11
- package/dist/{index-BHdZD8zK.d.mts → index-Bxriq3so.d.mts} +84 -21
- package/dist/{preloadResources-CjS2OfpU.d.mts → index-C8zVGYsk.d.mts} +37 -38
- package/dist/{index-D4u76eQW.d.mts → index-CRoPtISf.d.mts} +23 -73
- package/dist/{index-CbJiYrfv.d.mts → index-Chgz3d0i.d.mts} +1 -1
- package/dist/index-JdbZAIsm.d.mts +510 -0
- package/dist/{index-DHbyPo5j.d.mts → index-mqaqZNpe.d.mts} +253 -254
- package/dist/index.d.mts +8 -8
- package/dist/index.mjs +11 -11
- package/dist/{init-Y2MlSN9n.mjs → init-B_oUYDXI.mjs} +1 -1
- package/dist/integrations/event-gateway.d.mts +8 -3
- package/dist/integrations/event-gateway.mjs +4 -4
- package/dist/integrations/index.d.mts +3 -3
- package/dist/integrations/index.mjs +1 -1
- package/dist/integrations/jobs/index.d.mts +1 -1
- package/dist/integrations/jobs/index.mjs +18 -5
- package/dist/integrations/mcp/index.d.mts +72 -59
- package/dist/integrations/mcp/index.mjs +19 -4
- package/dist/integrations/mcp/testing.d.mts +2 -2
- package/dist/integrations/mcp/testing.mjs +2 -2
- package/dist/integrations/streamline.d.mts +78 -136
- package/dist/integrations/streamline.mjs +116 -54
- package/dist/integrations/webhooks.d.mts +60 -120
- package/dist/integrations/webhooks.mjs +36 -80
- package/dist/integrations/websocket-pushref-redis.d.mts +1 -1
- package/dist/integrations/websocket-pushref-redis.mjs +1 -1
- package/dist/integrations/websocket-redis.d.mts +1 -1
- package/dist/integrations/websocket-redis.mjs +1 -1
- package/dist/integrations/websocket.d.mts +3 -3
- package/dist/integrations/websocket.mjs +2 -2
- package/dist/{interface-DEWNOMza.d.mts → interface-C0395yUN.d.mts} +13 -1
- package/dist/{jsonBody-Cq9KYl2Z.mjs → jsonBody-B4YVGcql.mjs} +2 -2
- package/dist/{keys-Cmv4cBRa.mjs → keys-B2KtgMLJ.mjs} +7 -18
- package/dist/{loadResources-CblF7xt2.mjs → loadResources-Dn4lalw2.mjs} +3 -3
- package/dist/{loadResourcesFromEntry-99fIMC3p.mjs → loadResourcesFromEntry-3sLYbuse.mjs} +2 -2
- package/dist/logger/index.d.mts +1 -8
- package/dist/logger/index.mjs +3 -153
- package/dist/logger-DXjfIqjV.mjs +160 -0
- package/dist/{mcpPlugin-xF3Gw-HW.mjs → mcpPlugin-C08HalN5.mjs} +433 -170
- package/dist/{memory-CpDZ9NPw.mjs → memory-9Cp7611C.mjs} +36 -34
- package/dist/{metrics-BaKnq8ag.mjs → metrics-CWI11eRu.mjs} +39 -72
- package/dist/middleware/index.d.mts +2 -2
- package/dist/middleware/index.mjs +3 -3
- package/dist/migrations/index.mjs +2 -2
- package/dist/{module-BkQEesNQ.mjs → module-BSvOQC7E.mjs} +8 -8
- package/dist/multiTenant-cY0FkDiA.d.mts +104 -0
- package/dist/{multipartBody-C92aJ8am.mjs → multipartBody-CXM2a-Dy.mjs} +46 -7
- package/dist/{openapi-C3LFRzPq.mjs → openapi-DT_G4VhY.mjs} +48 -10
- package/dist/outbox-admin/index.d.mts +3 -3
- package/dist/outbox-admin/index.mjs +6 -18
- package/dist/{outbox-module-dsCyLxXH.mjs → outbox-module-Bjgzl6tg.mjs} +203 -33
- package/dist/{outbox-module-BHdUnuuK.d.mts → outbox-module-gZCHoE4H.d.mts} +79 -6
- package/dist/permissions/index.d.mts +5 -4
- package/dist/permissions/index.mjs +8 -6
- package/dist/{permissions-DiHXy8Ck.mjs → permissions-Cm0uYHvd.mjs} +149 -33
- package/dist/{pipe-CBVmUJ-j.mjs → pipe-BqeGT6UO.mjs} +2 -2
- package/dist/pipeline/index.d.mts +2 -2
- package/dist/pipeline/index.mjs +2 -2
- package/dist/plan-FVR6LP59.mjs +565 -0
- package/dist/{BaseController-DYJRZ0EJ.mjs → plugin-BccET1tL.mjs} +1256 -344
- package/dist/plugins/index.d.mts +14 -11
- package/dist/plugins/index.mjs +44 -22
- package/dist/plugins/response-cache.mjs +8 -14
- package/dist/plugins/tracing-entry.mjs +2 -2
- package/dist/{pluralize-2_U6QZQg.mjs → pluralize-CVIOcgMF.mjs} +1 -1
- package/dist/presets/filesUpload.d.mts +2 -2
- package/dist/presets/filesUpload.mjs +5 -5
- package/dist/presets/index.d.mts +26 -31
- package/dist/presets/index.mjs +4 -4
- package/dist/presets/multiTenant.d.mts +2 -107
- package/dist/presets/multiTenant.mjs +22 -162
- package/dist/presets/search.d.mts +2 -2
- package/dist/presets/search.mjs +2 -2
- package/dist/{presets-BdlugYTc.mjs → presets-Cpvj6fGO.mjs} +99 -107
- package/dist/{projection-Uvgp-EEj.mjs → projection-Ce4aQhQk.mjs} +2 -2
- package/dist/{pushref-registry-Dov9l4qE.d.mts → pushref-registry-UxCkoA4C.d.mts} +1 -1
- package/dist/{queryCachePlugin-BzqxconG.d.mts → queryCachePlugin-C2lZjTxk.d.mts} +1 -1
- package/dist/{queryCachePlugin-B5F09bYr.mjs → queryCachePlugin-CFWNH8aT.mjs} +12 -14
- package/dist/{rateLimitKey-CK-EU2L8.mjs → rateLimitKey-C4O-kPm0.mjs} +1 -1
- package/dist/{realtime-FnnD0n3Z.mjs → realtime-DmPE9S54.mjs} +25 -21
- package/dist/redis-stream-BMk-bpQS.d.mts +219 -0
- package/dist/{redis-Bzi1y3F9.d.mts → redis-vLvtcGy_.d.mts} +13 -24
- package/dist/redisShapes-CQksY67q.d.mts +55 -0
- package/dist/registry/index.d.mts +9 -4
- package/dist/registry/index.mjs +4 -4
- package/dist/{registry-D3y8VEJ1.mjs → registry-Bcz9G1bh.mjs} +10 -8
- package/dist/{renewingLease-B3VYh5aU.mjs → renewingLease-qODa7KJQ.mjs} +1 -1
- package/dist/{replyHelpers-DPHsdw-5.mjs → replyHelpers-CG_VtVgS.mjs} +2 -2
- package/dist/{requestContext-cSVKnYPk.mjs → requestContext-Dsu9P1bV.mjs} +1 -1
- package/dist/requirement-CbiLdQ1n.d.mts +22 -0
- package/dist/requirement-KDdoRcOP.mjs +35 -0
- package/dist/resourceRateLimit-Wf6d6FL9.d.mts +30 -0
- package/dist/resourceRateLimit-dSjVr1pk.mjs +180 -0
- package/dist/{rolldown-runtime-DjDuyglE.mjs → rolldown-runtime-D1w9Y1VJ.mjs} +1 -1
- package/dist/routeValidator-D36SW8qt.mjs +70 -0
- package/dist/{runtimeCapabilities-DSA_dH1_.mjs → runtimeCapabilities-0tccjRlj.mjs} +1 -1
- package/dist/{schedules-BktJb3Y3.mjs → schedules-C-OpmHBq.mjs} +46 -37
- package/dist/{schemaConverter-BLCSmNbr.mjs → schemaConverter-BORek3O-.mjs} +2 -2
- package/dist/{schemaIR-K16Bb5BX.mjs → schemaIR-C7Vbp71I.mjs} +2 -2
- package/dist/schemas/index.d.mts +10 -0
- package/dist/schemas/index.mjs +8 -3
- package/dist/scim/index.mjs +2 -2
- package/dist/scope/index.d.mts +2 -2
- package/dist/scope/index.mjs +6 -6
- package/dist/{scope-jSxuBTFM.mjs → scope-DQthe88b.mjs} +4 -4
- package/dist/shutdown-MqA81z_P.mjs +59 -0
- package/dist/{simpleEqualityMatcher-BiOnyflP.mjs → simpleEqualityMatcher-auW8QjXN.mjs} +1 -1
- package/dist/{sse-CH7Slxc1.mjs → sse-CYzQBVFe.mjs} +11 -15
- package/dist/{sseStream-CN-Lh1Tu.mjs → sseStream-B5_Afiqn.mjs} +2 -2
- package/dist/{store-helpers-9DX9-1j_.mjs → store-helpers-BOzk-CuX.mjs} +1 -1
- package/dist/{streaming-dvppnxsr.mjs → streaming-DjTQhZrN.mjs} +1 -1
- package/dist/subscriberIdentity-CW39uA8G.mjs +28 -0
- package/dist/tenancy--ed9eH_z.mjs +43 -0
- package/dist/tenantPreparation-BKon6Yox.mjs +67 -0
- package/dist/testing/cleanupStoreContract.mjs +1 -1
- package/dist/testing/index.d.mts +193 -175
- package/dist/testing/index.mjs +286 -212
- package/dist/testing/outboxStoreContract.mjs +137 -2
- package/dist/testing/requiredPorts.d.mts +17 -0
- package/dist/testing/requiredPorts.mjs +65 -0
- package/dist/testing/storageContract.mjs +1 -1
- package/dist/timing-qczZ0YC-.mjs +46 -0
- package/dist/{transactionContext-DCWMI6hV.mjs → transactionContext-DjFkrNG6.mjs} +1 -1
- package/dist/{typeGuards-D7tHFdH6.mjs → typeGuards-CySF12G-.mjs} +1 -1
- package/dist/types/index.d.mts +4 -4
- package/dist/types/index.mjs +1 -1
- package/dist/types/storage.mjs +1 -1
- package/dist/{types-CWQn9-IC.d.mts → types-B4Aq4Sdj.d.mts} +22 -9
- package/dist/{types-DfVDvkdP.d.mts → types-BJ-2XqVF.d.mts} +189 -204
- package/dist/{types-CDy3-zw1.mjs → types-Bk9NOgOC.mjs} +4 -4
- package/dist/usage/index.d.mts +2 -3
- package/dist/usage/index.mjs +5 -5
- package/dist/{userHelpers-DDMOZ_Pm.mjs → userHelpers-aklawRaK.mjs} +1 -1
- package/dist/utils/index.d.mts +84 -223
- package/dist/utils/index.mjs +13 -12
- package/dist/{utils-B-zj_b__.mjs → utils-ec1oTo1n.mjs} +315 -404
- package/dist/{validate-R0G_JrnK.mjs → validate-9yzHC1e2.mjs} +6 -14
- package/dist/{versioning-D6JIK8Tk.d.mts → versioning-Bsb51gGT.d.mts} +28 -13
- package/dist/{versioning-DuVWcu-F.mjs → versioning-fhhWHV74.mjs} +4 -4
- package/dist/{websocket-BOXVggMf.d.mts → websocket-Cs4awVyD.d.mts} +1 -1
- package/dist/{workScope-BhsWde4k.mjs → workScope-zpPyge4t.mjs} +2 -2
- package/package.json +578 -574
- package/skills/arc/SKILL.md +13 -4
- package/skills/arc/references/auth.md +8 -5
- package/skills/arc/references/integrations.md +1 -1
- package/skills/arc/references/mcp.md +8 -6
- package/skills/arc/references/multi-tenancy.md +19 -14
- package/skills/arc/references/production.md +27 -10
- package/dist/actionPermissions-DPwPWPOV.mjs +0 -350
- package/dist/index-CFfLKMH4.d.mts +0 -290
- package/dist/interface-BIpgzbZl.mjs +0 -17
- package/dist/redis-stream-BPKscsTB.d.mts +0 -260
- package/dist/registerRoute-DRsSHPSM.mjs +0 -364
- package/dist/sync/changelog.d.mts +0 -3
- package/dist/sync/changelog.mjs +0 -3
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/*! @classytic/arc v2.43.0 | MIT | https://classytic.com */
|
|
2
|
+
//#region src/events/subscriberIdentity.ts
|
|
3
|
+
const SUBSCRIBER_ID = Symbol.for("@classytic/arc/subscriber-id");
|
|
4
|
+
/** Assign a deployment-stable identity for durable per-handler progress; never use an index. */
|
|
5
|
+
function withSubscriberId(id, handler) {
|
|
6
|
+
if (typeof id !== "string" || !id.trim()) throw new Error("[Arc Events] subscriber id must be non-empty");
|
|
7
|
+
const wrapped = async (event) => {
|
|
8
|
+
await handler(event);
|
|
9
|
+
};
|
|
10
|
+
Object.defineProperty(wrapped, SUBSCRIBER_ID, { value: id });
|
|
11
|
+
return wrapped;
|
|
12
|
+
}
|
|
13
|
+
function getSubscriberId(handler) {
|
|
14
|
+
const id = Reflect.get(handler, SUBSCRIBER_ID);
|
|
15
|
+
return typeof id === "string" ? id : void 0;
|
|
16
|
+
}
|
|
17
|
+
/** Preserve explicit IDs and the implicit function name through framework wrappers. */
|
|
18
|
+
function preserveSubscriberIdentity(original, wrapper) {
|
|
19
|
+
const id = getSubscriberId(original);
|
|
20
|
+
if (id !== void 0) Object.defineProperty(wrapper, SUBSCRIBER_ID, { value: id });
|
|
21
|
+
Object.defineProperty(wrapper, "name", {
|
|
22
|
+
value: original.name,
|
|
23
|
+
configurable: true
|
|
24
|
+
});
|
|
25
|
+
return wrapper;
|
|
26
|
+
}
|
|
27
|
+
//#endregion
|
|
28
|
+
export { preserveSubscriberIdentity as n, withSubscriberId as r, getSubscriberId as t };
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/*! @classytic/arc v2.43.0 | MIT | https://classytic.com */
|
|
2
|
+
import { b as isElevated, c as getOrgId } from "./types-Bk9NOgOC.mjs";
|
|
3
|
+
//#region src/core/tenancy.ts
|
|
4
|
+
function eventAudienceOf(scope) {
|
|
5
|
+
const tenancy = resolveTenancy({ _scope: scope }, "organizationId");
|
|
6
|
+
return tenancy.mode === "scoped" ? {
|
|
7
|
+
orgId: tenancy.orgId,
|
|
8
|
+
platformView: false
|
|
9
|
+
} : { platformView: tenancy.reason === "platform" };
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Whether an event tagged `eventOrgId` may reach `audience`. Untenanted events
|
|
13
|
+
* reach everyone; a tenant's event reaches that tenant and the platform view,
|
|
14
|
+
* and nobody else — including a caller with no org at all (fail-closed).
|
|
15
|
+
*/
|
|
16
|
+
function isEventVisible(eventOrgId, audience) {
|
|
17
|
+
if (eventOrgId === void 0 || eventOrgId === null || eventOrgId === "") return true;
|
|
18
|
+
if (audience.platformView) return true;
|
|
19
|
+
return audience.orgId !== void 0 && String(eventOrgId) === audience.orgId;
|
|
20
|
+
}
|
|
21
|
+
function resolveTenancy(input, tenantField) {
|
|
22
|
+
if (!tenantField) return {
|
|
23
|
+
mode: "unscoped",
|
|
24
|
+
reason: "untenanted-resource"
|
|
25
|
+
};
|
|
26
|
+
if (input?._crossTenantRead === true) return {
|
|
27
|
+
mode: "unscoped",
|
|
28
|
+
reason: "cross-tenant"
|
|
29
|
+
};
|
|
30
|
+
const scope = input?._scope;
|
|
31
|
+
const orgId = scope ? getOrgId(scope) : void 0;
|
|
32
|
+
if (orgId) return {
|
|
33
|
+
mode: "scoped",
|
|
34
|
+
field: tenantField,
|
|
35
|
+
orgId
|
|
36
|
+
};
|
|
37
|
+
return {
|
|
38
|
+
mode: "unscoped",
|
|
39
|
+
reason: scope && isElevated(scope) ? "platform" : "no-org"
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
//#endregion
|
|
43
|
+
export { isEventVisible as n, resolveTenancy as r, eventAudienceOf as t };
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/*! @classytic/arc v2.43.0 | MIT | https://classytic.com */
|
|
2
|
+
import { t as conjoinPolicyFilters } from "./filter-merge-IpGywvym.mjs";
|
|
3
|
+
import { d as createDomainError } from "./errors-CtrywMHP.mjs";
|
|
4
|
+
import { b as isElevated, c as getOrgId, d as getScopeContext, m as getTeamId, v as hasOrgAccess } from "./types-Bk9NOgOC.mjs";
|
|
5
|
+
//#region src/permissions/tenantPreparation.ts
|
|
6
|
+
/** Tenant preparation shared by HTTP middleware and direct transport invocation. */
|
|
7
|
+
const PREPARATION = Symbol.for("arc.tenantPreparation");
|
|
8
|
+
function tagTenantPreparation(middleware, preparation) {
|
|
9
|
+
Object.defineProperty(middleware, PREPARATION, { value: preparation });
|
|
10
|
+
return middleware;
|
|
11
|
+
}
|
|
12
|
+
function isTenantPreparation(middleware) {
|
|
13
|
+
return typeof middleware === "function" && middleware[PREPARATION] !== void 0;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* The resource's tenant middlewares minus body INJECTION: an action payload is a command, not
|
|
17
|
+
* the document, so it is scoped (filters, `_tenantFields`) but never written into.
|
|
18
|
+
*/
|
|
19
|
+
function withoutTenantInjection(middlewares) {
|
|
20
|
+
return (middlewares ?? []).filter((middleware) => {
|
|
21
|
+
if (typeof middleware !== "function") return true;
|
|
22
|
+
const preparation = Reflect.get(middleware, PREPARATION);
|
|
23
|
+
return !(typeof preparation === "object" && preparation !== null && "mode" in preparation && preparation.mode === "inject");
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
function prepareTenancy(scope, preparation) {
|
|
27
|
+
if (preparation.mode === "crossTenant") return { crossTenant: true };
|
|
28
|
+
const elevated = isElevated(scope);
|
|
29
|
+
if (preparation.mode === "inject" && elevated && !getOrgId(scope)) return {};
|
|
30
|
+
if (preparation.mode !== "inject" && !elevated && !hasOrgAccess(scope)) {
|
|
31
|
+
if (preparation.mode === "flexible") return {};
|
|
32
|
+
throw createDomainError(scope.kind === "public" ? "arc.unauthorized" : "arc.forbidden", scope.kind === "public" ? "Authentication required for multi-tenant resources" : "Organization context required for this operation", scope.kind === "public" ? 401 : 403);
|
|
33
|
+
}
|
|
34
|
+
const fields = {};
|
|
35
|
+
const missing = [];
|
|
36
|
+
for (const spec of preparation.specs) {
|
|
37
|
+
const value = "contextKey" in spec ? getScopeContext(scope, spec.contextKey) : spec.type === "org" ? getOrgId(scope) : getTeamId(scope);
|
|
38
|
+
if (value === void 0) missing.push(spec.field);
|
|
39
|
+
else fields[spec.field] = value;
|
|
40
|
+
}
|
|
41
|
+
if (missing.length && (preparation.mode === "inject" || !elevated)) throw createDomainError("arc.forbidden", `Tenant context incomplete — missing: ${missing.join(", ")}`, 403);
|
|
42
|
+
return {
|
|
43
|
+
fields,
|
|
44
|
+
...preparation.mode === "inject" ? { inject: true } : { policy: fields }
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
function applyTenantPreparation(target, body, scope, preparation) {
|
|
48
|
+
const result = prepareTenancy(scope, preparation);
|
|
49
|
+
if (result.crossTenant) target._crossTenantRead = true;
|
|
50
|
+
if (result.policy && Object.keys(result.policy).length) target._policyFilters = conjoinPolicyFilters(target._policyFilters, result.policy);
|
|
51
|
+
if (result.fields && Object.keys(result.fields).length) target._tenantFields = {
|
|
52
|
+
...target._tenantFields,
|
|
53
|
+
...result.fields
|
|
54
|
+
};
|
|
55
|
+
if (result.inject && body && typeof body === "object") Object.assign(body, result.fields);
|
|
56
|
+
}
|
|
57
|
+
function prepareTenantContext(ctx, middlewares) {
|
|
58
|
+
const meta = ctx.metadata;
|
|
59
|
+
for (const middleware of middlewares ?? []) {
|
|
60
|
+
if (typeof middleware !== "function") continue;
|
|
61
|
+
const preparation = middleware[PREPARATION];
|
|
62
|
+
if (preparation) applyTenantPreparation(meta, ctx.body, meta._scope ?? { kind: "public" }, preparation);
|
|
63
|
+
if (meta._tenantFields) ctx._tenantFields = meta._tenantFields;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
//#endregion
|
|
67
|
+
export { withoutTenantInjection as a, tagTenantPreparation as i, isTenantPreparation as n, prepareTenantContext as r, applyTenantPreparation as t };
|
package/dist/testing/index.d.mts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
|
|
2
2
|
import { i as RequestScope } from "../types-Ddl9OjmE.mjs";
|
|
3
|
-
import { l as PermissionCheck, t as AnyRecord } from "../base-
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import "../index-
|
|
7
|
-
import { n as
|
|
3
|
+
import { l as PermissionCheck, t as AnyRecord } from "../base-CZyixEQR.mjs";
|
|
4
|
+
import { F as ResourceDefinition } from "../index-mqaqZNpe.mjs";
|
|
5
|
+
import { h as ModuleSetupContext, m as ModuleDisposer, o as ArcPorts, p as PortName, r as ArcModuleInput, u as ModuleName } from "../types-BJ-2XqVF.mjs";
|
|
6
|
+
import "../index-JdbZAIsm.mjs";
|
|
7
|
+
import { n as CreateAppOptions } from "../index-C8zVGYsk.mjs";
|
|
8
8
|
import { StorageContractSetup, StorageContractSetupResult, runStorageContract } from "./storageContract.mjs";
|
|
9
9
|
import { StandardRepo } from "@classytic/repo-core/repository";
|
|
10
10
|
import { FastifyInstance, FastifyServerOptions } from "fastify";
|
|
@@ -74,6 +74,180 @@ interface ArcAssertion {
|
|
|
74
74
|
}
|
|
75
75
|
declare function expectArc(response: ArcResponseLike): ArcAssertion;
|
|
76
76
|
//#endregion
|
|
77
|
+
//#region src/testing/testActor.d.ts
|
|
78
|
+
/** The header the testing harness uses to carry a request's actor. */
|
|
79
|
+
declare const TEST_ACTOR_HEADER = "x-test-actor";
|
|
80
|
+
/**
|
|
81
|
+
* What a test wants to be. An `organizationId` produces a `member` scope (the
|
|
82
|
+
* org-scoped case); without one the actor is `authenticated`.
|
|
83
|
+
*/
|
|
84
|
+
interface TestActor {
|
|
85
|
+
/**
|
|
86
|
+
* Defaults to a synthetic id derived from the roles. Pass one explicitly when
|
|
87
|
+
* a test distinguishes two actors — ownership and audit assertions do, and
|
|
88
|
+
* two actors sharing roles would otherwise share an id.
|
|
89
|
+
*/
|
|
90
|
+
userId?: string;
|
|
91
|
+
/** Global roles — what `requireRoles` reads. */
|
|
92
|
+
roles?: string[];
|
|
93
|
+
/** Tenant. Present ⇒ a `member` scope. */
|
|
94
|
+
organizationId?: string;
|
|
95
|
+
/** Org-level roles from membership records. Defaults to `roles`. */
|
|
96
|
+
orgRoles?: string[];
|
|
97
|
+
teamId?: string;
|
|
98
|
+
/** App-defined dimensions (branch, project, region) read via `getScopeContext`. */
|
|
99
|
+
context?: Record<string, string>;
|
|
100
|
+
/**
|
|
101
|
+
* Elevated (platform-admin) scope. The value is the `elevatedBy` audit string
|
|
102
|
+
* arc requires — an elevated scope must record who granted it. `true` is
|
|
103
|
+
* shorthand for `'test'`.
|
|
104
|
+
*
|
|
105
|
+
* An elevated scope carries no roles: elevation is a different kind of actor,
|
|
106
|
+
* not a role. A test needing a role gate AND elevation wants `member`.
|
|
107
|
+
*/
|
|
108
|
+
elevated?: string | boolean;
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Build the headers that make an injected request act as `actor`.
|
|
112
|
+
*
|
|
113
|
+
* A string is shorthand for a single role. `null` means "stay public", so a test
|
|
114
|
+
* can prove an endpoint is reachable anonymously.
|
|
115
|
+
*
|
|
116
|
+
* @example
|
|
117
|
+
* ```ts
|
|
118
|
+
* t.app.inject({ method: 'GET', url: '/pos/shifts/current',
|
|
119
|
+
* headers: testActorHeaders('manager', ORG) });
|
|
120
|
+
* ```
|
|
121
|
+
*/
|
|
122
|
+
declare function testActorHeaders(actor: TestActor | string | null, organizationId?: string): Record<string, string>;
|
|
123
|
+
/**
|
|
124
|
+
* Turn a spec into a `RequestScope`.
|
|
125
|
+
*
|
|
126
|
+
* `orgRoles` defaults to `roles`: a test that says "manager" means it at both
|
|
127
|
+
* levels, and making the caller repeat itself only produces tests that pass a
|
|
128
|
+
* global gate and mysteriously fail an org one.
|
|
129
|
+
*/
|
|
130
|
+
declare function scopeFromTestActor(spec: TestActor): RequestScope;
|
|
131
|
+
/**
|
|
132
|
+
* Build the `auth` option that makes the test actor arc's authenticator.
|
|
133
|
+
*
|
|
134
|
+
* It must be an authenticator, not a hook. Fastify encapsulates plugins and
|
|
135
|
+
* modules register DURING `createApp`, so a hook added to the root instance
|
|
136
|
+
* afterwards never runs for their routes — every request still resolves as
|
|
137
|
+
* public and every role gate still answers 401, which reads exactly like a
|
|
138
|
+
* broken authorization rule in the code under test. Registering as
|
|
139
|
+
* `auth: { type: 'authenticator' }` puts the scope where arc's own auth would,
|
|
140
|
+
* at the phase arc expects, so tenant filtering and permission checks behave
|
|
141
|
+
* identically to production.
|
|
142
|
+
*
|
|
143
|
+
* `defaultActor` applies to requests carrying no header. A malformed header
|
|
144
|
+
* REJECTS rather than falling back to public — a test that meant to be an admin
|
|
145
|
+
* and got quietly demoted is the most expensive way to fail.
|
|
146
|
+
*/
|
|
147
|
+
declare function testActorAuth(defaultActor?: TestActor): {
|
|
148
|
+
type: "authenticator";
|
|
149
|
+
authenticate: (request: unknown) => Promise<void>;
|
|
150
|
+
};
|
|
151
|
+
//#endregion
|
|
152
|
+
//#region src/testing/bootModuleApp.d.ts
|
|
153
|
+
/** Handed to a modules factory — the in-memory DB is live at call time. */
|
|
154
|
+
/**
|
|
155
|
+
* A provisioned test database — what the `database` seam yields. Arc
|
|
156
|
+
* core never names a driver: kits ship factories (`mongoMemoryDatabase`
|
|
157
|
+
* is the built-in default; a sqlitekit/pgkit testkit ships its own) and
|
|
158
|
+
* the connection flows through as an opaque generic.
|
|
159
|
+
*/
|
|
160
|
+
interface TestDatabase<TConn = unknown> {
|
|
161
|
+
/** Connection string, when the backend has one (informational). */
|
|
162
|
+
uri: string;
|
|
163
|
+
/** Driver connection handle passed to the modules factory. */
|
|
164
|
+
connection: TConn;
|
|
165
|
+
/** Stop/disconnect everything this factory started. MUST be idempotent-safe to call once. */
|
|
166
|
+
teardown(): Promise<void>;
|
|
167
|
+
}
|
|
168
|
+
/** The DB-provisioning seam — `bootModuleApp({ database })`. */
|
|
169
|
+
type TestDatabaseFactory<TConn = unknown> = (opts: {
|
|
170
|
+
replset?: boolean;
|
|
171
|
+
}) => Promise<TestDatabase<TConn>>;
|
|
172
|
+
interface TestkitContext<TConn = unknown> {
|
|
173
|
+
/** The provisioned database's connection string. */
|
|
174
|
+
uri: string;
|
|
175
|
+
/**
|
|
176
|
+
* Driver connection handle (the default factory yields mongoose's
|
|
177
|
+
* DEFAULT connection). Generic so DB-typed wrappers assert the driver
|
|
178
|
+
* type ONCE: `bootModuleApp<Connection>(...)`. Arc core stays DB-agnostic.
|
|
179
|
+
*/
|
|
180
|
+
connection: TConn;
|
|
181
|
+
}
|
|
182
|
+
type BootModulesInput<TConn = unknown> = ArcModuleInput | readonly ArcModuleInput[] | ((ctx: TestkitContext<TConn>) => ArcModuleInput | readonly ArcModuleInput[] | Promise<ArcModuleInput | readonly ArcModuleInput[]>);
|
|
183
|
+
interface BootModuleAppOptions<TConn = unknown> {
|
|
184
|
+
/**
|
|
185
|
+
* DB provisioner. Default: `mongoMemoryDatabase` (in-memory MongoDB —
|
|
186
|
+
* lazy devDep imports, nothing Mongo touches arc's runtime). Kits for
|
|
187
|
+
* other stores inject their own factory here; the rest of the harness
|
|
188
|
+
* (module resolution, app boot, teardown ordering) is DB-agnostic.
|
|
189
|
+
*/
|
|
190
|
+
database?: TestDatabaseFactory<TConn>;
|
|
191
|
+
/** Single-node replica set (transactions). Default: standalone. Passed to the database factory. */
|
|
192
|
+
replset?: boolean;
|
|
193
|
+
/**
|
|
194
|
+
* Extra `createApp` options merged over the testing defaults
|
|
195
|
+
* (`preset: 'testing'`, `auth: false`, `sensible: false` — utility
|
|
196
|
+
* plugins stay off so the testkit is dependency-light; re-enable per
|
|
197
|
+
* boot when a module genuinely exercises one).
|
|
198
|
+
*/
|
|
199
|
+
app?: Partial<CreateAppOptions>;
|
|
200
|
+
/**
|
|
201
|
+
* Default actor for every injected request that carries no `x-test-actor` header.
|
|
202
|
+
*
|
|
203
|
+
* The harness boots with `auth: false`, so without this every request is
|
|
204
|
+
* `PUBLIC_SCOPE` and role-gated routes answer 401 — correct, but it left tests with
|
|
205
|
+
* no supported way to be somebody. See `testActor.ts` for why the seam lives in the
|
|
206
|
+
* testing entrypoint and can never be reached from production.
|
|
207
|
+
*/
|
|
208
|
+
actor?: TestActor;
|
|
209
|
+
/** Host ports for the booted modules — typed and checked exactly as `createApp({ ports })`. */
|
|
210
|
+
ports?: ArcPorts;
|
|
211
|
+
}
|
|
212
|
+
interface ModuleTestApp<TConn = unknown> {
|
|
213
|
+
app: FastifyInstance;
|
|
214
|
+
/** The provisioned database's connection string. */
|
|
215
|
+
uri: string;
|
|
216
|
+
/** The mongoose `Connection` handed to the modules factory. */
|
|
217
|
+
connection: TConn;
|
|
218
|
+
/**
|
|
219
|
+
* Typed module-export accessor — `t.exports<CatalogEngine>('catalog')`
|
|
220
|
+
* instead of reaching through `(t.app as ...).arc.modules.catalog`.
|
|
221
|
+
* Delegates to `getModuleExports` (throws with the registered-module
|
|
222
|
+
* list when the name is unknown — same contract hosts get).
|
|
223
|
+
*/
|
|
224
|
+
exports<TExports = unknown>(name: string): TExports;
|
|
225
|
+
/** Close app → disconnect mongoose → stop the in-memory server. Idempotent. */
|
|
226
|
+
close(): Promise<void>;
|
|
227
|
+
}
|
|
228
|
+
/**
|
|
229
|
+
* The built-in DB factory: in-memory MongoDB via lazy devDep imports.
|
|
230
|
+
* Everything Mongo-specific about the harness lives HERE — the harness
|
|
231
|
+
* itself is driver-blind and any kit can replace this via `database`.
|
|
232
|
+
*/
|
|
233
|
+
declare function mongoMemoryDatabase(opts: {
|
|
234
|
+
replset?: boolean;
|
|
235
|
+
}): Promise<TestDatabase<unknown>>;
|
|
236
|
+
declare function bootModuleApp<TConn = unknown>(modules: BootModulesInput<TConn>, options?: BootModuleAppOptions<TConn>): Promise<ModuleTestApp<TConn>>;
|
|
237
|
+
//#endregion
|
|
238
|
+
//#region src/testing/assertModuleBootsAlone.d.ts
|
|
239
|
+
interface ModuleBootsAloneOptions<TConn = unknown> extends BootModuleAppOptions<TConn> {
|
|
240
|
+
/** Runs against the booted closure before it closes — drive the module's cross-module ports. */
|
|
241
|
+
probe?: (t: ModuleTestApp<TConn>) => void | Promise<void>;
|
|
242
|
+
}
|
|
243
|
+
interface ModuleBootsAloneResult {
|
|
244
|
+
/** The closure's modules (bridges excluded), in input order. */
|
|
245
|
+
readonly closure: readonly string[];
|
|
246
|
+
}
|
|
247
|
+
declare function assertModuleBootsAlone<TConn = unknown>(modules: BootModulesInput<TConn>,
|
|
248
|
+
/** The module — or several roots booted together, when a deployment rule pairs them. */
|
|
249
|
+
name: ModuleName | readonly ModuleName[], options?: ModuleBootsAloneOptions<TConn>): Promise<ModuleBootsAloneResult>;
|
|
250
|
+
//#endregion
|
|
77
251
|
//#region src/testing/authorizationConformance.d.ts
|
|
78
252
|
/** One surface's observed enforcement: the HTTP status + the filter arc passed the repo. */
|
|
79
253
|
interface SurfaceObservation {
|
|
@@ -341,181 +515,22 @@ declare function createBetterAuthTestHelpers(options?: BetterAuthTestHelpersOpti
|
|
|
341
515
|
*/
|
|
342
516
|
declare function setupBetterAuthTestApp(input: SetupBetterAuthTestAppInput): Promise<SetupBetterAuthTestAppResult>;
|
|
343
517
|
//#endregion
|
|
344
|
-
//#region src/testing/testActor.d.ts
|
|
345
|
-
/** The header the testing harness uses to carry a request's actor. */
|
|
346
|
-
declare const TEST_ACTOR_HEADER = "x-test-actor";
|
|
347
|
-
/**
|
|
348
|
-
* What a test wants to be. An `organizationId` produces a `member` scope (the
|
|
349
|
-
* org-scoped case); without one the actor is `authenticated`.
|
|
350
|
-
*/
|
|
351
|
-
interface TestActor {
|
|
352
|
-
/**
|
|
353
|
-
* Defaults to a synthetic id derived from the roles. Pass one explicitly when
|
|
354
|
-
* a test distinguishes two actors — ownership and audit assertions do, and
|
|
355
|
-
* two actors sharing roles would otherwise share an id.
|
|
356
|
-
*/
|
|
357
|
-
userId?: string;
|
|
358
|
-
/** Global roles — what `requireRoles` reads. */
|
|
359
|
-
roles?: string[];
|
|
360
|
-
/** Tenant. Present ⇒ a `member` scope. */
|
|
361
|
-
organizationId?: string;
|
|
362
|
-
/** Org-level roles from membership records. Defaults to `roles`. */
|
|
363
|
-
orgRoles?: string[];
|
|
364
|
-
teamId?: string;
|
|
365
|
-
/** App-defined dimensions (branch, project, region) read via `getScopeContext`. */
|
|
366
|
-
context?: Record<string, string>;
|
|
367
|
-
/**
|
|
368
|
-
* Elevated (platform-admin) scope. The value is the `elevatedBy` audit string
|
|
369
|
-
* arc requires — an elevated scope must record who granted it. `true` is
|
|
370
|
-
* shorthand for `'test'`.
|
|
371
|
-
*
|
|
372
|
-
* An elevated scope carries no roles: elevation is a different kind of actor,
|
|
373
|
-
* not a role. A test needing a role gate AND elevation wants `member`.
|
|
374
|
-
*/
|
|
375
|
-
elevated?: string | boolean;
|
|
376
|
-
}
|
|
377
|
-
/**
|
|
378
|
-
* Build the headers that make an injected request act as `actor`.
|
|
379
|
-
*
|
|
380
|
-
* A string is shorthand for a single role. `null` means "stay public", so a test
|
|
381
|
-
* can prove an endpoint is reachable anonymously.
|
|
382
|
-
*
|
|
383
|
-
* @example
|
|
384
|
-
* ```ts
|
|
385
|
-
* t.app.inject({ method: 'GET', url: '/pos/shifts/current',
|
|
386
|
-
* headers: testActorHeaders('manager', ORG) });
|
|
387
|
-
* ```
|
|
388
|
-
*/
|
|
389
|
-
declare function testActorHeaders(actor: TestActor | string | null, organizationId?: string): Record<string, string>;
|
|
390
|
-
/**
|
|
391
|
-
* Turn a spec into a `RequestScope`.
|
|
392
|
-
*
|
|
393
|
-
* `orgRoles` defaults to `roles`: a test that says "manager" means it at both
|
|
394
|
-
* levels, and making the caller repeat itself only produces tests that pass a
|
|
395
|
-
* global gate and mysteriously fail an org one.
|
|
396
|
-
*/
|
|
397
|
-
declare function scopeFromTestActor(spec: TestActor): RequestScope;
|
|
398
|
-
/**
|
|
399
|
-
* Build the `auth` option that makes the test actor arc's authenticator.
|
|
400
|
-
*
|
|
401
|
-
* It must be an authenticator, not a hook. Fastify encapsulates plugins and
|
|
402
|
-
* modules register DURING `createApp`, so a hook added to the root instance
|
|
403
|
-
* afterwards never runs for their routes — every request still resolves as
|
|
404
|
-
* public and every role gate still answers 401, which reads exactly like a
|
|
405
|
-
* broken authorization rule in the code under test. Registering as
|
|
406
|
-
* `auth: { type: 'authenticator' }` puts the scope where arc's own auth would,
|
|
407
|
-
* at the phase arc expects, so tenant filtering and permission checks behave
|
|
408
|
-
* identically to production.
|
|
409
|
-
*
|
|
410
|
-
* `defaultActor` applies to requests carrying no header. A malformed header
|
|
411
|
-
* REJECTS rather than falling back to public — a test that meant to be an admin
|
|
412
|
-
* and got quietly demoted is the most expensive way to fail.
|
|
413
|
-
*/
|
|
414
|
-
declare function testActorAuth(defaultActor?: TestActor): {
|
|
415
|
-
type: "authenticator";
|
|
416
|
-
authenticate: (request: unknown) => Promise<void>;
|
|
417
|
-
};
|
|
418
|
-
//#endregion
|
|
419
|
-
//#region src/testing/bootModuleApp.d.ts
|
|
420
|
-
/** Handed to a modules factory — the in-memory DB is live at call time. */
|
|
421
|
-
/**
|
|
422
|
-
* A provisioned test database — what the `database` seam yields. Arc
|
|
423
|
-
* core never names a driver: kits ship factories (`mongoMemoryDatabase`
|
|
424
|
-
* is the built-in default; a sqlitekit/pgkit testkit ships its own) and
|
|
425
|
-
* the connection flows through as an opaque generic.
|
|
426
|
-
*/
|
|
427
|
-
interface TestDatabase<TConn = unknown> {
|
|
428
|
-
/** Connection string, when the backend has one (informational). */
|
|
429
|
-
uri: string;
|
|
430
|
-
/** Driver connection handle passed to the modules factory. */
|
|
431
|
-
connection: TConn;
|
|
432
|
-
/** Stop/disconnect everything this factory started. MUST be idempotent-safe to call once. */
|
|
433
|
-
teardown(): Promise<void>;
|
|
434
|
-
}
|
|
435
|
-
/** The DB-provisioning seam — `bootModuleApp({ database })`. */
|
|
436
|
-
type TestDatabaseFactory<TConn = unknown> = (opts: {
|
|
437
|
-
replset?: boolean;
|
|
438
|
-
}) => Promise<TestDatabase<TConn>>;
|
|
439
|
-
interface TestkitContext<TConn = unknown> {
|
|
440
|
-
/** The provisioned database's connection string. */
|
|
441
|
-
uri: string;
|
|
442
|
-
/**
|
|
443
|
-
* Driver connection handle (the default factory yields mongoose's
|
|
444
|
-
* DEFAULT connection). Generic so DB-typed wrappers assert the driver
|
|
445
|
-
* type ONCE: `bootModuleApp<Connection>(...)`. Arc core stays DB-agnostic.
|
|
446
|
-
*/
|
|
447
|
-
connection: TConn;
|
|
448
|
-
}
|
|
449
|
-
type BootModulesInput<TConn = unknown> = ArcModuleInput | readonly ArcModuleInput[] | ((ctx: TestkitContext<TConn>) => ArcModuleInput | readonly ArcModuleInput[] | Promise<ArcModuleInput | readonly ArcModuleInput[]>);
|
|
450
|
-
interface BootModuleAppOptions<TConn = unknown> {
|
|
451
|
-
/**
|
|
452
|
-
* DB provisioner. Default: `mongoMemoryDatabase` (in-memory MongoDB —
|
|
453
|
-
* lazy devDep imports, nothing Mongo touches arc's runtime). Kits for
|
|
454
|
-
* other stores inject their own factory here; the rest of the harness
|
|
455
|
-
* (module resolution, app boot, teardown ordering) is DB-agnostic.
|
|
456
|
-
*/
|
|
457
|
-
database?: TestDatabaseFactory<TConn>;
|
|
458
|
-
/** Single-node replica set (transactions). Default: standalone. Passed to the database factory. */
|
|
459
|
-
replset?: boolean;
|
|
460
|
-
/**
|
|
461
|
-
* Extra `createApp` options merged over the testing defaults
|
|
462
|
-
* (`preset: 'testing'`, `auth: false`, `sensible: false` — utility
|
|
463
|
-
* plugins stay off so the testkit is dependency-light; re-enable per
|
|
464
|
-
* boot when a module genuinely exercises one).
|
|
465
|
-
*/
|
|
466
|
-
app?: Partial<CreateAppOptions>;
|
|
467
|
-
/**
|
|
468
|
-
* Default actor for every injected request that carries no `x-test-actor` header.
|
|
469
|
-
*
|
|
470
|
-
* The harness boots with `auth: false`, so without this every request is
|
|
471
|
-
* `PUBLIC_SCOPE` and role-gated routes answer 401 — correct, but it left tests with
|
|
472
|
-
* no supported way to be somebody. See `testActor.ts` for why the seam lives in the
|
|
473
|
-
* testing entrypoint and can never be reached from production.
|
|
474
|
-
*/
|
|
475
|
-
actor?: TestActor;
|
|
476
|
-
}
|
|
477
|
-
interface ModuleTestApp<TConn = unknown> {
|
|
478
|
-
app: FastifyInstance;
|
|
479
|
-
/** The provisioned database's connection string. */
|
|
480
|
-
uri: string;
|
|
481
|
-
/** The mongoose `Connection` handed to the modules factory. */
|
|
482
|
-
connection: TConn;
|
|
483
|
-
/**
|
|
484
|
-
* Typed module-export accessor — `t.exports<CatalogEngine>('catalog')`
|
|
485
|
-
* instead of reaching through `(t.app as ...).arc.modules.catalog`.
|
|
486
|
-
* Delegates to `getModuleExports` (throws with the registered-module
|
|
487
|
-
* list when the name is unknown — same contract hosts get).
|
|
488
|
-
*/
|
|
489
|
-
exports<TExports = unknown>(name: string): TExports;
|
|
490
|
-
/** Close app → disconnect mongoose → stop the in-memory server. Idempotent. */
|
|
491
|
-
close(): Promise<void>;
|
|
492
|
-
}
|
|
493
|
-
/**
|
|
494
|
-
* The built-in DB factory: in-memory MongoDB via lazy devDep imports.
|
|
495
|
-
* Everything Mongo-specific about the harness lives HERE — the harness
|
|
496
|
-
* itself is driver-blind and any kit can replace this via `database`.
|
|
497
|
-
*/
|
|
498
|
-
declare function mongoMemoryDatabase(opts: {
|
|
499
|
-
replset?: boolean;
|
|
500
|
-
}): Promise<TestDatabase<unknown>>;
|
|
501
|
-
declare function bootModuleApp<TConn = unknown>(modules: BootModulesInput<TConn>, options?: BootModuleAppOptions<TConn>): Promise<ModuleTestApp<TConn>>;
|
|
502
|
-
//#endregion
|
|
503
518
|
//#region src/testing/fixtures.d.ts
|
|
504
|
-
type FixtureFactory<T extends
|
|
519
|
+
type FixtureFactory<T extends object = AnyRecord> = (data: Partial<T>) => Promise<T>;
|
|
505
520
|
/** Delete hook invoked by `clear()` for records that were created through a factory. */
|
|
506
|
-
type FixtureDestroyer<T extends
|
|
507
|
-
interface FixtureRegistration<T extends
|
|
521
|
+
type FixtureDestroyer<T extends object = AnyRecord> = (record: T) => Promise<void>;
|
|
522
|
+
interface FixtureRegistration<T extends object = AnyRecord> {
|
|
508
523
|
create: FixtureFactory<T>;
|
|
509
524
|
/** Optional cleanup. Defaults to a no-op; adapters that support deletion should provide one. */
|
|
510
525
|
destroy?: FixtureDestroyer<T>;
|
|
511
526
|
}
|
|
512
527
|
interface TestFixtures {
|
|
513
528
|
/** Register a named factory. Later calls replace the earlier registration. */
|
|
514
|
-
register<T extends
|
|
529
|
+
register<T extends object = AnyRecord>(name: string, factoryOrRegistration: FixtureFactory<T> | FixtureRegistration<T>): void;
|
|
515
530
|
/** Create one record through the named factory. Tracked for cleanup. */
|
|
516
|
-
create<T extends
|
|
531
|
+
create<T extends object = AnyRecord>(name: string, data?: Partial<T>): Promise<T>;
|
|
517
532
|
/** Create many records with a shared template. Tracked for cleanup. */
|
|
518
|
-
createMany<T extends
|
|
533
|
+
createMany<T extends object = AnyRecord>(name: string, count: number, template?: Partial<T>): Promise<T[]>;
|
|
519
534
|
/**
|
|
520
535
|
* Run every registered `destroy` hook over the records this instance
|
|
521
536
|
* created, then forget them. Safe to call multiple times (idempotent).
|
|
@@ -524,7 +539,7 @@ interface TestFixtures {
|
|
|
524
539
|
*/
|
|
525
540
|
clear(): Promise<void>;
|
|
526
541
|
/** All records ever created by a given factory name (read-only snapshot). */
|
|
527
|
-
all<T extends
|
|
542
|
+
all<T extends object = AnyRecord>(name: string): readonly T[];
|
|
528
543
|
/** Registered factory names. */
|
|
529
544
|
readonly names: readonly string[];
|
|
530
545
|
}
|
|
@@ -591,7 +606,7 @@ interface MockRepository<T> extends StandardRepo<T> {
|
|
|
591
606
|
*
|
|
592
607
|
* await mockRepo.getById('1'); // Returns mocked product
|
|
593
608
|
*/
|
|
594
|
-
declare function createMockRepository<T extends
|
|
609
|
+
declare function createMockRepository<T extends object = AnyRecord>(overrides?: Partial<MockRepository<T>>): MockRepository<T>;
|
|
595
610
|
/**
|
|
596
611
|
* Create a mock user for authentication testing
|
|
597
612
|
*/
|
|
@@ -695,7 +710,7 @@ declare function createTestTimer(): {
|
|
|
695
710
|
//#region src/testing/moduleSetup.d.ts
|
|
696
711
|
interface TestModuleSetup {
|
|
697
712
|
/** Pass this as the second argument to `bootstrap` / `plugins` / `afterResources`. */
|
|
698
|
-
readonly context: ModuleSetupContext
|
|
713
|
+
readonly context: ModuleSetupContext<PortName, PortName>;
|
|
699
714
|
/**
|
|
700
715
|
* Disposers the module registered, in REGISTRATION order (`dispose()` runs
|
|
701
716
|
* them backwards). Assert on `.length` to prove a phase registered teardown
|
|
@@ -712,7 +727,10 @@ interface TestModuleSetup {
|
|
|
712
727
|
*/
|
|
713
728
|
dispose(): Promise<void>;
|
|
714
729
|
}
|
|
715
|
-
declare function createTestModuleSetup(
|
|
730
|
+
declare function createTestModuleSetup(options?: {
|
|
731
|
+
/** The `context.ports` the phases see — supply what the module declares. */
|
|
732
|
+
readonly ports?: ArcPorts;
|
|
733
|
+
}): TestModuleSetup;
|
|
716
734
|
//#endregion
|
|
717
735
|
//#region src/testing/scopeRoleGate.d.ts
|
|
718
736
|
/**
|
|
@@ -816,4 +834,4 @@ declare function createTestApp(options?: CreateTestAppOptions): Promise<TestAppC
|
|
|
816
834
|
*/
|
|
817
835
|
declare function createMinimalTestApp(options?: FastifyServerOptions): FastifyInstance;
|
|
818
836
|
//#endregion
|
|
819
|
-
export { type ArcAssertion, type ArcResponseLike, type AuthMode, type AuthResponse, type AuthorizationConformance, type BetterAuthTestHelpers, type BetterAuthTestHelpersOptions, type BetterAuthTestUser, type BootModuleAppOptions, type BootModulesInput, type CreateOrgInput, type CreateTestAppOptions, type DbMode, type FixtureDestroyer, type FixtureFactory, type FixtureRegistration, HttpTestHarness, type HttpTestHarnessOptions, type MockRepository, type ModuleTestApp, type OrgResponse, type RoleConfig, type SetupBetterAuthTestAppInput, type SetupBetterAuthTestAppResult, type SignInInput, type SignUpInput, type StorageContractSetup, type StorageContractSetupResult, type SurfaceObservation, TEST_ACTOR_HEADER, type TestActor, type TestAppContext, type TestAuthProvider, type TestAuthSession, type TestDatabase, type TestDatabaseFactory, type TestFixtures, type TestModuleSetup, type TestkitContext, bootModuleApp, createBetterAuthProvider, createBetterAuthTestHelpers, createCustomAuthProvider, createDataFactory, createHttpTestHarness, createJwtAuthProvider, createMinimalTestApp, createMockController, createMockReply, createMockRepository, createMockRequest, createMockUser, createSpy, createTestApp, createTestFixtures, createTestModuleSetup, createTestTimer, expectArc, mongoMemoryDatabase,
|
|
837
|
+
export { type ArcAssertion, type ArcResponseLike, type AuthMode, type AuthResponse, type AuthorizationConformance, type BetterAuthTestHelpers, type BetterAuthTestHelpersOptions, type BetterAuthTestUser, type BootModuleAppOptions, type BootModulesInput, type CreateOrgInput, type CreateTestAppOptions, type DbMode, type FixtureDestroyer, type FixtureFactory, type FixtureRegistration, HttpTestHarness, type HttpTestHarnessOptions, type MockRepository, type ModuleBootsAloneOptions, type ModuleBootsAloneResult, type ModuleTestApp, type OrgResponse, type RoleConfig, type SetupBetterAuthTestAppInput, type SetupBetterAuthTestAppResult, type SignInInput, type SignUpInput, type StorageContractSetup, type StorageContractSetupResult, type SurfaceObservation, TEST_ACTOR_HEADER, type TestActor, type TestAppContext, type TestAuthProvider, type TestAuthSession, type TestDatabase, type TestDatabaseFactory, type TestFixtures, type TestModuleSetup, type TestkitContext, assertModuleBootsAlone, bootModuleApp, createBetterAuthProvider, createBetterAuthTestHelpers, createCustomAuthProvider, createDataFactory, createHttpTestHarness, createJwtAuthProvider, createMinimalTestApp, createMockController, createMockReply, createMockRepository, createMockRequest, createMockUser, createSpy, createTestApp, createTestFixtures, createTestModuleSetup, createTestTimer, expectArc, mongoMemoryDatabase, runAuthorizationConformance, runStorageContract, safeParseBody, scopeFromTestActor, scopeRoleGate, setupBetterAuthTestApp, testActorAuth, testActorHeaders, waitFor };
|