@classytic/arc 2.30.0 → 2.31.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/LICENSE +1 -1
- package/README.md +7 -0
- package/TRADEMARK.md +22 -0
- package/dist/{BaseController-B5DwufbB.mjs → BaseController-De7GqHck.mjs} +16 -17
- package/dist/{EventTransport-QmY7A258.d.mts → EventTransport-2uVV34WU.d.mts} +1 -0
- package/dist/{EventTransport-C6YA8xY9.mjs → EventTransport-9mlTgqzE.mjs} +1 -0
- package/dist/{HookSystem-WkIougo6.mjs → HookSystem-BcmPyq5I.mjs} +2 -1
- package/dist/{QueryCache-DOMT_iq7.d.mts → QueryCache-DFA-QrU9.d.mts} +2 -1
- package/dist/{ResourceRegistry-y9Hnypus.mjs → ResourceRegistry-qq0mn2s9.mjs} +3 -2
- package/dist/{actionPermissions-CAphcmUG.mjs → actionPermissions-BE5NwaQk.mjs} +1 -0
- package/dist/audit/index.d.mts +3 -2
- package/dist/audit/index.mjs +2 -1
- package/dist/auth/audit.d.mts +1 -0
- package/dist/auth/audit.mjs +1 -0
- package/dist/auth/index.d.mts +5 -4
- package/dist/auth/index.mjs +6 -5
- package/dist/auth/redis-session.d.mts +2 -1
- package/dist/auth/redis-session.mjs +1 -0
- package/dist/{applyPermissionResult-Cf6zUCvb.mjs → authorizationDecision-ChvUQGG1.mjs} +19 -10
- package/dist/{buildHandler-BYS3Vkfz.mjs → buildHandler-CDrCJzmB.mjs} +4 -3
- package/dist/cache/index.d.mts +4 -3
- package/dist/cache/index.mjs +4 -3
- package/dist/{caching-_NJeDd1_.mjs → caching-BFHA6Scf.mjs} +2 -1
- package/dist/cleanup/index.d.mts +4 -3
- package/dist/cleanup/index.mjs +9 -13
- package/dist/cli/commands/describe.d.mts +2 -1
- package/dist/cli/commands/describe.mjs +3 -2
- package/dist/cli/commands/docs.d.mts +1 -0
- package/dist/cli/commands/docs.mjs +3 -2
- package/dist/cli/commands/doctor.d.mts +1 -0
- package/dist/cli/commands/doctor.mjs +1 -0
- package/dist/cli/commands/generate.d.mts +2 -1
- package/dist/cli/commands/generate.mjs +2 -1
- package/dist/cli/commands/init.d.mts +2 -1
- package/dist/cli/commands/init.mjs +2 -1
- package/dist/cli/commands/introspect.d.mts +1 -0
- package/dist/cli/commands/introspect.mjs +2 -1
- package/dist/cli/index.d.mts +3 -2
- package/dist/cli/index.mjs +3 -2
- package/dist/{constants-DNre0wRg.mjs → constants-D3Dv4LAV.mjs} +1 -0
- package/dist/context/index.d.mts +1 -0
- package/dist/context/index.mjs +3 -2
- package/dist/core/index.d.mts +3 -2
- package/dist/core/index.mjs +7 -6
- package/dist/{core-poalJLvp.mjs → core-BGpIxINN.mjs} +3 -2
- package/dist/{createActionRouter-BxuywVRB.mjs → createActionRouter-CDXEdasl.mjs} +8 -7
- package/dist/{createAggregationRouter-BiY-lGdY.mjs → createAggregationRouter-KrQDzx-k.mjs} +7 -6
- package/dist/{createApp-CXrKFciH.mjs → createApp-Df89BEni.mjs} +484 -209
- package/dist/{defineAggregation-ufWjUwml.mjs → defineAggregation-DbPPS8RI.mjs} +1 -0
- package/dist/{defineResource-CxWqDCAy.mjs → defineResource-Bv6EtN1x.mjs} +63 -38
- package/dist/discovery/index.d.mts +1 -0
- package/dist/discovery/index.mjs +1 -0
- package/dist/docs/index.d.mts +3 -2
- package/dist/docs/index.mjs +3 -2
- package/dist/{elevation-C9s5hFP_.d.mts → elevation-BXCfVcNG.d.mts} +1 -0
- package/dist/{elevation-CIz1GjGm.mjs → elevation-D2l1mh4g.mjs} +4 -3
- package/dist/encryption/index.d.mts +2 -1
- package/dist/encryption/index.mjs +3 -2
- package/dist/{errorHandler-Bq2OeEQL.mjs → errorHandler-C5NeD6bT.mjs} +3 -2
- package/dist/{errorHandler-C6yv4ZTW.d.mts → errorHandler-CpYUJmPA.d.mts} +1 -0
- package/dist/{errors-DHcHVAgk.d.mts → errors-BGdVwP25.d.mts} +1 -0
- package/dist/{errors-DkO8Nk6-.mjs → errors-Cj42AntO.mjs} +1 -0
- package/dist/{eventPlugin-D2NNR4py.mjs → eventPlugin-CkffEihm.mjs} +5 -4
- package/dist/{eventPlugin-Bg3cP2_u.d.mts → eventPlugin-GF5nnBIq.d.mts} +2 -1
- package/dist/events/index.d.mts +4 -3
- package/dist/events/index.mjs +16 -4
- package/dist/events/transports/redis-stream-entry.d.mts +2 -1
- package/dist/events/transports/redis-stream-entry.mjs +1 -0
- package/dist/events/transports/redis.d.mts +2 -1
- package/dist/events/transports/redis.mjs +1 -0
- package/dist/{externalPaths-BE6wFpYv.d.mts → externalPaths-CmGOxOtD.d.mts} +1 -0
- package/dist/factory/index.d.mts +9 -5
- package/dist/factory/index.mjs +7 -6
- package/dist/{fastify-augmentation-0WhPnUH7.d.mts → fastify-augmentation-zh0_AYHa.d.mts} +13 -4
- package/dist/{fields-CQ_7KQWw.mjs → fields-CB5Cuzk9.mjs} +1 -0
- package/dist/{fields-D3lhgzhD.d.mts → fields-I_EeRWND.d.mts} +1 -0
- package/dist/{generate-BnsL9GBm.d.mts → generate-DCGuEbqI.d.mts} +1 -0
- package/dist/{generate-D0wZupBg.mjs → generate-EcA6ui4i.mjs} +2 -1
- package/dist/{headers-BvlQSDMB.mjs → headers-3zvE0rM3.mjs} +2 -1
- package/dist/{health-CB_m4S32.mjs → health-DjZYjM7H.mjs} +1 -0
- package/dist/hooks/index.d.mts +2 -1
- package/dist/hooks/index.mjs +2 -1
- package/dist/idempotency/index.d.mts +3 -2
- package/dist/idempotency/index.mjs +4 -3
- package/dist/idempotency/redis.d.mts +2 -1
- package/dist/idempotency/redis.mjs +1 -0
- package/dist/{index-Cx7bro4R.d.mts → index-Bc2ox8_U.d.mts} +5 -4
- package/dist/{index-CG4yqMDB.d.mts → index-C-481Zf2.d.mts} +74 -18
- package/dist/{index-Ch16aAah.d.mts → index-CEJNZFtX.d.mts} +53 -15
- package/dist/index-CFThn1K9.d.mts +224 -0
- package/dist/{index-Q5gzEIWY.d.mts → index-CpzxqePp.d.mts} +107 -32
- package/dist/index.d.mts +9 -8
- package/dist/index.mjs +11 -10
- package/dist/{init-tlRSt667.mjs → init-BEiNZBsJ.mjs} +19 -23
- package/dist/{init-BPEu8cs-.d.mts → init-Cscs4lab.d.mts} +1 -0
- package/dist/integrations/event-gateway.d.mts +3 -2
- package/dist/integrations/event-gateway.mjs +2 -1
- package/dist/integrations/index.d.mts +4 -3
- package/dist/integrations/index.mjs +1 -0
- package/dist/integrations/jobs/index.d.mts +2 -1
- package/dist/integrations/jobs/index.mjs +108 -32
- package/dist/integrations/mcp/index.d.mts +6 -6
- package/dist/integrations/mcp/index.mjs +2 -1
- package/dist/integrations/mcp/testing.d.mts +3 -2
- package/dist/integrations/mcp/testing.mjs +2 -2
- package/dist/integrations/streamline.d.mts +1 -0
- package/dist/integrations/streamline.mjs +3 -2
- package/dist/integrations/webhooks.d.mts +1 -0
- package/dist/integrations/webhooks.mjs +1 -0
- package/dist/integrations/websocket-pushref-redis.d.mts +2 -1
- package/dist/integrations/websocket-pushref-redis.mjs +1 -0
- package/dist/integrations/websocket-redis.d.mts +2 -1
- package/dist/integrations/websocket-redis.mjs +1 -0
- package/dist/integrations/websocket.d.mts +3 -2
- package/dist/integrations/websocket.mjs +1 -0
- package/dist/{interface-BGZCzOGL.d.mts → interface-BOk3YnTz.d.mts} +1 -0
- package/dist/{interface-Uk8ONJv7.d.mts → interface-DEWNOMza.d.mts} +1 -0
- package/dist/{interface-n5hpKg1d.mjs → interface-DYzM0q-u.mjs} +1 -0
- package/dist/{jsonBody-BlzAoGCe.mjs → jsonBody-BYbLDi5D.mjs} +2 -1
- package/dist/{keys-Cd316u_y.mjs → keys-C0_pa1tY.mjs} +1 -0
- package/dist/{loadResources-DpU8pfjV.mjs → loadResources-yawi9tAE.mjs} +2 -1
- package/dist/{loadResourcesFromEntry-kvGT6EcO.mjs → loadResourcesFromEntry-BAjYZonx.mjs} +2 -1
- package/dist/logger/index.d.mts +1 -0
- package/dist/logger/index.mjs +1 -0
- package/dist/{mcpPlugin-nRzm5g2s.mjs → mcpPlugin-B_es0hiF.mjs} +22 -23
- package/dist/{memory-CmnzXE5Z.mjs → memory-DR6wpa9F.mjs} +2 -1
- package/dist/{metrics-fdxI5mn_.mjs → metrics-CUdw_wLB.mjs} +2 -1
- package/dist/middleware/index.d.mts +2 -1
- package/dist/middleware/index.mjs +3 -2
- package/dist/migrations/index.d.mts +1 -0
- package/dist/migrations/index.mjs +2 -1
- package/dist/module-0pFJA6YW.mjs +78 -0
- package/dist/{multipartBody-D0eBvkeq.mjs → multipartBody-wOgj_fnj.mjs} +1 -0
- package/dist/{openapi-B8Vkbxbn.mjs → openapi-B2L4js1v.mjs} +6 -5
- package/dist/permissions/index.d.mts +5 -4
- package/dist/permissions/index.mjs +6 -5
- package/dist/{permissions-Bfu_8y1U.mjs → permissions-L--QDAir.mjs} +214 -41
- package/dist/{pipe-BOLmGIxa.mjs → pipe-DRDNsI2T.mjs} +2 -1
- package/dist/pipeline/index.d.mts +2 -1
- package/dist/pipeline/index.mjs +2 -1
- package/dist/{pipeline-BJnpkY9i.mjs → pipeline-UPQFatit.mjs} +6 -5
- package/dist/plugins/index.d.mts +8 -7
- package/dist/plugins/index.mjs +16 -15
- package/dist/plugins/response-cache.d.mts +1 -0
- package/dist/plugins/response-cache.mjs +2 -1
- package/dist/plugins/tracing-entry.d.mts +2 -1
- package/dist/plugins/tracing-entry.mjs +2 -1
- package/dist/{pluralize-BhrgwZb9.mjs → pluralize-O0FZgESl.mjs} +1 -0
- package/dist/{preloadResources-DCfa2egz.d.mts → preloadResources-CyoJMSiO.d.mts} +117 -12
- package/dist/{preloadResources-DSY5HGpS.mjs → preloadResources-Dz6BjzM6.mjs} +1 -0
- package/dist/presets/filesUpload.d.mts +6 -5
- package/dist/presets/filesUpload.mjs +9 -11
- package/dist/presets/index.d.mts +3 -2
- package/dist/presets/index.mjs +2 -1
- package/dist/presets/multiTenant.d.mts +2 -1
- package/dist/presets/multiTenant.mjs +3 -2
- package/dist/presets/search.d.mts +3 -2
- package/dist/presets/search.mjs +2 -1
- package/dist/{presets-CJOEXLuk.mjs → presets-C4-g4_4P.mjs} +4 -3
- package/dist/{projection-UBDgfMwu.mjs → projection-BHw6xlOQ.mjs} +2 -1
- package/dist/{pushref-registry-BbcnpNTm.d.mts → pushref-registry-Dov9l4qE.d.mts} +1 -0
- package/dist/{queryCachePlugin-DSGuSVLW.mjs → queryCachePlugin-BZr8v46E.mjs} +5 -4
- package/dist/{queryCachePlugin-br4otiqF.d.mts → queryCachePlugin-BzqxconG.d.mts} +3 -2
- package/dist/{rateLimit-DrED-C4v.mjs → rateLimit-CMPY5Qms.mjs} +7 -3
- package/dist/{rateLimitKey-BoWRH3e7.mjs → rateLimitKey-Dn5JNmkj.mjs} +1 -0
- package/dist/{realtime-B9nmIY5V.mjs → realtime-C5NoxWFY.mjs} +20 -9
- package/dist/{redis-UDtLaNj6.d.mts → redis-CduhKsJi.d.mts} +2 -1
- package/dist/{redis-stream-uIaauNpZ.d.mts → redis-stream-qN6A-RFv.d.mts} +2 -1
- package/dist/{registerRoute-JkxIEBJq.mjs → registerRoute-CBN84fc7.mjs} +4 -3
- package/dist/registry/index.d.mts +3 -2
- package/dist/registry/index.mjs +3 -2
- package/dist/{registry-BYXUUg21.mjs → registry-4213U4zb.mjs} +2 -1
- package/dist/{renewingLease-3cuQH7h8.mjs → renewingLease-Dll8mGgc.mjs} +1 -0
- package/dist/{replyHelpers-67tRawQZ.mjs → replyHelpers-DIiZKGUt.mjs} +2 -1
- package/dist/{repositoryFilter-Dz8k3S4P.mjs → repositoryFilter-f9eAfq_l.mjs} +1 -0
- package/dist/{requestContext-MrlJwECf.mjs → requestContext-2TxXOJ7T.mjs} +1 -0
- package/dist/{rolldown-runtime-8H4AJuhK.mjs → rolldown-runtime-CUsjNX9R.mjs} +1 -0
- package/dist/{schedules-CjOd2MG9.mjs → schedules-DdtlICTf.mjs} +3 -2
- package/dist/{schemaConverter-DJvodiHj.mjs → schemaConverter-D--wdJM-.mjs} +70 -17
- package/dist/{schemaIR-Ca7ySNPx.mjs → schemaIR-Bm2enbnh.mjs} +2 -1
- package/dist/schemas/index.d.mts +1 -0
- package/dist/schemas/index.mjs +1 -0
- package/dist/scim/index.d.mts +1 -0
- package/dist/scim/index.mjs +2 -1
- package/dist/scope/index.d.mts +3 -2
- package/dist/scope/index.mjs +5 -4
- package/dist/{scope-DfaPntpo.mjs → scope-DHx5Fous.mjs} +4 -3
- package/dist/{sessionManager-BHOM3N_0.d.mts → sessionManager-qimlKV0P.d.mts} +1 -0
- package/dist/{simpleEqualityMatcher-Cg05HPsn.mjs → simpleEqualityMatcher-DjQJ7FKA.mjs} +1 -0
- package/dist/{sse-BQ1w0dfl.mjs → sse-CUTo9y0X.mjs} +5 -4
- package/dist/{sseStream-BQ7Qaynp.mjs → sseStream-B_5XcVVd.mjs} +2 -1
- package/dist/{storage-DLUZ04kK.d.mts → storage-Cx9Sz3Ou.d.mts} +1 -0
- package/dist/{store-helpers-DY6xq4sM.mjs → store-helpers-D1MP_gRG.mjs} +1 -0
- package/dist/{streaming-BQcW6_Kq.mjs → streaming-PSZwi8JV.mjs} +1 -0
- package/dist/{subscribe-helpers-DfzsivM6.mjs → subscribe-helpers-C0Tb2aUp.mjs} +2 -1
- package/dist/sync/changelog.d.mts +1 -0
- package/dist/sync/changelog.mjs +1 -0
- package/dist/testing/cleanupStoreContract.d.mts +2 -1
- package/dist/testing/cleanupStoreContract.mjs +1 -0
- package/dist/testing/index.d.mts +139 -12
- package/dist/testing/index.mjs +230 -16
- package/dist/testing/outboxStoreContract.d.mts +1 -0
- package/dist/testing/outboxStoreContract.mjs +1 -0
- package/dist/testing/storageContract.d.mts +2 -1
- package/dist/testing/storageContract.mjs +1 -0
- package/dist/{tracing-CsGKQJGg.d.mts → tracing-Dro5eH6j.d.mts} +1 -0
- package/dist/{transactionContext-PNdoyLLm.mjs → transactionContext-dAfBVUrk.mjs} +1 -0
- package/dist/{typeGuards-C9KXUly6.mjs → typeGuards-B0ryPo3I.mjs} +1 -0
- package/dist/types/index.d.mts +6 -5
- package/dist/types/index.mjs +1 -0
- package/dist/types/storage.d.mts +2 -1
- package/dist/types/storage.mjs +1 -0
- package/dist/{types-ChvvVNP8.d.mts → types-B7XeLEgK.d.mts} +34 -2
- package/dist/{types-Cc91nXuA.d.mts → types-ByHn9rEO.d.mts} +1 -0
- package/dist/{types-C_ZZRcD6.d.mts → types-C4QE8qnm.d.mts} +1 -0
- package/dist/{types-D_n8L6VY.d.mts → types-CBgvkp8O.d.mts} +51 -12
- package/dist/{types-DjC-KUCz.d.mts → types-ChuShpCP.d.mts} +1 -0
- package/dist/{types-Bmmn-fO0.d.mts → types-CwcUQ610.d.mts} +2 -6
- package/dist/{types-CSyCxl2V.mjs → types-Cys0ko9Z.mjs} +4 -3
- package/dist/usage/index.d.mts +3 -2
- package/dist/usage/index.mjs +0 -0
- package/dist/{userHelpers-Cg_y3fBz.mjs → userHelpers-ChYJ6CdK.mjs} +1 -0
- package/dist/utils/index.d.mts +16 -7
- package/dist/utils/index.mjs +10 -9
- package/dist/{utils-taUVD4p3.mjs → utils-vL8BrDE2.mjs} +3 -2
- package/dist/{validate-Bk44F2yT.mjs → validate-_KbifpHF.mjs} +2 -1
- package/dist/{versioning-gKWyryT3.mjs → versioning-CHDTor3P.mjs} +3 -2
- package/dist/{versioning-DXgY5BUm.d.mts → versioning-D_rhHfZm.d.mts} +5 -2
- package/dist/{websocket-_Pd0CgR0.d.mts → websocket-BOXVggMf.d.mts} +2 -1
- package/package.json +547 -539
- package/skills/arc/SKILL.md +22 -8
- package/skills/arc/references/api-reference.md +4 -0
- package/skills/arc/references/auth.md +3 -0
- package/skills/arc/references/integrations.md +33 -0
- package/skills/arc/references/mcp.md +6 -7
- package/skills/arc/references/production.md +24 -25
- package/skills/arc-code-review/references/anti-patterns.md +3 -3
- package/dist/index-CCzVR-N3.d.mts +0 -107
- package/dist/module-_-M7C5ES.mjs +0 -29
package/dist/testing/index.mjs
CHANGED
|
@@ -1,10 +1,13 @@
|
|
|
1
|
-
|
|
2
|
-
import { t as
|
|
3
|
-
import {
|
|
4
|
-
import { t as
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
1
|
+
/*! @classytic/arc v2.31.0 | MIT | https://classytic.com */
|
|
2
|
+
import { t as CRUD_OPERATIONS } from "../constants-D3Dv4LAV.mjs";
|
|
3
|
+
import { g as getUserId } from "../types-Cys0ko9Z.mjs";
|
|
4
|
+
import { t as BaseController } from "../BaseController-De7GqHck.mjs";
|
|
5
|
+
import { t as defineResource } from "../defineResource-Bv6EtN1x.mjs";
|
|
6
|
+
import { t as defineAggregation } from "../defineAggregation-DbPPS8RI.mjs";
|
|
7
|
+
import { S as requireOrgRole } from "../permissions-L--QDAir.mjs";
|
|
8
|
+
import { n as createApp } from "../createApp-Df89BEni.mjs";
|
|
9
|
+
import { n as getModuleExports } from "../module-0pFJA6YW.mjs";
|
|
10
|
+
import { n as preloadResourcesAsync, t as preloadResources } from "../preloadResources-Dz6BjzM6.mjs";
|
|
8
11
|
import { runStorageContract } from "./storageContract.mjs";
|
|
9
12
|
import Fastify from "fastify";
|
|
10
13
|
import { afterAll, describe, expect, it, vi } from "vitest";
|
|
@@ -304,18 +307,30 @@ function createSpy(_name = "spy") {
|
|
|
304
307
|
return spy;
|
|
305
308
|
}
|
|
306
309
|
/**
|
|
307
|
-
* Wait
|
|
310
|
+
* Wait until `condition` holds — the standard way to assert on asynchronous
|
|
311
|
+
* effects in arc's suites.
|
|
308
312
|
*
|
|
309
|
-
*
|
|
313
|
+
* **Prefer this over `await sleep(n)` + assert.** A fixed delay encodes a guess
|
|
314
|
+
* about scheduling: too short and it flakes the moment the pool is busy, too
|
|
315
|
+
* long and every run pays for the worst case. Polling returns as soon as the
|
|
316
|
+
* effect lands, so the fast path stays fast and a loaded machine simply waits —
|
|
317
|
+
* and the failure mode becomes "this never happened", which is the thing worth
|
|
318
|
+
* asserting.
|
|
319
|
+
*
|
|
320
|
+
* Supply `label`: without it a timeout reports only its own duration, which is
|
|
321
|
+
* the least useful sentence a flaky test can print.
|
|
322
|
+
*
|
|
323
|
+
* @example
|
|
324
|
+
* await waitFor(() => runs >= 3, { label: "3 scheduler ticks" });
|
|
310
325
|
*/
|
|
311
326
|
async function waitFor(condition, options = {}) {
|
|
312
|
-
const { timeout = 5e3, interval =
|
|
327
|
+
const { timeout = 5e3, interval = 25, label } = options;
|
|
313
328
|
const startTime = Date.now();
|
|
314
329
|
while (Date.now() - startTime < timeout) {
|
|
315
330
|
if (await condition()) return;
|
|
316
331
|
await new Promise((resolve) => setTimeout(resolve, interval));
|
|
317
332
|
}
|
|
318
|
-
throw new Error(`
|
|
333
|
+
throw new Error(label ? `Timed out after ${timeout}ms waiting for: ${label}` : `Timed out after ${timeout}ms waiting for a condition (pass "label" to name it)`);
|
|
319
334
|
}
|
|
320
335
|
/**
|
|
321
336
|
* Create a test timer that can be controlled
|
|
@@ -363,6 +378,7 @@ function createTestTimer() {
|
|
|
363
378
|
async function runAuthorizationConformance(opts) {
|
|
364
379
|
const rows = opts.rows ?? [{ _id: "1" }];
|
|
365
380
|
let listFilter;
|
|
381
|
+
let getFilter;
|
|
366
382
|
let aggFilter;
|
|
367
383
|
const repo = createMockRepository({
|
|
368
384
|
getAll: (async (params = {}) => {
|
|
@@ -378,6 +394,10 @@ async function runAuthorizationConformance(opts) {
|
|
|
378
394
|
hasPrev: false
|
|
379
395
|
};
|
|
380
396
|
}),
|
|
397
|
+
getOne: (async (filter) => {
|
|
398
|
+
getFilter = filter;
|
|
399
|
+
return rows[0] ?? null;
|
|
400
|
+
}),
|
|
381
401
|
aggregate: (async (req = {}) => {
|
|
382
402
|
aggFilter = req.filter;
|
|
383
403
|
return { rows: [{ tally: rows.length }] };
|
|
@@ -419,6 +439,10 @@ async function runAuthorizationConformance(opts) {
|
|
|
419
439
|
method: "GET",
|
|
420
440
|
url: "/confs"
|
|
421
441
|
});
|
|
442
|
+
const get = await app.inject({
|
|
443
|
+
method: "GET",
|
|
444
|
+
url: "/confs/1"
|
|
445
|
+
});
|
|
422
446
|
const aggregation = await app.inject({
|
|
423
447
|
method: "GET",
|
|
424
448
|
url: "/confs/aggregations/tally"
|
|
@@ -428,6 +452,10 @@ async function runAuthorizationConformance(opts) {
|
|
|
428
452
|
status: list.statusCode,
|
|
429
453
|
filter: listFilter
|
|
430
454
|
},
|
|
455
|
+
get: {
|
|
456
|
+
status: get.statusCode,
|
|
457
|
+
filter: getFilter
|
|
458
|
+
},
|
|
431
459
|
aggregation: {
|
|
432
460
|
status: aggregation.statusCode,
|
|
433
461
|
filter: aggFilter
|
|
@@ -775,6 +803,127 @@ async function setupBetterAuthTestApp(input) {
|
|
|
775
803
|
};
|
|
776
804
|
}
|
|
777
805
|
//#endregion
|
|
806
|
+
//#region src/testing/testActor.ts
|
|
807
|
+
/**
|
|
808
|
+
* Test-only actor installation — how a package's HTTP tests say "run this
|
|
809
|
+
* request as a manager in org X".
|
|
810
|
+
*
|
|
811
|
+
* Identity in arc comes from `request.scope` and only from `request.scope`;
|
|
812
|
+
* headers like `x-role` are not identity. That rule left tests with no seam —
|
|
813
|
+
* `bootModuleApp` boots with `auth: false`, so every injected request arrived as
|
|
814
|
+
* `PUBLIC_SCOPE` and any role-gated route answered 401 with no supported way to
|
|
815
|
+
* be somebody.
|
|
816
|
+
*
|
|
817
|
+
* The `x-test-actor` header is a TRANSPORT for the test's intent, not a trusted
|
|
818
|
+
* claim. The authenticator that reads it is installed by `bootModuleApp` alone,
|
|
819
|
+
* behind the `@classytic/arc/testing` entrypoint; `createApp` never registers
|
|
820
|
+
* it, so no production build turns a forged header into identity. It is
|
|
821
|
+
* per-request rather than boot-only because the interesting authorization tests
|
|
822
|
+
* are the ones where a cashier is refused what a manager is allowed.
|
|
823
|
+
*
|
|
824
|
+
* Gate the routes under test with arc's own primitives — `requireRoles`,
|
|
825
|
+
* `requireOrgMembership`, `allOf(...)`. A test-specific gate would be a second
|
|
826
|
+
* implementation of authorization whose semantics could drift from the one
|
|
827
|
+
* actually shipping.
|
|
828
|
+
*/
|
|
829
|
+
/** The header the testing harness uses to carry a request's actor. */
|
|
830
|
+
const TEST_ACTOR_HEADER = "x-test-actor";
|
|
831
|
+
/**
|
|
832
|
+
* Build the headers that make an injected request act as `actor`.
|
|
833
|
+
*
|
|
834
|
+
* A string is shorthand for a single role. `null` means "stay public", so a test
|
|
835
|
+
* can prove an endpoint is reachable anonymously.
|
|
836
|
+
*
|
|
837
|
+
* @example
|
|
838
|
+
* ```ts
|
|
839
|
+
* t.app.inject({ method: 'GET', url: '/pos/shifts/current',
|
|
840
|
+
* headers: testActorHeaders('manager', ORG) });
|
|
841
|
+
* ```
|
|
842
|
+
*/
|
|
843
|
+
function testActorHeaders(actor, organizationId) {
|
|
844
|
+
if (actor === null) return {};
|
|
845
|
+
const org = organizationId !== void 0 ? { organizationId } : {};
|
|
846
|
+
const spec = typeof actor === "string" ? {
|
|
847
|
+
roles: [actor],
|
|
848
|
+
...org
|
|
849
|
+
} : {
|
|
850
|
+
...actor,
|
|
851
|
+
...org
|
|
852
|
+
};
|
|
853
|
+
return { [TEST_ACTOR_HEADER]: JSON.stringify(spec) };
|
|
854
|
+
}
|
|
855
|
+
/**
|
|
856
|
+
* Turn a spec into a `RequestScope`.
|
|
857
|
+
*
|
|
858
|
+
* `orgRoles` defaults to `roles`: a test that says "manager" means it at both
|
|
859
|
+
* levels, and making the caller repeat itself only produces tests that pass a
|
|
860
|
+
* global gate and mysteriously fail an org one.
|
|
861
|
+
*/
|
|
862
|
+
function scopeFromTestActor(spec) {
|
|
863
|
+
const roles = spec.roles ?? [];
|
|
864
|
+
const userId = spec.userId ?? `test-${roles.join("-") || "anon"}`;
|
|
865
|
+
if (spec.elevated !== void 0 && spec.elevated !== false) return {
|
|
866
|
+
kind: "elevated",
|
|
867
|
+
userId,
|
|
868
|
+
elevatedBy: spec.elevated === true ? "test" : spec.elevated,
|
|
869
|
+
...spec.organizationId !== void 0 ? { organizationId: spec.organizationId } : {},
|
|
870
|
+
...spec.context !== void 0 ? { context: Object.freeze({ ...spec.context }) } : {}
|
|
871
|
+
};
|
|
872
|
+
if (spec.organizationId !== void 0) return {
|
|
873
|
+
kind: "member",
|
|
874
|
+
userId,
|
|
875
|
+
userRoles: roles,
|
|
876
|
+
organizationId: spec.organizationId,
|
|
877
|
+
orgRoles: spec.orgRoles ?? roles,
|
|
878
|
+
...spec.teamId !== void 0 ? { teamId: spec.teamId } : {},
|
|
879
|
+
...spec.context !== void 0 ? { context: Object.freeze({ ...spec.context }) } : {}
|
|
880
|
+
};
|
|
881
|
+
return {
|
|
882
|
+
kind: "authenticated",
|
|
883
|
+
userId,
|
|
884
|
+
userRoles: roles
|
|
885
|
+
};
|
|
886
|
+
}
|
|
887
|
+
/**
|
|
888
|
+
* Build the `auth` option that makes the test actor arc's authenticator.
|
|
889
|
+
*
|
|
890
|
+
* It must be an authenticator, not a hook. Fastify encapsulates plugins and
|
|
891
|
+
* modules register DURING `createApp`, so a hook added to the root instance
|
|
892
|
+
* afterwards never runs for their routes — every request still resolves as
|
|
893
|
+
* public and every role gate still answers 401, which reads exactly like a
|
|
894
|
+
* broken authorization rule in the code under test. Registering as
|
|
895
|
+
* `auth: { type: 'authenticator' }` puts the scope where arc's own auth would,
|
|
896
|
+
* at the phase arc expects, so tenant filtering and permission checks behave
|
|
897
|
+
* identically to production.
|
|
898
|
+
*
|
|
899
|
+
* `defaultActor` applies to requests carrying no header. A malformed header
|
|
900
|
+
* REJECTS rather than falling back to public — a test that meant to be an admin
|
|
901
|
+
* and got quietly demoted is the most expensive way to fail.
|
|
902
|
+
*/
|
|
903
|
+
function testActorAuth(defaultActor) {
|
|
904
|
+
const authenticate = async (request) => {
|
|
905
|
+
const req = request;
|
|
906
|
+
const raw = req.headers[TEST_ACTOR_HEADER];
|
|
907
|
+
let spec = defaultActor;
|
|
908
|
+
if (typeof raw === "string") try {
|
|
909
|
+
spec = JSON.parse(raw);
|
|
910
|
+
} catch {
|
|
911
|
+
throw new Error(`[arc-testing] malformed ${TEST_ACTOR_HEADER} header: ${raw}. Build it with testActorHeaders() rather than by hand.`);
|
|
912
|
+
}
|
|
913
|
+
if (spec === void 0) return;
|
|
914
|
+
req.scope = scopeFromTestActor(spec);
|
|
915
|
+
req.user = {
|
|
916
|
+
id: getUserId(req.scope) ?? "test-anon",
|
|
917
|
+
...spec.roles !== void 0 ? { role: spec.roles } : {},
|
|
918
|
+
...spec.organizationId !== void 0 ? { organizationId: spec.organizationId } : {}
|
|
919
|
+
};
|
|
920
|
+
};
|
|
921
|
+
return {
|
|
922
|
+
type: "authenticator",
|
|
923
|
+
authenticate
|
|
924
|
+
};
|
|
925
|
+
}
|
|
926
|
+
//#endregion
|
|
778
927
|
//#region src/testing/bootModuleApp.ts
|
|
779
928
|
/**
|
|
780
929
|
* The built-in DB factory: in-memory MongoDB via lazy devDep imports.
|
|
@@ -807,7 +956,6 @@ async function bootModuleApp(modules, options = {}) {
|
|
|
807
956
|
const db = await (options.database ?? mongoMemoryDatabase)({ replset: options.replset ?? false });
|
|
808
957
|
const ctx = {
|
|
809
958
|
uri: db.uri,
|
|
810
|
-
mongoUri: db.uri,
|
|
811
959
|
connection: db.connection
|
|
812
960
|
};
|
|
813
961
|
const resolved = typeof modules === "function" ? await modules(ctx) : modules;
|
|
@@ -816,7 +964,17 @@ async function bootModuleApp(modules, options = {}) {
|
|
|
816
964
|
try {
|
|
817
965
|
app = await createApp({
|
|
818
966
|
preset: "testing",
|
|
819
|
-
|
|
967
|
+
/**
|
|
968
|
+
* The test actor is arc's AUTHENTICATOR rather than a bolted-on hook, so the scope
|
|
969
|
+
* lands at the phase the rest of the pipeline expects — tenant filtering and
|
|
970
|
+
* permission checks then behave exactly as they do in production.
|
|
971
|
+
*
|
|
972
|
+
* This replaces the old `auth: false`. The authenticator is inert for a request
|
|
973
|
+
* carrying no `x-test-actor` header and no configured default, so a suite that
|
|
974
|
+
* never mentions an actor behaves as it did before. `options.app` still wins, so a
|
|
975
|
+
* suite exercising real auth can override it.
|
|
976
|
+
*/
|
|
977
|
+
auth: testActorAuth(options.actor),
|
|
820
978
|
sensible: false,
|
|
821
979
|
...options.app,
|
|
822
980
|
modules: list
|
|
@@ -830,7 +988,6 @@ async function bootModuleApp(modules, options = {}) {
|
|
|
830
988
|
return {
|
|
831
989
|
app,
|
|
832
990
|
uri: db.uri,
|
|
833
|
-
mongoUri: db.uri,
|
|
834
991
|
connection: db.connection,
|
|
835
992
|
exports: (name) => getModuleExports(app, name),
|
|
836
993
|
close: async () => {
|
|
@@ -1156,6 +1313,63 @@ function createHttpTestHarness(resource, optionsOrGetter) {
|
|
|
1156
1313
|
return new HttpTestHarness(resource, optionsOrGetter);
|
|
1157
1314
|
}
|
|
1158
1315
|
//#endregion
|
|
1316
|
+
//#region src/testing/scopeRoleGate.ts
|
|
1317
|
+
/**
|
|
1318
|
+
* `scopeRoleGate` — the ONE role gate for package tests.
|
|
1319
|
+
*
|
|
1320
|
+
* ## Why this is in the framework
|
|
1321
|
+
*
|
|
1322
|
+
* Nine `@classytic/arc-*` packages had their own copy of this helper, and all nine were
|
|
1323
|
+
* wrong in the same two ways:
|
|
1324
|
+
*
|
|
1325
|
+
* 1. They read the role from an `x-role` HEADER and INSTALLED the request scope from
|
|
1326
|
+
* inside the check — so identity came from the caller's own claim, and it arrived too
|
|
1327
|
+
* late for arc's tenant-filtering pipeline to see it.
|
|
1328
|
+
* 2. They returned the pre-2.30 `{ granted: boolean }` shape, which is neither a boolean
|
|
1329
|
+
* nor an `AuthorizationDecision`. It passed through `normalizeToDecision` untouched,
|
|
1330
|
+
* arrived with `effect: undefined`, and was read as DENY — the gate said yes and arc
|
|
1331
|
+
* refused anyway. Nothing type-errored, because the gate is cast at the call site.
|
|
1332
|
+
*
|
|
1333
|
+
* Two of the nine had already been fixed by hand, each with a docstring describing this
|
|
1334
|
+
* exact discovery. The knowledge reached neither of the other seven. That is the whole
|
|
1335
|
+
* case for a shared seam: a copy does not fail, it diverges.
|
|
1336
|
+
*
|
|
1337
|
+
* A test role gate is generic — "does the verified scope hold one of these roles" has no
|
|
1338
|
+
* domain in it — so it belongs here, beside the contract it depends on.
|
|
1339
|
+
*
|
|
1340
|
+
* Identity itself comes from `testActorHeaders()` + `bootModuleApp`'s authenticator
|
|
1341
|
+
* (see `testActor.ts`), which puts the scope where production puts it. This stays a pure
|
|
1342
|
+
* predicate over what arc verified.
|
|
1343
|
+
*
|
|
1344
|
+
* Kept in its own module rather than beside `testActor.ts` so the two can be edited
|
|
1345
|
+
* independently.
|
|
1346
|
+
*/
|
|
1347
|
+
/**
|
|
1348
|
+
* Grants iff the caller is an org member holding one of `allowed` as an ORG role.
|
|
1349
|
+
*
|
|
1350
|
+
* This is `requireOrgRole` under a name package tests reach for — deliberately a
|
|
1351
|
+
* delegation, not a reimplementation. It previously read `scope.userRoles` while
|
|
1352
|
+
* requiring an organization, so it disagreed with production for any actor whose
|
|
1353
|
+
* global and org roles differ: given
|
|
1354
|
+
* `{ roles: ["employee"], orgRoles: ["manager"], organizationId: "org-1" }`,
|
|
1355
|
+
* `requireOrgRole("manager")` allowed and this denied. The tests never caught it
|
|
1356
|
+
* because `scopeFromTestActor` defaults `orgRoles` to `roles`, making the two
|
|
1357
|
+
* dimensions identical for the common actor.
|
|
1358
|
+
*
|
|
1359
|
+
* Delegating also inherits the semantics a hand-rolled gate kept missing:
|
|
1360
|
+
* elevated scopes pass, and a service (API-key) identity is refused with a
|
|
1361
|
+
* message naming `requireServiceScope` instead of silently failing a role test.
|
|
1362
|
+
*
|
|
1363
|
+
* **Pair it with an org-bearing actor.** `testActorHeaders('manager', ORG)`
|
|
1364
|
+
* yields a `member` scope and passes; `testActorHeaders('manager')` yields an
|
|
1365
|
+
* `authenticated` scope with no tenant, which is refused — arc's generated CRUD
|
|
1366
|
+
* filters by tenant, so a gate that waves a tenant-less request through is how a
|
|
1367
|
+
* green test reads another org's rows.
|
|
1368
|
+
*/
|
|
1369
|
+
function scopeRoleGate(...allowed) {
|
|
1370
|
+
return requireOrgRole(...allowed);
|
|
1371
|
+
}
|
|
1372
|
+
//#endregion
|
|
1159
1373
|
//#region src/testing/testApp.ts
|
|
1160
1374
|
/**
|
|
1161
1375
|
* createTestApp — test app factory for arc
|
|
@@ -1221,7 +1435,7 @@ function pickDefaultAuth(authMode, callerAuth) {
|
|
|
1221
1435
|
};
|
|
1222
1436
|
}
|
|
1223
1437
|
async function createTestApp(options = {}) {
|
|
1224
|
-
const { createApp } = await import("../createApp-
|
|
1438
|
+
const { createApp } = await import("../createApp-Df89BEni.mjs").then((n) => n.r);
|
|
1225
1439
|
const { resources = [], db = "in-memory", connectMongoose = false, authMode = "jwt", defaultOrgId, plugins, auth: callerAuth, ...appOptions } = options;
|
|
1226
1440
|
let dbHandle;
|
|
1227
1441
|
let dbUri;
|
|
@@ -1291,4 +1505,4 @@ function createMinimalTestApp(options = {}) {
|
|
|
1291
1505
|
});
|
|
1292
1506
|
}
|
|
1293
1507
|
//#endregion
|
|
1294
|
-
export { HttpTestHarness, bootModuleApp, createBetterAuthProvider, createBetterAuthTestHelpers, createCustomAuthProvider, createDataFactory, createHttpTestHarness, createJwtAuthProvider, createMinimalTestApp, createMockController, createMockReply, createMockRepository, createMockRequest, createMockUser, createSpy, createTestApp, createTestFixtures, createTestTimer, expectArc, mongoMemoryDatabase, preloadResources, preloadResourcesAsync, runAuthorizationConformance, runStorageContract, safeParseBody, setupBetterAuthTestApp, waitFor };
|
|
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 };
|
package/dist/types/index.d.mts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
|
|
2
|
-
import { i as
|
|
3
|
-
import {
|
|
4
|
-
import { n as
|
|
5
|
-
|
|
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
|
+
import { i as RequestScope } from "../types-ChuShpCP.mjs";
|
|
4
|
+
import { a as UserBase, d as JWTPayload, f as ObjectId, i as PermissionContext, l as AnyRecord, m as UserOrganization, n as DataPolicy, p as UserLike, r as PermissionCheck, t as AuthorizationDecision, u as ArcRequest } from "../types-B7XeLEgK.mjs";
|
|
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 };
|
package/dist/types/index.mjs
CHANGED
package/dist/types/storage.d.mts
CHANGED
|
@@ -1,2 +1,3 @@
|
|
|
1
|
-
|
|
1
|
+
|
|
2
|
+
import { a as StorageReadResult, i as StorageReadRange, n as StorageContext, o as StorageUploadInput, r as StorageFile, t as Storage } from "../storage-Cx9Sz3Ou.mjs";
|
|
2
3
|
export { Storage, StorageContext, StorageFile, StorageReadRange, StorageReadResult, StorageUploadInput };
|
package/dist/types/storage.mjs
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
|
|
1
|
+
|
|
2
|
+
import { i as RequestScope } from "./types-ChuShpCP.mjs";
|
|
2
3
|
import { FastifyRequest } from "fastify";
|
|
3
4
|
//#region src/types/base.d.ts
|
|
4
5
|
declare module "fastify" {
|
|
@@ -53,7 +54,7 @@ interface JWTPayload {
|
|
|
53
54
|
[key: string]: unknown;
|
|
54
55
|
}
|
|
55
56
|
/**
|
|
56
|
-
* Typed Fastify request with Arc decorations. Use in `
|
|
57
|
+
* Typed Fastify request with Arc decorations. Use in `rawHandler` routes
|
|
57
58
|
* instead of `(req as any).user`.
|
|
58
59
|
*
|
|
59
60
|
* @example
|
|
@@ -241,8 +242,27 @@ type PermissionCheck<TDoc = Record<string, unknown>> = ((context: PermissionCont
|
|
|
241
242
|
interface PermissionCheckMeta {
|
|
242
243
|
/** Set by allowPublic() — marks the endpoint as publicly accessible */
|
|
243
244
|
_isPublic?: boolean;
|
|
245
|
+
/**
|
|
246
|
+
* Set by requireAuth() — a pure identity gate (must be signed in) that adds NO
|
|
247
|
+
* row-level or environmental condition. Marked so `allOf` does not mistake it
|
|
248
|
+
* for an opaque runtime branch and needlessly taint the composite `conditional`.
|
|
249
|
+
*/
|
|
250
|
+
_requiresAuth?: boolean;
|
|
244
251
|
/** Set by requireRoles() — the roles required for access */
|
|
245
252
|
_roles?: readonly string[];
|
|
253
|
+
/**
|
|
254
|
+
* Set by `requirePlatformRole()` — this gate consults PLATFORM roles only and
|
|
255
|
+
* can never be satisfied by an organization role.
|
|
256
|
+
*
|
|
257
|
+
* It exists because that property is otherwise unprovable from outside a
|
|
258
|
+
* check. `requireRoles(["ops"])` accepts an ORG role named `ops` by default,
|
|
259
|
+
* and `requireOrgRole("manager")` grants any org's manager — both return a
|
|
260
|
+
* bare allow with no policy, so a surface that must be platform-wide cannot
|
|
261
|
+
* tell them apart from a genuine operator gate by inspecting the decision.
|
|
262
|
+
* A global surface (see `jobsPlugin`'s `managementRoutes`) requires this
|
|
263
|
+
* marker at boot rather than trusting the caller's intent.
|
|
264
|
+
*/
|
|
265
|
+
_platformOnly?: boolean;
|
|
246
266
|
/** Set by requireOrgMembership() — org-level permission type */
|
|
247
267
|
_orgPermission?: string;
|
|
248
268
|
/** Set by requireOrgRole() — the org roles required for access */
|
|
@@ -290,6 +310,18 @@ interface PermissionCheckMeta {
|
|
|
290
310
|
scopes?: readonly string[];
|
|
291
311
|
dpop: boolean;
|
|
292
312
|
};
|
|
313
|
+
/**
|
|
314
|
+
* Set by `allOf(...)` when its surfaced role/scope meta is NECESSARY but NOT
|
|
315
|
+
* SUFFICIENT — the conjunction also contains an opaque runtime branch
|
|
316
|
+
* (ownership / custom / dynamic / flow-mode / quota …) or a contradictory
|
|
317
|
+
* constraint that flat metadata can't capture. `describePermission` propagates
|
|
318
|
+
* it as `conditional: true`, so `explainAccess` reports a role/scope MATCH as
|
|
319
|
+
* `conditional` (server decides) instead of a definitive — and unsound —
|
|
320
|
+
* `allow`. A role/scope MISMATCH is still a definitive `deny` (a necessary
|
|
321
|
+
* condition failed). Without this flag a composed gate could tell a UI "you can"
|
|
322
|
+
* when the runtime check will refuse.
|
|
323
|
+
*/
|
|
324
|
+
_conditional?: boolean;
|
|
293
325
|
}
|
|
294
326
|
//#endregion
|
|
295
327
|
export { UserBase as a, normalizeRoles as c, JWTPayload as d, ObjectId as f, PermissionContext as i, AnyRecord as l, UserOrganization as m, DataPolicy as n, getUserId as o, UserLike as p, PermissionCheck as r, getUserRoles as s, AuthorizationDecision as t, ArcRequest as u };
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
|
|
1
2
|
import { PurgeActor, PurgeEvidence, PurgeResourceResult, PurgeStrategyKind as PurgeStrategyKind$1, PurgeVerificationSummary } from "@classytic/primitives/retention";
|
|
2
3
|
//#region src/cleanup/types.d.ts
|
|
3
4
|
/** Ambient context every recipe method receives. Host-injected — never global. */
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
|
|
2
|
-
import { n as
|
|
1
|
+
|
|
2
|
+
import { n as DomainEvent, r as EventHandler } from "./EventTransport-2uVV34WU.mjs";
|
|
3
|
+
import { n as ErrorMapper } from "./errorHandler-CpYUJmPA.mjs";
|
|
3
4
|
import { FastifyInstance, FastifyPluginAsync } from "fastify";
|
|
4
5
|
import { LockAdapter } from "@classytic/repo-core/lock";
|
|
5
6
|
//#region src/plugins/health.d.ts
|
|
@@ -409,6 +410,13 @@ interface ArcModule<TExports = unknown> {
|
|
|
409
410
|
* *importing* a dependent until its dependency is ready. Use a thunk to gate
|
|
410
411
|
* whether a module is imported at all (region/tier packs); use `dependsOn`
|
|
411
412
|
* to order modules that are already selected.
|
|
413
|
+
*
|
|
414
|
+
* **Convention: every HARD sibling dependency belongs here** — including
|
|
415
|
+
* ones read lazily. `lazyRequiredModuleExports` defers the export READ to
|
|
416
|
+
* first use (and eagerly validates the module is composed), but only
|
|
417
|
+
* `dependsOn` orders composition so the sibling's engine exists before
|
|
418
|
+
* yours initializes. Omit the edge only for genuinely optional
|
|
419
|
+
* integrations (`lazyModuleExports` / `getOptionalModuleExports`).
|
|
412
420
|
*/
|
|
413
421
|
readonly dependsOn?: readonly string[];
|
|
414
422
|
/**
|
|
@@ -476,15 +484,21 @@ interface ArcModule<TExports = unknown> {
|
|
|
476
484
|
* Semantics:
|
|
477
485
|
* - Purely a supersede-the-app-fork switch — it filters the *app* resource
|
|
478
486
|
* list, never the module's own `resources` (those always register).
|
|
479
|
-
* -
|
|
480
|
-
*
|
|
481
|
-
*
|
|
482
|
-
* - Idempotent + tolerant: an `owns` entry with no matching app resource is
|
|
483
|
-
* a silent no-op — a module may pre-declare a name before any fork exists.
|
|
487
|
+
* - Idempotent + tolerant on the APP side: an `owns` entry with no matching
|
|
488
|
+
* app resource is a silent no-op — a module may pre-declare a name before
|
|
489
|
+
* any fork exists.
|
|
484
490
|
* - Collected across ALL modules; the app-side supersession is their union.
|
|
485
491
|
*
|
|
486
|
-
* Contract: if you `owns` a name, PROVIDE it
|
|
487
|
-
*
|
|
492
|
+
* **Contract (ENFORCED at boot): if you `owns` a name, PROVIDE it** — in
|
|
493
|
+
* THIS module's own `resources`. Arc resolves module resources first and
|
|
494
|
+
* fails boot when a claimed name is not among them, naming the module, the
|
|
495
|
+
* unmet names, and what it did supply. Without that check, a typo or a
|
|
496
|
+
* resource deleted without updating `owns` booted "successfully" with the
|
|
497
|
+
* app's route silently removed and nothing serving it.
|
|
498
|
+
*
|
|
499
|
+
* A sibling module supplying the name does NOT satisfy the claim — ownership
|
|
500
|
+
* is local to the module that declares it, which is the colocation this arm
|
|
501
|
+
* exists for.
|
|
488
502
|
*/
|
|
489
503
|
readonly owns?: readonly string[];
|
|
490
504
|
/**
|
|
@@ -569,9 +583,34 @@ interface ArcModule<TExports = unknown> {
|
|
|
569
583
|
*/
|
|
570
584
|
readonly scheduledJobs?: ModuleContribution<ScheduleDefinition>;
|
|
571
585
|
/**
|
|
572
|
-
* Teardown —
|
|
573
|
-
*
|
|
574
|
-
*
|
|
586
|
+
* Teardown — destroy engines, stop timers, flush the module's outbox.
|
|
587
|
+
* Modules close in REVERSE composition order (last composed, first closed),
|
|
588
|
+
* mirroring init, BEFORE the app-level `onClose` and before infra plugins'
|
|
589
|
+
* own close hooks (so teardown still sees a live DB/Redis).
|
|
590
|
+
*
|
|
591
|
+
* Boot is transactional: if any phase fails mid-boot — including THIS
|
|
592
|
+
* module's own `plugins`/`bootstrap` — arc immediately closes every module
|
|
593
|
+
* that entered an init phase, this closer included, in reverse order, then
|
|
594
|
+
* rethrows the original boot error. A closer runs AT MOST ONCE across the
|
|
595
|
+
* rollback and shutdown paths, and one throwing closer never blocks the
|
|
596
|
+
* rest (best-effort; failures are logged).
|
|
597
|
+
*
|
|
598
|
+
* Because rollback fires after a PARTIAL init — your `bootstrap` opened a
|
|
599
|
+
* client and threw on the next line, or your `plugins` ran but `bootstrap`
|
|
600
|
+
* never did — write closers defensively. Guard everything init may not have
|
|
601
|
+
* reached: `client?.close()`, not `client.close()`.
|
|
602
|
+
*
|
|
603
|
+
* ```ts
|
|
604
|
+
* let client: Client | undefined;
|
|
605
|
+
* defineModule({
|
|
606
|
+
* name: "billing",
|
|
607
|
+
* bootstrap: async () => {
|
|
608
|
+
* client = await openClient(); // allocated…
|
|
609
|
+
* await migrate(client); // …and this may throw
|
|
610
|
+
* },
|
|
611
|
+
* onClose: async () => { await client?.close(); }, // still runs
|
|
612
|
+
* });
|
|
613
|
+
* ```
|
|
575
614
|
*/
|
|
576
615
|
onClose?: (fastify: FastifyInstance) => void | Promise<void>;
|
|
577
616
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
|
|
1
|
+
|
|
2
|
+
import { V as ResourceDefinition } from "./index-CpzxqePp.mjs";
|
|
2
3
|
import { z } from "zod";
|
|
3
4
|
//#region src/integrations/mcp/types.d.ts
|
|
4
5
|
/** Behavioral hints for MCP clients (tool annotations per MCP spec) */
|
|
@@ -306,11 +307,6 @@ interface McpPluginOptions {
|
|
|
306
307
|
* ```
|
|
307
308
|
*/
|
|
308
309
|
expose?: string[];
|
|
309
|
-
/**
|
|
310
|
-
* @deprecated Use `expose` — same default-deny semantics with a clearer
|
|
311
|
-
* verb. Kept for backwards compatibility with pre-2.15.5 hosts.
|
|
312
|
-
*/
|
|
313
|
-
include?: string[];
|
|
314
310
|
/** Tool name prefix: 'crm' → 'crm_list_products' */
|
|
315
311
|
toolNamePrefix?: string;
|
|
316
312
|
/** Per-resource overrides (operations, descriptions, hideFields, names, toolNamePrefix) */
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
1
|
+
/*! @classytic/arc v2.31.0 | MIT | https://classytic.com */
|
|
2
|
+
import { l as UnauthorizedError, o as OrgRequiredError } from "./errors-Cj42AntO.mjs";
|
|
3
|
+
import { n as requireSingleHeaderValue } from "./headers-3zvE0rM3.mjs";
|
|
4
|
+
import { t as getUserId$1 } from "./userHelpers-ChYJ6CdK.mjs";
|
|
4
5
|
//#region src/scope/types.ts
|
|
5
6
|
/**
|
|
6
7
|
* Request Scope — The One Standard
|
package/dist/usage/index.d.mts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
|
|
2
|
-
import { i as
|
|
1
|
+
|
|
2
|
+
import { i as RequestScope } from "../types-ChuShpCP.mjs";
|
|
3
|
+
import { i as PermissionContext, r as PermissionCheck } from "../types-B7XeLEgK.mjs";
|
|
3
4
|
import { UsageBucket, UsageStore, usagePeriod } from "@classytic/repo-core/usage";
|
|
4
5
|
import { FastifyPluginAsync, FastifyRequest } from "fastify";
|
|
5
6
|
//#region src/usage/requireQuota.d.ts
|
package/dist/usage/index.mjs
CHANGED
|
Binary file
|