@classytic/arc 2.3.0 → 2.4.2

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 (175) hide show
  1. package/README.md +187 -18
  2. package/bin/arc.js +11 -3
  3. package/dist/BaseController-CkM5dUh_.mjs +1031 -0
  4. package/dist/{EventTransport-BkUDYZEb.d.mts → EventTransport-wc5hSLik.d.mts} +1 -1
  5. package/dist/{HookSystem-BsGV-j2l.mjs → HookSystem-COkyWztM.mjs} +2 -3
  6. package/dist/{ResourceRegistry-7Ic20ZMw.mjs → ResourceRegistry-DeCIFlix.mjs} +8 -5
  7. package/dist/adapters/index.d.mts +3 -5
  8. package/dist/adapters/index.mjs +2 -3
  9. package/dist/{prisma-DJbMt3yf.mjs → adapters-DTC4Ug66.mjs} +45 -12
  10. package/dist/audit/index.d.mts +4 -7
  11. package/dist/audit/index.mjs +2 -29
  12. package/dist/audit/mongodb.d.mts +1 -4
  13. package/dist/audit/mongodb.mjs +2 -3
  14. package/dist/auth/index.d.mts +7 -9
  15. package/dist/auth/index.mjs +65 -63
  16. package/dist/auth/redis-session.d.mts +1 -1
  17. package/dist/auth/redis-session.mjs +1 -2
  18. package/dist/{betterAuthOpenApi-DjWDddNc.mjs → betterAuthOpenApi-lz0IRbXJ.mjs} +4 -6
  19. package/dist/cache/index.d.mts +23 -23
  20. package/dist/cache/index.mjs +4 -6
  21. package/dist/{caching-GSDJcA6-.mjs → caching-BSXB-Xr7.mjs} +2 -24
  22. package/dist/chunk-BpYLSNr0.mjs +14 -0
  23. package/dist/circuitBreaker-BOBOpN2w.mjs +284 -0
  24. package/dist/circuitBreaker-JP2GdJ4b.d.mts +206 -0
  25. package/dist/cli/commands/describe.mjs +24 -7
  26. package/dist/cli/commands/docs.mjs +6 -7
  27. package/dist/cli/commands/doctor.d.mts +10 -0
  28. package/dist/cli/commands/doctor.mjs +156 -0
  29. package/dist/cli/commands/generate.mjs +66 -17
  30. package/dist/cli/commands/init.mjs +315 -45
  31. package/dist/cli/commands/introspect.mjs +2 -4
  32. package/dist/cli/index.d.mts +1 -10
  33. package/dist/cli/index.mjs +4 -153
  34. package/dist/{constants-DdXFXQtN.mjs → constants-Cxde4rpC.mjs} +1 -2
  35. package/dist/core/index.d.mts +3 -5
  36. package/dist/core/index.mjs +5 -4
  37. package/dist/core-C1XCMtqM.mjs +185 -0
  38. package/dist/{createApp-CgKOPhA4.mjs → createApp-ByWNRsZj.mjs} +64 -35
  39. package/dist/{defineResource-DWbpJYtm.mjs → defineResource-D9aY5Cy6.mjs} +108 -1157
  40. package/dist/discovery/index.mjs +37 -5
  41. package/dist/docs/index.d.mts +6 -9
  42. package/dist/docs/index.mjs +3 -21
  43. package/dist/dynamic/index.d.mts +93 -0
  44. package/dist/dynamic/index.mjs +122 -0
  45. package/dist/{elevation-DSTbVvYj.mjs → elevation-BEdACOLB.mjs} +5 -36
  46. package/dist/{elevation-DGo5shaX.d.mts → elevation-Ca_yveIO.d.mts} +41 -7
  47. package/dist/{errorHandler-C3GY3_ow.mjs → errorHandler--zp54tGc.mjs} +3 -5
  48. package/dist/errorHandler-Do4vVQ1f.d.mts +139 -0
  49. package/dist/{errors-DBANPbGr.mjs → errors-rxhfP7Hf.mjs} +1 -2
  50. package/dist/{eventPlugin-BEOvaDqo.mjs → eventPlugin-Ba00swHF.mjs} +25 -27
  51. package/dist/{eventPlugin-H6wDDjGO.d.mts → eventPlugin-iGrSEmwJ.d.mts} +105 -5
  52. package/dist/events/index.d.mts +72 -7
  53. package/dist/events/index.mjs +216 -4
  54. package/dist/events/transports/redis-stream-entry.d.mts +1 -1
  55. package/dist/events/transports/redis-stream-entry.mjs +19 -7
  56. package/dist/events/transports/redis.d.mts +1 -1
  57. package/dist/events/transports/redis.mjs +3 -4
  58. package/dist/factory/index.d.mts +23 -9
  59. package/dist/factory/index.mjs +48 -3
  60. package/dist/{fields-Bi_AVKSo.d.mts → fields-DFwdaWCq.d.mts} +1 -1
  61. package/dist/{fields-CTd_CrKr.mjs → fields-ipsbIRPK.mjs} +1 -2
  62. package/dist/hooks/index.d.mts +1 -3
  63. package/dist/hooks/index.mjs +2 -3
  64. package/dist/idempotency/index.d.mts +5 -5
  65. package/dist/idempotency/index.mjs +3 -7
  66. package/dist/idempotency/mongodb.d.mts +1 -1
  67. package/dist/idempotency/mongodb.mjs +4 -5
  68. package/dist/idempotency/redis.d.mts +1 -1
  69. package/dist/idempotency/redis.mjs +2 -5
  70. package/dist/{fastifyAdapter-6b_eRDBw.d.mts → index-BL8CaQih.d.mts} +56 -57
  71. package/dist/index-Diqcm14c.d.mts +369 -0
  72. package/dist/{prisma-Dy5S5F5i.d.mts → index-yhxyjqNb.d.mts} +4 -5
  73. package/dist/index.d.mts +100 -105
  74. package/dist/index.mjs +85 -58
  75. package/dist/integrations/event-gateway.d.mts +1 -1
  76. package/dist/integrations/event-gateway.mjs +8 -4
  77. package/dist/integrations/index.d.mts +4 -2
  78. package/dist/integrations/index.mjs +1 -1
  79. package/dist/integrations/jobs.d.mts +2 -2
  80. package/dist/integrations/jobs.mjs +63 -14
  81. package/dist/integrations/mcp/index.d.mts +219 -0
  82. package/dist/integrations/mcp/index.mjs +572 -0
  83. package/dist/integrations/mcp/testing.d.mts +53 -0
  84. package/dist/integrations/mcp/testing.mjs +104 -0
  85. package/dist/integrations/streamline.mjs +39 -19
  86. package/dist/integrations/webhooks.d.mts +56 -0
  87. package/dist/integrations/webhooks.mjs +139 -0
  88. package/dist/integrations/websocket-redis.d.mts +46 -0
  89. package/dist/integrations/websocket-redis.mjs +50 -0
  90. package/dist/integrations/websocket.d.mts +68 -2
  91. package/dist/integrations/websocket.mjs +96 -13
  92. package/dist/{interface-CSNjltAc.d.mts → interface-B4awm1RJ.d.mts} +2 -2
  93. package/dist/interface-DGmPxakH.d.mts +2213 -0
  94. package/dist/{keys-DhqDRxv3.mjs → keys-qcD-TVJl.mjs} +3 -4
  95. package/dist/{logger-ByrvQWZO.mjs → logger-Dz3j1ItV.mjs} +2 -4
  96. package/dist/{memory-B2v7KrCB.mjs → memory-Cb_7iy9e.mjs} +2 -4
  97. package/dist/metrics-Csh4nsvv.mjs +224 -0
  98. package/dist/migrations/index.d.mts +113 -44
  99. package/dist/migrations/index.mjs +84 -102
  100. package/dist/{mongodb-DNKEExbf.mjs → mongodb-BuQ7fNTg.mjs} +1 -4
  101. package/dist/{mongodb-ClykrfGo.d.mts → mongodb-CUpYfxfD.d.mts} +2 -3
  102. package/dist/{mongodb-Dg8O_gvd.d.mts → mongodb-bga9AbkD.d.mts} +2 -2
  103. package/dist/{openapi-9nB_kiuR.mjs → openapi-CBmZ6EQN.mjs} +4 -21
  104. package/dist/org/index.d.mts +12 -14
  105. package/dist/org/index.mjs +92 -119
  106. package/dist/org/types.d.mts +2 -2
  107. package/dist/org/types.mjs +1 -1
  108. package/dist/permissions/index.d.mts +4 -278
  109. package/dist/permissions/index.mjs +4 -579
  110. package/dist/permissions-CA5zg0yK.mjs +751 -0
  111. package/dist/plugins/index.d.mts +104 -107
  112. package/dist/plugins/index.mjs +203 -313
  113. package/dist/plugins/response-cache.mjs +4 -69
  114. package/dist/plugins/tracing-entry.d.mts +1 -1
  115. package/dist/plugins/tracing-entry.mjs +24 -11
  116. package/dist/{pluralize-CM-jZg7p.mjs → pluralize-CcT6qF0a.mjs} +12 -13
  117. package/dist/policies/index.d.mts +2 -2
  118. package/dist/policies/index.mjs +80 -83
  119. package/dist/presets/index.d.mts +26 -19
  120. package/dist/presets/index.mjs +2 -142
  121. package/dist/presets/multiTenant.d.mts +1 -4
  122. package/dist/presets/multiTenant.mjs +4 -6
  123. package/dist/presets-C9QXJV1u.mjs +422 -0
  124. package/dist/{queryCachePlugin-B6R0d4av.mjs → queryCachePlugin-ClosZdNS.mjs} +6 -27
  125. package/dist/{queryCachePlugin-Q6SYuHZ6.d.mts → queryCachePlugin-DcmETvcB.d.mts} +3 -3
  126. package/dist/queryParser-CgCtsjti.mjs +352 -0
  127. package/dist/{redis-UwjEp8Ea.d.mts → redis-CQ5YxMC5.d.mts} +2 -2
  128. package/dist/{redis-stream-CBg0upHI.d.mts → redis-stream-BW9UKLZM.d.mts} +9 -2
  129. package/dist/registry/index.d.mts +1 -4
  130. package/dist/registry/index.mjs +3 -4
  131. package/dist/{introspectionPlugin-B3JkrjwU.mjs → registry-I-ogLgL9.mjs} +1 -8
  132. package/dist/{requestContext-xi6OKBL-.mjs → requestContext-DYtmNpm5.mjs} +1 -3
  133. package/dist/resourceToTools-PMFE8HIv.mjs +533 -0
  134. package/dist/rpc/index.d.mts +90 -0
  135. package/dist/rpc/index.mjs +248 -0
  136. package/dist/{schemaConverter-Dtg0Kt9T.mjs → schemaConverter-DjzHpFam.mjs} +1 -2
  137. package/dist/schemas/index.d.mts +30 -30
  138. package/dist/schemas/index.mjs +2 -4
  139. package/dist/scope/index.d.mts +13 -2
  140. package/dist/scope/index.mjs +18 -5
  141. package/dist/{sessionManager-D_iEHjQl.d.mts → sessionManager-wbkYj2HL.d.mts} +2 -2
  142. package/dist/{sse-DkqQ1uxb.mjs → sse-BkViJPlT.mjs} +4 -25
  143. package/dist/testing/index.d.mts +551 -567
  144. package/dist/testing/index.mjs +1744 -1799
  145. package/dist/{tracing-8CEbhF0w.d.mts → tracing-bz_U4EM1.d.mts} +6 -1
  146. package/dist/{typeGuards-DwxA1t_L.mjs → typeGuards-Cj5Rgvlg.mjs} +1 -2
  147. package/dist/types/index.d.mts +4 -946
  148. package/dist/types/index.mjs +2 -4
  149. package/dist/types-BJmgxNbF.d.mts +275 -0
  150. package/dist/{types-RLkFVgaw.d.mts → types-BNUccdcf.d.mts} +2 -2
  151. package/dist/{types-Beqn1Un7.mjs → types-C6TQjtdi.mjs} +30 -2
  152. package/dist/{types-tKwaViYB.d.mts → types-Dt0-AI6E.d.mts} +68 -27
  153. package/dist/{types-DelU6kln.mjs → types-ZUu_h0jp.mjs} +1 -2
  154. package/dist/utils/index.d.mts +254 -351
  155. package/dist/utils/index.mjs +7 -6
  156. package/dist/utils-Dc0WhlIl.mjs +594 -0
  157. package/dist/versioning-BzfeHmhj.mjs +37 -0
  158. package/package.json +44 -10
  159. package/skills/arc/SKILL.md +518 -0
  160. package/skills/arc/references/auth.md +250 -0
  161. package/skills/arc/references/events.md +272 -0
  162. package/skills/arc/references/integrations.md +385 -0
  163. package/skills/arc/references/mcp.md +431 -0
  164. package/skills/arc/references/production.md +610 -0
  165. package/skills/arc/references/testing.md +183 -0
  166. package/dist/audited-CGdLiSlE.mjs +0 -140
  167. package/dist/chunk-C7Uep-_p.mjs +0 -20
  168. package/dist/circuitBreaker-CSS2VvL6.mjs +0 -1109
  169. package/dist/errorHandler-CW3OOeYq.d.mts +0 -72
  170. package/dist/interface-BtdYtQUA.d.mts +0 -1114
  171. package/dist/presets-BTeYbw7h.d.mts +0 -57
  172. package/dist/presets-CeFtfDR8.mjs +0 -119
  173. /package/dist/{errors-DAWRdiYP.d.mts → errors-CPpvPHT0.d.mts} +0 -0
  174. /package/dist/{externalPaths-SyPF2tgK.d.mts → externalPaths-DpO-s7r8.d.mts} +0 -0
  175. /package/dist/{interface-DTbsvIWe.d.mts → interface-D_BWALyZ.d.mts} +0 -0
@@ -1,12 +1,23 @@
1
1
  import fp from "fastify-plugin";
2
-
3
2
  //#region src/integrations/websocket.ts
3
+ /**
4
+ * Default adapter — no cross-instance broadcast (single-instance only).
5
+ * All methods are no-ops. Used when no adapter is configured.
6
+ */
7
+ var LocalWebSocketAdapter = class {
8
+ name = "local";
9
+ async publish() {}
10
+ async subscribe() {}
11
+ async close() {}
12
+ };
4
13
  var RoomManager = class {
5
14
  rooms = /* @__PURE__ */ new Map();
6
15
  clients = /* @__PURE__ */ new Map();
7
16
  maxPerRoom;
8
- constructor(maxPerRoom = 1e4) {
17
+ adapter;
18
+ constructor(maxPerRoom = 1e4, adapter) {
9
19
  this.maxPerRoom = maxPerRoom;
20
+ this.adapter = adapter;
10
21
  }
11
22
  addClient(client) {
12
23
  this.clients.set(client.id, client);
@@ -30,7 +41,7 @@ var RoomManager = class {
30
41
  const members = this.rooms.get(room);
31
42
  if (members && members.size >= this.maxPerRoom) return false;
32
43
  if (!this.rooms.has(room)) this.rooms.set(room, /* @__PURE__ */ new Set());
33
- this.rooms.get(room).add(clientId);
44
+ this.rooms.get(room)?.add(clientId);
34
45
  client.subscriptions.add(room);
35
46
  return true;
36
47
  }
@@ -65,6 +76,23 @@ var RoomManager = class {
65
76
  } catch {}
66
77
  }
67
78
  }
79
+ /**
80
+ * Broadcast locally AND through adapter (for cross-instance delivery).
81
+ * Use this instead of broadcast() when multi-instance is possible.
82
+ */
83
+ async broadcastWithAdapter(room, message, excludeClientId) {
84
+ this.broadcast(room, message, excludeClientId);
85
+ if (this.adapter) await this.adapter.publish(room, message);
86
+ }
87
+ /**
88
+ * Org-scoped broadcast locally AND through adapter.
89
+ * Uses a namespaced room key for the adapter so other instances
90
+ * can filter by org when delivering locally.
91
+ */
92
+ async broadcastToOrgWithAdapter(organizationId, room, message) {
93
+ this.broadcastToOrg(organizationId, room, message);
94
+ if (this.adapter) await this.adapter.publish(`org:${organizationId}:${room}`, message);
95
+ }
68
96
  getClient(clientId) {
69
97
  return this.clients.get(clientId);
70
98
  }
@@ -80,24 +108,34 @@ var RoomManager = class {
80
108
  };
81
109
  const websocketPluginImpl = async (fastify, options) => {
82
110
  let clientCounter = 0;
83
- const { path = "/ws", auth = true, resources = [], heartbeatInterval = 3e4, authenticate: customAuth, maxClientsPerRoom = 1e4, roomPolicy, maxMessageBytes = 16384, maxSubscriptionsPerClient = 100, exposeStats = false, onMessage, onConnect, onDisconnect } = options;
111
+ const { path = "/ws", auth = true, resources = [], heartbeatInterval = 3e4, authenticate: customAuth, maxClientsPerRoom = 1e4, roomPolicy, maxMessageBytes = 16384, maxSubscriptionsPerClient = 100, reauthInterval = 0, adapter, exposeStats = false, onMessage, onConnect, onDisconnect } = options;
84
112
  if (auth && !customAuth && !fastify.hasDecorator("authenticate")) throw new Error("[arc-websocket] auth is true but fastify.authenticate is not registered. Register an auth plugin before WebSocket, provide a custom authenticate function, or set auth: false.");
85
- const rooms = new RoomManager(maxClientsPerRoom);
113
+ const rooms = new RoomManager(maxClientsPerRoom, adapter);
114
+ if (adapter) await adapter.subscribe((room, message) => {
115
+ if (room.startsWith("org:")) {
116
+ const parts = room.split(":");
117
+ const orgId = parts[1];
118
+ const actualRoom = parts.slice(2).join(":");
119
+ rooms.broadcastToOrg(orgId, actualRoom, message);
120
+ } else rooms.broadcast(room, message);
121
+ });
86
122
  if (!fastify.hasDecorator("ws")) fastify.decorate("ws", {
87
123
  rooms,
88
124
  broadcast: (room, data) => {
89
- rooms.broadcast(room, JSON.stringify({
125
+ const msg = JSON.stringify({
90
126
  type: "broadcast",
91
127
  channel: room,
92
128
  data
93
- }));
129
+ });
130
+ rooms.broadcastWithAdapter(room, msg);
94
131
  },
95
132
  broadcastToOrg: (orgId, room, data) => {
96
- rooms.broadcastToOrg(orgId, room, JSON.stringify({
133
+ const msg = JSON.stringify({
97
134
  type: "broadcast",
98
135
  channel: room,
99
136
  data
100
- }));
137
+ });
138
+ rooms.broadcastToOrgWithAdapter(orgId, room, msg);
101
139
  },
102
140
  getStats: () => rooms.getStats()
103
141
  });
@@ -118,8 +156,8 @@ const websocketPluginImpl = async (fastify, options) => {
118
156
  organizationId: event.meta?.organizationId
119
157
  }
120
158
  });
121
- if (event.meta?.organizationId) rooms.broadcastToOrg(event.meta.organizationId, room, payload);
122
- else rooms.broadcast(room, payload);
159
+ if (event.meta?.organizationId) rooms.broadcastToOrgWithAdapter(event.meta.organizationId, room, payload);
160
+ else rooms.broadcastWithAdapter(room, payload);
123
161
  });
124
162
  eventUnsubscribers.push(unsub);
125
163
  }
@@ -186,6 +224,49 @@ const websocketPluginImpl = async (fastify, options) => {
186
224
  timestamp: Date.now()
187
225
  }));
188
226
  }, heartbeatInterval);
227
+ let reauthTimer;
228
+ if (reauthInterval > 0 && auth) reauthTimer = setInterval(async () => {
229
+ if (socket.readyState !== 1) return;
230
+ try {
231
+ if (customAuth) {
232
+ if (!await customAuth(request)) {
233
+ socket.send(JSON.stringify({
234
+ type: "error",
235
+ error: "Session expired"
236
+ }));
237
+ socket.close(4003, "Session expired");
238
+ return;
239
+ }
240
+ } else if (fastify.authenticate) {
241
+ let rejected = false;
242
+ const fakeReply = {
243
+ code() {
244
+ rejected = true;
245
+ return fakeReply;
246
+ },
247
+ send() {
248
+ return fakeReply;
249
+ },
250
+ sent: false
251
+ };
252
+ await fastify.authenticate(request, fakeReply);
253
+ if (rejected) {
254
+ socket.send(JSON.stringify({
255
+ type: "error",
256
+ error: "Session expired"
257
+ }));
258
+ socket.close(4003, "Session expired");
259
+ return;
260
+ }
261
+ }
262
+ } catch {
263
+ socket.send(JSON.stringify({
264
+ type: "error",
265
+ error: "Session expired"
266
+ }));
267
+ socket.close(4003, "Session expired");
268
+ }
269
+ }, reauthInterval);
189
270
  socket.on("message", async (raw) => {
190
271
  if ((typeof raw === "string" ? Buffer.byteLength(raw) : raw.length) > maxMessageBytes) {
191
272
  socket.send(JSON.stringify({
@@ -252,11 +333,13 @@ const websocketPluginImpl = async (fastify, options) => {
252
333
  });
253
334
  socket.on("close", async () => {
254
335
  if (heartbeatTimer) clearInterval(heartbeatTimer);
336
+ if (reauthTimer) clearInterval(reauthTimer);
255
337
  await onDisconnect?.(client);
256
338
  rooms.removeClient(clientId);
257
339
  });
258
340
  socket.on("error", () => {
259
341
  if (heartbeatTimer) clearInterval(heartbeatTimer);
342
+ if (reauthTimer) clearInterval(reauthTimer);
260
343
  rooms.removeClient(clientId);
261
344
  });
262
345
  });
@@ -276,6 +359,7 @@ const websocketPluginImpl = async (fastify, options) => {
276
359
  fastify.addHook("onClose", async () => {
277
360
  for (const unsub of eventUnsubscribers) unsub();
278
361
  eventUnsubscribers.length = 0;
362
+ if (adapter) await adapter.close();
279
363
  });
280
364
  };
281
365
  /** Pluggable WebSocket integration for Arc */
@@ -283,6 +367,5 @@ const websocketPlugin = fp(websocketPluginImpl, {
283
367
  name: "arc-websocket",
284
368
  fastify: "5.x"
285
369
  });
286
-
287
370
  //#endregion
288
- export { RoomManager, websocketPlugin as default, websocketPlugin };
371
+ export { LocalWebSocketAdapter, RoomManager, websocketPlugin as default, websocketPlugin };
@@ -41,7 +41,7 @@ interface IdempotencyStore {
41
41
  * Store a result for an idempotency key.
42
42
  * TTL is handled by the store implementation.
43
43
  */
44
- set(key: string, result: Omit<IdempotencyResult, 'key'>): Promise<void>;
44
+ set(key: string, result: Omit<IdempotencyResult, "key">): Promise<void>;
45
45
  /**
46
46
  * Try to acquire a lock for processing a key.
47
47
  * Returns true if lock acquired, false if already locked.
@@ -72,6 +72,6 @@ interface IdempotencyStore {
72
72
  /**
73
73
  * Helper to create a result object
74
74
  */
75
- declare function createIdempotencyResult(statusCode: number, body: unknown, headers: Record<string, string>, ttlMs: number): Omit<IdempotencyResult, 'key'>;
75
+ declare function createIdempotencyResult(statusCode: number, body: unknown, headers: Record<string, string>, ttlMs: number): Omit<IdempotencyResult, "key">;
76
76
  //#endregion
77
77
  export { createIdempotencyResult as i, IdempotencyResult as n, IdempotencyStore as r, IdempotencyLock as t };