@classytic/arc 2.7.3 → 2.7.7

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.
@@ -5,8 +5,9 @@ var HookSystem = class {
5
5
  warn;
6
6
  constructor(options) {
7
7
  this.hooks = /* @__PURE__ */ new Map();
8
- this.logger = options?.logger ?? { error: (...args) => console.error(...args) };
9
- this.warn = options?.logger?.warn ?? ((...args) => console.warn(...args));
8
+ const noop = () => {};
9
+ this.logger = options?.logger ?? { error: noop };
10
+ this.warn = options?.logger?.warn ?? noop;
10
11
  }
11
12
  /**
12
13
  * Generate hook key
@@ -1,7 +1,7 @@
1
1
  import { n as normalizeRoles, t as getUserRoles } from "../types-ZUu_h0jp.mjs";
2
2
  import { t as ArcError } from "../errors-Cg58SLNi.mjs";
3
3
  import { h as requireTeamMembership, l as requireOrgMembership, u as requireOrgRole } from "../permissions-CH4cNwJi.mjs";
4
- import { n as extractBetterAuthOpenApi } from "../betterAuthOpenApi-CCw3YX0g.mjs";
4
+ import { n as extractBetterAuthOpenApi } from "../betterAuthOpenApi-EkPaMWNM.mjs";
5
5
  import { createHmac, randomUUID, timingSafeEqual } from "node:crypto";
6
6
  import fp from "fastify-plugin";
7
7
  //#region src/auth/authPlugin.ts
@@ -677,7 +677,7 @@ function createBetterAuthAdapter(options) {
677
677
  if (!fastify.hasDecorator("authenticate")) fastify.decorate("authenticate", authenticate);
678
678
  if (!fastify.hasDecorator("optionalAuthenticate")) fastify.decorate("optionalAuthenticate", optionalAuthenticate);
679
679
  if (!extractedOpenApi && openapiOpt !== false && auth.api && typeof auth.api === "object") {
680
- const { extractBetterAuthOpenApi } = await import("../betterAuthOpenApi-CCw3YX0g.mjs").then((n) => n.t);
680
+ const { extractBetterAuthOpenApi } = await import("../betterAuthOpenApi-EkPaMWNM.mjs").then((n) => n.t);
681
681
  extractedOpenApi = extractBetterAuthOpenApi(auth.api, {
682
682
  basePath,
683
683
  userFields
@@ -1,5 +1,5 @@
1
1
  import { t as __exportAll } from "./chunk-BpYLSNr0.mjs";
2
- import { a as toJsonSchema } from "./schemaConverter-0TyONAwM.mjs";
2
+ import { a as toJsonSchema } from "./schemaConverter-Y5EejTnJ.mjs";
3
3
  //#region src/auth/betterAuthOpenApi.ts
4
4
  var betterAuthOpenApi_exports = /* @__PURE__ */ __exportAll({ extractBetterAuthOpenApi: () => extractBetterAuthOpenApi });
5
5
  /**
@@ -1,5 +1,5 @@
1
1
  import { t as ResourceRegistry } from "../../ResourceRegistry-DsHiG9cL.mjs";
2
- import { t as buildOpenApiSpec } from "../../openapi-BBSTVcMm.mjs";
2
+ import { t as buildOpenApiSpec } from "../../openapi-D7Z7VODz.mjs";
3
3
  import { dirname, resolve } from "node:path";
4
4
  import { pathToFileURL } from "node:url";
5
5
  import { mkdirSync, writeFileSync } from "node:fs";
@@ -1,5 +1,5 @@
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-Cxde4rpC.mjs";
2
2
  import { i as AccessControl, n as QueryResolver, r as BodySanitizer, t as BaseController } from "../BaseController-CpMfCXdn.mjs";
3
- import { n as createActionRouter, t as defineResourceVariants } from "../core-BWekSEju.mjs";
4
- import { c as createCrudHandlers, d as getControllerContext, f as getControllerScope, l as createFastifyHandler, n as defineResource, o as createCrudRouter, p as sendControllerResponse, s as createPermissionMiddleware, t as ResourceDefinition, u as createRequestContext } from "../defineResource-DZzyl4a4.mjs";
3
+ import { n as createActionRouter, t as defineResourceVariants } from "../core-B_zEeA2b.mjs";
4
+ import { c as createCrudHandlers, d as getControllerContext, f as getControllerScope, l as createFastifyHandler, n as defineResource, o as createCrudRouter, p as sendControllerResponse, s as createPermissionMiddleware, t as ResourceDefinition, u as createRequestContext } from "../defineResource-BW2dMCu9.mjs";
5
5
  export { AccessControl, BaseController, BodySanitizer, CRUD_OPERATIONS, DEFAULT_ID_FIELD, DEFAULT_LIMIT, DEFAULT_MAX_LIMIT, DEFAULT_SORT, DEFAULT_TENANT_FIELD, DEFAULT_UPDATE_METHOD, HOOK_OPERATIONS, HOOK_PHASES, MAX_FILTER_DEPTH, MAX_REGEX_LENGTH, MAX_SEARCH_LENGTH, MUTATION_OPERATIONS, QueryResolver, RESERVED_QUERY_PARAMS, ResourceDefinition, SYSTEM_FIELDS, createActionRouter, createCrudHandlers, createCrudRouter, createFastifyHandler, createPermissionMiddleware, createRequestContext, defineResource, defineResourceVariants, getControllerContext, getControllerScope, sendControllerResponse };
@@ -1,5 +1,5 @@
1
1
  import { n as normalizePermissionResult, t as applyPermissionResult } from "./applyPermissionResult-D6GPMsvh.mjs";
2
- import { n as defineResource } from "./defineResource-DZzyl4a4.mjs";
2
+ import { n as defineResource } from "./defineResource-BW2dMCu9.mjs";
3
3
  //#region src/core/createActionRouter.ts
4
4
  /**
5
5
  * Create action-based state transition endpoint
@@ -7,7 +7,7 @@ import { n as normalizePermissionResult, t as applyPermissionResult } from "./ap
7
7
  import { t as requestContext } from "./requestContext-xHIKedG6.mjs";
8
8
  import { i as getDefaultCrudSchemas } from "./utils-B-l6410F.mjs";
9
9
  import { r as ForbiddenError } from "./errors-Cg58SLNi.mjs";
10
- import { n as convertRouteSchema, t as convertOpenApiSchemas } from "./schemaConverter-0TyONAwM.mjs";
10
+ import { n as convertRouteSchema, t as convertOpenApiSchemas } from "./schemaConverter-Y5EejTnJ.mjs";
11
11
  import { t as hasEvents } from "./typeGuards-CcFZXgU7.mjs";
12
12
  import { r as getAvailablePresets, t as applyPresets } from "./presets-BFrGvvjL.mjs";
13
13
  //#region src/pipeline/pipe.ts
@@ -869,11 +869,6 @@ function assertValidConfig(config, options) {
869
869
  const errorMsg = formatValidationErrors(config.name ?? "unknown", result);
870
870
  throw new Error(errorMsg);
871
871
  }
872
- if (result.warnings.length > 0 && process.env.NODE_ENV !== "production") console.warn(formatValidationErrors(config.name ?? "unknown", {
873
- valid: true,
874
- errors: [],
875
- warnings: result.warnings
876
- }));
877
872
  }
878
873
  //#endregion
879
874
  //#region src/core/defineResource.ts
@@ -1,5 +1,5 @@
1
1
  import { t as getUserRoles } from "../types-ZUu_h0jp.mjs";
2
- import { n as openApiPlugin, r as openapi_default, t as buildOpenApiSpec } from "../openapi-BBSTVcMm.mjs";
2
+ import { n as openApiPlugin, r as openapi_default, t as buildOpenApiSpec } from "../openapi-D7Z7VODz.mjs";
3
3
  import fp from "fastify-plugin";
4
4
  //#region src/docs/scalar.ts
5
5
  const scalarPlugin = async (fastify, opts = {}) => {
@@ -1,5 +1,5 @@
1
1
  import { t as ArcQueryParser } from "../queryParser-CgCtsjti.mjs";
2
- import { n as defineResource } from "../defineResource-DZzyl4a4.mjs";
2
+ import { n as defineResource } from "../defineResource-BW2dMCu9.mjs";
3
3
  import { C as publicRead, T as readOnly, b as fullPublic, v as adminOnly, w as publicReadAdminWrite, x as ownerWithAdminBypass, y as authenticated } from "../permissions-CH4cNwJi.mjs";
4
4
  //#region src/dynamic/ArcDynamicLoader.ts
5
5
  const VALID_FIELD_TYPES = new Set([
@@ -1,4 +1,4 @@
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-2FlNl0mL.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-CKB47kiu.mjs";
2
2
  import { FastifyInstance } from "fastify";
3
3
 
4
4
  //#region src/factory/createApp.d.ts
@@ -163,14 +163,15 @@ async function loadResources(dir, options = {}) {
163
163
  }
164
164
  resources.push(resource);
165
165
  }
166
- if (!silent) {
166
+ const log = silent ? void 0 : options?.logger;
167
+ if (log) {
167
168
  if (failed.length) {
168
- console.warn(`[arc] loadResources: ${failed.length} file(s) failed to import:`);
169
- for (const f of failed) console.warn(` - ${f}`);
169
+ log.warn(`[arc] loadResources: ${failed.length} file(s) failed to import:`);
170
+ for (const f of failed) log.warn(` - ${f}`);
170
171
  }
171
172
  if (skipped.length) {
172
- console.warn(`[arc] loadResources: ${skipped.length} file(s) skipped (no default export with toPlugin):`);
173
- for (const f of skipped) console.warn(` - ${f}`);
173
+ log.warn(`[arc] loadResources: ${skipped.length} file(s) skipped (no default export with toPlugin):`);
174
+ for (const f of skipped) log.warn(` - ${f}`);
174
175
  }
175
176
  }
176
177
  return resources;
@@ -1,2 +1,2 @@
1
- import { a as beforeCreate, c as createHookSystem, i as afterUpdate, l as defineHook, n as afterCreate, o as beforeDelete, r as afterDelete, s as beforeUpdate, t as HookSystem } from "../HookSystem-D7lfx--K.mjs";
1
+ import { a as beforeCreate, c as createHookSystem, i as afterUpdate, l as defineHook, n as afterCreate, o as beforeDelete, r as afterDelete, s as beforeUpdate, t as HookSystem } from "../HookSystem-BNYKnrXF.mjs";
2
2
  export { HookSystem, afterCreate, afterDelete, afterUpdate, beforeCreate, beforeDelete, beforeUpdate, createHookSystem, defineHook };
@@ -9,9 +9,7 @@ var MongoIdempotencyStore = class {
9
9
  this.connection = options.connection;
10
10
  this.collectionName = options.collection ?? "arc_idempotency";
11
11
  this.ttlMs = options.ttlMs ?? 864e5;
12
- if (options.createIndex !== false) this.ensureIndex().catch((err) => {
13
- console.warn("[MongoIdempotencyStore] Failed to create index:", err);
14
- });
12
+ if (options.createIndex !== false) this.ensureIndex().catch(() => {});
15
13
  }
16
14
  get collection() {
17
15
  return this.connection.db.collection(this.collectionName);
package/dist/index.mjs CHANGED
@@ -3,10 +3,10 @@ import { a as createMongooseAdapter, i as MongooseAdapter, r as createPrismaAdap
3
3
  import { t as BaseController } from "./BaseController-CpMfCXdn.mjs";
4
4
  import { envelope } from "./types/index.mjs";
5
5
  import { n as applyFieldWritePermissions, r as fields, t as applyFieldReadPermissions } from "./fields-ipsbIRPK.mjs";
6
- import { t as defineResourceVariants } from "./core-BWekSEju.mjs";
6
+ import { t as defineResourceVariants } from "./core-B_zEeA2b.mjs";
7
7
  import { t as requestContext } from "./requestContext-xHIKedG6.mjs";
8
8
  import { d as createDomainError, i as NotFoundError, l as UnauthorizedError, r as ForbiddenError, t as ArcError, u as ValidationError } from "./errors-Cg58SLNi.mjs";
9
- import { a as validateResourceConfig, f as getControllerScope, i as formatValidationErrors, m as pipe, n as defineResource, r as assertValidConfig, t as ResourceDefinition } from "./defineResource-DZzyl4a4.mjs";
9
+ import { a as validateResourceConfig, f as getControllerScope, i as formatValidationErrors, m as pipe, n as defineResource, r as assertValidConfig, t as ResourceDefinition } from "./defineResource-BW2dMCu9.mjs";
10
10
  import { C as publicRead, S as presets_exports, T as readOnly, _ as when, a as createOrgPermissions, b as fullPublic, c as requireOrgInScope, d as requireOwnership, f as requireRoles, h as requireTeamMembership, i as createDynamicPermissionMatrix, l as requireOrgMembership, m as requireServiceScope, n as allowPublic, o as denyAll, p as requireScopeContext, r as anyOf, s as requireAuth, t as allOf, u as requireOrgRole, v as adminOnly, w as publicReadAdminWrite, x as ownerWithAdminBypass, y as authenticated } from "./permissions-CH4cNwJi.mjs";
11
11
  import { n as configureArcLogger, t as arcLog } from "./logger-DLg8-Ueg.mjs";
12
12
  //#region src/middleware/middleware.ts
@@ -128,6 +128,6 @@ function transform(name, handlerOrOptions) {
128
128
  }
129
129
  //#endregion
130
130
  //#region src/index.ts
131
- const version = "2.7.3";
131
+ const version = "2.7.7";
132
132
  //#endregion
133
133
  export { ArcError, BaseController, CRUD_OPERATIONS, DEFAULT_ID_FIELD, DEFAULT_LIMIT, DEFAULT_MAX_LIMIT, DEFAULT_SORT, DEFAULT_TENANT_FIELD, DEFAULT_UPDATE_METHOD, ForbiddenError, HOOK_OPERATIONS, HOOK_PHASES, MAX_FILTER_DEPTH, MAX_REGEX_LENGTH, MAX_SEARCH_LENGTH, MUTATION_OPERATIONS, MongooseAdapter, NotFoundError, PrismaAdapter, RESERVED_QUERY_PARAMS, ResourceDefinition, SYSTEM_FIELDS, UnauthorizedError, ValidationError, adminOnly, allOf, allowPublic, anyOf, applyFieldReadPermissions, applyFieldWritePermissions, arcLog, assertValidConfig, authenticated, configureArcLogger, createDomainError, createDynamicPermissionMatrix, createMongooseAdapter, createOrgPermissions, createPrismaAdapter, defineResource, defineResourceVariants, denyAll, envelope, fields, formatValidationErrors, fullPublic, getControllerScope, guard, intercept, middleware, ownerWithAdminBypass, presets_exports as permissions, pipe, publicRead, publicReadAdminWrite, readOnly, requestContext, requireAuth, requireOrgInScope, requireOrgMembership, requireOrgRole, requireOwnership, requireRoles, requireScopeContext, requireServiceScope, requireTeamMembership, sortMiddlewares, transform, validateResourceConfig, version, when };
@@ -1,28 +1,44 @@
1
1
  import { FastifyPluginAsync } from "fastify";
2
2
 
3
3
  //#region src/integrations/streamline.d.ts
4
+ /** Start options — matches @classytic/streamline v2.1 StartOptions */
5
+ interface WorkflowStartOptions {
6
+ meta?: Record<string, unknown>;
7
+ idempotencyKey?: string;
8
+ priority?: number;
9
+ }
4
10
  /** Minimal workflow interface — matches @classytic/streamline's createWorkflow() return */
5
11
  interface WorkflowLike {
6
12
  definition: {
7
13
  id: string;
8
14
  name?: string;
9
- steps: Record<string, unknown>;
15
+ steps: Record<string, unknown> | unknown[];
10
16
  };
11
17
  engine: {
12
- start(input: unknown, meta?: unknown): Promise<WorkflowRunLike>;
18
+ start(input: unknown, options?: WorkflowStartOptions): Promise<WorkflowRunLike>;
13
19
  execute(runId: string): Promise<WorkflowRunLike>;
14
20
  resume(runId: string, payload?: unknown): Promise<WorkflowRunLike>;
15
21
  cancel(runId: string): Promise<WorkflowRunLike>;
16
22
  pause?(runId: string): Promise<WorkflowRunLike>;
17
23
  rewindTo?(runId: string, stepId: string): Promise<WorkflowRunLike>;
18
24
  get(runId: string): Promise<WorkflowRunLike | null>;
25
+ waitFor?(runId: string, options?: {
26
+ timeout?: number;
27
+ }): Promise<WorkflowRunLike>;
19
28
  shutdown?(): void;
20
29
  };
21
- start(input: unknown, meta?: unknown): Promise<WorkflowRunLike>;
30
+ start(input: unknown, options?: WorkflowStartOptions): Promise<WorkflowRunLike>;
22
31
  resume(runId: string, payload?: unknown): Promise<WorkflowRunLike>;
23
32
  cancel(runId: string): Promise<WorkflowRunLike>;
24
33
  get(runId: string): Promise<WorkflowRunLike | null>;
25
34
  shutdown?(): void;
35
+ /** Streamline container for event bridging (streamline >=2.1) */
36
+ container?: {
37
+ eventBus: {
38
+ on(event: string, listener: (...args: unknown[]) => void): void;
39
+ off(event: string, listener: (...args: unknown[]) => void): void;
40
+ };
41
+ };
26
42
  }
27
43
  interface WorkflowRunLike {
28
44
  _id: string;
@@ -30,11 +46,14 @@ interface WorkflowRunLike {
30
46
  status: string;
31
47
  context?: unknown;
32
48
  input?: unknown;
33
- steps?: Record<string, unknown>;
49
+ steps?: unknown[];
34
50
  error?: unknown;
51
+ idempotencyKey?: string;
52
+ priority?: number;
53
+ concurrencyKey?: string;
54
+ stepLogs?: unknown[];
35
55
  createdAt?: Date;
36
56
  updatedAt?: Date;
37
- [key: string]: unknown;
38
57
  }
39
58
  interface StreamlinePluginOptions {
40
59
  /** Array of workflows created with createWorkflow() */
@@ -43,8 +62,21 @@ interface StreamlinePluginOptions {
43
62
  prefix?: string;
44
63
  /** Require authentication for all workflow endpoints (default: true) */
45
64
  auth?: boolean;
46
- /** Connect workflow events to Arc's event bus (default: true) */
65
+ /** Connect workflow lifecycle events to Arc's event bus (default: true) */
47
66
  bridgeEvents?: boolean;
67
+ /**
68
+ * Bridge step-level events (step:started, step:completed, step:failed) to Arc's event bus.
69
+ * Disabled by default — enable for dashboards or monitoring.
70
+ * Requires the workflow to expose `container.eventBus` (streamline >=2.1).
71
+ * @default false
72
+ */
73
+ bridgeStepEvents?: boolean;
74
+ /**
75
+ * Enable SSE streaming endpoint: GET /:workflowId/runs/:runId/stream
76
+ * Streams step-level events as Server-Sent Events for live UI updates.
77
+ * @default false
78
+ */
79
+ enableStreaming?: boolean;
48
80
  /** Custom permission check for workflow operations */
49
81
  permissions?: {
50
82
  start?: (request: unknown) => boolean | Promise<boolean>;
@@ -57,4 +89,4 @@ interface StreamlinePluginOptions {
57
89
  /** Pluggable streamline integration for Arc */
58
90
  declare const streamlinePlugin: FastifyPluginAsync<StreamlinePluginOptions>;
59
91
  //#endregion
60
- export { StreamlinePluginOptions, WorkflowLike, WorkflowRunLike, streamlinePlugin };
92
+ export { StreamlinePluginOptions, WorkflowLike, WorkflowRunLike, WorkflowStartOptions, streamlinePlugin };
@@ -1,6 +1,6 @@
1
1
  //#region src/integrations/streamline.ts
2
2
  const streamlinePluginImpl = async (fastify, options) => {
3
- const { workflows, prefix = "/workflows", auth = true, bridgeEvents = true, permissions: perms } = options;
3
+ const { workflows, prefix = "/workflows", auth = true, bridgeEvents = true, bridgeStepEvents = false, enableStreaming = false, permissions: perms } = options;
4
4
  const registry = /* @__PURE__ */ new Map();
5
5
  for (const wf of workflows) {
6
6
  const id = wf.definition.id;
@@ -22,8 +22,12 @@ const streamlinePluginImpl = async (fastify, options) => {
22
22
  success: false,
23
23
  error: "Forbidden"
24
24
  });
25
- const { input, meta } = request.body ?? {};
26
- const run = await wf.start(input, meta);
25
+ const { input, meta, idempotencyKey, priority } = request.body ?? {};
26
+ const run = await wf.start(input, {
27
+ meta,
28
+ idempotencyKey,
29
+ priority
30
+ });
27
31
  if (bridgeEvents && fastify.events?.publish) try {
28
32
  await fastify.events.publish(`workflow.${id}.started`, {
29
33
  runId: run._id,
@@ -105,6 +109,26 @@ const streamlinePluginImpl = async (fastify, options) => {
105
109
  data: run
106
110
  };
107
111
  });
112
+ fastify.post(`${routePrefix}/runs/:runId/execute`, { preHandler: authPreHandler }, async (request, _reply) => {
113
+ const { runId } = request.params;
114
+ return {
115
+ success: true,
116
+ data: await wf.engine.execute(runId)
117
+ };
118
+ });
119
+ if (wf.engine.waitFor) fastify.get(`${routePrefix}/runs/:runId/wait`, { preHandler: authPreHandler }, async (request, reply) => {
120
+ if (!await checkPerm("get", request)) return reply.status(403).send({
121
+ success: false,
122
+ error: "Forbidden"
123
+ });
124
+ const { runId } = request.params;
125
+ const { timeout } = request.query ?? {};
126
+ const timeoutMs = timeout ? Number.parseInt(timeout, 10) : 3e4;
127
+ return {
128
+ success: true,
129
+ data: await wf.engine.waitFor(runId, { timeout: Math.min(timeoutMs, 12e4) })
130
+ };
131
+ });
108
132
  if (wf.engine.pause) fastify.post(`${routePrefix}/runs/:runId/pause`, { preHandler: authPreHandler }, async (request, _reply) => {
109
133
  const { runId } = request.params;
110
134
  return {
@@ -124,6 +148,84 @@ const streamlinePluginImpl = async (fastify, options) => {
124
148
  data: await wf.engine.rewindTo?.(runId, stepId)
125
149
  };
126
150
  });
151
+ if (bridgeStepEvents && wf.container?.eventBus && fastify.events?.publish) for (const eventName of [
152
+ "step:started",
153
+ "step:completed",
154
+ "step:failed",
155
+ "step:skipped",
156
+ "step:retry-scheduled"
157
+ ]) wf.container.eventBus.on(eventName, (payload) => {
158
+ const p = payload;
159
+ fastify.events.publish(`workflow.${id}.${eventName}`, {
160
+ runId: p?.runId,
161
+ stepId: p?.stepId,
162
+ workflowId: id,
163
+ ...p
164
+ }).catch((err) => {
165
+ fastify.log.warn({
166
+ err,
167
+ workflowId: id
168
+ }, `Failed to bridge ${eventName}`);
169
+ });
170
+ });
171
+ if (enableStreaming && wf.container?.eventBus) fastify.get(`${routePrefix}/runs/:runId/stream`, { preHandler: authPreHandler }, async (request, reply) => {
172
+ if (!await checkPerm("get", request)) return reply.status(403).send({
173
+ success: false,
174
+ error: "Forbidden"
175
+ });
176
+ const { runId } = request.params;
177
+ if (!await wf.get(runId)) return reply.status(404).send({
178
+ success: false,
179
+ error: "Workflow run not found"
180
+ });
181
+ reply.raw.writeHead(200, {
182
+ "Content-Type": "text/event-stream",
183
+ "Cache-Control": "no-cache",
184
+ Connection: "keep-alive"
185
+ });
186
+ const events = [
187
+ "step:started",
188
+ "step:completed",
189
+ "step:failed",
190
+ "step:skipped",
191
+ "workflow:completed",
192
+ "workflow:failed",
193
+ "workflow:cancelled"
194
+ ];
195
+ const listeners = [];
196
+ let closed = false;
197
+ const send = (event, data) => {
198
+ if (closed) return;
199
+ try {
200
+ reply.raw.write(`event: ${event}\ndata: ${JSON.stringify(data)}\n\n`);
201
+ } catch {
202
+ cleanup();
203
+ }
204
+ };
205
+ const cleanup = () => {
206
+ if (closed) return;
207
+ closed = true;
208
+ for (const { event, fn } of listeners) wf.container?.eventBus.off(event, fn);
209
+ listeners.length = 0;
210
+ try {
211
+ reply.raw.end();
212
+ } catch {}
213
+ };
214
+ for (const eventName of events) {
215
+ const fn = (payload) => {
216
+ const p = payload;
217
+ if (p?.runId !== runId) return;
218
+ send(eventName, p);
219
+ if (eventName === "workflow:completed" || eventName === "workflow:failed" || eventName === "workflow:cancelled") cleanup();
220
+ };
221
+ wf.container.eventBus.on(eventName, fn);
222
+ listeners.push({
223
+ event: eventName,
224
+ fn
225
+ });
226
+ }
227
+ request.raw.on("close", cleanup);
228
+ });
127
229
  }
128
230
  fastify.get(prefix, { preHandler: authPreHandler }, async () => {
129
231
  return {
@@ -131,7 +233,7 @@ const streamlinePluginImpl = async (fastify, options) => {
131
233
  data: Array.from(registry.entries()).map(([id, wf]) => ({
132
234
  id,
133
235
  name: wf.definition.name ?? id,
134
- steps: Object.keys(wf.definition.steps)
236
+ steps: Array.isArray(wf.definition.steps) ? wf.definition.steps.map((s) => s.id ?? String(s)) : Object.keys(wf.definition.steps)
135
237
  }))
136
238
  };
137
239
  });
@@ -1,5 +1,5 @@
1
1
  import { t as getUserRoles } from "./types-ZUu_h0jp.mjs";
2
- import { n as convertRouteSchema } from "./schemaConverter-0TyONAwM.mjs";
2
+ import { n as convertRouteSchema } from "./schemaConverter-Y5EejTnJ.mjs";
3
3
  import fp from "fastify-plugin";
4
4
  //#region src/docs/openapi.ts
5
5
  const openApiPlugin = async (fastify, opts = {}) => {
@@ -2,7 +2,7 @@ import { p as MUTATION_OPERATIONS } from "../constants-Cxde4rpC.mjs";
2
2
  import { o as getOrgId } from "../types-AOD8fxIw.mjs";
3
3
  import { t as requestContext } from "../requestContext-xHIKedG6.mjs";
4
4
  import { t as hasEvents } from "../typeGuards-CcFZXgU7.mjs";
5
- import { t as HookSystem } from "../HookSystem-D7lfx--K.mjs";
5
+ import { t as HookSystem } from "../HookSystem-BNYKnrXF.mjs";
6
6
  import { t as ResourceRegistry } from "../ResourceRegistry-DsHiG9cL.mjs";
7
7
  import { n as caching_default, t as cachingPlugin } from "../caching-5DtLwIqb.mjs";
8
8
  import { t as errorHandlerPlugin } from "../errorHandler-CH8wk1eD.mjs";
@@ -44,7 +44,7 @@ try {
44
44
  function createTracerProvider(options) {
45
45
  if (!isAvailable) return null;
46
46
  const { serviceName = "@classytic/arc", serviceVersion, exporterUrl = "http://localhost:4318/v1/traces" } = options;
47
- const resolvedVersion = serviceVersion ?? "2.7.3";
47
+ const resolvedVersion = serviceVersion ?? "2.7.7";
48
48
  const exporter = new OTLPTraceExporter({ url: exporterUrl });
49
49
  const provider = new NodeTracerProvider({ resource: { attributes: {
50
50
  "service.name": serviceName,
@@ -34,10 +34,7 @@ function toJsonSchema(input) {
34
34
  if (typeof input !== "object") return void 0;
35
35
  if (isJsonSchema(input)) return input;
36
36
  if (isZodSchema(input)) {
37
- if (!_toJSONSchema) {
38
- console.warn("[Arc] Zod schema detected but zod is not installed. Install zod v4: npm install zod");
39
- return input;
40
- }
37
+ if (!_toJSONSchema) return input;
41
38
  try {
42
39
  return _toJSONSchema(input, { target: "openapi-3.0" });
43
40
  } catch {
@@ -1,5 +1,5 @@
1
1
  import { Ut as CrudRepository, Vt as ResourceDefinition, u as AnyRecord } from "../interface-B91alUzq.mjs";
2
- import { d as ResourceLike, r as CreateAppOptions } from "../types-2FlNl0mL.mjs";
2
+ import { d as ResourceLike, r as CreateAppOptions } from "../types-CKB47kiu.mjs";
3
3
  import Fastify, { FastifyInstance, FastifyServerOptions } from "fastify";
4
4
  import { Connection } from "mongoose";
5
5
  import { Mock } from "vitest";
@@ -107,6 +107,10 @@ interface LoadResourcesOptions {
107
107
  * @default false
108
108
  */
109
109
  silent?: boolean;
110
+ /** Optional logger for diagnostics. No output when omitted (silent by default). */
111
+ logger?: {
112
+ warn: (msg: string) => void;
113
+ };
110
114
  }
111
115
  /**
112
116
  * Scan a directory for resource files and import their default exports.
@@ -2,6 +2,6 @@ import { n as createQueryParser, t as ArcQueryParser } from "../queryParser-CgCt
2
2
  import { a as createCircuitBreaker, i as CircuitState, n as CircuitBreakerError, o as createCircuitBreakerRegistry, r as CircuitBreakerRegistry, t as CircuitBreaker } from "../circuitBreaker-l18oRgL5.mjs";
3
3
  import { _ as defineCompensation, a as getListQueryParams, c as listResponse, d as paginateWrapper, f as paginationSchema, g as wrapResponse, h as successResponseSchema, i as getDefaultCrudSchemas, l as messageWrapper, m as responses, n as deleteResponse, o as itemResponse, p as queryParams, r as errorResponseSchema, s as itemWrapper, t as createStateMachine, u as mutationResponse, v as withCompensation } from "../utils-B-l6410F.mjs";
4
4
  import { a as OrgAccessDeniedError, c as ServiceUnavailableError, f as createError, i as NotFoundError, l as UnauthorizedError, n as ConflictError, o as OrgRequiredError, p as isArcError, r as ForbiddenError, s as RateLimitError, t as ArcError, u as ValidationError } from "../errors-Cg58SLNi.mjs";
5
- import { a as toJsonSchema, i as isZodSchema, n as convertRouteSchema, r as isJsonSchema, t as convertOpenApiSchemas } from "../schemaConverter-0TyONAwM.mjs";
5
+ import { a as toJsonSchema, i as isZodSchema, n as convertRouteSchema, r as isJsonSchema, t as convertOpenApiSchemas } from "../schemaConverter-Y5EejTnJ.mjs";
6
6
  import { t as hasEvents } from "../typeGuards-CcFZXgU7.mjs";
7
7
  export { ArcError, ArcQueryParser, CircuitBreaker, CircuitBreakerError, CircuitBreakerRegistry, CircuitState, ConflictError, ForbiddenError, NotFoundError, OrgAccessDeniedError, OrgRequiredError, RateLimitError, ServiceUnavailableError, UnauthorizedError, ValidationError, convertOpenApiSchemas, convertRouteSchema, createCircuitBreaker, createCircuitBreakerRegistry, createError, createQueryParser, createStateMachine, defineCompensation, deleteResponse, errorResponseSchema, getDefaultCrudSchemas, getListQueryParams, hasEvents, isArcError, isJsonSchema, isZodSchema, itemResponse, itemWrapper, listResponse, messageWrapper, mutationResponse, paginateWrapper, paginationSchema, queryParams, responses, successResponseSchema, toJsonSchema, withCompensation, wrapResponse };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@classytic/arc",
3
- "version": "2.7.3",
3
+ "version": "2.7.7",
4
4
  "description": "Resource-oriented backend framework for Fastify — clean, minimal, powerful, tree-shakable",
5
5
  "type": "module",
6
6
  "exports": {
@@ -221,14 +221,14 @@
221
221
  "test:e2e": "vitest run tests/e2e",
222
222
  "test:unit": "vitest run tests/core tests/hooks tests/utils tests/plugins",
223
223
  "smoke": "node scripts/smoke-test.mjs",
224
- "prepublishOnly": "npm run typecheck && npm run test:ci && npm run build && npm run smoke"
224
+ "prepublishOnly": "npm run typecheck && npm run lint && npm run build && npm run test:ci && npm run smoke"
225
225
  },
226
226
  "engines": {
227
227
  "node": ">=22"
228
228
  },
229
229
  "peerDependencies": {
230
- "@classytic/mongokit": ">=3.5.5",
231
- "@classytic/streamline": ">=2.0.0",
230
+ "@classytic/mongokit": ">=3.5.6",
231
+ "@classytic/streamline": ">=2.1.0",
232
232
  "@fastify/cors": ">=11.0.0",
233
233
  "@fastify/helmet": ">=13.0.0",
234
234
  "@fastify/jwt": ">=10.0.0",
@@ -346,7 +346,8 @@
346
346
  "devDependencies": {
347
347
  "@better-auth/mongo-adapter": "^1.6.0",
348
348
  "@biomejs/biome": "^2.4.10",
349
- "@classytic/mongokit": "^3.5.5",
349
+ "@classytic/mongokit": "3.5.6",
350
+ "@classytic/streamline": "^2.1.0",
350
351
  "@fastify/cors": "^11.2.0",
351
352
  "@fastify/helmet": "^13.0.2",
352
353
  "@fastify/jwt": "^10.0.0",