@classytic/arc 2.4.2 → 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.
Files changed (104) hide show
  1. package/README.md +22 -6
  2. package/dist/{BaseController-CkM5dUh_.mjs → BaseController-AbbRx3e0.mjs} +5 -2
  3. package/dist/adapters/index.d.mts +2 -2
  4. package/dist/adapters/index.mjs +1 -1
  5. package/dist/{adapters-DTC4Ug66.mjs → adapters-CTn28N4y.mjs} +72 -11
  6. package/dist/audit/index.d.mts +1 -1
  7. package/dist/audit/index.mjs +11 -1
  8. package/dist/audit/mongodb.d.mts +1 -1
  9. package/dist/auth/index.d.mts +1 -1
  10. package/dist/auth/index.mjs +2 -2
  11. package/dist/cache/index.mjs +2 -2
  12. package/dist/cli/commands/describe.d.mts +1 -1
  13. package/dist/cli/commands/describe.mjs +1 -1
  14. package/dist/cli/commands/generate.d.mts +1 -1
  15. package/dist/cli/commands/generate.mjs +1 -1
  16. package/dist/cli/commands/init.d.mts +1 -1
  17. package/dist/cli/commands/init.mjs +13 -10
  18. package/dist/cli/commands/introspect.d.mts +1 -1
  19. package/dist/cli/commands/introspect.mjs +1 -1
  20. package/dist/cli/index.d.mts +4 -4
  21. package/dist/cli/index.mjs +4 -4
  22. package/dist/core/index.d.mts +2 -2
  23. package/dist/core/index.mjs +2 -2
  24. package/dist/{createApp-ByWNRsZj.mjs → createApp-Bol7DLUf.mjs} +404 -279
  25. package/dist/{defineResource-D9aY5Cy6.mjs → defineResource-bVKHjQzE.mjs} +116 -19
  26. package/dist/discovery/index.d.mts +1 -1
  27. package/dist/discovery/index.mjs +2 -2
  28. package/dist/docs/index.d.mts +1 -1
  29. package/dist/dynamic/index.d.mts +1 -1
  30. package/dist/dynamic/index.mjs +2 -2
  31. package/dist/{elevation-Ca_yveIO.d.mts → elevation-C_taLQrM.d.mts} +27 -1
  32. package/dist/{errorHandler--zp54tGc.mjs → errorHandler-r2595m8T.mjs} +5 -5
  33. package/dist/{errors-CPpvPHT0.d.mts → errors-CcVbl1-T.d.mts} +17 -1
  34. package/dist/{errors-rxhfP7Hf.mjs → errors-NoQKsbAT.mjs} +23 -1
  35. package/dist/{eventPlugin-iGrSEmwJ.d.mts → eventPlugin-DW45v4V5.d.mts} +30 -2
  36. package/dist/events/index.d.mts +2 -2
  37. package/dist/events/index.mjs +40 -10
  38. package/dist/events/transports/redis.d.mts +1 -1
  39. package/dist/events/transports/redis.mjs +1 -1
  40. package/dist/factory/index.d.mts +44 -23
  41. package/dist/factory/index.mjs +136 -2
  42. package/dist/hooks/index.d.mts +1 -1
  43. package/dist/idempotency/index.d.mts +3 -3
  44. package/dist/idempotency/mongodb.d.mts +1 -1
  45. package/dist/idempotency/redis.d.mts +1 -1
  46. package/dist/{index-yhxyjqNb.d.mts → index-BIsZ_su5.d.mts} +4 -8
  47. package/dist/{index-BL8CaQih.d.mts → index-Cb3gtbg7.d.mts} +2 -2
  48. package/dist/{index-Diqcm14c.d.mts → index-NGZksqM5.d.mts} +30 -1
  49. package/dist/index.d.mts +6 -6
  50. package/dist/index.mjs +8 -7
  51. package/dist/integrations/event-gateway.d.mts +1 -1
  52. package/dist/integrations/event-gateway.mjs +2 -2
  53. package/dist/integrations/index.d.mts +1 -1
  54. package/dist/integrations/jobs.d.mts +1 -1
  55. package/dist/integrations/jobs.mjs +1 -1
  56. package/dist/integrations/mcp/index.d.mts +4 -2
  57. package/dist/integrations/mcp/index.mjs +1 -1
  58. package/dist/integrations/mcp/testing.d.mts +1 -1
  59. package/dist/integrations/mcp/testing.mjs +1 -1
  60. package/dist/integrations/streamline.d.mts +1 -1
  61. package/dist/integrations/streamline.mjs +1 -1
  62. package/dist/integrations/websocket-redis.d.mts +1 -1
  63. package/dist/integrations/websocket-redis.mjs +1 -1
  64. package/dist/integrations/websocket.d.mts +1 -1
  65. package/dist/integrations/websocket.mjs +1 -1
  66. package/dist/{interface-DGmPxakH.d.mts → interface-DDW43OmS.d.mts} +194 -13
  67. package/dist/{memory-Cb_7iy9e.mjs → memory-BFAYkf8H.mjs} +1 -4
  68. package/dist/{mongodb-CUpYfxfD.d.mts → mongodb-kltrBPa1.d.mts} +10 -0
  69. package/dist/{mongodb-bga9AbkD.d.mts → mongodb-pMvOlR5_.d.mts} +1 -1
  70. package/dist/org/index.d.mts +1 -1
  71. package/dist/org/index.mjs +1 -1
  72. package/dist/permissions/index.d.mts +2 -2
  73. package/dist/permissions/index.mjs +2 -2
  74. package/dist/{permissions-CA5zg0yK.mjs → permissions-C8ImI8gC.mjs} +45 -3
  75. package/dist/plugins/index.d.mts +1 -1
  76. package/dist/plugins/index.mjs +3 -3
  77. package/dist/plugins/response-cache.d.mts +1 -1
  78. package/dist/plugins/response-cache.mjs +1 -1
  79. package/dist/plugins/tracing-entry.mjs +1 -1
  80. package/dist/presets/index.d.mts +2 -2
  81. package/dist/presets/index.mjs +2 -2
  82. package/dist/presets/multiTenant.d.mts +2 -2
  83. package/dist/presets/multiTenant.mjs +2 -2
  84. package/dist/{presets-C9QXJV1u.mjs → presets-BMfdy34e.mjs} +3 -3
  85. package/dist/{queryCachePlugin-ClosZdNS.mjs → queryCachePlugin-XtFplYO9.mjs} +1 -1
  86. package/dist/{redis-CQ5YxMC5.d.mts → redis-D0Qc-9EW.d.mts} +1 -1
  87. package/dist/registry/index.d.mts +1 -1
  88. package/dist/{resourceToTools-PMFE8HIv.mjs → resourceToTools-DH3c3e-T.mjs} +81 -7
  89. package/dist/scope/index.d.mts +2 -2
  90. package/dist/scope/index.mjs +2 -2
  91. package/dist/{sse-BkViJPlT.mjs → sse-BF7GR7IB.mjs} +1 -1
  92. package/dist/testing/index.d.mts +2 -2
  93. package/dist/testing/index.mjs +1 -1
  94. package/dist/types/index.d.mts +3 -3
  95. package/dist/types/index.mjs +23 -2
  96. package/dist/{types-C6TQjtdi.mjs → types-BhtYdxZU.mjs} +26 -1
  97. package/dist/{types-Dt0-AI6E.d.mts → types-D5hJ-k_3.d.mts} +189 -6
  98. package/dist/{types-BJmgxNbF.d.mts → types-D5rjsS_i.d.mts} +1 -1
  99. package/dist/utils/index.d.mts +2 -2
  100. package/dist/utils/index.mjs +1 -1
  101. package/package.json +7 -5
  102. package/skills/arc/SKILL.md +115 -8
  103. package/skills/arc/references/mcp.md +160 -2
  104. /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 isMember, n as PUBLIC_SCOPE, u as isElevated } from "./types-C6TQjtdi.mjs";
3
- import { t as BaseController } from "./BaseController-CkM5dUh_.mjs";
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-rxhfP7Hf.mjs";
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-C9QXJV1u.mjs";
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
- route.method === "GET" ? cacheMw : [
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 FLEXIBLE_PARAMS = [
1110
- "populate",
1186
+ const KEEP_TYPE = new Set([
1187
+ "page",
1188
+ "limit",
1189
+ "sort",
1190
+ "search",
1111
1191
  "select",
1112
- "lookup",
1113
- "aggregate"
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
- for (const key of FLEXIBLE_PARAMS) if (normalizedProps[key] && typeof normalizedProps[key] === "object") {
1119
- const { type: _type, ...rest } = normalizedProps[key];
1120
- normalizedProps[key] = rest;
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 = {
@@ -43,4 +43,4 @@ declare function discoverResources(options: DiscoveryOptions): Promise<Array<{
43
43
  /** Auto-discovery plugin for Arc resources */
44
44
  declare const discoveryPlugin: FastifyPluginAsync<DiscoveryPluginOptions>;
45
45
  //#endregion
46
- export { DiscoverableResource, DiscoveryOptions, DiscoveryPluginOptions, discoveryPlugin as default, discoveryPlugin, discoverResources };
46
+ export { DiscoverableResource, DiscoveryOptions, DiscoveryPluginOptions, discoverResources, discoveryPlugin };
@@ -1,6 +1,6 @@
1
+ import { readdir } from "node:fs/promises";
1
2
  import { extname, join, resolve } from "node:path";
2
3
  import { pathToFileURL } from "node:url";
3
- import { readdir } from "node:fs/promises";
4
4
  //#region src/discovery/index.ts
5
5
  /**
6
6
  * Arc Auto-Discovery Plugin
@@ -138,4 +138,4 @@ const discoveryPluginImpl = async (fastify, options) => {
138
138
  /** Auto-discovery plugin for Arc resources */
139
139
  const discoveryPlugin = discoveryPluginImpl;
140
140
  //#endregion
141
- export { discoveryPlugin as default, discoveryPlugin, discoverResources };
141
+ export { discoverResources, discoveryPlugin };
@@ -1,4 +1,4 @@
1
- import { Q as RegistryEntry } from "../interface-DGmPxakH.mjs";
1
+ import { $ as RegistryEntry } from "../interface-DDW43OmS.mjs";
2
2
  import { t as ExternalOpenApiPaths } from "../externalPaths-DpO-s7r8.mjs";
3
3
  import { FastifyPluginAsync } from "fastify";
4
4
 
@@ -1,4 +1,4 @@
1
- import { Lt as ResourceDefinition, n as DataAdapter } from "../interface-DGmPxakH.mjs";
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
@@ -1,6 +1,6 @@
1
1
  import { t as ArcQueryParser } from "../queryParser-CgCtsjti.mjs";
2
- import { n as defineResource } from "../defineResource-D9aY5Cy6.mjs";
3
- import { _ as ownerWithAdminBypass, b as publicReadAdminWrite, g as fullPublic, h as authenticated, m as adminOnly, x as readOnly, y as publicRead } from "../permissions-CA5zg0yK.mjs";
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, getOrgId as c, getUserId as d, getUserRoles as f, isMember as g, isElevated as h, elevationPlugin as i, getOrgRoles as l, isAuthenticated as m, ElevationOptions as n, PUBLIC_SCOPE as o, hasOrgAccess as p, _default as r, RequestScope as s, ElevationEvent as t, getTeamId as u };
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,11 +1,8 @@
1
1
  import { t as __exportAll } from "./chunk-BpYLSNr0.mjs";
2
- import { f as isArcError } from "./errors-rxhfP7Hf.mjs";
2
+ import { p as isArcError } from "./errors-NoQKsbAT.mjs";
3
3
  import fp from "fastify-plugin";
4
4
  //#region src/plugins/errorHandler.ts
5
- var errorHandler_exports = /* @__PURE__ */ __exportAll({
6
- default: () => errorHandlerPlugin,
7
- errorHandlerPlugin: () => errorHandlerPlugin
8
- });
5
+ var errorHandler_exports = /* @__PURE__ */ __exportAll({ errorHandlerPlugin: () => errorHandlerPlugin });
9
6
  async function errorHandlerPluginFn(fastify, options = {}) {
10
7
  const isProduction = process.env.NODE_ENV === "production";
11
8
  const { includeStack = !isProduction, onError, errorMap = {} } = options;
@@ -42,6 +39,9 @@ async function errorHandlerPluginFn(fastify, options = {}) {
42
39
  } else if ("statusCode" in error && typeof error.statusCode === "number") {
43
40
  statusCode = error.statusCode;
44
41
  response.code = statusCodeToCode(statusCode);
42
+ } else if ("status" in error && typeof error.status === "number") {
43
+ statusCode = error.status;
44
+ response.code = statusCodeToCode(statusCode);
45
45
  } else if (error.name && errorMap[error.name]) {
46
46
  const mapping = errorMap[error.name];
47
47
  statusCode = mapping.statusCode;
@@ -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, createError as f, ForbiddenError as i, ServiceUnavailableError as l, ConflictError as n, OrgAccessDeniedError as o, isArcError as p, ErrorDetails as r, OrgRequiredError as s, ArcError as t, UnauthorizedError as u };
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, createError as d, isArcError as f, NotFoundError as i, UnauthorizedError as l, ConflictError as n, OrgRequiredError as o, ForbiddenError as r, RateLimitError as s, ArcError as t, ValidationError as u };
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, EventRegistry as c, createEventRegistry as d, defineEvent as f, createDeadLetterPublisher as i, EventSchema as l, eventPlugin as n, EventDefinitionInput as o, RetryOptions as r, EventDefinitionOutput as s, EventPluginOptions as t, ValidationResult as u };
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 };
@@ -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 EventRegistry, d as createEventRegistry, f as defineEvent, i as createDeadLetterPublisher, l as EventSchema, n as eventPlugin, o as EventDefinitionInput, r as RetryOptions, s as EventDefinitionOutput, t as EventPluginOptions, u as ValidationResult } from "../eventPlugin-iGrSEmwJ.mjs";
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 };
@@ -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
- * Schema validation uses a minimal JSON Schema subset (type, required, properties)
12
- * to avoid pulling in AJV as a dependency. For full JSON Schema validation,
13
- * users can provide their own validator via the registry.
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
- * Minimal JSON Schema validator — handles the common subset:
115
- * - type: 'object'
116
- * - required: string[]
117
- * - properties with type checks
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 JSON Schema validation (patterns, formats, $ref, etc.),
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 = [];
@@ -73,4 +73,4 @@ declare class RedisEventTransport implements EventTransport {
73
73
  private isGlob;
74
74
  }
75
75
  //#endregion
76
- export { RedisEventTransport, RedisEventTransport as default, RedisEventTransportOptions, RedisLike };
76
+ export { RedisEventTransport, RedisEventTransportOptions, RedisLike };
@@ -120,4 +120,4 @@ var RedisEventTransport = class {
120
120
  }
121
121
  };
122
122
  //#endregion
123
- export { RedisEventTransport, RedisEventTransport as default };
123
+ export { RedisEventTransport };
@@ -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-Dt0-AI6E.mjs";
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
- * Security plugins are enabled by default (opt-out):
9
- * - helmet (security headers)
10
- * - cors (cross-origin requests)
11
- * - rateLimit (DDoS protection)
12
- * - underPressure (health monitoring)
13
- *
14
- * Note: Compression is not included due to known Fastify 5 issues.
15
- * Use a reverse proxy (Nginx, Caddy) or CDN for compression.
16
- *
17
- * @param options - Application configuration
18
- * @returns Configured Fastify instance
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 };