@classytic/arc 2.4.3 → 2.6.1
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 +22 -6
- package/dist/{BaseController-CkM5dUh_.mjs → BaseController-AbbRx3e0.mjs} +5 -2
- package/dist/adapters/index.d.mts +2 -2
- package/dist/adapters/index.mjs +1 -1
- package/dist/{adapters-DTC4Ug66.mjs → adapters-CTn28N4y.mjs} +72 -11
- package/dist/audit/index.d.mts +1 -1
- package/dist/audit/index.mjs +11 -1
- package/dist/audit/mongodb.d.mts +1 -1
- package/dist/auth/index.d.mts +1 -1
- package/dist/auth/index.mjs +2 -2
- package/dist/cli/commands/init.mjs +12 -9
- package/dist/core/index.d.mts +2 -2
- package/dist/core/index.mjs +2 -2
- package/dist/{createApp-CBgVaFyh.mjs → createApp-Bol7DLUf.mjs} +404 -279
- package/dist/{defineResource-B22gcNvn.mjs → defineResource-bVKHjQzE.mjs} +116 -19
- package/dist/discovery/index.mjs +1 -1
- package/dist/docs/index.d.mts +1 -1
- package/dist/dynamic/index.d.mts +1 -1
- package/dist/dynamic/index.mjs +2 -2
- package/dist/{elevation-Ca_yveIO.d.mts → elevation-C_taLQrM.d.mts} +27 -1
- package/dist/{errorHandler-DMbGdzBG.mjs → errorHandler-r2595m8T.mjs} +1 -1
- package/dist/{errors-CPpvPHT0.d.mts → errors-CcVbl1-T.d.mts} +17 -1
- package/dist/{errors-rxhfP7Hf.mjs → errors-NoQKsbAT.mjs} +23 -1
- package/dist/{eventPlugin-iGrSEmwJ.d.mts → eventPlugin-DW45v4V5.d.mts} +30 -2
- package/dist/events/index.d.mts +2 -2
- package/dist/events/index.mjs +40 -10
- package/dist/factory/index.d.mts +44 -23
- package/dist/factory/index.mjs +136 -2
- package/dist/hooks/index.d.mts +1 -1
- package/dist/idempotency/index.d.mts +3 -3
- package/dist/idempotency/mongodb.d.mts +1 -1
- package/dist/idempotency/redis.d.mts +1 -1
- package/dist/{index-yhxyjqNb.d.mts → index-BIsZ_su5.d.mts} +4 -8
- package/dist/{index-BL8CaQih.d.mts → index-Cb3gtbg7.d.mts} +2 -2
- package/dist/{index-Diqcm14c.d.mts → index-NGZksqM5.d.mts} +30 -1
- package/dist/index.d.mts +6 -6
- package/dist/index.mjs +8 -7
- package/dist/integrations/event-gateway.mjs +1 -1
- package/dist/integrations/index.d.mts +1 -1
- package/dist/integrations/mcp/index.d.mts +4 -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-DGmPxakH.d.mts → interface-DDW43OmS.d.mts} +194 -13
- package/dist/{mongodb-CUpYfxfD.d.mts → mongodb-kltrBPa1.d.mts} +10 -0
- package/dist/{mongodb-bga9AbkD.d.mts → mongodb-pMvOlR5_.d.mts} +1 -1
- package/dist/org/index.d.mts +1 -1
- package/dist/org/index.mjs +1 -1
- package/dist/permissions/index.d.mts +2 -2
- package/dist/permissions/index.mjs +2 -2
- package/dist/{permissions-Jk5x3sxz.mjs → permissions-C8ImI8gC.mjs} +44 -2
- package/dist/plugins/index.d.mts +1 -1
- package/dist/plugins/index.mjs +3 -3
- package/dist/plugins/tracing-entry.mjs +1 -1
- package/dist/presets/index.d.mts +1 -1
- package/dist/presets/index.mjs +1 -1
- package/dist/presets/multiTenant.d.mts +1 -1
- package/dist/presets/multiTenant.mjs +1 -1
- package/dist/{presets-OMPaHMTY.mjs → presets-BMfdy34e.mjs} +2 -2
- package/dist/{redis-CQ5YxMC5.d.mts → redis-D0Qc-9EW.d.mts} +1 -1
- package/dist/registry/index.d.mts +1 -1
- package/dist/{resourceToTools-PMFE8HIv.mjs → resourceToTools-DH3c3e-T.mjs} +81 -7
- package/dist/scope/index.d.mts +2 -2
- package/dist/scope/index.mjs +2 -2
- package/dist/{sse-BkViJPlT.mjs → sse-BF7GR7IB.mjs} +1 -1
- package/dist/testing/index.d.mts +2 -2
- package/dist/testing/index.mjs +1 -1
- package/dist/types/index.d.mts +3 -3
- package/dist/types/index.mjs +23 -2
- package/dist/{types-C6TQjtdi.mjs → types-BhtYdxZU.mjs} +26 -1
- package/dist/{types-Dt0-AI6E.d.mts → types-D5hJ-k_3.d.mts} +189 -6
- package/dist/{types-BJmgxNbF.d.mts → types-D5rjsS_i.d.mts} +1 -1
- package/dist/utils/index.d.mts +2 -2
- package/dist/utils/index.mjs +1 -1
- package/package.json +6 -5
- package/skills/arc/SKILL.md +104 -4
- package/skills/arc/references/mcp.md +160 -2
- /package/dist/{interface-B4awm1RJ.d.mts → interface-gr-7qo9j.d.mts} +0 -0
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
import { s as DEFAULT_UPDATE_METHOD, t as CRUD_OPERATIONS } from "./constants-Cxde4rpC.mjs";
|
|
2
|
-
import { d as
|
|
3
|
-
import { t as BaseController } from "./BaseController-
|
|
2
|
+
import { d as isElevated, f as isMember, n as PUBLIC_SCOPE } from "./types-BhtYdxZU.mjs";
|
|
3
|
+
import { t as BaseController } from "./BaseController-AbbRx3e0.mjs";
|
|
4
4
|
import { i as resolveEffectiveRoles, t as applyFieldReadPermissions } from "./fields-ipsbIRPK.mjs";
|
|
5
5
|
import { t as getUserRoles } from "./types-ZUu_h0jp.mjs";
|
|
6
6
|
import { t as requestContext } from "./requestContext-DYtmNpm5.mjs";
|
|
7
7
|
import { i as getDefaultCrudSchemas } from "./utils-Dc0WhlIl.mjs";
|
|
8
|
-
import { r as ForbiddenError } from "./errors-
|
|
8
|
+
import { r as ForbiddenError } from "./errors-NoQKsbAT.mjs";
|
|
9
9
|
import { n as convertRouteSchema, t as convertOpenApiSchemas } from "./schemaConverter-DjzHpFam.mjs";
|
|
10
10
|
import { t as hasEvents } from "./typeGuards-Cj5Rgvlg.mjs";
|
|
11
|
-
import { r as getAvailablePresets, t as applyPresets } from "./presets-
|
|
11
|
+
import { r as getAvailablePresets, t as applyPresets } from "./presets-BMfdy34e.mjs";
|
|
12
12
|
//#region src/pipeline/pipe.ts
|
|
13
13
|
/**
|
|
14
14
|
* Compose pipeline steps into an ordered array.
|
|
@@ -432,23 +432,31 @@ function createAdditionalRoutes(fastify, routes, controller, options) {
|
|
|
432
432
|
const authMw = buildAuthMiddleware(fastify, route.permissions);
|
|
433
433
|
const permissionMw = buildPermissionMiddleware(route.permissions, resourceName, opName);
|
|
434
434
|
const customPreHandlers = typeof route.preHandler === "function" ? route.preHandler(fastify) : route.preHandler ?? [];
|
|
435
|
+
const pluginMw = route.method === "GET" ? cacheMw : [
|
|
436
|
+
"POST",
|
|
437
|
+
"PUT",
|
|
438
|
+
"PATCH"
|
|
439
|
+
].includes(route.method) ? idempotencyMw : null;
|
|
435
440
|
const preHandler = [
|
|
441
|
+
...route.preAuth ?? [],
|
|
436
442
|
arcDecorator,
|
|
437
443
|
authMw,
|
|
438
444
|
permissionMw,
|
|
439
|
-
|
|
440
|
-
"POST",
|
|
441
|
-
"PUT",
|
|
442
|
-
"PATCH"
|
|
443
|
-
].includes(route.method) ? idempotencyMw : null,
|
|
445
|
+
pluginMw,
|
|
444
446
|
...customPreHandlers
|
|
445
447
|
].filter(Boolean);
|
|
448
|
+
const isStream = route.streamResponse === true;
|
|
446
449
|
fastify.route({
|
|
447
450
|
method: route.method,
|
|
448
451
|
url: route.path,
|
|
449
452
|
schema,
|
|
450
453
|
preHandler: preHandler.length > 0 ? preHandler : void 0,
|
|
451
|
-
handler,
|
|
454
|
+
handler: isStream ? async (request, reply) => {
|
|
455
|
+
reply.raw.setHeader("Content-Type", "text/event-stream");
|
|
456
|
+
reply.raw.setHeader("Cache-Control", "no-cache");
|
|
457
|
+
reply.raw.setHeader("Connection", "keep-alive");
|
|
458
|
+
return handler(request, reply);
|
|
459
|
+
} : handler,
|
|
452
460
|
...rateLimitConfig ? { config: rateLimitConfig } : {}
|
|
453
461
|
});
|
|
454
462
|
}
|
|
@@ -940,6 +948,70 @@ function defineResource(config) {
|
|
|
940
948
|
handler: hook.handler,
|
|
941
949
|
priority: hook.priority ?? 10
|
|
942
950
|
})));
|
|
951
|
+
if (config.hooks) {
|
|
952
|
+
const h = config.hooks;
|
|
953
|
+
const inlineHooks = [];
|
|
954
|
+
const toCtx = (ctx) => ({
|
|
955
|
+
data: ctx.data ?? ctx.result ?? {},
|
|
956
|
+
user: ctx.user,
|
|
957
|
+
meta: ctx.meta
|
|
958
|
+
});
|
|
959
|
+
if (h.beforeCreate) {
|
|
960
|
+
const fn = h.beforeCreate;
|
|
961
|
+
inlineHooks.push({
|
|
962
|
+
operation: "create",
|
|
963
|
+
phase: "before",
|
|
964
|
+
priority: 10,
|
|
965
|
+
handler: (ctx) => fn(toCtx(ctx))
|
|
966
|
+
});
|
|
967
|
+
}
|
|
968
|
+
if (h.afterCreate) {
|
|
969
|
+
const fn = h.afterCreate;
|
|
970
|
+
inlineHooks.push({
|
|
971
|
+
operation: "create",
|
|
972
|
+
phase: "after",
|
|
973
|
+
priority: 10,
|
|
974
|
+
handler: (ctx) => fn(toCtx(ctx))
|
|
975
|
+
});
|
|
976
|
+
}
|
|
977
|
+
if (h.beforeUpdate) {
|
|
978
|
+
const fn = h.beforeUpdate;
|
|
979
|
+
inlineHooks.push({
|
|
980
|
+
operation: "update",
|
|
981
|
+
phase: "before",
|
|
982
|
+
priority: 10,
|
|
983
|
+
handler: (ctx) => fn(toCtx(ctx))
|
|
984
|
+
});
|
|
985
|
+
}
|
|
986
|
+
if (h.afterUpdate) {
|
|
987
|
+
const fn = h.afterUpdate;
|
|
988
|
+
inlineHooks.push({
|
|
989
|
+
operation: "update",
|
|
990
|
+
phase: "after",
|
|
991
|
+
priority: 10,
|
|
992
|
+
handler: (ctx) => fn(toCtx(ctx))
|
|
993
|
+
});
|
|
994
|
+
}
|
|
995
|
+
if (h.beforeDelete) {
|
|
996
|
+
const fn = h.beforeDelete;
|
|
997
|
+
inlineHooks.push({
|
|
998
|
+
operation: "delete",
|
|
999
|
+
phase: "before",
|
|
1000
|
+
priority: 10,
|
|
1001
|
+
handler: (ctx) => fn(toCtx(ctx))
|
|
1002
|
+
});
|
|
1003
|
+
}
|
|
1004
|
+
if (h.afterDelete) {
|
|
1005
|
+
const fn = h.afterDelete;
|
|
1006
|
+
inlineHooks.push({
|
|
1007
|
+
operation: "delete",
|
|
1008
|
+
phase: "after",
|
|
1009
|
+
priority: 10,
|
|
1010
|
+
handler: (ctx) => fn(toCtx(ctx))
|
|
1011
|
+
});
|
|
1012
|
+
}
|
|
1013
|
+
resource._pendingHooks.push(...inlineHooks);
|
|
1014
|
+
}
|
|
943
1015
|
if (!config.skipRegistry) try {
|
|
944
1016
|
let openApiSchemas = config.openApiSchemas;
|
|
945
1017
|
if (!openApiSchemas && config.adapter?.generateSchemas) {
|
|
@@ -982,6 +1054,7 @@ var ResourceDefinition = class {
|
|
|
982
1054
|
pipe;
|
|
983
1055
|
fields;
|
|
984
1056
|
cache;
|
|
1057
|
+
skipGlobalPrefix;
|
|
985
1058
|
tenantField;
|
|
986
1059
|
idField;
|
|
987
1060
|
queryParser;
|
|
@@ -993,6 +1066,7 @@ var ResourceDefinition = class {
|
|
|
993
1066
|
this.displayName = config.displayName ?? `${capitalize(config.name)}s`;
|
|
994
1067
|
this.tag = config.tag ?? this.displayName;
|
|
995
1068
|
this.prefix = config.prefix ?? `/${config.name}s`;
|
|
1069
|
+
this.skipGlobalPrefix = config.skipGlobalPrefix ?? false;
|
|
996
1070
|
this.adapter = config.adapter;
|
|
997
1071
|
this.controller = config.controller;
|
|
998
1072
|
this.schemaOptions = config.schemaOptions ?? {};
|
|
@@ -1013,6 +1087,7 @@ var ResourceDefinition = class {
|
|
|
1013
1087
|
this.queryParser = config.queryParser;
|
|
1014
1088
|
this._appliedPresets = config._appliedPresets ?? [];
|
|
1015
1089
|
this._pendingHooks = config._pendingHooks ?? [];
|
|
1090
|
+
if (config.onRegister) this._onRegister = config.onRegister;
|
|
1016
1091
|
}
|
|
1017
1092
|
/** Get repository from adapter (if available) */
|
|
1018
1093
|
get repository() {
|
|
@@ -1067,6 +1142,8 @@ var ResourceDefinition = class {
|
|
|
1067
1142
|
pattern,
|
|
1068
1143
|
tags
|
|
1069
1144
|
});
|
|
1145
|
+
const onRegister = self._onRegister;
|
|
1146
|
+
if (onRegister) await onRegister(fastify);
|
|
1070
1147
|
await fastify.register(async (instance) => {
|
|
1071
1148
|
const typedInstance = instance;
|
|
1072
1149
|
let schemas = null;
|
|
@@ -1106,18 +1183,38 @@ var ResourceDefinition = class {
|
|
|
1106
1183
|
}
|
|
1107
1184
|
const listQuerySchema = self._registryMeta?.openApiSchemas?.listQuery;
|
|
1108
1185
|
if (listQuerySchema) {
|
|
1109
|
-
const
|
|
1110
|
-
"
|
|
1186
|
+
const KEEP_TYPE = new Set([
|
|
1187
|
+
"page",
|
|
1188
|
+
"limit",
|
|
1189
|
+
"sort",
|
|
1190
|
+
"search",
|
|
1111
1191
|
"select",
|
|
1112
|
-
"
|
|
1113
|
-
|
|
1114
|
-
|
|
1192
|
+
"after"
|
|
1193
|
+
]);
|
|
1194
|
+
const TYPE_DEPENDENT = new Set([
|
|
1195
|
+
"type",
|
|
1196
|
+
"minimum",
|
|
1197
|
+
"maximum",
|
|
1198
|
+
"minLength",
|
|
1199
|
+
"maxLength",
|
|
1200
|
+
"pattern",
|
|
1201
|
+
"format",
|
|
1202
|
+
"exclusiveMinimum",
|
|
1203
|
+
"exclusiveMaximum",
|
|
1204
|
+
"multipleOf",
|
|
1205
|
+
"minItems",
|
|
1206
|
+
"maxItems",
|
|
1207
|
+
"uniqueItems"
|
|
1208
|
+
]);
|
|
1115
1209
|
const props = listQuerySchema.properties;
|
|
1116
1210
|
const normalizedProps = props ? { ...props } : void 0;
|
|
1117
|
-
if (normalizedProps) {
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1211
|
+
if (normalizedProps) for (const key of Object.keys(normalizedProps)) {
|
|
1212
|
+
if (KEEP_TYPE.has(key)) continue;
|
|
1213
|
+
const prop = normalizedProps[key];
|
|
1214
|
+
if (prop && typeof prop === "object" && "type" in prop) {
|
|
1215
|
+
const cleaned = {};
|
|
1216
|
+
for (const [k, v] of Object.entries(prop)) if (!TYPE_DEPENDENT.has(k)) cleaned[k] = v;
|
|
1217
|
+
normalizedProps[key] = Object.keys(cleaned).length > 0 ? cleaned : {};
|
|
1121
1218
|
}
|
|
1122
1219
|
}
|
|
1123
1220
|
const normalizedSchema = {
|
package/dist/discovery/index.mjs
CHANGED
package/dist/docs/index.d.mts
CHANGED
package/dist/dynamic/index.d.mts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Bt as ResourceDefinition, n as DataAdapter } from "../interface-DDW43OmS.mjs";
|
|
2
2
|
import { t as PermissionCheck } from "../types-BNUccdcf.mjs";
|
|
3
3
|
|
|
4
4
|
//#region src/dynamic/ArcDynamicLoader.d.ts
|
package/dist/dynamic/index.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { t as ArcQueryParser } from "../queryParser-CgCtsjti.mjs";
|
|
2
|
-
import { n as defineResource } from "../defineResource-
|
|
3
|
-
import {
|
|
2
|
+
import { n as defineResource } from "../defineResource-bVKHjQzE.mjs";
|
|
3
|
+
import { S as readOnly, _ as fullPublic, b as publicRead, g as authenticated, h as adminOnly, v as ownerWithAdminBypass, x as publicReadAdminWrite } from "../permissions-C8ImI8gC.mjs";
|
|
4
4
|
//#region src/dynamic/ArcDynamicLoader.ts
|
|
5
5
|
const VALID_FIELD_TYPES = new Set([
|
|
6
6
|
"string",
|
|
@@ -93,6 +93,32 @@ declare function getUserId(scope: RequestScope): string | undefined;
|
|
|
93
93
|
* ```
|
|
94
94
|
*/
|
|
95
95
|
declare function getUserRoles(scope: RequestScope): string[];
|
|
96
|
+
/**
|
|
97
|
+
* Org context — canonical extraction from a Fastify request.
|
|
98
|
+
*
|
|
99
|
+
* Works regardless of auth type (JWT, Better Auth, custom) by reading
|
|
100
|
+
* `request.scope` and `request.user`. Eliminates the need for each resource
|
|
101
|
+
* to re-invent org extraction from headers/user/scope.
|
|
102
|
+
*
|
|
103
|
+
* @example
|
|
104
|
+
* ```typescript
|
|
105
|
+
* import { getOrgContext } from '@classytic/arc/scope';
|
|
106
|
+
*
|
|
107
|
+
* handler: async (request, reply) => {
|
|
108
|
+
* const { userId, organizationId, roles, orgRoles } = getOrgContext(request);
|
|
109
|
+
* }
|
|
110
|
+
* ```
|
|
111
|
+
*/
|
|
112
|
+
declare function getOrgContext(request: {
|
|
113
|
+
scope?: RequestScope;
|
|
114
|
+
user?: Record<string, unknown> | null;
|
|
115
|
+
headers?: Record<string, string | string[] | undefined>;
|
|
116
|
+
}): {
|
|
117
|
+
userId: string | undefined;
|
|
118
|
+
organizationId: string | undefined;
|
|
119
|
+
roles: string[];
|
|
120
|
+
orgRoles: string[];
|
|
121
|
+
};
|
|
96
122
|
/** Default public scope — used as initial decoration value */
|
|
97
123
|
declare const PUBLIC_SCOPE: Readonly<RequestScope>;
|
|
98
124
|
/** Default authenticated scope — used when user is logged in but no org */
|
|
@@ -118,4 +144,4 @@ interface ElevationEvent {
|
|
|
118
144
|
declare const elevationPlugin: FastifyPluginAsync<ElevationOptions>;
|
|
119
145
|
declare const _default: FastifyPluginAsync<ElevationOptions>;
|
|
120
146
|
//#endregion
|
|
121
|
-
export { AUTHENTICATED_SCOPE as a,
|
|
147
|
+
export { isMember as _, AUTHENTICATED_SCOPE as a, getOrgContext as c, getTeamId as d, getUserId as f, isElevated as g, isAuthenticated as h, elevationPlugin as i, getOrgId as l, hasOrgAccess as m, ElevationOptions as n, PUBLIC_SCOPE as o, getUserRoles as p, _default as r, RequestScope as s, ElevationEvent as t, getOrgRoles as u };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { t as __exportAll } from "./chunk-BpYLSNr0.mjs";
|
|
2
|
-
import {
|
|
2
|
+
import { p as isArcError } from "./errors-NoQKsbAT.mjs";
|
|
3
3
|
import fp from "fastify-plugin";
|
|
4
4
|
//#region src/plugins/errorHandler.ts
|
|
5
5
|
var errorHandler_exports = /* @__PURE__ */ __exportAll({ errorHandlerPlugin: () => errorHandlerPlugin });
|
|
@@ -116,9 +116,25 @@ declare class ServiceUnavailableError extends ArcError {
|
|
|
116
116
|
* Create error from status code
|
|
117
117
|
*/
|
|
118
118
|
declare function createError(statusCode: number, message: string, details?: Record<string, unknown>): ArcError;
|
|
119
|
+
/**
|
|
120
|
+
* Create a domain-specific error with automatic HTTP status mapping.
|
|
121
|
+
*
|
|
122
|
+
* Eliminates manual `if (err.code === 'X') return status` boilerplate.
|
|
123
|
+
* Arc's error handler automatically maps `statusCode` to HTTP response.
|
|
124
|
+
*
|
|
125
|
+
* @example
|
|
126
|
+
* ```typescript
|
|
127
|
+
* import { createDomainError } from '@classytic/arc';
|
|
128
|
+
*
|
|
129
|
+
* throw createDomainError('MEMBER_NOT_FOUND', 'Member does not exist', 404);
|
|
130
|
+
* throw createDomainError('SELF_REFERRAL', 'Cannot refer yourself', 422);
|
|
131
|
+
* throw createDomainError('INSUFFICIENT_BALANCE', 'Not enough credits', 402, { balance: 0 });
|
|
132
|
+
* ```
|
|
133
|
+
*/
|
|
134
|
+
declare function createDomainError(code: string, message: string, statusCode?: number, details?: Record<string, unknown>): ArcError;
|
|
119
135
|
/**
|
|
120
136
|
* Check if error is an Arc error
|
|
121
137
|
*/
|
|
122
138
|
declare function isArcError(error: unknown): error is ArcError;
|
|
123
139
|
//#endregion
|
|
124
|
-
export { NotFoundError as a, RateLimitError as c, ValidationError as d,
|
|
140
|
+
export { NotFoundError as a, RateLimitError as c, ValidationError as d, createDomainError as f, ForbiddenError as i, ServiceUnavailableError as l, isArcError as m, ConflictError as n, OrgAccessDeniedError as o, createError as p, ErrorDetails as r, OrgRequiredError as s, ArcError as t, UnauthorizedError as u };
|
|
@@ -201,10 +201,32 @@ function createError(statusCode, message, details) {
|
|
|
201
201
|
});
|
|
202
202
|
}
|
|
203
203
|
/**
|
|
204
|
+
* Create a domain-specific error with automatic HTTP status mapping.
|
|
205
|
+
*
|
|
206
|
+
* Eliminates manual `if (err.code === 'X') return status` boilerplate.
|
|
207
|
+
* Arc's error handler automatically maps `statusCode` to HTTP response.
|
|
208
|
+
*
|
|
209
|
+
* @example
|
|
210
|
+
* ```typescript
|
|
211
|
+
* import { createDomainError } from '@classytic/arc';
|
|
212
|
+
*
|
|
213
|
+
* throw createDomainError('MEMBER_NOT_FOUND', 'Member does not exist', 404);
|
|
214
|
+
* throw createDomainError('SELF_REFERRAL', 'Cannot refer yourself', 422);
|
|
215
|
+
* throw createDomainError('INSUFFICIENT_BALANCE', 'Not enough credits', 402, { balance: 0 });
|
|
216
|
+
* ```
|
|
217
|
+
*/
|
|
218
|
+
function createDomainError(code, message, statusCode = 400, details) {
|
|
219
|
+
return new ArcError(message, {
|
|
220
|
+
code,
|
|
221
|
+
statusCode,
|
|
222
|
+
details
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
/**
|
|
204
226
|
* Check if error is an Arc error
|
|
205
227
|
*/
|
|
206
228
|
function isArcError(error) {
|
|
207
229
|
return error instanceof ArcError;
|
|
208
230
|
}
|
|
209
231
|
//#endregion
|
|
210
|
-
export { OrgAccessDeniedError as a, ServiceUnavailableError as c,
|
|
232
|
+
export { OrgAccessDeniedError as a, ServiceUnavailableError as c, createDomainError as d, createError as f, NotFoundError as i, UnauthorizedError as l, ConflictError as n, OrgRequiredError as o, isArcError as p, ForbiddenError as r, RateLimitError as s, ArcError as t, ValidationError as u };
|
|
@@ -37,6 +37,30 @@ interface ValidationResult {
|
|
|
37
37
|
valid: boolean;
|
|
38
38
|
errors?: string[];
|
|
39
39
|
}
|
|
40
|
+
/** Custom validator function — replaces the built-in minimal validator. */
|
|
41
|
+
type CustomValidator = (schema: EventSchema, payload: unknown) => ValidationResult;
|
|
42
|
+
interface EventRegistryOptions {
|
|
43
|
+
/**
|
|
44
|
+
* Custom validator to replace the built-in minimal validator.
|
|
45
|
+
* Use this for full JSON Schema validation (AJV, Zod, etc.).
|
|
46
|
+
*
|
|
47
|
+
* @example
|
|
48
|
+
* ```typescript
|
|
49
|
+
* import Ajv from 'ajv';
|
|
50
|
+
* const ajv = new Ajv();
|
|
51
|
+
*
|
|
52
|
+
* const registry = createEventRegistry({
|
|
53
|
+
* validate: (schema, payload) => {
|
|
54
|
+
* const valid = ajv.validate(schema, payload);
|
|
55
|
+
* return valid
|
|
56
|
+
* ? { valid: true }
|
|
57
|
+
* : { valid: false, errors: ajv.errorsText().split(', ') };
|
|
58
|
+
* },
|
|
59
|
+
* });
|
|
60
|
+
* ```
|
|
61
|
+
*/
|
|
62
|
+
validate?: CustomValidator;
|
|
63
|
+
}
|
|
40
64
|
interface EventRegistry {
|
|
41
65
|
/** Register an event definition */
|
|
42
66
|
register(definition: EventDefinitionOutput): void;
|
|
@@ -69,8 +93,12 @@ declare function defineEvent<T = unknown>(input: EventDefinitionInput): EventDef
|
|
|
69
93
|
*
|
|
70
94
|
* The registry is opt-in — unregistered events pass validation.
|
|
71
95
|
* This allows gradual adoption without breaking existing code.
|
|
96
|
+
*
|
|
97
|
+
* @param options.validate - Custom validator replacing the built-in minimal validator.
|
|
98
|
+
* The built-in validator only checks top-level object structure (type, required, property types).
|
|
99
|
+
* For nested objects, arrays, enums, patterns, or $ref, provide AJV or similar.
|
|
72
100
|
*/
|
|
73
|
-
declare function createEventRegistry(): EventRegistry;
|
|
101
|
+
declare function createEventRegistry(options?: EventRegistryOptions): EventRegistry;
|
|
74
102
|
//#endregion
|
|
75
103
|
//#region src/events/retry.d.ts
|
|
76
104
|
interface RetryOptions {
|
|
@@ -221,4 +249,4 @@ declare module "fastify" {
|
|
|
221
249
|
}
|
|
222
250
|
declare const eventPlugin: FastifyPluginAsync<EventPluginOptions>;
|
|
223
251
|
//#endregion
|
|
224
|
-
export { withRetry as a,
|
|
252
|
+
export { withRetry as a, EventDefinitionOutput as c, EventSchema as d, ValidationResult as f, createDeadLetterPublisher as i, EventRegistry as l, defineEvent as m, eventPlugin as n, CustomValidator as o, createEventRegistry as p, RetryOptions as r, EventDefinitionInput as s, EventPluginOptions as t, EventRegistryOptions as u };
|
package/dist/events/index.d.mts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { a as MemoryEventTransport, i as EventTransport, n as EventHandler, o as MemoryEventTransportOptions, r as EventLogger, s as createEvent, t as DomainEvent } from "../EventTransport-wc5hSLik.mjs";
|
|
2
|
-
import { a as withRetry, c as
|
|
2
|
+
import { a as withRetry, c as EventDefinitionOutput, d as EventSchema, f as ValidationResult, i as createDeadLetterPublisher, l as EventRegistry, m as defineEvent, n as eventPlugin, o as CustomValidator, p as createEventRegistry, r as RetryOptions, s as EventDefinitionInput, t as EventPluginOptions, u as EventRegistryOptions } from "../eventPlugin-DW45v4V5.mjs";
|
|
3
3
|
import { RedisEventTransportOptions, RedisLike } from "./transports/redis.mjs";
|
|
4
4
|
import { r as RedisStreamTransportOptions, t as RedisStreamLike } from "../redis-stream-BW9UKLZM.mjs";
|
|
5
5
|
|
|
@@ -115,4 +115,4 @@ declare class MemoryOutboxStore implements OutboxStore {
|
|
|
115
115
|
acknowledge(eventId: string): Promise<void>;
|
|
116
116
|
}
|
|
117
117
|
//#endregion
|
|
118
|
-
export { ARC_LIFECYCLE_EVENTS, type ArcLifecycleEvent, CACHE_EVENTS, CRUD_EVENT_SUFFIXES, type CacheEvent, type CrudEventSuffix, type DomainEvent, type EventDefinitionInput, type EventDefinitionOutput, type EventHandler, type EventLogger, EventOutbox, type EventOutboxOptions, type EventPluginOptions, type EventRegistry, type EventSchema, type EventTransport, MemoryEventTransport, type MemoryEventTransportOptions, MemoryOutboxStore, type OutboxStore, type RedisEventTransportOptions, type RedisLike, type RedisStreamLike, type RedisStreamTransportOptions, type RetryOptions, type ValidationResult, createDeadLetterPublisher, createEvent, createEventRegistry, crudEventType, defineEvent, eventPlugin, withRetry };
|
|
118
|
+
export { ARC_LIFECYCLE_EVENTS, type ArcLifecycleEvent, CACHE_EVENTS, CRUD_EVENT_SUFFIXES, type CacheEvent, type CrudEventSuffix, type CustomValidator, type DomainEvent, type EventDefinitionInput, type EventDefinitionOutput, type EventHandler, type EventLogger, EventOutbox, type EventOutboxOptions, type EventPluginOptions, type EventRegistry, type EventRegistryOptions, type EventSchema, type EventTransport, MemoryEventTransport, type MemoryEventTransportOptions, MemoryOutboxStore, type OutboxStore, type RedisEventTransportOptions, type RedisLike, type RedisStreamLike, type RedisStreamTransportOptions, type RetryOptions, type ValidationResult, createDeadLetterPublisher, createEvent, createEventRegistry, crudEventType, defineEvent, eventPlugin, withRetry };
|
package/dist/events/index.mjs
CHANGED
|
@@ -8,9 +8,27 @@ import { a as MemoryEventTransport, i as withRetry, o as createEvent, r as creat
|
|
|
8
8
|
* 2. EventRegistry — catalog of all known events + payload validation
|
|
9
9
|
* 3. .create() helper — build DomainEvent with auto-generated metadata
|
|
10
10
|
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
11
|
+
* The built-in validator checks: object type, required fields, and top-level
|
|
12
|
+
* property types. It does NOT recurse into nested objects, validate arrays,
|
|
13
|
+
* enums, patterns, formats, or $ref. This is intentional — it's a lightweight
|
|
14
|
+
* guard, not a full JSON Schema engine.
|
|
15
|
+
*
|
|
16
|
+
* For full validation, pass a custom `validate` function to `createEventRegistry()`:
|
|
17
|
+
*
|
|
18
|
+
* @example
|
|
19
|
+
* ```typescript
|
|
20
|
+
* import Ajv from 'ajv';
|
|
21
|
+
* const ajv = new Ajv();
|
|
22
|
+
*
|
|
23
|
+
* const registry = createEventRegistry({
|
|
24
|
+
* validate: (schema, payload) => {
|
|
25
|
+
* const valid = ajv.validate(schema, payload);
|
|
26
|
+
* return valid
|
|
27
|
+
* ? { valid: true }
|
|
28
|
+
* : { valid: false, errors: ajv.errorsText().split(', ') };
|
|
29
|
+
* },
|
|
30
|
+
* });
|
|
31
|
+
* ```
|
|
14
32
|
*
|
|
15
33
|
* @example
|
|
16
34
|
* ```typescript
|
|
@@ -67,8 +85,13 @@ function defineEvent(input) {
|
|
|
67
85
|
*
|
|
68
86
|
* The registry is opt-in — unregistered events pass validation.
|
|
69
87
|
* This allows gradual adoption without breaking existing code.
|
|
88
|
+
*
|
|
89
|
+
* @param options.validate - Custom validator replacing the built-in minimal validator.
|
|
90
|
+
* The built-in validator only checks top-level object structure (type, required, property types).
|
|
91
|
+
* For nested objects, arrays, enums, patterns, or $ref, provide AJV or similar.
|
|
70
92
|
*/
|
|
71
|
-
function createEventRegistry() {
|
|
93
|
+
function createEventRegistry(options) {
|
|
94
|
+
const customValidator = options?.validate;
|
|
72
95
|
const definitions = /* @__PURE__ */ new Map();
|
|
73
96
|
function registryKey(name, version) {
|
|
74
97
|
return `${name}:v${version}`;
|
|
@@ -106,18 +129,25 @@ function createEventRegistry() {
|
|
|
106
129
|
}
|
|
107
130
|
if (!latest) return { valid: true };
|
|
108
131
|
if (!latest.schema) return { valid: true };
|
|
132
|
+
if (customValidator) return customValidator(latest.schema, payload);
|
|
109
133
|
return validatePayload(payload, latest.schema);
|
|
110
134
|
}
|
|
111
135
|
};
|
|
112
136
|
}
|
|
113
137
|
/**
|
|
114
|
-
*
|
|
115
|
-
*
|
|
116
|
-
*
|
|
117
|
-
* -
|
|
138
|
+
* Built-in minimal validator — lightweight guard, NOT a full JSON Schema engine.
|
|
139
|
+
*
|
|
140
|
+
* Checks:
|
|
141
|
+
* - payload is an object (not null, not array)
|
|
142
|
+
* - required fields are present
|
|
143
|
+
* - top-level property types match (string, number, boolean, array, object)
|
|
144
|
+
*
|
|
145
|
+
* Does NOT check:
|
|
146
|
+
* - nested object properties
|
|
147
|
+
* - array item types
|
|
148
|
+
* - enum, pattern, format, minLength, minimum, $ref
|
|
118
149
|
*
|
|
119
|
-
* For full
|
|
120
|
-
* use AJV directly or provide a custom validator.
|
|
150
|
+
* For full validation, pass a custom `validate` function to `createEventRegistry()`.
|
|
121
151
|
*/
|
|
122
152
|
function validatePayload(payload, schema) {
|
|
123
153
|
const errors = [];
|
package/dist/factory/index.d.mts
CHANGED
|
@@ -1,38 +1,33 @@
|
|
|
1
|
-
import { a as CustomPluginAuthOption, c as RawBodyOptions, i as CustomAuthenticatorOption, l as UnderPressureOptions, n as BetterAuthOption, o as JwtAuthOption, r as CreateAppOptions, s as MultipartOptions, t as AuthOption } from "../types-
|
|
1
|
+
import { a as CustomPluginAuthOption, c as RawBodyOptions, d as ResourceLike, f as loadResources, i as CustomAuthenticatorOption, l as UnderPressureOptions, n as BetterAuthOption, o as JwtAuthOption, r as CreateAppOptions, s as MultipartOptions, t as AuthOption, u as LoadResourcesOptions } from "../types-D5hJ-k_3.mjs";
|
|
2
2
|
import { FastifyInstance } from "fastify";
|
|
3
3
|
|
|
4
4
|
//#region src/factory/createApp.d.ts
|
|
5
5
|
/**
|
|
6
|
-
* Create a production-ready Fastify application with Arc framework
|
|
6
|
+
* Create a production-ready Fastify application with Arc framework.
|
|
7
7
|
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
8
|
+
* Boot order:
|
|
9
|
+
* ```
|
|
10
|
+
* 0. Logger, validation, preset merge
|
|
11
|
+
* 1. Create Fastify instance
|
|
12
|
+
* 2. Security plugins (Helmet, CORS, Rate Limit) — opt-out
|
|
13
|
+
* 3. Utility plugins (Under Pressure, Sensible, Multipart, Raw Body)
|
|
14
|
+
* 4. Arc core (fastify.arc, events)
|
|
15
|
+
* 5. Arc plugins (requestId, health, caching, SSE, metrics, versioning)
|
|
16
|
+
* 6. Auth (scope decoration, auth strategy, elevation, error handler)
|
|
17
|
+
* 7. plugins() — user infra (DB, docs, webhooks)
|
|
18
|
+
* 8. bootstrap[] — domain init (singletons, event handlers)
|
|
19
|
+
* 9. resources[] — auto-discovered routes (prefix + skipGlobalPrefix)
|
|
20
|
+
* 10. afterResources() — post-registration wiring
|
|
21
|
+
* 11. onReady/onClose — lifecycle hooks
|
|
22
|
+
* ```
|
|
19
23
|
*/
|
|
20
24
|
declare function createApp(options: CreateAppOptions): Promise<FastifyInstance>;
|
|
21
25
|
/**
|
|
22
26
|
* Quick factory for common scenarios
|
|
23
27
|
*/
|
|
24
28
|
declare const ArcFactory: {
|
|
25
|
-
/**
|
|
26
|
-
* Create production app with strict security
|
|
27
|
-
*/
|
|
28
29
|
production(options: Omit<CreateAppOptions, "preset">): Promise<FastifyInstance>;
|
|
29
|
-
/**
|
|
30
|
-
* Create development app with relaxed security
|
|
31
|
-
*/
|
|
32
30
|
development(options: Omit<CreateAppOptions, "preset">): Promise<FastifyInstance>;
|
|
33
|
-
/**
|
|
34
|
-
* Create testing app with minimal setup
|
|
35
|
-
*/
|
|
36
31
|
testing(options: Omit<CreateAppOptions, "preset">): Promise<FastifyInstance>;
|
|
37
32
|
};
|
|
38
33
|
//#endregion
|
|
@@ -69,9 +64,35 @@ declare const developmentPreset: Partial<CreateAppOptions>;
|
|
|
69
64
|
* Testing preset - minimal setup, fast startup
|
|
70
65
|
*/
|
|
71
66
|
declare const testingPreset: Partial<CreateAppOptions>;
|
|
67
|
+
/**
|
|
68
|
+
* Edge/Serverless preset — minimal cold-start overhead
|
|
69
|
+
*
|
|
70
|
+
* Strips non-essential plugins to reduce startup time. Designed for:
|
|
71
|
+
* - Cloudflare Workers (requires `nodejs_compat` flag + `toFetchHandler()`)
|
|
72
|
+
* - AWS Lambda via `@fastify/aws-lambda` or `toFetchHandler()`
|
|
73
|
+
* - Vercel Serverless Functions (Node.js runtime)
|
|
74
|
+
* - Google Cloud Functions (Node.js runtime)
|
|
75
|
+
* - Any environment where the platform handles security, rate limiting, and health checks
|
|
76
|
+
*
|
|
77
|
+
* Use with `toFetchHandler()` from `@classytic/arc/factory` for edge runtimes:
|
|
78
|
+
* ```typescript
|
|
79
|
+
* import { createApp, toFetchHandler } from '@classytic/arc/factory';
|
|
80
|
+
* const app = await createApp({ preset: 'edge' });
|
|
81
|
+
* export default { fetch: toFetchHandler(app) };
|
|
82
|
+
* ```
|
|
83
|
+
*
|
|
84
|
+
* **Edge runtime requirements:**
|
|
85
|
+
* - Cloudflare Workers: enable `nodejs_compat` in wrangler.toml
|
|
86
|
+
* - Vercel: use Node.js runtime (not Edge Runtime) or enable nodejs compat
|
|
87
|
+
*
|
|
88
|
+
* Arc uses `node:crypto` and `AsyncLocalStorage` — both supported by
|
|
89
|
+
* modern edge runtimes with Node.js compat flags. No TCP server is needed
|
|
90
|
+
* when using `toFetchHandler()` (routes through Fastify's `.inject()`).
|
|
91
|
+
*/
|
|
92
|
+
declare const edgePreset: Partial<CreateAppOptions>;
|
|
72
93
|
/**
|
|
73
94
|
* Get preset by name
|
|
74
95
|
*/
|
|
75
96
|
declare function getPreset(name: "production" | "development" | "testing" | "edge"): Partial<CreateAppOptions>;
|
|
76
97
|
//#endregion
|
|
77
|
-
export { ArcFactory, type AuthOption, type BetterAuthOption, type CreateAppOptions, type CustomAuthenticatorOption, type CustomPluginAuthOption, type FetchHandlerOptions, type JwtAuthOption, type MultipartOptions, type RawBodyOptions, type UnderPressureOptions, createApp, developmentPreset, getPreset, productionPreset, testingPreset, toFetchHandler };
|
|
98
|
+
export { ArcFactory, type AuthOption, type BetterAuthOption, type CreateAppOptions, type CustomAuthenticatorOption, type CustomPluginAuthOption, type FetchHandlerOptions, type JwtAuthOption, type LoadResourcesOptions, type MultipartOptions, type RawBodyOptions, type ResourceLike, type UnderPressureOptions, createApp, developmentPreset, edgePreset, getPreset, loadResources, productionPreset, testingPreset, toFetchHandler };
|