@classytic/arc 2.25.0 → 2.30.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -0
- package/dist/{BaseController--M8EMzt0.mjs → BaseController-B5DwufbB.mjs} +15 -11
- package/dist/EventTransport-C6YA8xY9.mjs +103 -0
- package/dist/{HookSystem-C1vB_Gc9.mjs → HookSystem-WkIougo6.mjs} +15 -35
- package/dist/applyPermissionResult-Cf6zUCvb.mjs +257 -0
- package/dist/audit/index.d.mts +2 -2
- package/dist/audit/index.mjs +1 -1
- package/dist/auth/index.d.mts +2 -2
- package/dist/auth/index.mjs +1 -1
- package/dist/{buildHandler-Dtwd8fdz.mjs → buildHandler-BYS3Vkfz.mjs} +32 -6
- package/dist/cleanup/index.d.mts +313 -0
- package/dist/cleanup/index.mjs +1267 -0
- package/dist/cli/commands/describe.d.mts +1 -1
- package/dist/cli/commands/docs.mjs +1 -1
- package/dist/core/index.d.mts +2 -2
- package/dist/core/index.mjs +5 -3
- package/dist/core-poalJLvp.mjs +175 -0
- package/dist/{createActionRouter-B6GvAuAX.mjs → createActionRouter-BxuywVRB.mjs} +2 -2
- package/dist/{createAggregationRouter-wzwFa_dW.mjs → createAggregationRouter-BiY-lGdY.mjs} +7 -20
- package/dist/{preloadResources-DfyngIN9.mjs → createApp-CXrKFciH.mjs} +25 -76
- package/dist/defineAggregation-ufWjUwml.mjs +47 -0
- package/dist/{core-EUNu0u07.mjs → defineResource-CxWqDCAy.mjs} +36 -261
- package/dist/docs/index.d.mts +1 -1
- package/dist/docs/index.mjs +1 -1
- package/dist/{eventPlugin-DrQ_FesK.mjs → eventPlugin-D2NNR4py.mjs} +2 -103
- package/dist/events/index.d.mts +28 -2
- package/dist/events/index.mjs +24 -343
- package/dist/factory/index.d.mts +4 -3
- package/dist/factory/index.mjs +3 -1
- package/dist/{fastify-augmentation-dPNBVeIj.d.mts → fastify-augmentation-0WhPnUH7.d.mts} +3 -2
- package/dist/{applyPermissionResult-Tw4iXtlf.mjs → fields-CQ_7KQWw.mjs} +1 -97
- package/dist/hooks/index.d.mts +1 -1
- package/dist/hooks/index.mjs +1 -1
- package/dist/idempotency/index.mjs +1 -1
- package/dist/index-CCzVR-N3.d.mts +107 -0
- package/dist/{index-BMnbzvtO.d.mts → index-Ch16aAah.d.mts} +201 -21
- package/dist/{index-f4PTrk3B.d.mts → index-Cx7bro4R.d.mts} +4 -4
- package/dist/{index-DPRH0Cme.d.mts → index-Q5gzEIWY.d.mts} +97 -66
- package/dist/index.d.mts +6 -6
- package/dist/index.mjs +8 -6
- package/dist/integrations/index.d.mts +1 -1
- package/dist/integrations/mcp/index.d.mts +6 -6
- 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/{mcpPlugin-Cb7olw0j.mjs → mcpPlugin-nRzm5g2s.mjs} +36 -43
- package/dist/middleware/index.d.mts +1 -1
- package/dist/migrations/index.d.mts +0 -3
- package/dist/migrations/index.mjs +0 -3
- package/dist/module-_-M7C5ES.mjs +29 -0
- package/dist/{openapi-DrLtog_B.mjs → openapi-B8Vkbxbn.mjs} +1 -1
- package/dist/permissions/index.d.mts +3 -3
- package/dist/permissions/index.mjs +4 -3
- package/dist/{permissions-BrtiBKGs.mjs → permissions-Bfu_8y1U.mjs} +318 -289
- package/dist/pipeline/index.d.mts +1 -1
- package/dist/{pipeline-BN5G05rd.mjs → pipeline-BJnpkY9i.mjs} +4 -18
- package/dist/plugins/index.d.mts +4 -4
- package/dist/plugins/index.mjs +2 -2
- package/dist/plugins/tracing-entry.mjs +1 -1
- package/dist/{preloadResources-0oe-JSSS.d.mts → preloadResources-DCfa2egz.d.mts} +3 -2
- package/dist/preloadResources-DSY5HGpS.mjs +45 -0
- package/dist/presets/filesUpload.d.mts +2 -2
- package/dist/presets/filesUpload.mjs +1 -1
- package/dist/presets/index.d.mts +14 -2
- package/dist/presets/index.mjs +1 -1
- package/dist/presets/multiTenant.d.mts +1 -1
- package/dist/presets/search.d.mts +2 -2
- package/dist/presets/search.mjs +1 -1
- package/dist/{presets-DwFDVKP0.mjs → presets-CJOEXLuk.mjs} +25 -9
- package/dist/projection-UBDgfMwu.mjs +17 -0
- package/dist/{realtime-Cuw_deA-.mjs → realtime-B9nmIY5V.mjs} +2 -1
- package/dist/{registerRoute-v0Wb6Plt.mjs → registerRoute-JkxIEBJq.mjs} +3 -1
- package/dist/registry/index.d.mts +2 -2
- package/dist/registry/index.mjs +1 -1
- package/dist/{registry-vcOCtPij.mjs → registry-BYXUUg21.mjs} +16 -1
- package/dist/repositoryFilter-Dz8k3S4P.mjs +44 -0
- package/dist/subscribe-helpers-DfzsivM6.mjs +339 -0
- package/dist/testing/cleanupStoreContract.d.mts +10 -0
- package/dist/testing/cleanupStoreContract.mjs +375 -0
- package/dist/testing/index.d.mts +34 -5
- package/dist/testing/index.mjs +316 -211
- package/dist/testing/outboxStoreContract.d.mts +13 -0
- package/dist/testing/outboxStoreContract.mjs +247 -0
- package/dist/types/index.d.mts +3 -3
- package/dist/{types-QFGc3Cjv.d.mts → types-Bmmn-fO0.d.mts} +1 -1
- package/dist/types-C_ZZRcD6.d.mts +450 -0
- package/dist/{types-Dmw-6Qeo.d.mts → types-ChvvVNP8.d.mts} +93 -88
- package/dist/{index-KUoxjIq1.d.mts → types-D_n8L6VY.d.mts} +33 -106
- package/dist/usage/index.d.mts +1 -1
- package/dist/usage/index.mjs +0 -0
- package/dist/utils/index.d.mts +2 -5
- package/package.json +539 -524
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Nt as RateLimitConfig, V as ResourceDefinition, et as RouteSchemaOptions } from "../../index-
|
|
1
|
+
import { Nt as RateLimitConfig, V as ResourceDefinition, et as RouteSchemaOptions } from "../../index-Q5gzEIWY.mjs";
|
|
2
2
|
//#region src/cli/commands/describe.d.ts
|
|
3
3
|
interface DescribedResource {
|
|
4
4
|
name: string;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { t as buildOpenApiSpec } from "../../openapi-
|
|
1
|
+
import { t as buildOpenApiSpec } from "../../openapi-B8Vkbxbn.mjs";
|
|
2
2
|
import { t as loadResourcesFromEntry } from "../../loadResourcesFromEntry-kvGT6EcO.mjs";
|
|
3
3
|
import { dirname, resolve } from "node:path";
|
|
4
4
|
import { mkdirSync, writeFileSync } from "node:fs";
|
package/dist/core/index.d.mts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import { $t as SlugExt, An as ControllerConfigurableOptions, B as defineResource, Jt as BaseController, Mn as ListResult, Nn as BodySanitizer, Pn as BodySanitizerConfig, Qt as TreeMixin, V as ResourceDefinition, Xt as SoftDeleteMixin, Yt as SoftDeleteExt, Zt as TreeExt, an as QueryResolverConfig, ct as AggMeasureShorthand, dt as AggregationDateRangeRequirement, en as SlugMixin, ft as AggregationIndexHint, gt as AggregationsMap, ht as AggregationRateLimit, in as QueryResolver, jn as ControllerConstructionOptions, kn as BaseControllerOptions, lt as AggregationCacheConfig, mt as AggregationMaterializedResult, nn as BulkMixin, on as AccessControl, pt as AggregationMaterializedContext, rn as BaseCrudController, sn as AccessControlConfig, st as AggMeasureInput, tn as BulkExt, ut as AggregationConfig } from "../index-
|
|
2
|
-
import { A as DEFAULT_ID_FIELD, B as MAX_FILTER_DEPTH, C as DefineActionConfig, D as defineAggregation, E as createPermissionMiddleware, F as DEFAULT_UPDATE_METHOD, G as RESERVED_QUERY_PARAMS, H as MAX_SEARCH_LENGTH, I as HOOK_OPERATIONS, K as SYSTEM_FIELDS, L as HOOK_PHASES, M as DEFAULT_MAX_LIMIT, N as DEFAULT_SORT, O as CRUD_OPERATIONS, P as DEFAULT_TENANT_FIELD, R as HookOperation, S as mergeResourceConfig, T as createCrudRouter, U as MUTATION_OPERATIONS, V as MAX_REGEX_LENGTH, W as MutationOperation, _ as getEntityQuery, a as formatValidationErrors, b as ResourceSeams, c as isFieldReadable, d as createRequestContext, f as getControllerContext, g as getEntityIdField, h as getEntityId, i as assertValidConfig, j as DEFAULT_LIMIT, k as CrudOperation, l as createCrudHandlers, m as sendControllerResponse, n as ValidateOptions, o as validateResourceConfig, p as getControllerScope, r as ValidationResult, s as collectReadBlockedFields, t as ConfigError, u as createFastifyHandler, v as defineResourceVariants, w as defineAction, x as SeamedResourceConfig, y as AdapterLike, z as HookPhase } from "../index-
|
|
1
|
+
import { $t as SlugExt, An as ControllerConfigurableOptions, B as defineResource, Jt as BaseController, Mn as ListResult, Nn as BodySanitizer, Pn as BodySanitizerConfig, Qt as TreeMixin, V as ResourceDefinition, Xt as SoftDeleteMixin, Yt as SoftDeleteExt, Zt as TreeExt, an as QueryResolverConfig, ct as AggMeasureShorthand, dt as AggregationDateRangeRequirement, en as SlugMixin, ft as AggregationIndexHint, gt as AggregationsMap, ht as AggregationRateLimit, in as QueryResolver, jn as ControllerConstructionOptions, kn as BaseControllerOptions, lt as AggregationCacheConfig, mt as AggregationMaterializedResult, nn as BulkMixin, on as AccessControl, pt as AggregationMaterializedContext, rn as BaseCrudController, sn as AccessControlConfig, st as AggMeasureInput, tn as BulkExt, ut as AggregationConfig } from "../index-Q5gzEIWY.mjs";
|
|
2
|
+
import { A as DEFAULT_ID_FIELD, B as MAX_FILTER_DEPTH, C as DefineActionConfig, D as defineAggregation, E as createPermissionMiddleware, F as DEFAULT_UPDATE_METHOD, G as RESERVED_QUERY_PARAMS, H as MAX_SEARCH_LENGTH, I as HOOK_OPERATIONS, K as SYSTEM_FIELDS, L as HOOK_PHASES, M as DEFAULT_MAX_LIMIT, N as DEFAULT_SORT, O as CRUD_OPERATIONS, P as DEFAULT_TENANT_FIELD, R as HookOperation, S as mergeResourceConfig, T as createCrudRouter, U as MUTATION_OPERATIONS, V as MAX_REGEX_LENGTH, W as MutationOperation, _ as getEntityQuery, a as formatValidationErrors, b as ResourceSeams, c as isFieldReadable, d as createRequestContext, f as getControllerContext, g as getEntityIdField, h as getEntityId, i as assertValidConfig, j as DEFAULT_LIMIT, k as CrudOperation, l as createCrudHandlers, m as sendControllerResponse, n as ValidateOptions, o as validateResourceConfig, p as getControllerScope, r as ValidationResult, s as collectReadBlockedFields, t as ConfigError, u as createFastifyHandler, v as defineResourceVariants, w as defineAction, x as SeamedResourceConfig, y as AdapterLike, z as HookPhase } from "../index-Cx7bro4R.mjs";
|
|
3
3
|
export { AccessControl, type AccessControlConfig, type AdapterLike, type AggMeasureInput, type AggMeasureShorthand, type AggregationCacheConfig, type AggregationConfig, type AggregationDateRangeRequirement, type AggregationIndexHint, type AggregationMaterializedContext, type AggregationMaterializedResult, type AggregationRateLimit, type AggregationsMap, BaseController, type BaseControllerOptions, BaseCrudController, BodySanitizer, type BodySanitizerConfig, type BulkExt, BulkMixin, CRUD_OPERATIONS, type ConfigError, type ControllerConfigurableOptions, type ControllerConstructionOptions, CrudOperation, DEFAULT_ID_FIELD, DEFAULT_LIMIT, DEFAULT_MAX_LIMIT, DEFAULT_SORT, DEFAULT_TENANT_FIELD, DEFAULT_UPDATE_METHOD, type DefineActionConfig, HOOK_OPERATIONS, HOOK_PHASES, HookOperation, HookPhase, type ListResult, MAX_FILTER_DEPTH, MAX_REGEX_LENGTH, MAX_SEARCH_LENGTH, MUTATION_OPERATIONS, MutationOperation, QueryResolver, type QueryResolverConfig, RESERVED_QUERY_PARAMS, ResourceDefinition, type ResourceSeams, SYSTEM_FIELDS, type SeamedResourceConfig, type SlugExt, SlugMixin, type SoftDeleteExt, SoftDeleteMixin, type TreeExt, TreeMixin, type ValidateOptions, type ValidationResult, assertValidConfig, collectReadBlockedFields, createCrudHandlers, createCrudRouter, createFastifyHandler, createPermissionMiddleware, createRequestContext, defineAction, defineAggregation, defineResource, defineResourceVariants, formatValidationErrors, getControllerContext, getControllerScope, getEntityId, getEntityIdField, getEntityQuery, isFieldReadable, mergeResourceConfig, sendControllerResponse, validateResourceConfig };
|
package/dist/core/index.mjs
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
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-DNre0wRg.mjs";
|
|
2
|
-
import { a as BulkMixin, c as collectReadBlockedFields, f as AccessControl, i as SlugMixin, l as isFieldReadable, n as TreeMixin, o as BaseCrudController, r as SoftDeleteMixin, s as QueryResolver, t as BaseController, u as BodySanitizer } from "../BaseController
|
|
3
|
-
import { a as
|
|
4
|
-
import { a as createFastifyHandler, c as getControllerScope, i as createCrudHandlers, l as sendControllerResponse, o as createRequestContext, s as getControllerContext } from "../pipeline-
|
|
2
|
+
import { a as BulkMixin, c as collectReadBlockedFields, f as AccessControl, i as SlugMixin, l as isFieldReadable, n as TreeMixin, o as BaseCrudController, r as SoftDeleteMixin, s as QueryResolver, t as BaseController, u as BodySanitizer } from "../BaseController-B5DwufbB.mjs";
|
|
3
|
+
import { a as ResourceDefinition, i as validateResourceConfig, n as assertValidConfig, o as createCrudRouter, r as formatValidationErrors, s as createPermissionMiddleware, t as defineResource } from "../defineResource-CxWqDCAy.mjs";
|
|
4
|
+
import { a as createFastifyHandler, c as getControllerScope, i as createCrudHandlers, l as sendControllerResponse, o as createRequestContext, s as getControllerContext } from "../pipeline-BJnpkY9i.mjs";
|
|
5
|
+
import { t as defineAggregation } from "../defineAggregation-ufWjUwml.mjs";
|
|
6
|
+
import { a as mergeResourceConfig, i as defineResourceVariants, n as getEntityIdField, o as defineAction, r as getEntityQuery, t as getEntityId } from "../core-poalJLvp.mjs";
|
|
5
7
|
export { AccessControl, BaseController, BaseCrudController, BodySanitizer, BulkMixin, 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, SlugMixin, SoftDeleteMixin, TreeMixin, assertValidConfig, collectReadBlockedFields, createCrudHandlers, createCrudRouter, createFastifyHandler, createPermissionMiddleware, createRequestContext, defineAction, defineAggregation, defineResource, defineResourceVariants, formatValidationErrors, getControllerContext, getControllerScope, getEntityId, getEntityIdField, getEntityQuery, isFieldReadable, mergeResourceConfig, sendControllerResponse, validateResourceConfig };
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
import "./constants-DNre0wRg.mjs";
|
|
2
|
+
import { t as defineResource } from "./defineResource-CxWqDCAy.mjs";
|
|
3
|
+
//#region src/core/defineAction.ts
|
|
4
|
+
/**
|
|
5
|
+
* Build an `ActionDefinition` with a typed handler. The literal schema
|
|
6
|
+
* type captured here flows into `data`, so `defineAction({ schema:
|
|
7
|
+
* z.object({...}), handler })` produces fully-typed code with no
|
|
8
|
+
* `as MyShape` cast.
|
|
9
|
+
*
|
|
10
|
+
* Behaviorally identical to a bare `ActionDefinition` object — same
|
|
11
|
+
* validation path (AJV), same permission resolution, same MCP wiring.
|
|
12
|
+
* The runtime shape is unchanged; only the type-level inference is new.
|
|
13
|
+
*/
|
|
14
|
+
function defineAction(config) {
|
|
15
|
+
return {
|
|
16
|
+
handler: config.handler,
|
|
17
|
+
permissions: config.permissions,
|
|
18
|
+
schema: config.schema,
|
|
19
|
+
description: config.description,
|
|
20
|
+
id: config.id,
|
|
21
|
+
mcp: config.mcp
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
//#endregion
|
|
25
|
+
//#region src/core/defineResource/seams.ts
|
|
26
|
+
/** Plain object test — literals only; class instances must replace, not merge. */
|
|
27
|
+
function isPlainObject(value) {
|
|
28
|
+
if (value === null || typeof value !== "object" || Array.isArray(value)) return false;
|
|
29
|
+
const proto = Object.getPrototypeOf(value);
|
|
30
|
+
return proto === Object.prototype || proto === null;
|
|
31
|
+
}
|
|
32
|
+
/** Recursive plain-object merge; nested arrays REPLACE (they're value lists). */
|
|
33
|
+
function mergePlain(base, next) {
|
|
34
|
+
const out = { ...base };
|
|
35
|
+
for (const [key, value] of Object.entries(next)) {
|
|
36
|
+
if (value === void 0) continue;
|
|
37
|
+
const existing = out[key];
|
|
38
|
+
out[key] = isPlainObject(existing) && isPlainObject(value) ? mergePlain(existing, value) : value;
|
|
39
|
+
}
|
|
40
|
+
return out;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Canonical slot-aware merge of a base resource config with host seams.
|
|
44
|
+
* Later seams win over earlier ones; `undefined` values never clobber.
|
|
45
|
+
*
|
|
46
|
+
* Merge semantics (value-driven — no per-slot registry to drift):
|
|
47
|
+
*
|
|
48
|
+
* - **Top-level arrays CONCAT** — `routes`, `middlewares`, `presets`,
|
|
49
|
+
* `routeGuards`, `disabledRoutes`: additive collections, base first.
|
|
50
|
+
* - **Plain objects merge recursively** — `permissions`, `actions`,
|
|
51
|
+
* `schemaOptions` (incl. `fieldRules`), `aggregations`, `customSchemas`,
|
|
52
|
+
* `fields`, `cache`, `events`: per-key composition. Arrays NESTED inside
|
|
53
|
+
* them replace (they're value lists — enums, sort specs), not concat.
|
|
54
|
+
* - **Everything else last-wins** — class instances (`adapter`,
|
|
55
|
+
* `controller`, `queryParser`), functions, scalars.
|
|
56
|
+
*
|
|
57
|
+
* The single `as` at the return is the one documented erasure for the
|
|
58
|
+
* {@link AdapterLike}-widened seam slots — inside the helper so no module
|
|
59
|
+
* author ever writes one (mirrors mongokit's one-cast-at-the-boundary rule).
|
|
60
|
+
*/
|
|
61
|
+
function mergeResourceConfig(base, ...seams) {
|
|
62
|
+
const out = { ...base };
|
|
63
|
+
for (const seam of seams) {
|
|
64
|
+
if (!seam) continue;
|
|
65
|
+
for (const [key, value] of Object.entries(seam)) {
|
|
66
|
+
if (value === void 0) continue;
|
|
67
|
+
const existing = out[key];
|
|
68
|
+
if (Array.isArray(existing) && Array.isArray(value)) out[key] = [...existing, ...value];
|
|
69
|
+
else if (isPlainObject(existing) && isPlainObject(value)) out[key] = mergePlain(existing, value);
|
|
70
|
+
else if (Array.isArray(value)) out[key] = [...value];
|
|
71
|
+
else out[key] = value;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
return out;
|
|
75
|
+
}
|
|
76
|
+
//#endregion
|
|
77
|
+
//#region src/core/defineResourceVariants.ts
|
|
78
|
+
/**
|
|
79
|
+
* Define multiple resources from a shared base config and per-variant overrides.
|
|
80
|
+
*
|
|
81
|
+
* Each variant is independently passed through `defineResource()` — the
|
|
82
|
+
* returned `ResourceDefinition`s are real, fully-registered resources.
|
|
83
|
+
* Register each one's plugin in your app:
|
|
84
|
+
*
|
|
85
|
+
* ```typescript
|
|
86
|
+
* await app.register(articlePublic.toPlugin());
|
|
87
|
+
* await app.register(articleAdmin.toPlugin());
|
|
88
|
+
* ```
|
|
89
|
+
*
|
|
90
|
+
* @param base Shared config — adapter, queryParser, schemaOptions, hooks, etc.
|
|
91
|
+
* Must NOT include `name` or `prefix` (those are per-variant).
|
|
92
|
+
* @param variants Map of variant key → override. Each variant must declare
|
|
93
|
+
* its own `name` and `prefix`. Other fields override the base.
|
|
94
|
+
* @returns A record where each key from `variants` maps to a real
|
|
95
|
+
* `ResourceDefinition` ready for `.toPlugin()` registration.
|
|
96
|
+
*/
|
|
97
|
+
function defineResourceVariants(base, variants) {
|
|
98
|
+
const out = {};
|
|
99
|
+
for (const key of Object.keys(variants)) {
|
|
100
|
+
const override = variants[key];
|
|
101
|
+
out[key] = defineResource({
|
|
102
|
+
...base,
|
|
103
|
+
...override
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
return out;
|
|
107
|
+
}
|
|
108
|
+
//#endregion
|
|
109
|
+
//#region src/core/entityHelpers.ts
|
|
110
|
+
/**
|
|
111
|
+
* Per-request entity helpers — read the resource binding (idField + the
|
|
112
|
+
* URL `:id` value) off `req.arc`.
|
|
113
|
+
*
|
|
114
|
+
* Action handlers receive their `id` argument as the raw URL `:id` value.
|
|
115
|
+
* When the resource declares a custom `idField` (`slug`, `reportId`, …)
|
|
116
|
+
* that's NOT the document `_id`, a naive `Model.findById(id)` silently
|
|
117
|
+
* returns null. The historical footgun was a `findById(id)` typo where
|
|
118
|
+
* the handler author hadn't realised that `:id` resolves to the friendly
|
|
119
|
+
* handle.
|
|
120
|
+
*
|
|
121
|
+
* `getEntityQuery(req)` produces the canonical filter shape so handlers
|
|
122
|
+
* compose lookups with no resource-config recall:
|
|
123
|
+
*
|
|
124
|
+
* ```ts
|
|
125
|
+
* actions: {
|
|
126
|
+
* archive: {
|
|
127
|
+
* handler: async (id, data, req) => {
|
|
128
|
+
* const doc = await Model.findOne(getEntityQuery(req));
|
|
129
|
+
* if (!doc) throw new NotFoundError("Order");
|
|
130
|
+
* // ...
|
|
131
|
+
* },
|
|
132
|
+
* },
|
|
133
|
+
* }
|
|
134
|
+
* ```
|
|
135
|
+
*
|
|
136
|
+
* The router populates `req.arc.idField` and `req.arc.entityId` before
|
|
137
|
+
* invoking the handler — these helpers are zero-cost reads.
|
|
138
|
+
*/
|
|
139
|
+
/**
|
|
140
|
+
* Read the resource's configured `idField` for the current request.
|
|
141
|
+
* Falls back to the framework default (`_id`) when the route hasn't
|
|
142
|
+
* bound an idField — keeps handlers safe to author without checking
|
|
143
|
+
* the resource config.
|
|
144
|
+
*/
|
|
145
|
+
function getEntityIdField(req) {
|
|
146
|
+
return req.arc?.idField ?? "_id";
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Read the URL `:id` path param value as the resource handle.
|
|
150
|
+
* Returns `undefined` when the route has no `:id` segment (collection
|
|
151
|
+
* routes) — handlers that need the entity must be on row routes.
|
|
152
|
+
*/
|
|
153
|
+
function getEntityId(req) {
|
|
154
|
+
if (req.arc?.entityId !== void 0) return req.arc.entityId;
|
|
155
|
+
return req.params?.id;
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* Compose a `findOne` filter that resolves the current request's
|
|
159
|
+
* entity, regardless of whether the resource binds `_id` or a custom
|
|
160
|
+
* field. Idiomatic shape for arc action handlers.
|
|
161
|
+
*
|
|
162
|
+
* ```ts
|
|
163
|
+
* const doc = await Model.findOne(getEntityQuery(req));
|
|
164
|
+
* ```
|
|
165
|
+
*
|
|
166
|
+
* Returns `{}` when the route has no entity context (collection routes
|
|
167
|
+
* or tests bypassing the router) — caller decides what that means.
|
|
168
|
+
*/
|
|
169
|
+
function getEntityQuery(req) {
|
|
170
|
+
const id = getEntityId(req);
|
|
171
|
+
if (id === void 0) return {};
|
|
172
|
+
return { [getEntityIdField(req)]: id };
|
|
173
|
+
}
|
|
174
|
+
//#endregion
|
|
175
|
+
export { mergeResourceConfig as a, defineResourceVariants as i, getEntityIdField as n, defineAction as o, getEntityQuery as r, getEntityId as t };
|
|
@@ -2,8 +2,8 @@ import { t as __exportAll } from "./rolldown-runtime-8H4AJuhK.mjs";
|
|
|
2
2
|
import "./constants-DNre0wRg.mjs";
|
|
3
3
|
import { f as createError } from "./errors-DkO8Nk6-.mjs";
|
|
4
4
|
import { a as buildRouteHooks, i as selectPluginMw, l as buildAuthMiddlewareForPermissions, n as buildRouteConfig, r as resolveRouterPluginMw, s as routeHookOptions, t as buildRateLimitConfig, u as buildArcDecorator } from "./rateLimit-DrED-C4v.mjs";
|
|
5
|
-
import { n as buildActionPermissionMw, t as tryRegisterRoute } from "./registerRoute-
|
|
6
|
-
import { l as sendControllerResponse, r as resolvePipelineSteps, t as buildActionPipelineHandler } from "./pipeline-
|
|
5
|
+
import { n as buildActionPermissionMw, t as tryRegisterRoute } from "./registerRoute-JkxIEBJq.mjs";
|
|
6
|
+
import { l as sendControllerResponse, r as resolvePipelineSteps, t as buildActionPipelineHandler } from "./pipeline-BJnpkY9i.mjs";
|
|
7
7
|
import { n as schemaIRToJsonSchemaBranch, t as normalizeSchemaIR } from "./schemaIR-Ca7ySNPx.mjs";
|
|
8
8
|
//#region src/core/createActionRouter.ts
|
|
9
9
|
var createActionRouter_exports = /* @__PURE__ */ __exportAll({
|
|
@@ -1,7 +1,9 @@
|
|
|
1
|
-
import { f as createError
|
|
1
|
+
import { f as createError } from "./errors-DkO8Nk6-.mjs";
|
|
2
|
+
import { u as getRequestScope } from "./types-CSyCxl2V.mjs";
|
|
3
|
+
import { r as evaluateAndApplyPermission } from "./applyPermissionResult-Cf6zUCvb.mjs";
|
|
2
4
|
import { a as buildRouteHooks, c as buildAuthMiddleware, i as selectPluginMw, r as resolveRouterPluginMw, s as routeHookOptions, t as buildRateLimitConfig, u as buildArcDecorator } from "./rateLimit-DrED-C4v.mjs";
|
|
3
5
|
import { n as validateAggregations } from "./validate-Bk44F2yT.mjs";
|
|
4
|
-
import { t as buildAggregationHandler } from "./buildHandler-
|
|
6
|
+
import { t as buildAggregationHandler } from "./buildHandler-BYS3Vkfz.mjs";
|
|
5
7
|
//#region src/core/aggregation/createAggregationRouter.ts
|
|
6
8
|
/**
|
|
7
9
|
* Register one Fastify route per aggregation. No-op when the map is
|
|
@@ -38,15 +40,9 @@ function registerOne(fastify, normalized, ctx) {
|
|
|
38
40
|
const config = normalized.base;
|
|
39
41
|
const authMw = buildAuthMiddleware(fastify, config.permissions);
|
|
40
42
|
const permissionFn = config.permissions;
|
|
41
|
-
const permissionMw = async (req,
|
|
43
|
+
const permissionMw = async (req, reply) => {
|
|
42
44
|
const ctx = buildPermissionContextLite(req, normalized.name);
|
|
43
|
-
|
|
44
|
-
if (!normalizePermissionGranted(raw)) {
|
|
45
|
-
const status = req.user ? 403 : 401;
|
|
46
|
-
const reason = normalizePermissionReason(raw);
|
|
47
|
-
if (status === 401) throw new UnauthorizedError(reason ?? "Authentication required to access this aggregation.");
|
|
48
|
-
throw new ForbiddenError(reason ?? "You do not have permission to access this aggregation.");
|
|
49
|
-
}
|
|
45
|
+
await evaluateAndApplyPermission(permissionFn, ctx, req, reply, { defaultDenialMessage: (user) => user ? "You do not have permission to access this aggregation." : "Authentication required to access this aggregation." });
|
|
50
46
|
};
|
|
51
47
|
const hooks = buildRouteHooks({
|
|
52
48
|
arcDecorator,
|
|
@@ -99,20 +95,11 @@ function buildPermissionContextLite(req, aggregationName) {
|
|
|
99
95
|
const reqWithExtras = req;
|
|
100
96
|
return {
|
|
101
97
|
user: reqWithExtras.user ?? null,
|
|
98
|
+
scope: getRequestScope(req),
|
|
102
99
|
request: req,
|
|
103
100
|
resource: reqWithExtras.arc?.resource ?? "aggregation",
|
|
104
101
|
action: `aggregation:${aggregationName}`
|
|
105
102
|
};
|
|
106
103
|
}
|
|
107
|
-
/** PermissionCheck returns `boolean | PermissionResult`. Pull `granted`. */
|
|
108
|
-
function normalizePermissionGranted(raw) {
|
|
109
|
-
if (typeof raw === "boolean") return raw;
|
|
110
|
-
return raw.granted;
|
|
111
|
-
}
|
|
112
|
-
/** Pull `reason` when the check returned a structured `PermissionResult`. */
|
|
113
|
-
function normalizePermissionReason(raw) {
|
|
114
|
-
if (typeof raw === "boolean") return void 0;
|
|
115
|
-
return raw.reason;
|
|
116
|
-
}
|
|
117
104
|
//#endregion
|
|
118
105
|
export { createAggregationRouter };
|
|
@@ -4,6 +4,7 @@ import { n as PUBLIC_SCOPE } from "./types-CSyCxl2V.mjs";
|
|
|
4
4
|
import { t as createTenantKeyGenerator } from "./rateLimitKey-BoWRH3e7.mjs";
|
|
5
5
|
import { i as createRequestIdGenerator, r as mergeHealthChecks } from "./health-CB_m4S32.mjs";
|
|
6
6
|
import { t as parseJsonBody } from "./jsonBody-BlzAoGCe.mjs";
|
|
7
|
+
import { r as wrapWithBoundary } from "./subscribe-helpers-DfzsivM6.mjs";
|
|
7
8
|
import Fastify from "fastify";
|
|
8
9
|
import qs from "qs";
|
|
9
10
|
//#region src/factory/module/resolve.ts
|
|
@@ -100,8 +101,9 @@ async function subscribeModuleEventHandlers(fastify, modules) {
|
|
|
100
101
|
if (prior !== void 0) throw new Error(`[arc] duplicate event-handler name "${def.name}" — declared by module "${prior}" and module "${m.name}". Named event handlers must be unique across the module graph; prefix names with the owning module.`);
|
|
101
102
|
owner.set(def.name, m.name);
|
|
102
103
|
}
|
|
104
|
+
const handler = applyBoundary(def, m.name, fastify);
|
|
103
105
|
const patterns = Array.isArray(def.event) ? def.event : [def.event];
|
|
104
|
-
for (const pattern of patterns) unsubscribes.push(await bus.subscribe(pattern,
|
|
106
|
+
for (const pattern of patterns) unsubscribes.push(await bus.subscribe(pattern, handler));
|
|
105
107
|
}
|
|
106
108
|
}
|
|
107
109
|
return unsubscribes;
|
|
@@ -111,6 +113,25 @@ async function subscribeModuleEventHandlers(fastify, modules) {
|
|
|
111
113
|
throw err;
|
|
112
114
|
}
|
|
113
115
|
}
|
|
116
|
+
/**
|
|
117
|
+
* Apply the declaration's opt-in error boundary. Without `boundary`, the raw
|
|
118
|
+
* handler is subscribed and a throw reaches the transport — which is what makes
|
|
119
|
+
* the durable path work (unacked → redelivered → DLQ). With it, failures are
|
|
120
|
+
* logged through `fastify.log` and swallowed, for handlers whose retry would
|
|
121
|
+
* only delay the next event's resync.
|
|
122
|
+
*
|
|
123
|
+
* The boundary label falls back to `<module>.<pattern>` so an unnamed handler
|
|
124
|
+
* still produces an attributable log line instead of "anonymous".
|
|
125
|
+
*/
|
|
126
|
+
function applyBoundary(def, moduleName, fastify) {
|
|
127
|
+
if (!def.boundary) return def.handler;
|
|
128
|
+
const onError = typeof def.boundary === "object" ? def.boundary.onError : void 0;
|
|
129
|
+
return wrapWithBoundary(def.handler, {
|
|
130
|
+
logger: fastify.log,
|
|
131
|
+
name: def.name ?? `${moduleName}.${String(def.event)}`,
|
|
132
|
+
...onError ? { onError } : {}
|
|
133
|
+
});
|
|
134
|
+
}
|
|
114
135
|
/** Best-effort reverse-order teardown; every unsubscribe is attempted. */
|
|
115
136
|
async function unsubscribeModuleEventHandlers(unsubscribes) {
|
|
116
137
|
const errors = [];
|
|
@@ -214,34 +235,6 @@ function describeModuleCycle(remaining, byName) {
|
|
|
214
235
|
return `[arc] module dependency cycle: ${cycle ? cycle.join(" → ") : [...remaining].join(", ")}. Modules cannot dependsOn each other circularly — break it with a shared module both point at, or wire the softer direction through an event/port instead of a hard dependsOn.`;
|
|
215
236
|
}
|
|
216
237
|
//#endregion
|
|
217
|
-
//#region src/factory/module/index.ts
|
|
218
|
-
/**
|
|
219
|
-
* Identity helper for authoring a typed module — mirrors `defineResource`.
|
|
220
|
-
* Gives inference + a single obvious construction site; does no work.
|
|
221
|
-
*
|
|
222
|
-
* `TExports` is inferred from the `bootstrap` return value, so a module
|
|
223
|
-
* author gets a typed public export for free:
|
|
224
|
-
*
|
|
225
|
-
* ```ts
|
|
226
|
-
* export const accountingModule = (deps: Deps) =>
|
|
227
|
-
* defineModule({
|
|
228
|
-
* name: "accounting",
|
|
229
|
-
* bootstrap: async () => createAccountingEngine(deps), // TExports inferred
|
|
230
|
-
* });
|
|
231
|
-
* ```
|
|
232
|
-
*/
|
|
233
|
-
function defineModule(module) {
|
|
234
|
-
return module;
|
|
235
|
-
}
|
|
236
|
-
function getModuleExports(fastify, name) {
|
|
237
|
-
const modules = fastify.arc?.modules;
|
|
238
|
-
if (!modules || !Object.hasOwn(modules, name)) {
|
|
239
|
-
const available = modules ? Object.keys(modules) : [];
|
|
240
|
-
throw new Error(`[arc] no public export recorded for module "${name}".\nA module's export is its bootstrap() return value; it is readable only AFTER that module's bootstrap ran (dependsOn / list order = init order).\nModules with recorded exports: ${available.length ? available.join(", ") : "(none)"}.`);
|
|
241
|
-
}
|
|
242
|
-
return modules[name];
|
|
243
|
-
}
|
|
244
|
-
//#endregion
|
|
245
238
|
//#region src/factory/presets.ts
|
|
246
239
|
/**
|
|
247
240
|
* Production preset - strict security, performance optimized
|
|
@@ -467,7 +460,7 @@ async function registerArcCore(fastify, config, trackPlugin) {
|
|
|
467
460
|
await fastify.register(arcCorePlugin, { emitEvents: config.arcPlugins?.emitEvents !== false });
|
|
468
461
|
trackPlugin("arc-core");
|
|
469
462
|
if (config.arcPlugins?.events !== false) {
|
|
470
|
-
const { default: eventPlugin } = await import("./eventPlugin-
|
|
463
|
+
const { default: eventPlugin } = await import("./eventPlugin-D2NNR4py.mjs").then((n) => n.n);
|
|
471
464
|
const eventOpts = typeof config.arcPlugins?.events === "object" ? config.arcPlugins.events : {};
|
|
472
465
|
await fastify.register(eventPlugin, {
|
|
473
466
|
...eventOpts,
|
|
@@ -539,7 +532,7 @@ async function registerArcPlugins(fastify, config, trackPlugin, modules, moduleH
|
|
|
539
532
|
}
|
|
540
533
|
if (config.arcPlugins?.realtime) if (config.arcPlugins?.events === false) fastify.log.warn("Realtime plugin requires events plugin (arcPlugins.events). Realtime disabled.");
|
|
541
534
|
else {
|
|
542
|
-
const { default: realtimePlugin } = await import("./realtime-
|
|
535
|
+
const { default: realtimePlugin } = await import("./realtime-B9nmIY5V.mjs").then((n) => n.r);
|
|
543
536
|
const opts = config.arcPlugins.realtime === true ? {} : config.arcPlugins.realtime;
|
|
544
537
|
await fastify.register(realtimePlugin, opts);
|
|
545
538
|
trackPlugin("arc-realtime", opts);
|
|
@@ -1466,48 +1459,4 @@ const ArcFactory = {
|
|
|
1466
1459
|
}
|
|
1467
1460
|
};
|
|
1468
1461
|
//#endregion
|
|
1469
|
-
|
|
1470
|
-
/**
|
|
1471
|
-
* Normalize an eager `import.meta.glob` result into a `ResourceLike[]`.
|
|
1472
|
-
*
|
|
1473
|
-
* Accepts either:
|
|
1474
|
-
* - `{ import: 'default' }` form: values are the resource directly
|
|
1475
|
-
* - default form: values are the full module — picks first export with `toPlugin()`
|
|
1476
|
-
*
|
|
1477
|
-
* Throws if any module doesn't yield a valid `ResourceLike`.
|
|
1478
|
-
*/
|
|
1479
|
-
function preloadResources(globResult) {
|
|
1480
|
-
const resources = [];
|
|
1481
|
-
for (const [path, value] of Object.entries(globResult)) {
|
|
1482
|
-
const resource = pickResource(value);
|
|
1483
|
-
if (!resource) throw new Error(`preloadResources: ${path} does not export a valid resource.\n Expected: a default export OR a named export with toPlugin().`);
|
|
1484
|
-
resources.push(resource);
|
|
1485
|
-
}
|
|
1486
|
-
return resources.sort((a, b) => (a.name ?? "").localeCompare(b.name ?? ""));
|
|
1487
|
-
}
|
|
1488
|
-
/**
|
|
1489
|
-
* Normalize a lazy `import.meta.glob` result into a `Promise<ResourceLike[]>`.
|
|
1490
|
-
*
|
|
1491
|
-
* Use this when resources depend on prior bootstrap (e.g., engine init) and
|
|
1492
|
-
* cannot be evaluated at import time of the preload file.
|
|
1493
|
-
*/
|
|
1494
|
-
async function preloadResourcesAsync(globResult) {
|
|
1495
|
-
return (await Promise.all(Object.entries(globResult).map(async ([path, loader]) => {
|
|
1496
|
-
const resource = pickResource(await loader());
|
|
1497
|
-
if (!resource) throw new Error(`preloadResourcesAsync: ${path} does not export a valid resource.\n Expected: a default export OR a named export with toPlugin().`);
|
|
1498
|
-
return resource;
|
|
1499
|
-
}))).sort((a, b) => (a.name ?? "").localeCompare(b.name ?? ""));
|
|
1500
|
-
}
|
|
1501
|
-
function pickResource(value) {
|
|
1502
|
-
if (!value || typeof value !== "object") return void 0;
|
|
1503
|
-
if (typeof value.toPlugin === "function") return value;
|
|
1504
|
-
const mod = value;
|
|
1505
|
-
const candidates = [
|
|
1506
|
-
mod.default,
|
|
1507
|
-
mod.resource,
|
|
1508
|
-
...Object.values(mod)
|
|
1509
|
-
];
|
|
1510
|
-
for (const c of candidates) if (c && typeof c === "object" && typeof c.toPlugin === "function") return c;
|
|
1511
|
-
}
|
|
1512
|
-
//#endregion
|
|
1513
|
-
export { collectModuleScheduledJobs as _, createApp_exports as a, resolveModule as b, getPreset as c, workerPreset as d, defineModule as f, collectModuleHealthChecks as g, subscribeModuleEventHandlers as h, createApp as i, productionPreset as l, orderModules as m, preloadResourcesAsync as n, developmentPreset as o, getModuleExports as p, ArcFactory as r, edgePreset as s, preloadResources as t, testingPreset as u, collectModuleWorkflows as v, resolveContribution as y };
|
|
1462
|
+
export { edgePreset as a, testingPreset as c, subscribeModuleEventHandlers as d, collectModuleHealthChecks as f, resolveModule as g, resolveContribution as h, developmentPreset as i, workerPreset as l, collectModuleWorkflows as m, createApp as n, getPreset as o, collectModuleScheduledJobs as p, createApp_exports as r, productionPreset as s, ArcFactory as t, orderModules as u };
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
//#region src/core/aggregation/defineAggregation.ts
|
|
2
|
+
/**
|
|
3
|
+
* Declare a single resource aggregation. Exported configs flow into
|
|
4
|
+
* `defineResource({ aggregations: { ... } })` either as named keys or
|
|
5
|
+
* via auto-discovery patterns (loadAggregations — future).
|
|
6
|
+
*
|
|
7
|
+
* @example Inline
|
|
8
|
+
* ```ts
|
|
9
|
+
* defineResource({
|
|
10
|
+
* name: 'order',
|
|
11
|
+
* aggregations: {
|
|
12
|
+
* revenueByStatus: defineAggregation({
|
|
13
|
+
* groupBy: 'status',
|
|
14
|
+
* measures: { count: 'count', revenue: 'sum:totalPrice' },
|
|
15
|
+
* permissions: requireRoles(['admin']),
|
|
16
|
+
* }),
|
|
17
|
+
* },
|
|
18
|
+
* });
|
|
19
|
+
* ```
|
|
20
|
+
*
|
|
21
|
+
* @example Multi-file
|
|
22
|
+
* ```ts
|
|
23
|
+
* // orders/aggregations/revenue-by-status.ts
|
|
24
|
+
* import { defineAggregation } from '@classytic/arc';
|
|
25
|
+
*
|
|
26
|
+
* export const revenueByStatus = defineAggregation({
|
|
27
|
+
* groupBy: 'status',
|
|
28
|
+
* measures: { count: 'count', revenue: 'sum:totalPrice' },
|
|
29
|
+
* permissions: requireRoles(['admin']),
|
|
30
|
+
* timeout: 5000,
|
|
31
|
+
* cache: { staleTime: 60 },
|
|
32
|
+
* });
|
|
33
|
+
*
|
|
34
|
+
* // orders/order.resource.ts
|
|
35
|
+
* import * as aggregations from './aggregations/index.js';
|
|
36
|
+
*
|
|
37
|
+
* defineResource({
|
|
38
|
+
* name: 'order',
|
|
39
|
+
* aggregations, // 30+ entries flow in
|
|
40
|
+
* });
|
|
41
|
+
* ```
|
|
42
|
+
*/
|
|
43
|
+
function defineAggregation(config) {
|
|
44
|
+
return config;
|
|
45
|
+
}
|
|
46
|
+
//#endregion
|
|
47
|
+
export { defineAggregation as t };
|