@classytic/arc 2.15.3 → 2.15.4
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.
|
@@ -376,20 +376,29 @@ function packageJsonTemplate(config) {
|
|
|
376
376
|
test: "vitest run",
|
|
377
377
|
"test:watch": "vitest"
|
|
378
378
|
};
|
|
379
|
+
const imports = config.typescript ? {
|
|
380
|
+
"#config/*": "./dist/config/*",
|
|
381
|
+
"#shared/*": "./dist/shared/*",
|
|
382
|
+
"#resources/*": "./dist/resources/*",
|
|
383
|
+
"#plugins/*": "./dist/plugins/*",
|
|
384
|
+
"#services/*": "./dist/services/*",
|
|
385
|
+
"#lib/*": "./dist/lib/*",
|
|
386
|
+
"#utils/*": "./dist/utils/*"
|
|
387
|
+
} : {
|
|
388
|
+
"#config/*": "./src/config/*",
|
|
389
|
+
"#shared/*": "./src/shared/*",
|
|
390
|
+
"#resources/*": "./src/resources/*",
|
|
391
|
+
"#plugins/*": "./src/plugins/*",
|
|
392
|
+
"#services/*": "./src/services/*",
|
|
393
|
+
"#lib/*": "./src/lib/*",
|
|
394
|
+
"#utils/*": "./src/utils/*"
|
|
395
|
+
};
|
|
379
396
|
return JSON.stringify({
|
|
380
397
|
name: config.name,
|
|
381
398
|
version: "1.0.0",
|
|
382
399
|
type: "module",
|
|
383
400
|
main: config.typescript ? "dist/index.js" : "src/index.js",
|
|
384
|
-
imports
|
|
385
|
-
"#config/*": "./src/config/*",
|
|
386
|
-
"#shared/*": "./src/shared/*",
|
|
387
|
-
"#resources/*": "./src/resources/*",
|
|
388
|
-
"#plugins/*": "./src/plugins/*",
|
|
389
|
-
"#services/*": "./src/services/*",
|
|
390
|
-
"#lib/*": "./src/lib/*",
|
|
391
|
-
"#utils/*": "./src/utils/*"
|
|
392
|
-
},
|
|
401
|
+
imports,
|
|
393
402
|
scripts,
|
|
394
403
|
dependencies,
|
|
395
404
|
devDependencies,
|
package/dist/index.mjs
CHANGED
|
@@ -6,6 +6,6 @@ import { C as allowPublic, D as requireAuth, O as requireOwnership, S as allOf,
|
|
|
6
6
|
import { v as getControllerScope } from "./routerShared-DrOa-26E.mjs";
|
|
7
7
|
import { a as defineResource, i as defineResourceVariants, l as defineAggregation, o as ResourceDefinition } from "./core-CvmOqEms.mjs";
|
|
8
8
|
//#region src/index.ts
|
|
9
|
-
const version = "2.15.
|
|
9
|
+
const version = "2.15.4";
|
|
10
10
|
//#endregion
|
|
11
11
|
export { ArcError, BaseController, BaseCrudController, BulkMixin, 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, NotFoundError, RESERVED_QUERY_PARAMS, ResourceDefinition, SYSTEM_FIELDS, SlugMixin, SoftDeleteMixin, TreeMixin, UnauthorizedError, ValidationError, adminOnly, allOf, allowPublic, anyOf, applyFieldReadPermissions, applyFieldWritePermissions, authenticated, createDomainError, createDynamicPermissionMatrix, createOrgPermissions, defineAggregation, defineResource, defineResourceVariants, denyAll, fields, fullPublic, getControllerScope, getUserId, ownerWithAdminBypass, presets_exports as permissions, publicRead, publicReadAdminWrite, readOnly, requireAuth, requireOrgInScope, requireOrgMembership, requireOrgRole, requireOwnership, requireRoles, requireScopeContext, requireServiceScope, requireTeamMembership, version, when };
|
|
@@ -50,7 +50,40 @@ interface WorkflowLike {
|
|
|
50
50
|
}; /** Repository — used by the list-runs endpoint to query workflow_runs. */
|
|
51
51
|
repository?: {
|
|
52
52
|
getAll(params: Record<string, unknown>, options?: Record<string, unknown>): Promise<unknown>;
|
|
53
|
+
/**
|
|
54
|
+
* Tenant-scoped lookup by id. Used by the DELETE handler for a
|
|
55
|
+
* defense-in-depth pre-flight: streamline 2.3.3's `wf.get(runId)` /
|
|
56
|
+
* `engine.get` does NOT accept tenant options, so a cross-tenant
|
|
57
|
+
* runId can leak data through the engine path. Going through the
|
|
58
|
+
* repository here means mongokit's tenant-filter plugin scopes the
|
|
59
|
+
* read — cross-tenant requests get a clean 404 and DELETEs only
|
|
60
|
+
* touch rows the caller actually owns.
|
|
61
|
+
*/
|
|
62
|
+
getById?(id: string, options?: Record<string, unknown>): Promise<WorkflowRunLike | null>;
|
|
63
|
+
/**
|
|
64
|
+
* Hard-delete a run by id. Routed through mongokit's inherited
|
|
65
|
+
* `Repository.delete()` so multi-tenant scope + audit/cache plugins
|
|
66
|
+
* fire. Wired into `DELETE /:workflowId/runs/:runId` — operator
|
|
67
|
+
* escape hatch for dead-lettered or stuck rows.
|
|
68
|
+
*/
|
|
69
|
+
delete?(id: string, options?: Record<string, unknown>): Promise<unknown>;
|
|
53
70
|
};
|
|
71
|
+
/**
|
|
72
|
+
* Streamline >= 2.3.2 — explicit deploy-time index sync (TTL on
|
|
73
|
+
* terminal runs + tenant compounds). When the host configured
|
|
74
|
+
* `createContainer({ retention })`, arc's app-level deploy hook
|
|
75
|
+
* should call `await container.syncRetentionIndexes()` after
|
|
76
|
+
* `mongoose.connect`. Optional so older streamline versions
|
|
77
|
+
* (and partial mocks) still satisfy the structural shape.
|
|
78
|
+
*/
|
|
79
|
+
syncRetentionIndexes?: () => Promise<void>;
|
|
80
|
+
/**
|
|
81
|
+
* Streamline >= 2.3.2 — stop background sweepers and release timers.
|
|
82
|
+
* Arc's `onClose` hook below calls this on every workflow's container
|
|
83
|
+
* during graceful shutdown so SIGTERM doesn't leave the stale-run
|
|
84
|
+
* sweeper running. Optional + idempotent.
|
|
85
|
+
*/
|
|
86
|
+
dispose?: () => void;
|
|
54
87
|
};
|
|
55
88
|
}
|
|
56
89
|
interface WorkflowRunLike {
|
|
@@ -67,7 +100,36 @@ interface WorkflowRunLike {
|
|
|
67
100
|
stepLogs?: unknown[];
|
|
68
101
|
createdAt?: Date;
|
|
69
102
|
updatedAt?: Date;
|
|
103
|
+
/**
|
|
104
|
+
* Streamline >= 2.3.3 — pinned definition version (semver) the run
|
|
105
|
+
* started under. Hosts surfacing a "stuck on old version" UI read this
|
|
106
|
+
* to decide whether to nudge a migration. Optional for back-compat
|
|
107
|
+
* with runs created before 2.3.3.
|
|
108
|
+
*/
|
|
109
|
+
definitionVersion?: string;
|
|
110
|
+
/**
|
|
111
|
+
* Streamline >= 2.3.3 — count of stale-recovery / sweeper transitions
|
|
112
|
+
* applied to this run. Sweeper dead-letters once this hits
|
|
113
|
+
* `RetentionOptions.maxStaleRecoveries`; UIs can highlight runs trending
|
|
114
|
+
* toward dead-letter.
|
|
115
|
+
*/
|
|
116
|
+
recoveryAttempts?: number;
|
|
70
117
|
}
|
|
118
|
+
/**
|
|
119
|
+
* Streamline >= 2.3.3 dead-letter discriminator. The run.status stays
|
|
120
|
+
* `'failed'`; the discrimination is `error.code`:
|
|
121
|
+
* - `'stale_heartbeat'` — sweeper terminated; transient crash signal.
|
|
122
|
+
* - `'dead_lettered'` — exceeded `maxStaleRecoveries`; permanent.
|
|
123
|
+
* - `'VERSION_MISMATCH'` — engine deployed a step graph the run can't
|
|
124
|
+
* resume against; admin must rewind / migrate / cancel.
|
|
125
|
+
*
|
|
126
|
+
* Hosts switch on `error.code` for dashboards / alerting.
|
|
127
|
+
*/
|
|
128
|
+
declare const STREAMLINE_FAILURE_CODES: {
|
|
129
|
+
readonly STALE_HEARTBEAT: "stale_heartbeat";
|
|
130
|
+
readonly DEAD_LETTERED: "dead_lettered";
|
|
131
|
+
readonly VERSION_MISMATCH: "VERSION_MISMATCH";
|
|
132
|
+
};
|
|
71
133
|
interface StreamlinePluginOptions {
|
|
72
134
|
/** Array of workflows created with createWorkflow() */
|
|
73
135
|
workflows: WorkflowLike[];
|
|
@@ -176,7 +238,14 @@ declare const STREAMLINE_BUS_EVENTS: readonly ["step:started", "step:completed",
|
|
|
176
238
|
* the run is still active after them.
|
|
177
239
|
*/
|
|
178
240
|
declare const STREAMLINE_TERMINAL_EVENTS: readonly ["workflow:completed", "workflow:failed", "workflow:cancelled"];
|
|
179
|
-
/**
|
|
241
|
+
/**
|
|
242
|
+
* Pluggable streamline integration for Arc.
|
|
243
|
+
*
|
|
244
|
+
* Wrapped in `fastify-plugin` so Fastify treats `options.prefix` as a
|
|
245
|
+
* plain plugin option (NOT an encapsulation prefix). Without the wrapper,
|
|
246
|
+
* Fastify would prepend `options.prefix` to every route, then the plugin
|
|
247
|
+
* code would prepend it again — the duplicate-prefix bug.
|
|
248
|
+
*/
|
|
180
249
|
declare const streamlinePlugin: FastifyPluginAsync<StreamlinePluginOptions>;
|
|
181
250
|
//#endregion
|
|
182
|
-
export { STREAMLINE_BUS_EVENTS, STREAMLINE_TERMINAL_EVENTS, StreamlinePluginOptions, WorkflowLike, WorkflowRunLike, WorkflowStartOptions, streamlinePlugin };
|
|
251
|
+
export { STREAMLINE_BUS_EVENTS, STREAMLINE_FAILURE_CODES, STREAMLINE_TERMINAL_EVENTS, StreamlinePluginOptions, WorkflowLike, WorkflowRunLike, WorkflowStartOptions, streamlinePlugin };
|
|
@@ -1,6 +1,22 @@
|
|
|
1
1
|
import { f as createError, i as NotFoundError, r as ForbiddenError } from "../errors-j4aJm1Wg.mjs";
|
|
2
|
+
import fp from "fastify-plugin";
|
|
2
3
|
//#region src/integrations/streamline.ts
|
|
3
4
|
/**
|
|
5
|
+
* Streamline >= 2.3.3 dead-letter discriminator. The run.status stays
|
|
6
|
+
* `'failed'`; the discrimination is `error.code`:
|
|
7
|
+
* - `'stale_heartbeat'` — sweeper terminated; transient crash signal.
|
|
8
|
+
* - `'dead_lettered'` — exceeded `maxStaleRecoveries`; permanent.
|
|
9
|
+
* - `'VERSION_MISMATCH'` — engine deployed a step graph the run can't
|
|
10
|
+
* resume against; admin must rewind / migrate / cancel.
|
|
11
|
+
*
|
|
12
|
+
* Hosts switch on `error.code` for dashboards / alerting.
|
|
13
|
+
*/
|
|
14
|
+
const STREAMLINE_FAILURE_CODES = {
|
|
15
|
+
STALE_HEARTBEAT: "stale_heartbeat",
|
|
16
|
+
DEAD_LETTERED: "dead_lettered",
|
|
17
|
+
VERSION_MISMATCH: "VERSION_MISMATCH"
|
|
18
|
+
};
|
|
19
|
+
/**
|
|
4
20
|
* Full event list published on a streamline workflow's internal `eventBus`
|
|
5
21
|
* (tracks streamline 2.3's `EventPayloadMap` in
|
|
6
22
|
* `@classytic/streamline/src/core/events.ts`).
|
|
@@ -44,6 +60,7 @@ const STREAMLINE_TERMINAL_EVENTS = [
|
|
|
44
60
|
];
|
|
45
61
|
const streamlinePluginImpl = async (fastify, options) => {
|
|
46
62
|
const { workflows, prefix = "/workflows", auth = true, bridgeEvents = true, enableStreaming = false, enableHookEndpoint = false, tenantResolver, bypassTenantResolver, permissions: perms } = options;
|
|
63
|
+
const routeScope = prefix;
|
|
47
64
|
const bridgeBus = options.bridgeBusEvents ?? false;
|
|
48
65
|
const registry = /* @__PURE__ */ new Map();
|
|
49
66
|
for (const wf of workflows) {
|
|
@@ -70,7 +87,7 @@ const streamlinePluginImpl = async (fastify, options) => {
|
|
|
70
87
|
return check(request);
|
|
71
88
|
};
|
|
72
89
|
for (const [id, wf] of registry) {
|
|
73
|
-
const routePrefix = `${
|
|
90
|
+
const routePrefix = `${routeScope}/${id}`;
|
|
74
91
|
fastify.post(`${routePrefix}/start`, { preHandler: authPreHandler }, async (request, reply) => {
|
|
75
92
|
if (!await checkPerm("start", request)) throw new ForbiddenError();
|
|
76
93
|
const { input, meta, idempotencyKey, priority } = request.body ?? {};
|
|
@@ -161,6 +178,24 @@ const streamlinePluginImpl = async (fastify, options) => {
|
|
|
161
178
|
const { runId } = request.params;
|
|
162
179
|
return await wf.engine.execute(runId);
|
|
163
180
|
});
|
|
181
|
+
const deleteRepo = wf.container?.repository;
|
|
182
|
+
const repoDeleteFn = deleteRepo?.delete;
|
|
183
|
+
const repoGetByIdFn = deleteRepo?.getById;
|
|
184
|
+
if (repoDeleteFn && repoGetByIdFn) fastify.delete(`${routePrefix}/runs/:runId`, { preHandler: authPreHandler }, async (request, reply) => {
|
|
185
|
+
if (!await checkPerm("cancel", request)) throw new ForbiddenError();
|
|
186
|
+
const { runId } = request.params;
|
|
187
|
+
const tenantOpts = resolveTenantOpts(request);
|
|
188
|
+
const repoOpts = {
|
|
189
|
+
...tenantOpts.tenantId !== void 0 ? { tenantId: tenantOpts.tenantId } : {},
|
|
190
|
+
...tenantOpts.bypassTenant ? { bypassTenant: true } : {}
|
|
191
|
+
};
|
|
192
|
+
if (!await repoGetByIdFn(runId, repoOpts)) throw new NotFoundError(`Workflow run ${runId} not found`);
|
|
193
|
+
try {
|
|
194
|
+
await wf.cancel(runId);
|
|
195
|
+
} catch {}
|
|
196
|
+
await repoDeleteFn(runId, repoOpts);
|
|
197
|
+
return reply.status(204).send();
|
|
198
|
+
});
|
|
164
199
|
if (wf.engine.waitFor) fastify.get(`${routePrefix}/runs/:runId/wait`, { preHandler: authPreHandler }, async (request, _reply) => {
|
|
165
200
|
if (!await checkPerm("get", request)) throw new ForbiddenError();
|
|
166
201
|
const { runId } = request.params;
|
|
@@ -239,7 +274,7 @@ const streamlinePluginImpl = async (fastify, options) => {
|
|
|
239
274
|
}
|
|
240
275
|
if (enableHookEndpoint) {
|
|
241
276
|
let resumeHookFn;
|
|
242
|
-
fastify.post(`${
|
|
277
|
+
fastify.post(`${routeScope}/hooks/:token`, { preHandler: authPreHandler }, async (request, _reply) => {
|
|
243
278
|
if (!resumeHookFn) resumeHookFn = (await import("@classytic/streamline")).resumeHook;
|
|
244
279
|
const { token } = request.params;
|
|
245
280
|
const result = await resumeHookFn(token, request.body);
|
|
@@ -249,7 +284,7 @@ const streamlinePluginImpl = async (fastify, options) => {
|
|
|
249
284
|
};
|
|
250
285
|
});
|
|
251
286
|
}
|
|
252
|
-
fastify.get(
|
|
287
|
+
fastify.get(routeScope || "/", { preHandler: authPreHandler }, async () => {
|
|
253
288
|
return Array.from(registry.entries()).map(([id, wf]) => ({
|
|
254
289
|
id,
|
|
255
290
|
name: wf.definition.name ?? id,
|
|
@@ -257,10 +292,23 @@ const streamlinePluginImpl = async (fastify, options) => {
|
|
|
257
292
|
}));
|
|
258
293
|
});
|
|
259
294
|
fastify.addHook("onClose", async () => {
|
|
260
|
-
for (const wf of registry.values())
|
|
295
|
+
for (const wf of registry.values()) {
|
|
296
|
+
wf.shutdown?.();
|
|
297
|
+
wf.container?.dispose?.();
|
|
298
|
+
}
|
|
261
299
|
});
|
|
262
300
|
};
|
|
263
|
-
/**
|
|
264
|
-
|
|
301
|
+
/**
|
|
302
|
+
* Pluggable streamline integration for Arc.
|
|
303
|
+
*
|
|
304
|
+
* Wrapped in `fastify-plugin` so Fastify treats `options.prefix` as a
|
|
305
|
+
* plain plugin option (NOT an encapsulation prefix). Without the wrapper,
|
|
306
|
+
* Fastify would prepend `options.prefix` to every route, then the plugin
|
|
307
|
+
* code would prepend it again — the duplicate-prefix bug.
|
|
308
|
+
*/
|
|
309
|
+
const streamlinePlugin = fp(streamlinePluginImpl, {
|
|
310
|
+
name: "streamline-routes",
|
|
311
|
+
fastify: "5.x"
|
|
312
|
+
});
|
|
265
313
|
//#endregion
|
|
266
|
-
export { STREAMLINE_BUS_EVENTS, STREAMLINE_TERMINAL_EVENTS, streamlinePlugin };
|
|
314
|
+
export { STREAMLINE_BUS_EVENTS, STREAMLINE_FAILURE_CODES, STREAMLINE_TERMINAL_EVENTS, streamlinePlugin };
|
|
@@ -58,7 +58,7 @@ try {
|
|
|
58
58
|
function createTracerProvider(options) {
|
|
59
59
|
if (!isAvailable || !NodeTracerProvider || !BatchSpanProcessor || !OTLPTraceExporter) return null;
|
|
60
60
|
const { serviceName = "@classytic/arc", serviceVersion, exporterUrl = "http://localhost:4318/v1/traces" } = options;
|
|
61
|
-
const resolvedVersion = serviceVersion ?? "2.15.
|
|
61
|
+
const resolvedVersion = serviceVersion ?? "2.15.4";
|
|
62
62
|
const exporter = new OTLPTraceExporter({ url: exporterUrl });
|
|
63
63
|
const provider = new NodeTracerProvider({ resource: { attributes: {
|
|
64
64
|
"service.name": serviceName,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@classytic/arc",
|
|
3
|
-
"version": "2.15.
|
|
3
|
+
"version": "2.15.4",
|
|
4
4
|
"description": "Resource-oriented backend framework for Fastify - clean, minimal, powerful, tree-shakable",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -245,9 +245,9 @@
|
|
|
245
245
|
"node": ">=22"
|
|
246
246
|
},
|
|
247
247
|
"peerDependencies": {
|
|
248
|
-
"@classytic/primitives": ">=0.
|
|
248
|
+
"@classytic/primitives": ">=0.5.0",
|
|
249
249
|
"@classytic/repo-core": ">=0.4.1",
|
|
250
|
-
"@classytic/streamline": ">=2.3.
|
|
250
|
+
"@classytic/streamline": ">=2.3.3",
|
|
251
251
|
"@fastify/cors": ">=11.0.0",
|
|
252
252
|
"@fastify/helmet": ">=13.0.0",
|
|
253
253
|
"@fastify/jwt": ">=10.0.0",
|
|
@@ -361,7 +361,7 @@
|
|
|
361
361
|
"@classytic/primitives": "^0.4.0",
|
|
362
362
|
"@classytic/repo-core": "^0.4.1",
|
|
363
363
|
"@classytic/sqlitekit": "^0.3.1",
|
|
364
|
-
"@classytic/streamline": "^2.3.
|
|
364
|
+
"@classytic/streamline": "^2.3.3",
|
|
365
365
|
"@fastify/cors": "^11.2.0",
|
|
366
366
|
"@fastify/helmet": "^13.0.2",
|
|
367
367
|
"@fastify/jwt": "^10.0.0",
|
|
@@ -420,4 +420,4 @@
|
|
|
420
420
|
"type": "git",
|
|
421
421
|
"url": "https://github.com/classytic/arc.git"
|
|
422
422
|
}
|
|
423
|
-
}
|
|
423
|
+
}
|
package/skills/arc/SKILL.md
CHANGED
|
@@ -27,7 +27,7 @@ progressive_disclosure:
|
|
|
27
27
|
entry_point:
|
|
28
28
|
summary: "Resource-oriented Fastify framework: defineResource(), presets, permissions, QueryCache, events, multi-tenant, OpenAPI, MCP"
|
|
29
29
|
when_to_use: "Building REST APIs with Fastify, resource CRUD, authentication, presets, caching, events, or production deployment"
|
|
30
|
-
quick_start: "1. arc init my-api --mongokit --
|
|
30
|
+
quick_start: "1. npx @classytic/arc init my-api --mongokit --better-auth --single --ts 2. defineResource({ name, adapter, presets, permissions }) 3. createApp({ preset: 'production', resources, auth })"
|
|
31
31
|
---
|
|
32
32
|
|
|
33
33
|
# @classytic/arc
|
|
@@ -39,11 +39,11 @@ One `defineResource()` call → REST + auth + permissions + events + cache + Ope
|
|
|
39
39
|
## Scaffold a project
|
|
40
40
|
|
|
41
41
|
```bash
|
|
42
|
-
npx @classytic/arc@latest init my-api --mongokit --
|
|
42
|
+
npx @classytic/arc@latest init my-api --mongokit --better-auth --single --ts
|
|
43
43
|
cd my-api && npm install && npm run dev
|
|
44
44
|
```
|
|
45
45
|
|
|
46
|
-
Flags: `--mongokit | --custom`, `--
|
|
46
|
+
Flags: `--mongokit | --custom`, `--better-auth | --jwt`, `--single | --multi`, `--ts | --js`, `--edge`, `--force`, `--skip-install`. Defaults: `--mongokit --better-auth --single --ts`. The scaffold seeds full `dependencies` + `devDependencies` so `npm install` works without the CLI's pre-pass.
|
|
47
47
|
|
|
48
48
|
## createApp()
|
|
49
49
|
|
|
@@ -691,6 +691,19 @@ the kit's multi-tenant plugin reads `context.organizationId`, casts
|
|
|
691
691
|
correctly, and merges into the request. Authors never inject the
|
|
692
692
|
tenant key into `aggReq.filter` themselves.
|
|
693
693
|
|
|
694
|
+
**2.15.3 — `multiTenantPreset` now wires `/aggregations/:name`.** Pre-2.15.3
|
|
695
|
+
the preset only scoped CRUD; aggregation routes leaked across orgs for any
|
|
696
|
+
caller whose `scope.kind !== 'member'`. Adding `multiTenantPreset({ tenantField: 'organizationId' })`
|
|
697
|
+
now emits an `aggregations` middleware slot alongside the five CRUD slots, so
|
|
698
|
+
member callers see only their org and `kind: 'elevated'` callers WITHOUT a
|
|
699
|
+
target org get `bypassTenant: true` (platform-admin cross-tenant dashboards).
|
|
700
|
+
**Kit config note:** set `scope: true` (or `scope: { fieldType: 'objectId' }`)
|
|
701
|
+
on revenue/order/etc. engines — the pre-2.15.2 advice to use `scope: false`
|
|
702
|
+
"to avoid double-scoping with arc" is no longer correct; arc 2.15.2+
|
|
703
|
+
deliberately leaves `aggReq.filter` clean and relies on the kit. Required
|
|
704
|
+
peers: `@classytic/repo-core ≥ 0.4.1`, `@classytic/mongokit ≥ 3.13.2`,
|
|
705
|
+
`@classytic/sqlitekit ≥ 0.3.1`.
|
|
706
|
+
|
|
694
707
|
**Caller filters via query string compose with `groupBy` / measures:**
|
|
695
708
|
|
|
696
709
|
```
|
|
@@ -833,14 +846,17 @@ Full testing recipes → [references/testing.md](references/testing.md).
|
|
|
833
846
|
|
|
834
847
|
## CLI
|
|
835
848
|
|
|
849
|
+
The bin is `arc` (registered by `@classytic/arc`). Outside an arc project use `npx @classytic/arc <cmd>`; inside one (devDep installed) bare `arc` resolves through `node_modules/.bin`.
|
|
850
|
+
|
|
836
851
|
```bash
|
|
837
|
-
arc init my-api --mongokit --
|
|
838
|
-
arc generate resource product
|
|
839
|
-
arc generate resource product --mcp
|
|
840
|
-
arc generate mcp analytics
|
|
841
|
-
arc docs ./openapi.json --entry ./dist/index.js
|
|
842
|
-
arc introspect --entry ./dist/index.js
|
|
843
|
-
arc
|
|
852
|
+
npx @classytic/arc init my-api --mongokit --better-auth --single --ts # scaffold (also: --custom, --jwt, --multi, --js, --edge)
|
|
853
|
+
npx @classytic/arc generate resource product # generate a resource (alias: arc g r product)
|
|
854
|
+
npx @classytic/arc generate resource product --mcp # + MCP tools file
|
|
855
|
+
npx @classytic/arc generate mcp analytics # standalone MCP tools file
|
|
856
|
+
npx @classytic/arc docs ./openapi.json --entry ./dist/index.js # emit OpenAPI
|
|
857
|
+
npx @classytic/arc introspect --entry ./dist/index.js
|
|
858
|
+
npx @classytic/arc describe ./dist/resources.js --json # JSON metadata for AI agents
|
|
859
|
+
npx @classytic/arc doctor
|
|
844
860
|
```
|
|
845
861
|
|
|
846
862
|
Set `"mcp": true` in `.arcrc` to always generate `.mcp.ts` alongside resources.
|