@classytic/arc 2.9.1 → 2.10.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +20 -91
- package/dist/{BaseController-Vu2yc56T.mjs → BaseController-DVNKvoX4.mjs} +154 -170
- package/dist/{ResourceRegistry-Dq3_zBQP.mjs → ResourceRegistry-CcN2LVrc.mjs} +1 -1
- package/dist/actionPermissions-TUVR3uiZ.mjs +22 -0
- package/dist/adapters/index.d.mts +3 -3
- package/dist/adapters/index.mjs +2 -2
- package/dist/{adapters-BBqAVvPK.mjs → adapters-BXY4i-hw.mjs} +210 -41
- package/dist/audit/index.d.mts +38 -3
- package/dist/audit/index.mjs +54 -22
- package/dist/auth/index.d.mts +2 -2
- package/dist/auth/index.mjs +3 -3
- package/dist/cache/index.d.mts +17 -15
- package/dist/cache/index.mjs +16 -15
- package/dist/{caching-CjybdRwx.mjs → caching-3h93rkJM.mjs} +8 -3
- package/dist/cli/commands/describe.mjs +1 -1
- package/dist/cli/commands/docs.mjs +2 -2
- package/dist/cli/commands/init.mjs +1 -1
- package/dist/cli/commands/introspect.mjs +1 -1
- package/dist/context/index.d.mts +58 -0
- package/dist/context/index.mjs +2 -0
- package/dist/core/index.d.mts +2 -2
- package/dist/core/index.mjs +3 -4
- package/dist/{defineResource-C__jkwvs.mjs → core-3MWJosCH.mjs} +174 -94
- package/dist/{createActionRouter-DH1YFL9m.mjs → createActionRouter-C8UUB3Px.mjs} +1 -1
- package/dist/{createApp-CBJUJKGP.mjs → createApp-BwnEAO2h.mjs} +53 -19
- package/dist/docs/index.d.mts +1 -1
- package/dist/docs/index.mjs +2 -2
- package/dist/{elevation-DxQ6ACbt.mjs → elevation-Dci0AYLT.mjs} +2 -2
- package/dist/errorHandler-2ii4RIYr.d.mts +114 -0
- package/dist/{errorHandler-CZDW4EXS.mjs → errorHandler-CSxe7KIM.mjs} +1 -1
- package/dist/{eventPlugin-Dl7MoVWH.mjs → eventPlugin-ByU4Cv0e.mjs} +1 -1
- package/dist/{eventPlugin-BxvaCIZF.d.mts → eventPlugin-D1ThQ1Pp.d.mts} +1 -1
- package/dist/events/index.d.mts +8 -5
- package/dist/events/index.mjs +87 -52
- package/dist/events/transports/redis-stream-entry.d.mts +1 -1
- package/dist/events/transports/redis.d.mts +1 -1
- package/dist/factory/index.d.mts +1 -1
- package/dist/factory/index.mjs +1 -1
- package/dist/{types-DZi1aYhm.d.mts → fields-C8Y0XLAu.d.mts} +122 -2
- package/dist/hooks/index.d.mts +1 -1
- package/dist/idempotency/index.d.mts +5 -2
- package/dist/idempotency/index.mjs +46 -37
- package/dist/{interface-YrWsmKqE.d.mts → index-BGbpGVyM.d.mts} +2107 -2756
- package/dist/{index-CtGKT0lf.d.mts → index-BziRPS4H.d.mts} +81 -7
- package/dist/{index-C-xjcA6F.d.mts → index-C_Noptz-.d.mts} +284 -409
- package/dist/{index-Cibkchnx.d.mts → index-EqQN6p0W.d.mts} +3 -3
- package/dist/index.d.mts +6 -219
- package/dist/index.mjs +10 -131
- package/dist/integrations/event-gateway.d.mts +1 -1
- package/dist/integrations/event-gateway.mjs +1 -1
- package/dist/integrations/index.d.mts +1 -1
- package/dist/integrations/mcp/index.d.mts +2 -2
- package/dist/integrations/mcp/index.mjs +1 -1
- package/dist/integrations/mcp/testing.d.mts +1 -1
- package/dist/integrations/mcp/testing.mjs +1 -1
- package/dist/interface-yhyb_pLY.d.mts +77 -0
- package/dist/logger/index.d.mts +81 -0
- package/dist/{logger-CDjpjySd.mjs → logger/index.mjs} +1 -6
- package/dist/{memory-BFAYkf8H.mjs → memory-DqI-449b.mjs} +23 -8
- package/dist/middleware/index.d.mts +109 -0
- package/dist/middleware/index.mjs +70 -0
- package/dist/multipartBody-CUQGVlM_.mjs +123 -0
- package/dist/{openapi-CXuTG1M9.mjs → openapi-DpNpqBmo.mjs} +9 -7
- package/dist/org/index.d.mts +2 -2
- package/dist/permissions/index.d.mts +3 -4
- package/dist/permissions/index.mjs +5 -5
- package/dist/{permissions-oNZawnkR.mjs → permissions-wkqRwicB.mjs} +315 -397
- package/dist/pipe-CGJxqDGx.mjs +62 -0
- package/dist/pipeline/index.d.mts +62 -0
- package/dist/pipeline/index.mjs +53 -0
- package/dist/plugins/index.d.mts +23 -3
- package/dist/plugins/index.mjs +9 -11
- package/dist/plugins/response-cache.mjs +1 -1
- package/dist/plugins/tracing-entry.mjs +1 -1
- package/dist/presets/filesUpload.d.mts +3 -3
- package/dist/presets/filesUpload.mjs +255 -1
- package/dist/presets/index.d.mts +1 -1
- package/dist/presets/index.mjs +2 -2
- package/dist/presets/multiTenant.d.mts +1 -1
- package/dist/presets/multiTenant.mjs +43 -9
- package/dist/presets/search.d.mts +91 -4
- package/dist/presets/search.mjs +1 -1
- package/dist/{presets-hM4WhNWY.mjs → presets-CrwOvuXI.mjs} +1 -1
- package/dist/{queryCachePlugin-DbUVroUG.mjs → queryCachePlugin-ChLNZvFT.mjs} +9 -9
- package/dist/{queryCachePlugin-CnTZZTC5.d.mts → queryCachePlugin-Dumka73q.d.mts} +1 -1
- package/dist/{queryParser-Cs-6SHQK.mjs → queryParser-NR__Qiju.mjs} +69 -2
- package/dist/{redis-stream-Bz-4q96t.d.mts → redis-stream-bkO88VHx.d.mts} +1 -1
- package/dist/registry/index.d.mts +1 -1
- package/dist/registry/index.mjs +1 -1
- package/dist/{requestContext-DYtmNpm5.mjs → requestContext-C38GskNt.mjs} +1 -1
- package/dist/{resourceToTools-C3cWymnW.mjs → resourceToTools-BhF3JV5p.mjs} +8 -3
- package/dist/scope/index.d.mts +2 -2
- package/dist/scope/index.mjs +2 -2
- package/dist/{sse-CJpt7LGI.mjs → sse-D8UeDwis.mjs} +1 -1
- package/dist/{store-helpers-DFiZl5TL.mjs → store-helpers-DYYUQbQN.mjs} +4 -0
- package/dist/testing/index.d.mts +6 -5
- package/dist/testing/index.mjs +17 -10
- package/dist/types/index.d.mts +5 -5
- package/dist/types/index.mjs +1 -31
- package/dist/types-CDnTEpga.mjs +27 -0
- package/dist/{types-CoSzA-s-.d.mts → types-CVKBssX5.d.mts} +1 -1
- package/dist/{types-CunEX4UX.d.mts → types-CVdgPXBW.d.mts} +20 -7
- package/dist/utils/index.d.mts +277 -3
- package/dist/utils/index.mjs +4 -5
- package/dist/{utils-B7FuRr9w.mjs → utils-LMwVidKy.mjs} +303 -2
- package/dist/{versioning-Cm8qoFDg.mjs → versioning-B6mimogM.mjs} +3 -5
- package/dist/versioning-CeUXHfjw.d.mts +117 -0
- package/package.json +31 -18
- package/skills/arc/SKILL.md +8 -12
- package/skills/arc/references/production.md +0 -41
- package/dist/circuitBreaker-CvXkjfrW.d.mts +0 -206
- package/dist/circuitBreaker-l18oRgL5.mjs +0 -284
- package/dist/core-DNncu0xF.mjs +0 -34
- package/dist/dynamic/index.d.mts +0 -93
- package/dist/dynamic/index.mjs +0 -122
- package/dist/errorHandler-DixGcttC.d.mts +0 -218
- package/dist/fields-BC7zcmI9.d.mts +0 -121
- package/dist/filesUpload-q8oHt--L.mjs +0 -377
- package/dist/interface-DplgQO2e.d.mts +0 -54
- package/dist/policies/index.d.mts +0 -425
- package/dist/policies/index.mjs +0 -318
- package/dist/rpc/index.d.mts +0 -90
- package/dist/rpc/index.mjs +0 -248
- /package/dist/{EventTransport-CqZ8FyM_.d.mts → EventTransport-CfVEGaEl.d.mts} +0 -0
- /package/dist/{applyPermissionResult-bqGpo9ML.mjs → applyPermissionResult-QhV1Pa-g.mjs} +0 -0
- /package/dist/{constants-Cxde4rpC.mjs → constants-BhY1OHoH.mjs} +0 -0
- /package/dist/{elevation-B6S5csVA.d.mts → elevation-s5ykdNHr.d.mts} +0 -0
- /package/dist/{errors-CqWnSqM-.mjs → errors-BqdUDja_.mjs} +0 -0
- /package/dist/{fields-CU6FlaDV.mjs → fields-CTMWOUDt.mjs} +0 -0
- /package/dist/{keys-qcD-TVJl.mjs → keys-nWQGUTu1.mjs} +0 -0
- /package/dist/{types-ZUu_h0jp.mjs → types-D57iXYb8.mjs} +0 -0
- /package/dist/{types-BD85MlEK.d.mts → types-tgR4Pt8F.d.mts} +0 -0
package/dist/cache/index.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { i as versionKey, n as hashParams, r as tagVersionKey, t as buildQueryKey } from "../keys-
|
|
2
|
-
import { t as MemoryCacheStore } from "../memory-
|
|
3
|
-
import { r as QueryCache, t as queryCachePlugin } from "../queryCachePlugin-
|
|
1
|
+
import { i as versionKey, n as hashParams, r as tagVersionKey, t as buildQueryKey } from "../keys-nWQGUTu1.mjs";
|
|
2
|
+
import { t as MemoryCacheStore } from "../memory-DqI-449b.mjs";
|
|
3
|
+
import { r as QueryCache, t as queryCachePlugin } from "../queryCachePlugin-ChLNZvFT.mjs";
|
|
4
4
|
//#region src/cache/redis.ts
|
|
5
5
|
/**
|
|
6
6
|
* Redis-backed cache store.
|
|
@@ -11,14 +11,14 @@ var RedisCacheStore = class {
|
|
|
11
11
|
name = "redis-cache";
|
|
12
12
|
client;
|
|
13
13
|
prefix;
|
|
14
|
-
|
|
14
|
+
defaultTtlSeconds;
|
|
15
15
|
maxEntryBytes;
|
|
16
16
|
_hits = 0;
|
|
17
17
|
_misses = 0;
|
|
18
18
|
constructor(options) {
|
|
19
19
|
this.client = options.client;
|
|
20
20
|
this.prefix = options.prefix ?? "arc:cache:";
|
|
21
|
-
this.
|
|
21
|
+
this.defaultTtlSeconds = options.defaultTtlSeconds ?? 60;
|
|
22
22
|
this.maxEntryBytes = options.maxEntryBytes ?? 0;
|
|
23
23
|
}
|
|
24
24
|
async get(key) {
|
|
@@ -36,22 +36,23 @@ var RedisCacheStore = class {
|
|
|
36
36
|
return;
|
|
37
37
|
}
|
|
38
38
|
}
|
|
39
|
-
async set(key, value,
|
|
40
|
-
const
|
|
41
|
-
if (!Number.isFinite(
|
|
39
|
+
async set(key, value, ttlSeconds) {
|
|
40
|
+
const effectiveTtlSeconds = ttlSeconds ?? this.defaultTtlSeconds;
|
|
41
|
+
if (!Number.isFinite(effectiveTtlSeconds) || effectiveTtlSeconds <= 0) return;
|
|
42
42
|
const payload = JSON.stringify(value);
|
|
43
43
|
if (this.maxEntryBytes > 0 && Buffer.byteLength(payload, "utf8") > this.maxEntryBytes) return;
|
|
44
|
-
await this.client.set(this.withPrefix(key), payload, {
|
|
44
|
+
await this.client.set(this.withPrefix(key), payload, { EX: Math.ceil(effectiveTtlSeconds) });
|
|
45
45
|
}
|
|
46
46
|
async delete(key) {
|
|
47
47
|
await this.client.del(this.withPrefix(key));
|
|
48
48
|
}
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
async
|
|
54
|
-
|
|
49
|
+
/**
|
|
50
|
+
* Invalidate keys. Pass a glob pattern to delete a subset (`user:*:v2`);
|
|
51
|
+
* omit to clear every key under this store's prefix.
|
|
52
|
+
*/
|
|
53
|
+
async clear(pattern) {
|
|
54
|
+
const scanPattern = pattern ? `${this.prefix}${pattern.includes("*") ? pattern : `${pattern}*`}` : `${this.prefix}*`;
|
|
55
|
+
await this.scanAndDelete(scanPattern);
|
|
55
56
|
}
|
|
56
57
|
stats() {
|
|
57
58
|
return {
|
|
@@ -34,7 +34,7 @@ const cachingPlugin = async (fastify, opts = {}) => {
|
|
|
34
34
|
if (rule?.staleWhileRevalidate) parts.push(`stale-while-revalidate=${rule.staleWhileRevalidate}`);
|
|
35
35
|
return parts.join(", ");
|
|
36
36
|
}
|
|
37
|
-
fastify.addHook("
|
|
37
|
+
fastify.addHook("preSerialization", async (request, reply, payload) => {
|
|
38
38
|
const url = request.url;
|
|
39
39
|
if (exclude.some((p) => url.startsWith(p))) return payload;
|
|
40
40
|
const method = request.method.toUpperCase();
|
|
@@ -48,13 +48,18 @@ const cachingPlugin = async (fastify, opts = {}) => {
|
|
|
48
48
|
const rule = findRule(url);
|
|
49
49
|
reply.header("cache-control", buildCacheControl(rule));
|
|
50
50
|
}
|
|
51
|
-
if (etag && payload) {
|
|
52
|
-
|
|
51
|
+
if (etag && payload != null) {
|
|
52
|
+
let body;
|
|
53
|
+
if (typeof payload === "string") body = payload;
|
|
54
|
+
else if (Buffer.isBuffer(payload)) body = payload.toString("utf-8");
|
|
55
|
+
else body = JSON.stringify(payload);
|
|
56
|
+
const tag = `"${fnv1a(body)}"`;
|
|
53
57
|
reply.header("etag", tag);
|
|
54
58
|
if (conditional) {
|
|
55
59
|
const ifNoneMatch = request.headers["if-none-match"];
|
|
56
60
|
if (ifNoneMatch && ifNoneMatch === tag) {
|
|
57
61
|
reply.code(304);
|
|
62
|
+
reply.serializer((p) => typeof p === "string" ? p : "");
|
|
58
63
|
return "";
|
|
59
64
|
}
|
|
60
65
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { t as ResourceRegistry } from "../../ResourceRegistry-
|
|
2
|
-
import { t as buildOpenApiSpec } from "../../openapi-
|
|
1
|
+
import { t as ResourceRegistry } from "../../ResourceRegistry-CcN2LVrc.mjs";
|
|
2
|
+
import { t as buildOpenApiSpec } from "../../openapi-DpNpqBmo.mjs";
|
|
3
3
|
import { dirname, resolve } from "node:path";
|
|
4
4
|
import { pathToFileURL } from "node:url";
|
|
5
5
|
import { mkdirSync, writeFileSync } from "node:fs";
|
|
@@ -102,7 +102,7 @@ async function installDependencies(projectPath, config, pm) {
|
|
|
102
102
|
];
|
|
103
103
|
if (config.auth === "better-auth") deps.push("better-auth@^1.6.0", "mongodb@latest");
|
|
104
104
|
else deps.push("@fastify/jwt@latest", "bcryptjs@latest");
|
|
105
|
-
if (config.adapter === "mongokit") deps.push("@classytic/mongokit@^3.
|
|
105
|
+
if (config.adapter === "mongokit") deps.push("@classytic/mongokit@^3.11.0", "@classytic/repo-core@^0.2.0", "mongoose@^9.4.1");
|
|
106
106
|
const devDeps = ["vitest@latest", "pino-pretty@latest"];
|
|
107
107
|
if (config.typescript) devDeps.push("typescript@latest", "@types/node@latest", "tsx@latest");
|
|
108
108
|
const installCmd = getInstallCommand(pm, deps, false);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { t as ResourceRegistry } from "../../ResourceRegistry-
|
|
1
|
+
import { t as ResourceRegistry } from "../../ResourceRegistry-CcN2LVrc.mjs";
|
|
2
2
|
import { resolve } from "node:path";
|
|
3
3
|
import { pathToFileURL } from "node:url";
|
|
4
4
|
//#region src/cli/commands/introspect.ts
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
2
|
+
|
|
3
|
+
//#region src/context/requestContext.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* Shape of the request-scoped context store.
|
|
6
|
+
* Populated by Arc's onRequest hook in arcCorePlugin.
|
|
7
|
+
*/
|
|
8
|
+
interface RequestStore {
|
|
9
|
+
/** Unique request identifier */
|
|
10
|
+
requestId?: string;
|
|
11
|
+
/** Authenticated user (if any) */
|
|
12
|
+
user?: {
|
|
13
|
+
id?: string;
|
|
14
|
+
_id?: string;
|
|
15
|
+
roles?: string[];
|
|
16
|
+
[key: string]: unknown;
|
|
17
|
+
} | null;
|
|
18
|
+
/** Active organization ID (multi-tenant) */
|
|
19
|
+
organizationId?: string;
|
|
20
|
+
/** Active team ID (team-scoped resources) */
|
|
21
|
+
teamId?: string;
|
|
22
|
+
/** Current resource name (set by arcDecorator in CRUD routes) */
|
|
23
|
+
resourceName?: string;
|
|
24
|
+
/** Request start time (for timing) */
|
|
25
|
+
startTime: number;
|
|
26
|
+
/** Additional context — extensible by app */
|
|
27
|
+
[key: string]: unknown;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Request context API.
|
|
31
|
+
*
|
|
32
|
+
* - `get()` — returns current store or undefined if outside request scope
|
|
33
|
+
* - `run(store, fn)` — run a function with a specific store (used by Arc internals)
|
|
34
|
+
* - `getStore()` — alias for get() (matches Node.js API naming)
|
|
35
|
+
*/
|
|
36
|
+
declare const requestContext: {
|
|
37
|
+
/**
|
|
38
|
+
* Get the current request context.
|
|
39
|
+
* Returns undefined if called outside a request lifecycle.
|
|
40
|
+
*/
|
|
41
|
+
get(): RequestStore | undefined;
|
|
42
|
+
/**
|
|
43
|
+
* Alias for get() — matches Node.js AsyncLocalStorage API naming.
|
|
44
|
+
*/
|
|
45
|
+
getStore(): RequestStore | undefined;
|
|
46
|
+
/**
|
|
47
|
+
* Run a function within a specific request context.
|
|
48
|
+
* Used internally by Arc's onRequest hook.
|
|
49
|
+
*/
|
|
50
|
+
run<T>(store: RequestStore, fn: () => T): T;
|
|
51
|
+
/**
|
|
52
|
+
* The underlying AsyncLocalStorage instance.
|
|
53
|
+
* Exposed for advanced use cases (testing, custom integrations).
|
|
54
|
+
*/
|
|
55
|
+
storage: AsyncLocalStorage<RequestStore>;
|
|
56
|
+
};
|
|
57
|
+
//#endregion
|
|
58
|
+
export { type RequestStore, requestContext };
|
package/dist/core/index.d.mts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { C as MAX_REGEX_LENGTH, D as RESERVED_QUERY_PARAMS, E as MutationOperation, O as SYSTEM_FIELDS, S as MAX_FILTER_DEPTH, T as MUTATION_OPERATIONS, _ as DEFAULT_UPDATE_METHOD, a as getControllerScope, b as HookOperation, c as createCrudRouter, d as CrudOperation, f as DEFAULT_ID_FIELD, g as DEFAULT_TENANT_FIELD, h as DEFAULT_SORT, i as getControllerContext, l as createPermissionMiddleware, m as DEFAULT_MAX_LIMIT, n as createFastifyHandler, o as sendControllerResponse, p as DEFAULT_LIMIT, r as createRequestContext, s as defineResourceVariants, t as createCrudHandlers, u as CRUD_OPERATIONS, v as HOOK_OPERATIONS, w as MAX_SEARCH_LENGTH, x as HookPhase, y as HOOK_PHASES } from "../index-
|
|
1
|
+
import { B as ResourceDefinition, V as defineResource, an as BaseControllerOptions, cn as BodySanitizer, dn as AccessControlConfig, in as BaseController, ln as BodySanitizerConfig, on as QueryResolver, sn as QueryResolverConfig, un as AccessControl } from "../index-BGbpGVyM.mjs";
|
|
2
|
+
import { C as MAX_REGEX_LENGTH, D as RESERVED_QUERY_PARAMS, E as MutationOperation, O as SYSTEM_FIELDS, S as MAX_FILTER_DEPTH, T as MUTATION_OPERATIONS, _ as DEFAULT_UPDATE_METHOD, a as getControllerScope, b as HookOperation, c as createCrudRouter, d as CrudOperation, f as DEFAULT_ID_FIELD, g as DEFAULT_TENANT_FIELD, h as DEFAULT_SORT, i as getControllerContext, l as createPermissionMiddleware, m as DEFAULT_MAX_LIMIT, n as createFastifyHandler, o as sendControllerResponse, p as DEFAULT_LIMIT, r as createRequestContext, s as defineResourceVariants, t as createCrudHandlers, u as CRUD_OPERATIONS, v as HOOK_OPERATIONS, w as MAX_SEARCH_LENGTH, x as HookPhase, y as HOOK_PHASES } from "../index-EqQN6p0W.mjs";
|
|
3
3
|
export { AccessControl, AccessControlConfig, BaseController, BaseControllerOptions, BodySanitizer, BodySanitizerConfig, CRUD_OPERATIONS, CrudOperation, DEFAULT_ID_FIELD, DEFAULT_LIMIT, DEFAULT_MAX_LIMIT, DEFAULT_SORT, DEFAULT_TENANT_FIELD, DEFAULT_UPDATE_METHOD, HOOK_OPERATIONS, HOOK_PHASES, HookOperation, HookPhase, MAX_FILTER_DEPTH, MAX_REGEX_LENGTH, MAX_SEARCH_LENGTH, MUTATION_OPERATIONS, MutationOperation, QueryResolver, QueryResolverConfig, RESERVED_QUERY_PARAMS, ResourceDefinition, SYSTEM_FIELDS, createCrudHandlers, createCrudRouter, createFastifyHandler, createPermissionMiddleware, createRequestContext, defineResource, defineResourceVariants, getControllerContext, getControllerScope, sendControllerResponse };
|
package/dist/core/index.mjs
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import { a as DEFAULT_SORT, c as HOOK_OPERATIONS, d as MAX_REGEX_LENGTH, f as MAX_SEARCH_LENGTH, h as SYSTEM_FIELDS, i as DEFAULT_MAX_LIMIT, l as HOOK_PHASES, m as RESERVED_QUERY_PARAMS, n as DEFAULT_ID_FIELD, o as DEFAULT_TENANT_FIELD, p as MUTATION_OPERATIONS, r as DEFAULT_LIMIT, s as DEFAULT_UPDATE_METHOD, t as CRUD_OPERATIONS, u as MAX_FILTER_DEPTH } from "../constants-
|
|
2
|
-
import { i as AccessControl, n as QueryResolver, r as BodySanitizer, t as BaseController } from "../BaseController-
|
|
3
|
-
import { c as
|
|
4
|
-
import { t as defineResourceVariants } from "../core-DNncu0xF.mjs";
|
|
1
|
+
import { a as DEFAULT_SORT, c as HOOK_OPERATIONS, d as MAX_REGEX_LENGTH, f as MAX_SEARCH_LENGTH, h as SYSTEM_FIELDS, i as DEFAULT_MAX_LIMIT, l as HOOK_PHASES, m as RESERVED_QUERY_PARAMS, n as DEFAULT_ID_FIELD, o as DEFAULT_TENANT_FIELD, p as MUTATION_OPERATIONS, r as DEFAULT_LIMIT, s as DEFAULT_UPDATE_METHOD, t as CRUD_OPERATIONS, u as MAX_FILTER_DEPTH } from "../constants-BhY1OHoH.mjs";
|
|
2
|
+
import { i as AccessControl, n as QueryResolver, r as BodySanitizer, t as BaseController } from "../BaseController-DVNKvoX4.mjs";
|
|
3
|
+
import { c as createPermissionMiddleware, d as createRequestContext, f as getControllerContext, l as createCrudHandlers, m as sendControllerResponse, n as ResourceDefinition, p as getControllerScope, r as defineResource, s as createCrudRouter, t as defineResourceVariants, u as createFastifyHandler } from "../core-3MWJosCH.mjs";
|
|
5
4
|
export { AccessControl, BaseController, BodySanitizer, CRUD_OPERATIONS, DEFAULT_ID_FIELD, DEFAULT_LIMIT, DEFAULT_MAX_LIMIT, DEFAULT_SORT, DEFAULT_TENANT_FIELD, DEFAULT_UPDATE_METHOD, HOOK_OPERATIONS, HOOK_PHASES, MAX_FILTER_DEPTH, MAX_REGEX_LENGTH, MAX_SEARCH_LENGTH, MUTATION_OPERATIONS, QueryResolver, RESERVED_QUERY_PARAMS, ResourceDefinition, SYSTEM_FIELDS, createCrudHandlers, createCrudRouter, createFastifyHandler, createPermissionMiddleware, createRequestContext, defineResource, defineResourceVariants, getControllerContext, getControllerScope, sendControllerResponse };
|
|
@@ -1,73 +1,29 @@
|
|
|
1
|
-
import { s as DEFAULT_UPDATE_METHOD, t as CRUD_OPERATIONS } from "./constants-
|
|
2
|
-
import { _ as isElevated, n as PUBLIC_SCOPE, v as isMember } from "./types-AOD8fxIw.mjs";
|
|
3
|
-
import { t as BaseController } from "./BaseController-
|
|
4
|
-
import { i as resolveEffectiveRoles, t as applyFieldReadPermissions } from "./fields-
|
|
5
|
-
import { t as getUserRoles } from "./types-
|
|
6
|
-
import {
|
|
7
|
-
import { t as
|
|
8
|
-
import {
|
|
9
|
-
import { i as getDefaultCrudSchemas } from "./utils-B7FuRr9w.mjs";
|
|
1
|
+
import { s as DEFAULT_UPDATE_METHOD, t as CRUD_OPERATIONS } from "./constants-BhY1OHoH.mjs";
|
|
2
|
+
import { _ as isElevated, n as PUBLIC_SCOPE, o as getOrgId, p as getUserId, v as isMember } from "./types-AOD8fxIw.mjs";
|
|
3
|
+
import { t as BaseController } from "./BaseController-DVNKvoX4.mjs";
|
|
4
|
+
import { i as resolveEffectiveRoles, t as applyFieldReadPermissions } from "./fields-CTMWOUDt.mjs";
|
|
5
|
+
import { t as getUserRoles } from "./types-D57iXYb8.mjs";
|
|
6
|
+
import { t as requestContext } from "./requestContext-C38GskNt.mjs";
|
|
7
|
+
import { n as normalizePermissionResult, t as applyPermissionResult } from "./applyPermissionResult-QhV1Pa-g.mjs";
|
|
8
|
+
import { i as getDefaultCrudSchemas } from "./utils-LMwVidKy.mjs";
|
|
10
9
|
import { n as convertRouteSchema, t as convertOpenApiSchemas } from "./schemaConverter-BxFDdtXu.mjs";
|
|
11
10
|
import { t as hasEvents } from "./typeGuards-Cj5Rgvlg.mjs";
|
|
12
|
-
import {
|
|
13
|
-
|
|
11
|
+
import { t as executePipeline } from "./pipe-CGJxqDGx.mjs";
|
|
12
|
+
import { r as getAvailablePresets, t as applyPresets } from "./presets-CrwOvuXI.mjs";
|
|
13
|
+
import { t as resolveActionPermission } from "./actionPermissions-TUVR3uiZ.mjs";
|
|
14
|
+
//#region src/scope/projection.ts
|
|
14
15
|
/**
|
|
15
|
-
*
|
|
16
|
-
*
|
|
16
|
+
* Compute the request-scope projection. Returns `undefined` when no
|
|
17
|
+
* scope is attached (public / unscoped routes) so hosts can idiomatically
|
|
18
|
+
* write `ctx.scope?.organizationId` without a double-null check.
|
|
17
19
|
*/
|
|
18
|
-
function
|
|
19
|
-
return
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
if (!step.operations || step.operations.length === 0) return true;
|
|
26
|
-
return step.operations.includes(operation);
|
|
27
|
-
}
|
|
28
|
-
/**
|
|
29
|
-
* Execute a pipeline against a request context.
|
|
30
|
-
*
|
|
31
|
-
* This is the core runtime that createCrudRouter uses to execute pipelines.
|
|
32
|
-
* External usage is not needed — this is wired automatically when `pipe` is set.
|
|
33
|
-
*
|
|
34
|
-
* @param steps - Pipeline steps to execute
|
|
35
|
-
* @param ctx - The pipeline context (extends IRequestContext)
|
|
36
|
-
* @param handler - The actual controller method to call
|
|
37
|
-
* @param operation - The CRUD operation name
|
|
38
|
-
* @returns The controller response (possibly modified by interceptors)
|
|
39
|
-
*/
|
|
40
|
-
async function executePipeline(steps, ctx, handler, operation) {
|
|
41
|
-
const guards = [];
|
|
42
|
-
const transforms = [];
|
|
43
|
-
const interceptors = [];
|
|
44
|
-
for (const step of steps) {
|
|
45
|
-
if (!appliesTo(step, operation)) continue;
|
|
46
|
-
switch (step._type) {
|
|
47
|
-
case "guard":
|
|
48
|
-
guards.push(step);
|
|
49
|
-
break;
|
|
50
|
-
case "transform":
|
|
51
|
-
transforms.push(step);
|
|
52
|
-
break;
|
|
53
|
-
case "interceptor":
|
|
54
|
-
interceptors.push(step);
|
|
55
|
-
break;
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
for (const g of guards) if (!await g.handler(ctx)) throw new ForbiddenError(`Guard '${g.name}' denied access`);
|
|
59
|
-
let currentCtx = ctx;
|
|
60
|
-
for (const t of transforms) {
|
|
61
|
-
const result = await t.handler(currentCtx);
|
|
62
|
-
if (result) currentCtx = result;
|
|
63
|
-
}
|
|
64
|
-
let chain = () => handler(currentCtx);
|
|
65
|
-
for (let i = interceptors.length - 1; i >= 0; i--) {
|
|
66
|
-
const interceptor = interceptors[i];
|
|
67
|
-
const next = chain;
|
|
68
|
-
chain = () => interceptor.handler(currentCtx, next);
|
|
69
|
-
}
|
|
70
|
-
return chain();
|
|
20
|
+
function buildRequestScopeProjection(scope) {
|
|
21
|
+
if (!scope) return void 0;
|
|
22
|
+
return {
|
|
23
|
+
organizationId: getOrgId(scope),
|
|
24
|
+
userId: getUserId(scope),
|
|
25
|
+
orgRoles: isMember(scope) ? scope.orgRoles : void 0
|
|
26
|
+
};
|
|
71
27
|
}
|
|
72
28
|
//#endregion
|
|
73
29
|
//#region src/core/fastifyAdapter.ts
|
|
@@ -119,6 +75,8 @@ function createRequestContext(req) {
|
|
|
119
75
|
queryCache: srv && "queryCache" in srv ? srv.queryCache : void 0,
|
|
120
76
|
log: req.log
|
|
121
77
|
};
|
|
78
|
+
const rawScope = reqWithExtras.scope;
|
|
79
|
+
const scopeProjection = buildRequestScopeProjection(rawScope);
|
|
122
80
|
return {
|
|
123
81
|
query: reqWithExtras.query ?? {},
|
|
124
82
|
body: reqWithExtras.body ?? {},
|
|
@@ -135,10 +93,11 @@ function createRequestContext(req) {
|
|
|
135
93
|
};
|
|
136
94
|
})() : null,
|
|
137
95
|
context: requestContext,
|
|
96
|
+
scope: scopeProjection,
|
|
138
97
|
metadata: {
|
|
139
98
|
...reqWithExtras.context,
|
|
140
99
|
arc: reqWithExtras.arc,
|
|
141
|
-
_scope:
|
|
100
|
+
_scope: rawScope,
|
|
142
101
|
_ownershipCheck: reqWithExtras._ownershipCheck,
|
|
143
102
|
_policyFilters: reqWithExtras._policyFilters ?? {},
|
|
144
103
|
log: reqWithExtras.log
|
|
@@ -667,6 +626,52 @@ function createPermissionMiddleware(permission, resourceName, action) {
|
|
|
667
626
|
return buildPermissionMiddleware(permission, resourceName, action);
|
|
668
627
|
}
|
|
669
628
|
//#endregion
|
|
629
|
+
//#region src/core/schemaOptions.ts
|
|
630
|
+
/**
|
|
631
|
+
* Inject the tenant-scoping field rule into `schemaOptions.fieldRules`:
|
|
632
|
+
*
|
|
633
|
+
* { [tenantField]: { systemManaged: true, preserveForElevated: true } }
|
|
634
|
+
*
|
|
635
|
+
* Why both flags: `systemManaged` tells `BodySanitizer` to strip the
|
|
636
|
+
* field from inbound bodies (so member clients can't forge a target
|
|
637
|
+
* tenant). `preserveForElevated` exempts elevated-admin scopes from the
|
|
638
|
+
* strip, so platform admins without a pinned org can still pick a target
|
|
639
|
+
* org via the request body (the only channel they have —
|
|
640
|
+
* `BaseController.create` can't re-stamp from scope when scope has no
|
|
641
|
+
* orgId).
|
|
642
|
+
*
|
|
643
|
+
* **Returns a new `RouteSchemaOptions`** — the input is never mutated.
|
|
644
|
+
* Callers should assign the return value to whatever config slot they
|
|
645
|
+
* read from downstream (always the `resolvedConfig`, never raw `config`).
|
|
646
|
+
*
|
|
647
|
+
* **No-op when:**
|
|
648
|
+
* - `tenantField` is `false` (platform-universal resource)
|
|
649
|
+
* - `tenantField` is undefined
|
|
650
|
+
* - The caller already declared `fieldRules[tenantField].systemManaged`
|
|
651
|
+
* (even as `false`) — explicit opt-outs are respected
|
|
652
|
+
*
|
|
653
|
+
* `preserveForElevated` defaults to `true` but is preserved verbatim
|
|
654
|
+
* when the caller set it explicitly.
|
|
655
|
+
*/
|
|
656
|
+
function autoInjectTenantFieldRules(schemaOptions, tenantField) {
|
|
657
|
+
if (tenantField === false || tenantField === void 0) return schemaOptions;
|
|
658
|
+
const fieldName = tenantField || "organizationId";
|
|
659
|
+
const existing = schemaOptions?.fieldRules ?? {};
|
|
660
|
+
const existingRule = existing[fieldName];
|
|
661
|
+
if (existingRule && existingRule.systemManaged !== void 0) return schemaOptions;
|
|
662
|
+
return {
|
|
663
|
+
...schemaOptions ?? {},
|
|
664
|
+
fieldRules: {
|
|
665
|
+
...existing,
|
|
666
|
+
[fieldName]: {
|
|
667
|
+
...existingRule ?? {},
|
|
668
|
+
systemManaged: true,
|
|
669
|
+
preserveForElevated: existingRule?.preserveForElevated ?? true
|
|
670
|
+
}
|
|
671
|
+
}
|
|
672
|
+
};
|
|
673
|
+
}
|
|
674
|
+
//#endregion
|
|
670
675
|
//#region src/core/validateResourceConfig.ts
|
|
671
676
|
/**
|
|
672
677
|
* Resource Configuration Validator
|
|
@@ -708,7 +713,7 @@ function validateResourceConfig(config, options = {}) {
|
|
|
708
713
|
else if (!config.adapter.repository) errors.push({
|
|
709
714
|
field: "adapter.repository",
|
|
710
715
|
message: "Adapter must provide a repository",
|
|
711
|
-
suggestion: "Ensure your adapter returns a valid
|
|
716
|
+
suggestion: "Ensure your adapter returns a valid StandardRepo (see @classytic/repo-core)"
|
|
712
717
|
});
|
|
713
718
|
} else if (!config.adapter && !config.routes?.length) warnings.push({
|
|
714
719
|
field: "config",
|
|
@@ -926,6 +931,7 @@ function defineResource(config) {
|
|
|
926
931
|
const originalPresets = (config.presets ?? []).map((p) => typeof p === "string" ? p : p.name);
|
|
927
932
|
const resolvedConfig = config.presets?.length ? applyPresets(config, config.presets) : config;
|
|
928
933
|
resolvedConfig._appliedPresets = originalPresets;
|
|
934
|
+
resolvedConfig.schemaOptions = autoInjectTenantFieldRules(resolvedConfig.schemaOptions, resolvedConfig.tenantField);
|
|
929
935
|
let controller = resolvedConfig.controller;
|
|
930
936
|
if (!controller && hasCrudRoutes && repository) {
|
|
931
937
|
const qp = resolvedConfig.queryParser;
|
|
@@ -941,6 +947,7 @@ function defineResource(config) {
|
|
|
941
947
|
maxLimit: maxLimitFromParser,
|
|
942
948
|
tenantField: resolvedConfig.tenantField,
|
|
943
949
|
idField: resolvedConfig.idField,
|
|
950
|
+
...resolvedConfig.defaultSort !== void 0 ? { defaultSort: resolvedConfig.defaultSort } : {},
|
|
944
951
|
matchesFilter: config.adapter?.matchesFilter,
|
|
945
952
|
cache: resolvedConfig.cache,
|
|
946
953
|
onFieldWriteDenied: resolvedConfig.onFieldWriteDenied,
|
|
@@ -965,11 +972,17 @@ function defineResource(config) {
|
|
|
965
972
|
if (config.hooks) {
|
|
966
973
|
const h = config.hooks;
|
|
967
974
|
const inlineHooks = [];
|
|
968
|
-
const toCtx = (ctx) =>
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
975
|
+
const toCtx = (ctx) => {
|
|
976
|
+
const context = ctx.context;
|
|
977
|
+
const rawScope = context?._scope;
|
|
978
|
+
return {
|
|
979
|
+
data: ctx.data ?? ctx.result ?? {},
|
|
980
|
+
user: ctx.user,
|
|
981
|
+
context,
|
|
982
|
+
scope: buildRequestScopeProjection(rawScope),
|
|
983
|
+
meta: ctx.meta
|
|
984
|
+
};
|
|
985
|
+
};
|
|
973
986
|
if (h.beforeCreate) {
|
|
974
987
|
const fn = h.beforeCreate;
|
|
975
988
|
inlineHooks.push({
|
|
@@ -1028,15 +1041,15 @@ function defineResource(config) {
|
|
|
1028
1041
|
}
|
|
1029
1042
|
if (!config.skipRegistry) try {
|
|
1030
1043
|
let openApiSchemas;
|
|
1031
|
-
if (
|
|
1044
|
+
if (resolvedConfig.adapter?.generateSchemas) {
|
|
1032
1045
|
const adapterContext = {
|
|
1033
|
-
idField:
|
|
1034
|
-
resourceName:
|
|
1046
|
+
idField: resolvedConfig.idField,
|
|
1047
|
+
resourceName: resolvedConfig.name
|
|
1035
1048
|
};
|
|
1036
|
-
const generated =
|
|
1049
|
+
const generated = resolvedConfig.adapter.generateSchemas(resolvedConfig.schemaOptions, adapterContext);
|
|
1037
1050
|
if (generated) openApiSchemas = generated;
|
|
1038
1051
|
}
|
|
1039
|
-
if (
|
|
1052
|
+
if (resolvedConfig.idField && resolvedConfig.idField !== "_id" && openApiSchemas?.params && typeof openApiSchemas.params === "object") {
|
|
1040
1053
|
const params = openApiSchemas.params;
|
|
1041
1054
|
const properties = params.properties;
|
|
1042
1055
|
const idProp = properties?.id;
|
|
@@ -1047,7 +1060,7 @@ function defineResource(config) {
|
|
|
1047
1060
|
delete cleanedId.pattern;
|
|
1048
1061
|
delete cleanedId.minLength;
|
|
1049
1062
|
delete cleanedId.maxLength;
|
|
1050
|
-
if (!cleanedId.description) cleanedId.description = `${
|
|
1063
|
+
if (!cleanedId.description) cleanedId.description = `${resolvedConfig.idField} (custom ID field)`;
|
|
1051
1064
|
openApiSchemas = {
|
|
1052
1065
|
...openApiSchemas,
|
|
1053
1066
|
params: {
|
|
@@ -1061,7 +1074,7 @@ function defineResource(config) {
|
|
|
1061
1074
|
}
|
|
1062
1075
|
}
|
|
1063
1076
|
}
|
|
1064
|
-
const queryParser =
|
|
1077
|
+
const queryParser = resolvedConfig.queryParser;
|
|
1065
1078
|
if (queryParser?.getQuerySchema) {
|
|
1066
1079
|
const querySchema = queryParser.getQuerySchema();
|
|
1067
1080
|
if (querySchema) openApiSchemas = {
|
|
@@ -1069,13 +1082,13 @@ function defineResource(config) {
|
|
|
1069
1082
|
listQuery: querySchema
|
|
1070
1083
|
};
|
|
1071
1084
|
}
|
|
1072
|
-
if (
|
|
1085
|
+
if (resolvedConfig.openApiSchemas) openApiSchemas = {
|
|
1073
1086
|
...openApiSchemas,
|
|
1074
|
-
...
|
|
1087
|
+
...resolvedConfig.openApiSchemas
|
|
1075
1088
|
};
|
|
1076
1089
|
if (openApiSchemas) openApiSchemas = convertOpenApiSchemas(openApiSchemas);
|
|
1077
1090
|
resource._registryMeta = {
|
|
1078
|
-
module:
|
|
1091
|
+
module: resolvedConfig.module,
|
|
1079
1092
|
openApiSchemas
|
|
1080
1093
|
};
|
|
1081
1094
|
} catch {}
|
|
@@ -1287,8 +1300,8 @@ var ResourceDefinition = class {
|
|
|
1287
1300
|
fields: self.fields
|
|
1288
1301
|
});
|
|
1289
1302
|
if (self.actions && Object.keys(self.actions).length > 0) {
|
|
1290
|
-
const { createActionRouter } = await import("./createActionRouter-
|
|
1291
|
-
createActionRouter(instance, normalizeActionsToRouterConfig(self.actions, self.actionPermissions, self.tag));
|
|
1303
|
+
const { createActionRouter } = await import("./createActionRouter-C8UUB3Px.mjs").then((n) => n.n);
|
|
1304
|
+
createActionRouter(instance, normalizeActionsToRouterConfig(self.actions, self.actionPermissions, self.tag, self.permissions, self.name, typedInstance.log));
|
|
1292
1305
|
}
|
|
1293
1306
|
if (self.events && Object.keys(self.events).length > 0) typedInstance.log?.debug?.(`Resource '${self.name}' defined ${Object.keys(self.events).length} events`);
|
|
1294
1307
|
}, { prefix: self.prefix });
|
|
@@ -1354,18 +1367,53 @@ function capitalize(str) {
|
|
|
1354
1367
|
return str.charAt(0).toUpperCase() + str.slice(1);
|
|
1355
1368
|
}
|
|
1356
1369
|
/**
|
|
1357
|
-
* Normalize ActionsMap into the ActionRouterConfig shape that
|
|
1370
|
+
* Normalize `ActionsMap` into the `ActionRouterConfig` shape that
|
|
1371
|
+
* `createActionRouter` expects.
|
|
1372
|
+
*
|
|
1373
|
+
* **Permission fallback chain (fail-closed, v2.10.5):**
|
|
1374
|
+
* Actions mutate state, so "no permission declared" historically meant
|
|
1375
|
+
* "authenticated users can call it" — a silent authz hole for apps using
|
|
1376
|
+
* the function shorthand `actions: { send: async (id, data, req) => ... }`.
|
|
1377
|
+
*
|
|
1378
|
+
* The chain is now:
|
|
1379
|
+
* 1. `ActionDefinition.permissions` — explicit per-action check.
|
|
1380
|
+
* 2. Resource-level `actionPermissions` — explicit global-for-actions.
|
|
1381
|
+
* 3. Resource-level `permissions.update` — sensible default (actions mutate).
|
|
1382
|
+
* 4. Boot-time error — forces the author to pick an explicit gate.
|
|
1383
|
+
*
|
|
1384
|
+
* When step 3 fires, we log a warning (not a throw) so upgrading apps
|
|
1385
|
+
* aren't bricked by the behavior change, but the gap is visible. Apps
|
|
1386
|
+
* that genuinely want public actions must declare `allowPublic()`
|
|
1387
|
+
* explicitly — auth-by-accident is no longer a supported state.
|
|
1358
1388
|
*/
|
|
1359
|
-
function normalizeActionsToRouterConfig(actions, globalAuth, tag) {
|
|
1389
|
+
function normalizeActionsToRouterConfig(actions, globalAuth, tag, resourcePermissions, resourceName, log) {
|
|
1360
1390
|
const handlers = {};
|
|
1361
1391
|
const permissions = {};
|
|
1362
1392
|
const schemas = {};
|
|
1363
|
-
for (const [name, entry] of Object.entries(actions))
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1393
|
+
for (const [name, entry] of Object.entries(actions)) {
|
|
1394
|
+
const explicit = typeof entry !== "function" && entry.permissions ? entry.permissions : void 0;
|
|
1395
|
+
if (typeof entry === "function") handlers[name] = entry;
|
|
1396
|
+
else {
|
|
1397
|
+
const def = entry;
|
|
1398
|
+
handlers[name] = def.handler;
|
|
1399
|
+
if (def.permissions) permissions[name] = def.permissions;
|
|
1400
|
+
if (def.schema) schemas[name] = def.schema;
|
|
1401
|
+
}
|
|
1402
|
+
const effective = resolveActionPermission({
|
|
1403
|
+
action: entry,
|
|
1404
|
+
resourcePermissions,
|
|
1405
|
+
resourceActionPermissions: void 0,
|
|
1406
|
+
globalAuth
|
|
1407
|
+
});
|
|
1408
|
+
if (!explicit && !globalAuth && effective && effective === resourcePermissions?.update) {
|
|
1409
|
+
permissions[name] = effective;
|
|
1410
|
+
log?.warn?.({
|
|
1411
|
+
resource: resourceName,
|
|
1412
|
+
action: name,
|
|
1413
|
+
fallback: "permissions.update"
|
|
1414
|
+
}, `[Arc] Action '${resourceName}.${name}' has no explicit permission — falling back to the resource's \`permissions.update\` gate. Declare \`actions.${name}.permissions\` (or resource \`actionPermissions\`) to silence this.`);
|
|
1415
|
+
}
|
|
1416
|
+
if (!effective) throw new Error(`[Arc] Resource '${resourceName}': action '${name}' has no permission gate and the resource defines no \`permissions.update\` fallback. Declare one of:\n - \`actions.${name}.permissions: <PermissionCheck>\` (per-action)\n - \`actionPermissions: <PermissionCheck>\` (resource-wide)\n - \`permissions.update: <PermissionCheck>\` (inherited by actions)\nUse \`allowPublic()\` if you genuinely want the action unauthenticated.`);
|
|
1369
1417
|
}
|
|
1370
1418
|
return {
|
|
1371
1419
|
tag,
|
|
@@ -1376,4 +1424,36 @@ function normalizeActionsToRouterConfig(actions, globalAuth, tag) {
|
|
|
1376
1424
|
};
|
|
1377
1425
|
}
|
|
1378
1426
|
//#endregion
|
|
1379
|
-
|
|
1427
|
+
//#region src/core/defineResourceVariants.ts
|
|
1428
|
+
/**
|
|
1429
|
+
* Define multiple resources from a shared base config and per-variant overrides.
|
|
1430
|
+
*
|
|
1431
|
+
* Each variant is independently passed through `defineResource()` — the
|
|
1432
|
+
* returned `ResourceDefinition`s are real, fully-registered resources.
|
|
1433
|
+
* Register each one's plugin in your app:
|
|
1434
|
+
*
|
|
1435
|
+
* ```typescript
|
|
1436
|
+
* await app.register(articlePublic.toPlugin());
|
|
1437
|
+
* await app.register(articleAdmin.toPlugin());
|
|
1438
|
+
* ```
|
|
1439
|
+
*
|
|
1440
|
+
* @param base Shared config — adapter, queryParser, schemaOptions, hooks, etc.
|
|
1441
|
+
* Must NOT include `name` or `prefix` (those are per-variant).
|
|
1442
|
+
* @param variants Map of variant key → override. Each variant must declare
|
|
1443
|
+
* its own `name` and `prefix`. Other fields override the base.
|
|
1444
|
+
* @returns A record where each key from `variants` maps to a real
|
|
1445
|
+
* `ResourceDefinition` ready for `.toPlugin()` registration.
|
|
1446
|
+
*/
|
|
1447
|
+
function defineResourceVariants(base, variants) {
|
|
1448
|
+
const out = {};
|
|
1449
|
+
for (const key of Object.keys(variants)) {
|
|
1450
|
+
const override = variants[key];
|
|
1451
|
+
out[key] = defineResource({
|
|
1452
|
+
...base,
|
|
1453
|
+
...override
|
|
1454
|
+
});
|
|
1455
|
+
}
|
|
1456
|
+
return out;
|
|
1457
|
+
}
|
|
1458
|
+
//#endregion
|
|
1459
|
+
export { formatValidationErrors as a, createPermissionMiddleware as c, createRequestContext as d, getControllerContext as f, assertValidConfig as i, createCrudHandlers as l, sendControllerResponse as m, ResourceDefinition as n, validateResourceConfig as o, getControllerScope as p, defineResource as r, createCrudRouter as s, defineResourceVariants as t, createFastifyHandler as u };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { t as __exportAll } from "./chunk-BpYLSNr0.mjs";
|
|
2
|
-
import { n as normalizePermissionResult, t as applyPermissionResult } from "./applyPermissionResult-
|
|
2
|
+
import { n as normalizePermissionResult, t as applyPermissionResult } from "./applyPermissionResult-QhV1Pa-g.mjs";
|
|
3
3
|
import { a as toJsonSchema } from "./schemaConverter-BxFDdtXu.mjs";
|
|
4
4
|
//#region src/core/createActionRouter.ts
|
|
5
5
|
var createActionRouter_exports = /* @__PURE__ */ __exportAll({
|