@classytic/arc 2.20.0 → 2.22.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{BaseController-D-MHZ27_.mjs → BaseController-C6FlRWxF.mjs} +2 -2
- package/dist/{EventTransport-IEn7Tn-Q.d.mts → EventTransport-QmY7A258.d.mts} +0 -1
- package/dist/{QueryCache-TRfXau3W.d.mts → QueryCache-Z1B38koh.d.mts} +0 -1
- package/dist/{actionPermissions-C9RMiIpc.mjs → actionPermissions-ze70xXJi.mjs} +1 -1
- package/dist/audit/index.d.mts +9 -5
- package/dist/auth/audit.d.mts +7 -4
- package/dist/auth/audit.mjs +5 -0
- package/dist/auth/index.d.mts +35 -14
- package/dist/auth/index.mjs +6 -3
- package/dist/auth/redis-session.d.mts +1 -2
- package/dist/cache/index.d.mts +2 -3
- package/dist/cli/commands/describe.d.mts +1 -2
- package/dist/cli/commands/docs.mjs +1 -1
- package/dist/cli/commands/init.mjs +1 -1
- package/dist/cli/index.mjs +1 -1
- package/dist/context/index.d.mts +2 -2
- package/dist/core/index.d.mts +3 -3
- package/dist/core/index.mjs +4 -4
- package/dist/{core-CWhyGkiM.mjs → core-b6fgFgnd.mjs} +129 -17
- package/dist/{createActionRouter-HlQso0jO.mjs → createActionRouter-h1cEEaEX.mjs} +10 -6
- package/dist/{createAggregationRouter-5LzPzgw1.mjs → createAggregationRouter-B1C0kvTL.mjs} +3 -3
- package/dist/{createApp-D6uwzCKu.mjs → createApp-Ci7yG5gv.mjs} +390 -192
- package/dist/discovery/index.d.mts +0 -1
- package/dist/docs/index.d.mts +1 -2
- package/dist/docs/index.mjs +1 -1
- package/dist/{elevation-xGUBq6p_.d.mts → elevation-C9s5hFP_.d.mts} +0 -1
- package/dist/encryption/index.d.mts +2 -144
- package/dist/encryption/index.mjs +9 -5
- package/dist/{errorHandler-Dxf6P_kV.mjs → errorHandler-BTzXSwIg.mjs} +24 -3
- package/dist/{errorHandler-BtUUDPEa.d.mts → errorHandler-C6yv4ZTW.d.mts} +13 -1
- package/dist/{errors-DOHkjQlD.d.mts → errors-DHcHVAgk.d.mts} +0 -1
- package/dist/{eventPlugin-DbBFd3x_.d.mts → eventPlugin-A6EbthXR.d.mts} +10 -6
- package/dist/events/index.d.mts +9 -6
- package/dist/events/transports/redis-stream-entry.d.mts +1 -1
- package/dist/events/transports/redis.d.mts +1 -2
- package/dist/factory/index.d.mts +30 -9
- package/dist/factory/index.mjs +35 -4
- package/dist/{fastify-augmentation-CCQJEDgh.d.mts → fastify-augmentation-9_fuetB4.d.mts} +2 -3
- package/dist/fields-D3lhgzhD.d.mts +121 -0
- package/dist/health-ScyjIVo5.mjs +202 -0
- package/dist/hooks/index.d.mts +1 -1
- package/dist/idempotency/index.d.mts +4 -3
- package/dist/idempotency/index.mjs +2 -0
- package/dist/idempotency/redis.d.mts +1 -1
- package/dist/{types-BHMKlMBU.d.mts → index-BEu4wjbL.d.mts} +241 -80
- package/dist/{index-DUa8WHz3.d.mts → index-D8MKYGDa.d.mts} +88 -7
- package/dist/{index-_--GyzcO.d.mts → index-DbtgCOCj.d.mts} +1324 -1228
- package/dist/{index-CdFkIMEd.d.mts → index-ogIHvOjo.d.mts} +36 -7
- package/dist/index.d.mts +8 -7
- package/dist/index.mjs +6 -6
- package/dist/{init-YH_nslQR.mjs → init-DleK3p9f.mjs} +272 -139
- package/dist/integrations/event-gateway.d.mts +2 -3
- package/dist/integrations/index.d.mts +2 -2
- package/dist/integrations/jobs.d.mts +0 -1
- package/dist/integrations/mcp/index.d.mts +6 -7
- package/dist/integrations/mcp/index.mjs +1 -1
- package/dist/integrations/mcp/testing.d.mts +1 -2
- package/dist/integrations/mcp/testing.mjs +1 -1
- package/dist/integrations/streamline.d.mts +2 -2
- package/dist/integrations/webhooks.d.mts +8 -5
- package/dist/integrations/webhooks.mjs +9 -5
- package/dist/integrations/websocket-pushref-redis.d.mts +1 -2
- package/dist/integrations/websocket-redis.d.mts +1 -2
- package/dist/integrations/websocket.d.mts +2 -2
- package/dist/jsonBody-BlzAoGCe.mjs +43 -0
- package/dist/{loadResources-B8UmpITt.mjs → loadResources-DpU8pfjV.mjs} +4 -3
- package/dist/logger/index.d.mts +26 -1
- package/dist/logger/index.mjs +39 -1
- package/dist/{mcpPlugin-CuW6L_uD.mjs → mcpPlugin-ByA2tf6D.mjs} +8 -4
- package/dist/middleware/index.d.mts +1 -2
- package/dist/{module-JYH9V0I8.d.mts → module-B4u10iOJ.d.mts} +71 -1
- package/dist/{openapi-Dq6RbEGK.mjs → openapi-BRpMZD7C.mjs} +3 -3
- package/dist/permissions/index.d.mts +4 -3
- package/dist/permissions/index.mjs +2 -2
- package/dist/{permissions-CgbOLJZG.mjs → permissions-BJMuKMl7.mjs} +33 -1
- package/dist/pipeline/index.d.mts +1 -2
- package/dist/plugins/index.d.mts +96 -7
- package/dist/plugins/index.mjs +86 -268
- package/dist/plugins/response-cache.d.mts +5 -3
- package/dist/plugins/response-cache.mjs +1 -0
- package/dist/plugins/tracing-entry.d.mts +1 -1
- package/dist/plugins/tracing-entry.mjs +7 -4
- package/dist/presets/filesUpload.d.mts +4 -5
- package/dist/presets/filesUpload.mjs +1 -1
- package/dist/presets/index.d.mts +1 -2
- package/dist/presets/index.mjs +1 -1
- package/dist/presets/multiTenant.d.mts +1 -2
- package/dist/presets/search.d.mts +2 -3
- package/dist/presets/search.mjs +1 -1
- package/dist/{presets-DWsYQdbV.mjs → presets-gw0ciIUD.mjs} +1 -1
- package/dist/{pushref-registry-DnCqL3aa.d.mts → pushref-registry-BbcnpNTm.d.mts} +4 -2
- package/dist/{queryCachePlugin-xzFyKAjT.d.mts → queryCachePlugin-CfYWq-Ak.d.mts} +1 -2
- package/dist/rateLimitKey-BoWRH3e7.mjs +14 -0
- package/dist/{redis-C3W0qbz8.d.mts → redis-UDtLaNj6.d.mts} +0 -1
- package/dist/{redis-stream-DsP1vqZB.d.mts → redis-stream-iVR2l7aS.d.mts} +1 -2
- package/dist/{registerRoute-DGSREwPw.mjs → registerRoute-xHc42fwB.mjs} +38 -18
- package/dist/registry/index.d.mts +2 -2
- package/dist/requestId-5BhTVzBv.mjs +113 -0
- package/dist/schemaConverter-Ds5reSGs.mjs +168 -0
- package/dist/{schemaIR-ajetw3Fv.mjs → schemaIR-B-6SPe7g.mjs} +2 -2
- package/dist/schemas/index.d.mts +0 -1
- package/dist/scim/index.d.mts +0 -1
- package/dist/scim/index.mjs +6 -7
- package/dist/scope/index.d.mts +2 -3
- package/dist/scope/index.mjs +2 -76
- package/dist/scope-k15RXfcq.mjs +64 -0
- package/dist/{sessionManager-BXG-HGwG.d.mts → sessionManager-BHOM3N_0.d.mts} +10 -6
- package/dist/{storage-Co_PQpeQ.d.mts → storage-DLUZ04kK.d.mts} +8 -4
- package/dist/testing/index.d.mts +83 -4
- package/dist/testing/index.mjs +74 -4
- package/dist/testing/storageContract.d.mts +1 -2
- package/dist/{tracing-D8jlGXP7.d.mts → tracing-CsGKQJGg.d.mts} +0 -1
- package/dist/types/index.d.mts +4 -4
- package/dist/types/storage.d.mts +1 -1
- package/dist/{types-C9b2HR5W.d.mts → types-Bc42CUyk.d.mts} +1 -2
- package/dist/types-Cc91nXuA.d.mts +132 -0
- package/dist/{fields-C7ahdGYf.d.mts → types-Csxga7d3.d.mts} +2 -123
- package/dist/{types-LNebCoJx.d.mts → types-DH0GupI7.d.mts} +8 -4
- package/dist/usage/index.d.mts +142 -0
- package/dist/usage/index.mjs +0 -0
- package/dist/utils/index.d.mts +220 -144
- package/dist/utils/index.mjs +3 -3
- package/dist/{utils-ChjYQpjn.mjs → utils-FhJ88TJE.mjs} +129 -39
- package/dist/{versioning-D_eBCfNr.d.mts → versioning-DLwklPlG.d.mts} +1 -7
- package/dist/{websocket-MlYlrYq0.d.mts → websocket-uuWqM6UY.d.mts} +5 -4
- package/llms.txt +39 -0
- package/package.json +453 -447
- package/skills/arc/SKILL.md +14 -8
- package/skills/arc/references/api-reference.md +34 -0
- package/skills/arc/references/auth.md +21 -4
- package/skills/arc/references/mcp.md +1 -1
- package/skills/arc/references/production.md +44 -2
- package/skills/arc-code-review/SKILL.md +1 -1
- package/skills/arc-code-review/references/anti-patterns.md +71 -18
- package/skills/arc-code-review/references/arc-cheatsheet.md +15 -2
- package/skills/arc-code-review/references/migration-recipes.md +7 -7
- package/skills/arc-code-review/references/mongokit-migration.md +4 -3
- package/skills/arc-module-publishing/SKILL.md +243 -0
- package/skills/arc-module-publishing/references/authoring-modules.md +327 -0
- package/skills/arc-module-publishing/references/package-manifest.md +157 -0
- package/skills/arc-module-publishing/references/publishing-workflow.md +182 -0
- package/dist/preloadResources-CHw3_oA_.mjs +0 -45
- package/dist/schemaConverter-U0MnO9vd.mjs +0 -110
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { t as __exportAll } from "./rolldown-runtime-8H4AJuhK.mjs";
|
|
1
2
|
import * as fs from "node:fs/promises";
|
|
2
3
|
import * as path from "node:path";
|
|
3
4
|
import { dirname, join } from "node:path";
|
|
@@ -17,7 +18,7 @@ function createAdapterTemplate(config) {
|
|
|
17
18
|
|
|
18
19
|
import { createMongooseAdapter } from '@classytic/mongokit/adapter';
|
|
19
20
|
import { buildCrudSchemasFromModel } from '@classytic/mongokit';
|
|
20
|
-
${ts ? "import type { Model } from 'mongoose';\nimport type { Repository } from '@classytic/mongokit';" : ""}
|
|
21
|
+
${ts ? "import type { DataAdapter } from '@classytic/repo-core/adapter';\nimport type { Model } from 'mongoose';\nimport type { Repository } from '@classytic/mongokit';" : ""}
|
|
21
22
|
|
|
22
23
|
/**
|
|
23
24
|
* Create a MongoKit-powered adapter for a resource.
|
|
@@ -30,7 +31,10 @@ ${ts ? "import type { Model } from 'mongoose';\nimport type { Repository } from
|
|
|
30
31
|
export function createAdapter${ts ? "<TDoc = unknown>" : ""}(
|
|
31
32
|
model${ts ? ": Model<TDoc>" : ""},
|
|
32
33
|
repository${ts ? ": Repository<TDoc>" : ""}
|
|
33
|
-
) {
|
|
34
|
+
)${ts ? ": DataAdapter<TDoc>" : ""} {
|
|
35
|
+
// Explicit return type keeps the declaration portable — under pnpm's
|
|
36
|
+
// non-hoisted layout the inferred type references mongokit's internal
|
|
37
|
+
// paths and tsc fails with TS2742/TS2883.
|
|
34
38
|
return createMongooseAdapter({
|
|
35
39
|
model,
|
|
36
40
|
repository,
|
|
@@ -73,6 +77,60 @@ export function createAdapter${ts ? "<TDoc = unknown>" : ""}(
|
|
|
73
77
|
`;
|
|
74
78
|
}
|
|
75
79
|
//#endregion
|
|
80
|
+
//#region src/cli/commands/init/templates/agents.ts
|
|
81
|
+
function agentsGuideTemplate(config) {
|
|
82
|
+
return `# CLAUDE.md — agent guide for this app
|
|
83
|
+
|
|
84
|
+
> This file doubles as AGENTS.md — copy or symlink if your tool reads that name.
|
|
85
|
+
|
|
86
|
+
**This is an \`@classytic/arc\` application.** Resources are DECLARED, not hand-rolled:
|
|
87
|
+
one \`defineResource()\` = REST + validation + auth + permissions + events + caching +
|
|
88
|
+
OpenAPI + MCP tools. Data layer: ${config.adapter === "mongokit" ? "@classytic/mongokit (Mongoose)" : "a custom DataAdapter kit"}.
|
|
89
|
+
|
|
90
|
+
## The one rule
|
|
91
|
+
|
|
92
|
+
**Hand-rolling is a bug.** Before writing ANY route, validation, pagination, auth check,
|
|
93
|
+
cron, counter, or audit code by hand, prove arc lacks it — check the table below, then
|
|
94
|
+
\`node_modules/@classytic/arc/skills/arc/SKILL.md\` (full framework guide + references).
|
|
95
|
+
|
|
96
|
+
## About to write X? Arc already has Y
|
|
97
|
+
|
|
98
|
+
| If you're about to write… | Use instead |
|
|
99
|
+
|---|---|
|
|
100
|
+
| \`fastify.get/post/...\` CRUD routes for an entity | \`defineResource({ name, adapter, permissions })\` |
|
|
101
|
+
| \`if (req.user.role !== 'admin') return 403\` | \`permissions: { update: requireRoles(['admin']) }\` |
|
|
102
|
+
| Reading \`req.user._id\` / org id directly | \`getUserId(scope)\` / \`requireOrgId(scope)\` from \`@classytic/arc/scope\` |
|
|
103
|
+
| Hand-written \`schema: { body }\` / JSON Schema | \`schemaOptions.fieldRules\` (or \`customSchemas\` for wire≠model) |
|
|
104
|
+
| toJSON transforms hiding \`password\`/internals | \`fieldRules: { password: { hidden: true } }\` |
|
|
105
|
+
| Manual \`req.query\` filter/sort/pagination parsing | the resource's \`queryParser\` (allow-listed fields) |
|
|
106
|
+
| Soft delete / slug lookup / tree / bulk / file upload | \`presets: ['softDelete', 'slugLookup', ...]\` |
|
|
107
|
+
| \`setInterval\` sweeps + hand-rolled leader lock | \`schedulesPlugin\` (\`@classytic/arc/plugins\`) |
|
|
108
|
+
| A background/queue worker entrypoint | \`createWorker(sameOptions)\` (\`@classytic/arc/factory\`) |
|
|
109
|
+
| Usage counters / quotas / plan rate limits | \`usagePlugin\` + \`requireQuota\` + \`rateLimit.plan\` |
|
|
110
|
+
| Audit log rows / "who changed what" endpoints | \`audit: true\` + \`history: true\` on the resource |
|
|
111
|
+
| Manual idempotency keys / dedupe | \`idempotencyPlugin\` |
|
|
112
|
+
| Event emitters / webhooks plumbing | \`eventPlugin\` + resource \`events:\` (+ outbox for guarantees) |
|
|
113
|
+
| Custom MCP/AI tool handlers | generated automatically from every resource |
|
|
114
|
+
|
|
115
|
+
## Load-bearing conventions
|
|
116
|
+
|
|
117
|
+
- \`request.user\` is \`undefined\` on public routes — always guard.
|
|
118
|
+
- Never read \`scope.organizationId\` directly — use \`@classytic/arc/scope\` accessors.
|
|
119
|
+
- The DB connection belongs in \`createApp({ beforeBoot })\` — not before \`createApp\`.
|
|
120
|
+
- Custom endpoints: \`routes:\`/\`actions:\` on the resource (they inherit auth/permissions/
|
|
121
|
+
OpenAPI/MCP) — a bare \`fastify.get()\` gets none of that.
|
|
122
|
+
|
|
123
|
+
## Commands
|
|
124
|
+
|
|
125
|
+
\`\`\`bash
|
|
126
|
+
npm run dev # watch mode
|
|
127
|
+
npm test # vitest
|
|
128
|
+
npx arc generate resource <name> # scaffold a resource (model/repo/resource files)
|
|
129
|
+
npx arc describe # list registered resources + routes
|
|
130
|
+
\`\`\`
|
|
131
|
+
`;
|
|
132
|
+
}
|
|
133
|
+
//#endregion
|
|
76
134
|
//#region src/cli/commands/init/templates/app.ts
|
|
77
135
|
function indexTemplate(config) {
|
|
78
136
|
const ts = config.typescript;
|
|
@@ -439,8 +497,8 @@ function betterAuthSetupTemplate(config) {
|
|
|
439
497
|
import { mongodbAdapter } from '@better-auth/mongo-adapter';` : "";
|
|
440
498
|
const stubsImport = useStubs ? `import { registerBetterAuthStubs } from '@classytic/mongokit/better-auth';` : "";
|
|
441
499
|
const pluginImports = [
|
|
442
|
-
useOrg ? `import { organization } from 'better-auth/plugins';` : "",
|
|
443
|
-
useBearer ? `import { bearer } from 'better-auth/plugins';` : "",
|
|
500
|
+
useOrg ? `import { organization } from 'better-auth/plugins/organization';` : "",
|
|
501
|
+
useBearer ? `import { bearer } from 'better-auth/plugins/bearer';` : "",
|
|
444
502
|
useApiKey ? `import { apiKey } from '@better-auth/api-key';` : ""
|
|
445
503
|
].filter(Boolean).join("\n");
|
|
446
504
|
const dbAdapter = useMongo ? config.typescript ? `database: mongodbAdapter(mongoose.connection.getClient().db() as unknown as never),` : `database: mongodbAdapter(mongoose.connection.getClient().db()),` : `// Configure your database adapter here
|
|
@@ -484,20 +542,13 @@ ${[
|
|
|
484
542
|
].filter(Boolean).join("\n")}
|
|
485
543
|
import config from '#config/index.js';
|
|
486
544
|
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
export function getAuth()${ts ? ": ReturnType<typeof betterAuth>" : ""} {
|
|
495
|
-
if (process.env.NODE_ENV === 'production' && !process.env.BETTER_AUTH_SECRET) {
|
|
496
|
-
throw new Error('BETTER_AUTH_SECRET is required in production (min 32 chars)');
|
|
497
|
-
}
|
|
498
|
-
|
|
499
|
-
if (!_auth) {
|
|
500
|
-
_auth = betterAuth({
|
|
545
|
+
// Factory keeps the CONCRETE betterAuth({...}) instantiation as the source
|
|
546
|
+
// of truth for the instance type — annotating with the wide
|
|
547
|
+
// \`Auth<BetterAuthOptions>\` (aka \`ReturnType<typeof betterAuth>\`) does not
|
|
548
|
+
// typecheck because Better Auth's \`Auth<T>\` is generic over the exact
|
|
549
|
+
// options object.
|
|
550
|
+
function createAuth() {
|
|
551
|
+
return betterAuth({
|
|
501
552
|
secret: config.betterAuth.secret,
|
|
502
553
|
baseURL: process.env.BETTER_AUTH_URL || \`http://localhost:\${config.server.port}\`,
|
|
503
554
|
basePath: '/api/auth',
|
|
@@ -545,6 +596,26 @@ ${orgPluginUsage}
|
|
|
545
596
|
enabled: process.env.NODE_ENV === 'production',
|
|
546
597
|
},
|
|
547
598
|
});
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
let _auth${ts ? ": ReturnType<typeof createAuth> | null" : ""} = null;
|
|
602
|
+
|
|
603
|
+
/**
|
|
604
|
+
* Get the Better Auth instance (lazy singleton)
|
|
605
|
+
*
|
|
606
|
+
* Must be called AFTER database connection is established.
|
|
607
|
+
*/
|
|
608
|
+
export function getAuth()${ts ? ": ReturnType<typeof createAuth>" : ""} {
|
|
609
|
+
if (process.env.NODE_ENV === 'production' && !process.env.BETTER_AUTH_SECRET) {
|
|
610
|
+
throw new Error('BETTER_AUTH_SECRET is required in production (min 32 chars)');
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
// Narrow through a local — assigning the module-level \`_auth\` directly
|
|
614
|
+
// would lose the non-null narrowing across the calls below.
|
|
615
|
+
let auth = _auth;
|
|
616
|
+
if (!auth) {
|
|
617
|
+
auth = createAuth();
|
|
618
|
+
_auth = auth;
|
|
548
619
|
${useStubs ? `
|
|
549
620
|
// Register stub Mongoose models for Better Auth's collections so
|
|
550
621
|
// \`.populate('user')\` / \`ref: 'organization'\` resolve against BA-owned
|
|
@@ -552,7 +623,7 @@ ${useStubs ? `
|
|
|
552
623
|
registerBetterAuthStubs(mongoose, {${stubPluginsList ? ` plugins: [${stubPluginsList}]` : ""}${stubExtras} });
|
|
553
624
|
` : ""} }
|
|
554
625
|
|
|
555
|
-
return
|
|
626
|
+
return auth;
|
|
556
627
|
}
|
|
557
628
|
|
|
558
629
|
export default getAuth;
|
|
@@ -692,6 +763,9 @@ export async function getUserProfile(request${ts ? ": FastifyRequest" : ""}, rep
|
|
|
692
763
|
try {
|
|
693
764
|
const requestUser = (request${ts ? " as unknown as { user?: { _id?: string; id?: string } }" : ""}).user;
|
|
694
765
|
const userId = requestUser?._id || requestUser?.id;
|
|
766
|
+
if (!userId) {
|
|
767
|
+
return reply.code(401).send({ error: 'Authentication required', code: 'arc.unauthorized' });
|
|
768
|
+
}
|
|
695
769
|
const user = await userRepository.getById(userId);
|
|
696
770
|
|
|
697
771
|
if (!user) {
|
|
@@ -712,6 +786,9 @@ export async function updateUserProfile(request${ts ? ": FastifyRequest" : ""},
|
|
|
712
786
|
try {
|
|
713
787
|
const requestUser = (request${ts ? " as unknown as { user?: { _id?: string; id?: string } }" : ""}).user;
|
|
714
788
|
const userId = requestUser?._id || requestUser?.id;
|
|
789
|
+
if (!userId) {
|
|
790
|
+
return reply.code(401).send({ error: 'Authentication required', code: 'arc.unauthorized' });
|
|
791
|
+
}
|
|
715
792
|
const updates${ts ? ": Record<string, unknown>" : ""} = { ...request.body${ts ? " as Record<string, unknown>" : ""} };
|
|
716
793
|
|
|
717
794
|
// Prevent updating protected fields — auth-managed only
|
|
@@ -840,20 +917,29 @@ function authTestTemplate(config) {
|
|
|
840
917
|
*/
|
|
841
918
|
|
|
842
919
|
import { describe, it, expect, beforeAll, afterAll } from 'vitest';
|
|
843
|
-
${config.adapter === "mongokit" ? "import mongoose from 'mongoose';\n" : ""}import { createAppInstance } from '../src/app.js';
|
|
920
|
+
${config.adapter === "mongokit" ? "import mongoose from 'mongoose';\nimport { MongoMemoryServer } from 'mongodb-memory-server';\n" : ""}import { createAppInstance } from '../src/app.js';
|
|
844
921
|
${ts ? "import type { FastifyInstance } from 'fastify';\n" : ""}
|
|
922
|
+
// Resources mount under the app's resourcePrefix (see src/app.${ts ? "ts" : "js"}).
|
|
923
|
+
const API = '/api';
|
|
924
|
+
|
|
845
925
|
describe('Auth', () => {
|
|
846
926
|
let app${ts ? ": FastifyInstance" : ""};
|
|
847
|
-
const testUser = {
|
|
927
|
+
${config.adapter === "mongokit" ? ` let mongo${ts ? ": MongoMemoryServer | undefined" : ""};\n` : ""} const testUser = {
|
|
848
928
|
name: 'Test User',
|
|
849
929
|
email: 'test@example.com',
|
|
850
930
|
password: 'password123',
|
|
851
931
|
};
|
|
852
932
|
|
|
853
933
|
beforeAll(async () => {
|
|
854
|
-
${config.adapter === "mongokit" ? `
|
|
855
|
-
|
|
856
|
-
|
|
934
|
+
${config.adapter === "mongokit" ? ` // In-memory MongoDB — no local mongod required. Set MONGODB_URI to use
|
|
935
|
+
// a real instance instead (e.g. a CI service container).
|
|
936
|
+
let uri = process.env.MONGODB_URI;
|
|
937
|
+
if (!uri) {
|
|
938
|
+
mongo = await MongoMemoryServer.create();
|
|
939
|
+
uri = mongo.getUri();
|
|
940
|
+
}
|
|
941
|
+
await mongoose.connect(uri);
|
|
942
|
+
// Clean up test data (matters when MONGODB_URI points at a shared DB)
|
|
857
943
|
await mongoose.connection.collection('users').deleteMany({ email: testUser.email });
|
|
858
944
|
` : ""}
|
|
859
945
|
app = await createAppInstance();
|
|
@@ -862,27 +948,29 @@ ${config.adapter === "mongokit" ? ` const testDbUri = process.env.MONGODB_URI
|
|
|
862
948
|
|
|
863
949
|
afterAll(async () => {
|
|
864
950
|
${config.adapter === "mongokit" ? ` await mongoose.connection.collection('users').deleteMany({ email: testUser.email });
|
|
951
|
+
await app.close();
|
|
865
952
|
await mongoose.connection.close();
|
|
866
|
-
|
|
867
|
-
|
|
953
|
+
await mongo?.stop();
|
|
954
|
+
` : ` await app.close();
|
|
955
|
+
`} });
|
|
868
956
|
|
|
869
957
|
describe('POST /auth/register', () => {
|
|
870
958
|
it('should register a new user', async () => {
|
|
871
959
|
const response = await app.inject({
|
|
872
960
|
method: 'POST',
|
|
873
|
-
url:
|
|
961
|
+
url: \`\${API}/auth/register\`,
|
|
874
962
|
payload: testUser,
|
|
875
963
|
});
|
|
876
964
|
|
|
877
965
|
expect(response.statusCode).toBe(201);
|
|
878
966
|
const body = JSON.parse(response.body);
|
|
879
|
-
expect(body.
|
|
967
|
+
expect(body.message).toBeDefined();
|
|
880
968
|
});
|
|
881
969
|
|
|
882
970
|
it('should reject duplicate email', async () => {
|
|
883
971
|
const response = await app.inject({
|
|
884
972
|
method: 'POST',
|
|
885
|
-
url:
|
|
973
|
+
url: \`\${API}/auth/register\`,
|
|
886
974
|
payload: testUser,
|
|
887
975
|
});
|
|
888
976
|
|
|
@@ -894,13 +982,13 @@ ${config.adapter === "mongokit" ? ` await mongoose.connection.collection('use
|
|
|
894
982
|
it('should login with valid credentials', async () => {
|
|
895
983
|
const response = await app.inject({
|
|
896
984
|
method: 'POST',
|
|
897
|
-
url:
|
|
985
|
+
url: \`\${API}/auth/login\`,
|
|
898
986
|
payload: { email: testUser.email, password: testUser.password },
|
|
899
987
|
});
|
|
900
988
|
|
|
901
989
|
expect(response.statusCode).toBe(200);
|
|
902
990
|
const body = JSON.parse(response.body);
|
|
903
|
-
expect(body.
|
|
991
|
+
expect(body.user).toBeDefined();
|
|
904
992
|
expect(body.accessToken).toBeDefined();
|
|
905
993
|
expect(body.refreshToken).toBeDefined();
|
|
906
994
|
});
|
|
@@ -908,7 +996,7 @@ ${config.adapter === "mongokit" ? ` await mongoose.connection.collection('use
|
|
|
908
996
|
it('should reject invalid credentials', async () => {
|
|
909
997
|
const response = await app.inject({
|
|
910
998
|
method: 'POST',
|
|
911
|
-
url:
|
|
999
|
+
url: \`\${API}/auth/login\`,
|
|
912
1000
|
payload: { email: testUser.email, password: 'wrongpassword' },
|
|
913
1001
|
});
|
|
914
1002
|
|
|
@@ -920,7 +1008,7 @@ ${config.adapter === "mongokit" ? ` await mongoose.connection.collection('use
|
|
|
920
1008
|
it('should require authentication', async () => {
|
|
921
1009
|
const response = await app.inject({
|
|
922
1010
|
method: 'GET',
|
|
923
|
-
url:
|
|
1011
|
+
url: \`\${API}/users/me\`,
|
|
924
1012
|
});
|
|
925
1013
|
|
|
926
1014
|
expect(response.statusCode).toBe(401);
|
|
@@ -952,11 +1040,16 @@ ${config.adapter === "mongokit" ? ` await mongoose.connection.collection('use
|
|
|
952
1040
|
* `installDependencies` (runs the package manager's `install` against
|
|
953
1041
|
* the declared ranges). One source — no drift.
|
|
954
1042
|
*/
|
|
1043
|
+
var dependency_plan_exports = /* @__PURE__ */ __exportAll({
|
|
1044
|
+
SCAFFOLD_DEP_VERSIONS: () => SCAFFOLD_DEP_VERSIONS,
|
|
1045
|
+
primeLatestScaffoldVersions: () => primeLatestScaffoldVersions,
|
|
1046
|
+
resolveScaffoldDependencies: () => resolveScaffoldDependencies
|
|
1047
|
+
});
|
|
955
1048
|
const SCAFFOLD_DEP_VERSIONS = {
|
|
956
1049
|
core: {
|
|
957
1050
|
"@classytic/arc": "^2.20.0",
|
|
958
|
-
"@classytic/primitives": "^0.
|
|
959
|
-
"@classytic/repo-core": "^0.
|
|
1051
|
+
"@classytic/primitives": "^0.11.0",
|
|
1052
|
+
"@classytic/repo-core": "^0.8.0",
|
|
960
1053
|
"@fastify/cors": "^11.2.0",
|
|
961
1054
|
"@fastify/helmet": "^13.0.2",
|
|
962
1055
|
"@fastify/rate-limit": "^10.3.0",
|
|
@@ -976,7 +1069,7 @@ const SCAFFOLD_DEP_VERSIONS = {
|
|
|
976
1069
|
},
|
|
977
1070
|
authBetterAuthApiKey: { "@better-auth/api-key": "^1.6.11" },
|
|
978
1071
|
adapterMongokit: {
|
|
979
|
-
"@classytic/mongokit": "^3.
|
|
1072
|
+
"@classytic/mongokit": "^3.21.0",
|
|
980
1073
|
mongoose: "^9.6.2"
|
|
981
1074
|
},
|
|
982
1075
|
devCommon: {
|
|
@@ -988,7 +1081,7 @@ const SCAFFOLD_DEP_VERSIONS = {
|
|
|
988
1081
|
devTypescript: {
|
|
989
1082
|
"@types/node": "^22.10.0",
|
|
990
1083
|
tsx: "^4.22.3",
|
|
991
|
-
typescript: "^
|
|
1084
|
+
typescript: "^7.0.2"
|
|
992
1085
|
},
|
|
993
1086
|
typesJwt: { "@types/bcryptjs": "^3.0.0" }
|
|
994
1087
|
};
|
|
@@ -1036,6 +1129,51 @@ function floorLessThan(a, b) {
|
|
|
1036
1129
|
* floor>` when the static pin's floor is BELOW the declared peer floor
|
|
1037
1130
|
* (never lowered — static pins may legitimately be ahead of the floor).
|
|
1038
1131
|
*/
|
|
1132
|
+
const latestCache = /* @__PURE__ */ new Map();
|
|
1133
|
+
function allScaffoldPackageNames() {
|
|
1134
|
+
const names = /* @__PURE__ */ new Set();
|
|
1135
|
+
for (const group of Object.values(SCAFFOLD_DEP_VERSIONS)) for (const name of Object.keys(group)) names.add(name);
|
|
1136
|
+
return [...names];
|
|
1137
|
+
}
|
|
1138
|
+
async function primeLatestScaffoldVersions(options) {
|
|
1139
|
+
const timeoutMs = options?.timeoutMs ?? 3500;
|
|
1140
|
+
const registry = (options?.registry ?? process.env.npm_config_registry ?? "https://registry.npmjs.org").replace(/\/$/, "");
|
|
1141
|
+
const names = allScaffoldPackageNames();
|
|
1142
|
+
const results = await Promise.allSettled(names.map(async (name) => {
|
|
1143
|
+
const res = await fetch(`${registry}/${name}/latest`, {
|
|
1144
|
+
signal: AbortSignal.timeout(timeoutMs),
|
|
1145
|
+
headers: { accept: "application/json" }
|
|
1146
|
+
});
|
|
1147
|
+
if (!res.ok) throw new Error(String(res.status));
|
|
1148
|
+
const body = await res.json();
|
|
1149
|
+
if (typeof body.version === "string" && /^\d+\.\d+\.\d+$/.test(body.version)) latestCache.set(name, body.version);
|
|
1150
|
+
}));
|
|
1151
|
+
return {
|
|
1152
|
+
resolved: latestCache.size,
|
|
1153
|
+
total: results.length
|
|
1154
|
+
};
|
|
1155
|
+
}
|
|
1156
|
+
function parseBareVersion(v) {
|
|
1157
|
+
const m = /^(\d+)\.(\d+)\.(\d+)$/.exec(v);
|
|
1158
|
+
return m ? [
|
|
1159
|
+
Number(m[1]),
|
|
1160
|
+
Number(m[2]),
|
|
1161
|
+
Number(m[3])
|
|
1162
|
+
] : void 0;
|
|
1163
|
+
}
|
|
1164
|
+
function applyLatestOverlay(dependencies) {
|
|
1165
|
+
if (latestCache.size === 0) return;
|
|
1166
|
+
for (const [name, currentRange] of Object.entries(dependencies)) {
|
|
1167
|
+
const latest = latestCache.get(name);
|
|
1168
|
+
if (!latest || latest.includes("-")) continue;
|
|
1169
|
+
const latestV = parseBareVersion(latest);
|
|
1170
|
+
const currentFloor = parseFloor(currentRange);
|
|
1171
|
+
if (!latestV || !currentFloor) continue;
|
|
1172
|
+
if (!floorLessThan(currentFloor, latestV)) continue;
|
|
1173
|
+
if (latestV[0] !== currentFloor[0] && !name.startsWith("@classytic/")) continue;
|
|
1174
|
+
dependencies[name] = `^${latest}`;
|
|
1175
|
+
}
|
|
1176
|
+
}
|
|
1039
1177
|
function overlayLiveArcVersions(dependencies) {
|
|
1040
1178
|
const own = readOwnPackageJson();
|
|
1041
1179
|
if (!own) return;
|
|
@@ -1061,6 +1199,7 @@ function resolveScaffoldDependencies(config) {
|
|
|
1061
1199
|
const devDependencies = { ...SCAFFOLD_DEP_VERSIONS.devCommon };
|
|
1062
1200
|
if (config.auth === "better-auth") {
|
|
1063
1201
|
Object.assign(dependencies, SCAFFOLD_DEP_VERSIONS.authBetterAuth);
|
|
1202
|
+
devDependencies["@fastify/jwt"] = SCAFFOLD_DEP_VERSIONS.authJwt["@fastify/jwt"];
|
|
1064
1203
|
if (config.apiKey) Object.assign(dependencies, SCAFFOLD_DEP_VERSIONS.authBetterAuthApiKey);
|
|
1065
1204
|
} else {
|
|
1066
1205
|
Object.assign(dependencies, SCAFFOLD_DEP_VERSIONS.authJwt);
|
|
@@ -1068,6 +1207,8 @@ function resolveScaffoldDependencies(config) {
|
|
|
1068
1207
|
}
|
|
1069
1208
|
if (config.adapter === "mongokit") Object.assign(dependencies, SCAFFOLD_DEP_VERSIONS.adapterMongokit);
|
|
1070
1209
|
if (config.typescript) Object.assign(devDependencies, SCAFFOLD_DEP_VERSIONS.devTypescript);
|
|
1210
|
+
applyLatestOverlay(dependencies);
|
|
1211
|
+
applyLatestOverlay(devDependencies);
|
|
1071
1212
|
overlayLiveArcVersions(dependencies);
|
|
1072
1213
|
return {
|
|
1073
1214
|
dependencies: sortByKey(dependencies),
|
|
@@ -1159,6 +1300,7 @@ function tsconfigTemplate() {
|
|
|
1159
1300
|
declarationMap: true,
|
|
1160
1301
|
sourceMap: true,
|
|
1161
1302
|
resolveJsonModule: true,
|
|
1303
|
+
types: ["node"],
|
|
1162
1304
|
paths: {
|
|
1163
1305
|
"#shared/*": ["./src/shared/*"],
|
|
1164
1306
|
"#resources/*": ["./src/resources/*"],
|
|
@@ -2053,7 +2195,13 @@ import { describe, it, beforeAll, afterAll } from 'vitest';
|
|
|
2053
2195
|
import { createTestApp, expectArc } from '@classytic/arc/testing';
|
|
2054
2196
|
import type { TestAppContext${ts ? ", TestAuthProvider" : ""} } from '@classytic/arc/testing';
|
|
2055
2197
|
import exampleResource from '../src/resources/example/example.resource.js';
|
|
2056
|
-
|
|
2198
|
+
${config.tenant === "multi" ? `
|
|
2199
|
+
// Valid ObjectId hex — the example model types organizationId and createdBy
|
|
2200
|
+
// as ObjectId (createdBy is auto-stamped from the caller's user id).
|
|
2201
|
+
const TEST_ORG_ID = '64f000000000000000000001';
|
|
2202
|
+
const MANAGER_ID = '64f0000000000000000000a1';
|
|
2203
|
+
const STAFF_ID = '64f0000000000000000000a2';
|
|
2204
|
+
` : ""}
|
|
2057
2205
|
describe('Example Resource', () => {
|
|
2058
2206
|
let ctx${ts ? ": TestAppContext" : ""};
|
|
2059
2207
|
// Narrowed once below — \`authMode: 'jwt'\` guarantees a provider, but
|
|
@@ -2068,17 +2216,70 @@ ${config.adapter === "mongokit" ? " connectMongoose: true,\n" : ""} });
|
|
|
2068
2216
|
|
|
2069
2217
|
if (!ctx.auth) throw new Error('Test auth provider was not configured');
|
|
2070
2218
|
auth = ctx.auth;
|
|
2071
|
-
|
|
2219
|
+
${config.tenant === "multi" ? `
|
|
2220
|
+
// Arc's JWT auth builds a MEMBER scope straight from token claims —
|
|
2221
|
+
// \`organizationId\` + \`orgRoles\` land on \`request.scope\`. The org-staff
|
|
2222
|
+
// permission set reads those org roles; the tenant preset filters and
|
|
2223
|
+
// stamps rows by the org id.
|
|
2224
|
+
auth.register('manager', {
|
|
2225
|
+
user: { id: MANAGER_ID, role: 'user', organizationId: TEST_ORG_ID, orgRoles: ['manager'] },
|
|
2226
|
+
orgId: TEST_ORG_ID,
|
|
2227
|
+
});
|
|
2228
|
+
auth.register('staff', {
|
|
2229
|
+
user: { id: STAFF_ID, role: 'user', organizationId: TEST_ORG_ID, orgRoles: ['staff'] },
|
|
2230
|
+
orgId: TEST_ORG_ID,
|
|
2231
|
+
});
|
|
2232
|
+
` : `
|
|
2072
2233
|
// Arc's permission engine reads singular user.role — string,
|
|
2073
2234
|
// comma-separated string, or array all normalise via getUserRoles().
|
|
2235
|
+
// The id must be ObjectId-shaped: the example model's createdBy is an
|
|
2236
|
+
// ObjectId, auto-stamped from the caller's user id.
|
|
2074
2237
|
auth.register('admin', {
|
|
2075
|
-
user: { id: '
|
|
2076
|
-
orgId: 'org-1',
|
|
2238
|
+
user: { id: '64f0000000000000000000ad', role: 'admin' },
|
|
2077
2239
|
});
|
|
2078
|
-
});
|
|
2240
|
+
`} });
|
|
2079
2241
|
|
|
2080
2242
|
afterAll(() => ctx.close());
|
|
2243
|
+
${config.tenant === "multi" ? `
|
|
2244
|
+
describe('GET /examples', () => {
|
|
2245
|
+
it('should reject unauthenticated listing (org-staff gated)', async () => {
|
|
2246
|
+
const res = await ctx.app.inject({ method: 'GET', url: '/examples' });
|
|
2247
|
+
expectArc(res).unauthorized();
|
|
2248
|
+
});
|
|
2249
|
+
|
|
2250
|
+
it('should list for an org member (rows scoped to their org)', async () => {
|
|
2251
|
+
const res = await ctx.app.inject({
|
|
2252
|
+
method: 'GET',
|
|
2253
|
+
url: '/examples',
|
|
2254
|
+
headers: auth.as('staff').headers,
|
|
2255
|
+
});
|
|
2256
|
+
expectArc(res).ok().paginated();
|
|
2257
|
+
});
|
|
2258
|
+
});
|
|
2259
|
+
|
|
2260
|
+
describe('POST /examples', () => {
|
|
2261
|
+
it('should require authentication', async () => {
|
|
2262
|
+
const res = await ctx.app.inject({
|
|
2263
|
+
method: 'POST',
|
|
2264
|
+
url: '/examples',
|
|
2265
|
+
payload: { name: 'Test Example' },
|
|
2266
|
+
});
|
|
2267
|
+
expectArc(res).unauthorized();
|
|
2268
|
+
});
|
|
2081
2269
|
|
|
2270
|
+
it('should create for an org manager — organizationId stamped server-side', async () => {
|
|
2271
|
+
const res = await ctx.app.inject({
|
|
2272
|
+
method: 'POST',
|
|
2273
|
+
url: '/examples',
|
|
2274
|
+
headers: auth.as('manager').headers,
|
|
2275
|
+
// No organizationId in the body — the tenant preset injects it from
|
|
2276
|
+
// the caller's scope (the field is systemManaged in the schema).
|
|
2277
|
+
payload: { name: 'Test Example' },
|
|
2278
|
+
});
|
|
2279
|
+
expectArc(res).ok(201);
|
|
2280
|
+
});
|
|
2281
|
+
});
|
|
2282
|
+
` : `
|
|
2082
2283
|
describe('GET /examples', () => {
|
|
2083
2284
|
it('should return a list of examples (public)', async () => {
|
|
2084
2285
|
const res = await ctx.app.inject({ method: 'GET', url: '/examples' });
|
|
@@ -2103,9 +2304,10 @@ ${config.adapter === "mongokit" ? " connectMongoose: true,\n" : ""} });
|
|
|
2103
2304
|
headers: auth.as('admin').headers,
|
|
2104
2305
|
payload: { name: 'Test Example' },
|
|
2105
2306
|
});
|
|
2106
|
-
expectArc(res).ok();
|
|
2307
|
+
expectArc(res).ok(201);
|
|
2107
2308
|
});
|
|
2108
2309
|
});
|
|
2310
|
+
`}
|
|
2109
2311
|
|
|
2110
2312
|
// Add more tests as needed:
|
|
2111
2313
|
// - GET /examples/:id (expectArc(res).ok().hasData({ name: '...' }))
|
|
@@ -2495,113 +2697,35 @@ function flexibleMultiTenantPresetTemplate(config) {
|
|
|
2495
2697
|
return `/**
|
|
2496
2698
|
* Flexible Multi-Tenant Preset
|
|
2497
2699
|
*
|
|
2498
|
-
*
|
|
2700
|
+
* Thin wrapper around arc's built-in 'multiTenantPreset' with public reads:
|
|
2701
|
+
*
|
|
2702
|
+
* - list/get WITHOUT org context → allowed through unfiltered (public data)
|
|
2703
|
+
* - org context present → rows filtered + stamped to the caller's org
|
|
2704
|
+
* - elevated (platform admin) → unfiltered, cross-tenant
|
|
2705
|
+
* - create/update fail closed without org context, and update overwrites any
|
|
2706
|
+
* client-supplied tenant field (no cross-tenant document hops)
|
|
2499
2707
|
*
|
|
2500
|
-
*
|
|
2501
|
-
*
|
|
2502
|
-
*
|
|
2503
|
-
*
|
|
2708
|
+
* The built-in preset also registers 'systemManaged' field rules for the
|
|
2709
|
+
* tenant field, so generated request schemas never demand it in the body —
|
|
2710
|
+
* the server stamps it from the caller's scope instead. Don't hand-roll
|
|
2711
|
+
* tenant middleware: without those field rules Fastify's validation rejects
|
|
2712
|
+
* creates before injection can run.
|
|
2504
2713
|
*
|
|
2505
|
-
*
|
|
2714
|
+
* Want members-only reads? Drop 'allowPublic' below, or use
|
|
2715
|
+
* 'multiTenantPreset({ tenantField })' directly on the resource.
|
|
2506
2716
|
*/
|
|
2507
2717
|
${ts ? `
|
|
2508
|
-
import {
|
|
2509
|
-
import type { RequestScope } from '@classytic/arc/scope';
|
|
2718
|
+
import { multiTenantPreset } from '@classytic/arc/presets/tenant';
|
|
2510
2719
|
|
|
2511
2720
|
interface FlexibleMultiTenantOptions {
|
|
2512
2721
|
tenantField?: string;
|
|
2513
2722
|
}
|
|
2514
|
-
|
|
2515
|
-
interface PresetMiddlewares {
|
|
2516
|
-
list: ((request: any, reply: any) => Promise<void>)[];
|
|
2517
|
-
get: ((request: any, reply: any) => Promise<void>)[];
|
|
2518
|
-
create: ((request: any, reply: any) => Promise<void>)[];
|
|
2519
|
-
update: ((request: any, reply: any) => Promise<void>)[];
|
|
2520
|
-
delete: ((request: any, reply: any) => Promise<void>)[];
|
|
2521
|
-
}
|
|
2522
|
-
|
|
2523
|
-
interface Preset {
|
|
2524
|
-
[key: string]: unknown;
|
|
2525
|
-
name: string;
|
|
2526
|
-
middlewares: PresetMiddlewares;
|
|
2527
|
-
}
|
|
2528
2723
|
` : `
|
|
2529
|
-
const {
|
|
2724
|
+
const { multiTenantPreset } = require('@classytic/arc/presets/tenant');
|
|
2530
2725
|
`}
|
|
2531
|
-
|
|
2532
|
-
* Create flexible tenant filter middleware.
|
|
2533
|
-
* Only filters when org context is present.
|
|
2534
|
-
*/
|
|
2535
|
-
function createFlexibleTenantFilter(tenantField${ts ? ": string" : ""}) {
|
|
2536
|
-
return async (request${ts ? ": any" : ""}, reply${ts ? ": any" : ""}) => {
|
|
2537
|
-
const scope${ts ? ": RequestScope" : ""} = request.scope ?? { kind: 'public' };
|
|
2538
|
-
|
|
2539
|
-
// Elevated scope — platform admin sees all, no filter
|
|
2540
|
-
if (isElevated(scope)) {
|
|
2541
|
-
request.log?.debug?.({ msg: 'Elevated scope — no tenant filter' });
|
|
2542
|
-
return;
|
|
2543
|
-
}
|
|
2544
|
-
|
|
2545
|
-
// Member scope — filter by org
|
|
2546
|
-
if (isMember(scope)) {
|
|
2547
|
-
request.query = request.query ?? {};
|
|
2548
|
-
request.query._policyFilters = {
|
|
2549
|
-
...(request.query._policyFilters ?? {}),
|
|
2550
|
-
[tenantField]: scope.organizationId,
|
|
2551
|
-
};
|
|
2552
|
-
request.log?.debug?.({ msg: 'Tenant filter applied', orgId: scope.organizationId, tenantField });
|
|
2553
|
-
return;
|
|
2554
|
-
}
|
|
2555
|
-
|
|
2556
|
-
// Public / authenticated — no org context, show all data (public routes)
|
|
2557
|
-
request.log?.debug?.({ msg: 'No org context — showing all data' });
|
|
2558
|
-
};
|
|
2559
|
-
}
|
|
2560
|
-
|
|
2561
|
-
/**
|
|
2562
|
-
* Create tenant injection middleware.
|
|
2563
|
-
* Injects tenant ID into request body on create.
|
|
2564
|
-
*/
|
|
2565
|
-
function createTenantInjection(tenantField${ts ? ": string" : ""}) {
|
|
2566
|
-
return async (request${ts ? ": any" : ""}, reply${ts ? ": any" : ""}) => {
|
|
2567
|
-
const scope${ts ? ": RequestScope" : ""} = request.scope ?? { kind: 'public' };
|
|
2568
|
-
const orgId = getOrgId(scope);
|
|
2569
|
-
|
|
2570
|
-
// Fail-closed: Require orgId for create operations
|
|
2571
|
-
if (!orgId) {
|
|
2572
|
-
return reply.code(403).send({
|
|
2573
|
-
error: 'Organization context required to create resources',
|
|
2574
|
-
code: 'arc.org_required',
|
|
2575
|
-
});
|
|
2576
|
-
}
|
|
2577
|
-
|
|
2578
|
-
if (request.body) {
|
|
2579
|
-
request.body[tenantField] = orgId;
|
|
2580
|
-
}
|
|
2581
|
-
};
|
|
2582
|
-
}
|
|
2583
|
-
|
|
2584
|
-
/**
|
|
2585
|
-
* Flexible Multi-Tenant Preset
|
|
2586
|
-
*
|
|
2587
|
-
* @param options.tenantField - Field name in database (default: 'organizationId')
|
|
2588
|
-
*/
|
|
2589
|
-
export function flexibleMultiTenantPreset(options${ts ? ": FlexibleMultiTenantOptions = {}" : " = {}"})${ts ? ": Preset" : ""} {
|
|
2726
|
+
export function flexibleMultiTenantPreset(options${ts ? ": FlexibleMultiTenantOptions = {}" : " = {}"}) {
|
|
2590
2727
|
const { tenantField = 'organizationId' } = options;
|
|
2591
|
-
|
|
2592
|
-
const tenantFilter = createFlexibleTenantFilter(tenantField);
|
|
2593
|
-
const tenantInjection = createTenantInjection(tenantField);
|
|
2594
|
-
|
|
2595
|
-
return {
|
|
2596
|
-
name: 'flexibleMultiTenant',
|
|
2597
|
-
middlewares: {
|
|
2598
|
-
list: [tenantFilter],
|
|
2599
|
-
get: [tenantFilter],
|
|
2600
|
-
create: [tenantInjection],
|
|
2601
|
-
update: [tenantFilter],
|
|
2602
|
-
delete: [tenantFilter],
|
|
2603
|
-
},
|
|
2604
|
-
};
|
|
2728
|
+
return multiTenantPreset({ tenantField, allowPublic: ['list', 'get'] });
|
|
2605
2729
|
}
|
|
2606
2730
|
|
|
2607
2731
|
export default flexibleMultiTenantPreset;
|
|
@@ -3013,6 +3137,7 @@ async function createProjectStructure(projectPath, config) {
|
|
|
3013
3137
|
files["docker-compose.yml"] = dockerComposeTemplate(config);
|
|
3014
3138
|
}
|
|
3015
3139
|
if (config.edge) files["wrangler.toml"] = wranglerTemplate(config);
|
|
3140
|
+
files["CLAUDE.md"] = agentsGuideTemplate(config);
|
|
3016
3141
|
files[".arcrc"] = `${JSON.stringify({
|
|
3017
3142
|
adapter: config.adapter,
|
|
3018
3143
|
auth: config.auth,
|
|
@@ -3312,6 +3437,14 @@ async function init(options = {}) {
|
|
|
3312
3437
|
}
|
|
3313
3438
|
const packageManager = detectPackageManager();
|
|
3314
3439
|
console.log(`Using package manager: ${packageManager}\n`);
|
|
3440
|
+
if (!process.env.VITEST && process.env.ARC_INIT_OFFLINE !== "1") {
|
|
3441
|
+
const { primeLatestScaffoldVersions } = await Promise.resolve().then(() => dependency_plan_exports);
|
|
3442
|
+
const { resolved, total } = await primeLatestScaffoldVersions().catch(() => ({
|
|
3443
|
+
resolved: 0,
|
|
3444
|
+
total: 0
|
|
3445
|
+
}));
|
|
3446
|
+
console.log(resolved > 0 ? `Resolved ${resolved}/${total} dependency versions from npm (latest stable)\n` : "Registry unreachable — using pinned fallback versions\n");
|
|
3447
|
+
}
|
|
3315
3448
|
await createProjectStructure(projectPath, config);
|
|
3316
3449
|
if (!options.skipInstall) {
|
|
3317
3450
|
console.log("\n📥 Installing dependencies...\n");
|
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
import { n as DomainEvent } from "../EventTransport-
|
|
2
|
-
import { c as WebSocketClient, l as WebSocketMessage } from "../websocket-
|
|
1
|
+
import { n as DomainEvent } from "../EventTransport-QmY7A258.mjs";
|
|
2
|
+
import { c as WebSocketClient, l as WebSocketMessage } from "../websocket-uuWqM6UY.mjs";
|
|
3
3
|
import { FastifyPluginAsync, FastifyRequest } from "fastify";
|
|
4
|
-
|
|
5
4
|
//#region src/integrations/event-gateway.d.ts
|
|
6
5
|
interface EventGatewayOptions {
|
|
7
6
|
/** Require auth for all real-time connections (default: true) */
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { c as WebSocketClient, l as WebSocketMessage, u as WebSocketPluginOptions } from "../websocket-
|
|
1
|
+
import { c as WebSocketClient, l as WebSocketMessage, u as WebSocketPluginOptions } from "../websocket-uuWqM6UY.mjs";
|
|
2
2
|
import { EventGatewayOptions } from "./event-gateway.mjs";
|
|
3
3
|
import { JobDefinition, JobDispatchOptions, JobDispatcher, JobMeta, JobsPluginOptions, QueueStats } from "./jobs.mjs";
|
|
4
|
-
import { c as McpAuthResult, d as PromptDefinition, g as ToolDefinition, h as ToolContext, l as McpPluginOptions, m as ToolAnnotations, n as CallToolResult, o as CrudOperation, r as CreateMcpServerConfig, t as BetterAuthHandler, u as McpResourceConfig } from "../types-
|
|
4
|
+
import { c as McpAuthResult, d as PromptDefinition, g as ToolDefinition, h as ToolContext, l as McpPluginOptions, m as ToolAnnotations, n as CallToolResult, o as CrudOperation, r as CreateMcpServerConfig, t as BetterAuthHandler, u as McpResourceConfig } from "../types-Bc42CUyk.mjs";
|
|
5
5
|
import { StreamlinePluginOptions, WorkflowLike, WorkflowRunLike } from "./streamline.mjs";
|
|
6
6
|
import { WebhookDeliveryRecord, WebhookManager, WebhookPluginOptions, WebhookStore, WebhookSubscription } from "./webhooks.mjs";
|
|
7
7
|
export type { BetterAuthHandler, CallToolResult, CreateMcpServerConfig, CrudOperation, EventGatewayOptions, JobDefinition, JobDispatchOptions, JobDispatcher, JobMeta, JobsPluginOptions, McpAuthResult, McpPluginOptions, McpResourceConfig, PromptDefinition, QueueStats, StreamlinePluginOptions, ToolAnnotations, ToolContext, ToolDefinition, WebSocketClient, WebSocketMessage, WebSocketPluginOptions, WebhookDeliveryRecord, WebhookManager, WebhookPluginOptions, WebhookStore, WebhookSubscription, WorkflowLike, WorkflowRunLike };
|