@classytic/arc 2.11.2 → 2.11.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.
- package/LICENSE +21 -21
- package/README.md +20 -21
- package/bin/arc.js +2 -2
- package/dist/{BaseController-JNV08qOT.mjs → BaseController-swXruJ2_.mjs} +2 -2
- package/dist/EventTransport-BFQjw9pB.mjs +133 -0
- package/dist/{QueryCache-DOBNHBE0.d.mts → QueryCache-D41bfdBB.d.mts} +1 -1
- package/dist/{actionPermissions-C8YYU92K.mjs → actionPermissions-sUUKDhtP.mjs} +4 -2
- package/dist/adapters/index.d.mts +3 -3
- package/dist/adapters/index.mjs +2 -2
- package/dist/{adapters-D0tT2Tyo.mjs → adapters-DUUiiimH.mjs} +17 -2
- package/dist/audit/index.d.mts +2 -2
- package/dist/auth/index.d.mts +4 -4
- package/dist/auth/index.mjs +1 -1
- package/dist/auth/redis-session.d.mts +1 -1
- package/dist/cache/index.d.mts +3 -3
- package/dist/cli/commands/docs.mjs +1 -1
- package/dist/cli/commands/generate.d.mts +0 -2
- package/dist/cli/commands/generate.mjs +16 -16
- package/dist/cli/commands/init.mjs +149 -65
- package/dist/context/index.mjs +1 -1
- package/dist/core/index.d.mts +2 -2
- package/dist/core/index.mjs +3 -3
- package/dist/{core-DXdSSFW-.mjs → core-CbcQRIch.mjs} +25 -8
- package/dist/{createActionRouter-BwaSM0No.mjs → createActionRouter-CIKOcNA7.mjs} +74 -14
- package/dist/{createApp-P1d6rjPy.mjs → createApp-C9bRrqlX.mjs} +4 -6
- package/dist/defineEvent-D1Ky9M1D.mjs +188 -0
- package/dist/docs/index.d.mts +2 -2
- package/dist/docs/index.mjs +1 -1
- package/dist/{eventPlugin--5HIkdPU.mjs → eventPlugin-Cts2-Tfj.mjs} +9 -135
- package/dist/{eventPlugin-CUNjYYRY.d.mts → eventPlugin-DDJoNEPL.d.mts} +34 -7
- package/dist/events/index.d.mts +164 -5
- package/dist/events/index.mjs +138 -182
- package/dist/events/transports/redis-stream-entry.d.mts +1 -1
- package/dist/events/transports/redis-stream-entry.mjs +204 -31
- package/dist/events/transports/redis.d.mts +1 -1
- package/dist/factory/index.d.mts +1 -1
- package/dist/factory/index.mjs +1 -1
- package/dist/{fields-C8Y0XLAu.d.mts → fields-BRjxOAFp.d.mts} +1 -1
- package/dist/hooks/index.d.mts +1 -1
- package/dist/idempotency/index.d.mts +3 -3
- package/dist/idempotency/index.mjs +1 -1
- package/dist/idempotency/redis.d.mts +1 -1
- package/dist/{index-6u4_Gg6G.d.mts → index-CXXRbnf8.d.mts} +51 -5
- package/dist/{index-DdQ3O9Pg.d.mts → index-D9t1KNaB.d.mts} +2 -2
- package/dist/{index-BbMrcvGp.d.mts → index-Rg8axYPz.d.mts} +12 -4
- package/dist/{index-BdXnTPRj.d.mts → index-m8mOOlFW.d.mts} +3 -3
- package/dist/{index-BYCqHCVu.d.mts → index-rHjXmJar.d.mts} +3 -3
- package/dist/index.d.mts +7 -7
- package/dist/index.mjs +7 -7
- package/dist/integrations/event-gateway.d.mts +2 -2
- package/dist/integrations/index.d.mts +2 -2
- package/dist/integrations/mcp/index.d.mts +2 -2
- package/dist/integrations/mcp/index.mjs +1 -1
- package/dist/integrations/mcp/testing.d.mts +1 -1
- package/dist/integrations/mcp/testing.mjs +1 -1
- package/dist/integrations/websocket-redis.d.mts +1 -1
- package/dist/integrations/websocket.d.mts +1 -1
- package/dist/middleware/index.d.mts +1 -1
- package/dist/{openapi-C0L9ar7m.mjs → openapi-D7G1V7ex.mjs} +2 -2
- package/dist/org/index.d.mts +2 -2
- package/dist/permissions/index.d.mts +2 -2
- package/dist/permissions/index.mjs +1 -1
- package/dist/{permissions-B4vU9L0Q.mjs → permissions-gd_aUWrR.mjs} +42 -0
- package/dist/pipeline/index.d.mts +1 -1
- package/dist/plugins/index.d.mts +5 -5
- package/dist/plugins/index.mjs +1 -1
- package/dist/plugins/tracing-entry.d.mts +1 -1
- package/dist/plugins/tracing-entry.mjs +1 -1
- package/dist/presets/filesUpload.d.mts +4 -4
- package/dist/presets/filesUpload.mjs +1 -1
- package/dist/presets/index.d.mts +1 -1
- package/dist/presets/index.mjs +1 -1
- package/dist/presets/multiTenant.d.mts +1 -1
- package/dist/presets/search.d.mts +2 -2
- package/dist/presets/search.mjs +1 -1
- package/dist/{presets-k604Lj99.mjs → presets-Z7P5w4gF.mjs} +1 -1
- package/dist/{queryCachePlugin-BUXBSm4F.d.mts → queryCachePlugin-CqMdLI2-.d.mts} +2 -2
- package/dist/{redis-Cm1gnRDf.d.mts → redis-DiMkdHEl.d.mts} +1 -1
- package/dist/redis-stream-xTGxB2bm.d.mts +232 -0
- package/dist/registry/index.d.mts +1 -1
- package/dist/{requestContext-CfRkaxwf.mjs → requestContext-C5XeK3VA.mjs} +15 -0
- package/dist/{resourceToTools--okX6QBr.mjs → resourceToTools-CxNmI6xF.mjs} +7 -6
- package/dist/{routerShared-DeESFp4a.mjs → routerShared-BqLRb5l7.mjs} +60 -3
- package/dist/scope/index.d.mts +2 -2
- package/dist/testing/index.d.mts +2 -2
- package/dist/testing/index.mjs +1 -1
- package/dist/testing/storageContract.d.mts +1 -1
- package/dist/types/index.d.mts +4 -4
- package/dist/types/storage.d.mts +1 -1
- package/dist/{types-9beEMe25.d.mts → types-BQ9TJQNy.d.mts} +1 -1
- package/dist/{types-BH7dEGvU.d.mts → types-D7KpfiL1.d.mts} +10 -10
- package/dist/utils/index.d.mts +1 -1
- package/dist/utils/index.mjs +1 -1
- package/dist/{utils-D3Yxnrwr.mjs → utils-CcYTj09l.mjs} +1 -1
- package/dist/{versioning-M9lNLhO8.d.mts → versioning-DsglKfM_.d.mts} +1 -1
- package/package.json +3 -1
- package/skills/arc/SKILL.md +409 -769
- package/skills/arc/references/events.md +489 -489
- package/dist/redis-stream-CM8TXTix.d.mts +0 -110
- /package/dist/{EventTransport-CfVEGaEl.d.mts → EventTransport-CYNUXdCJ.d.mts} +0 -0
- /package/dist/{elevation-s5ykdNHr.d.mts → elevation-BQQXZ_VR.d.mts} +0 -0
- /package/dist/{errorHandler-Co3lnVmJ.d.mts → errorHandler-DEWmGWPz.d.mts} +0 -0
- /package/dist/{externalPaths-Bapitwvd.d.mts → externalPaths-BD5nw6St.d.mts} +0 -0
- /package/dist/{interface-CkkWm5uR.d.mts → interface-DfLGcus7.d.mts} +0 -0
- /package/dist/{interface-Da0r7Lna.d.mts → interface-beEtJyWM.d.mts} +0 -0
- /package/dist/{pluralize-BneOJkpi.mjs → pluralize-CWP6MB39.mjs} +0 -0
- /package/dist/{schemaIR-BlG9bY7v.mjs → schemaIR-Dy2p4MxS.mjs} +0 -0
- /package/dist/{sessionManager-D-oNWHz3.d.mts → sessionManager-C4Le_UB3.d.mts} +0 -0
- /package/dist/{storage-BwGQXUpd.d.mts → storage-Dfzt4VTl.d.mts} +0 -0
- /package/dist/{store-helpers-BhrzxvyQ.mjs → store-helpers-Cp4uKC1U.mjs} +0 -0
- /package/dist/{tracing-DokiEsuz.d.mts → tracing-QJVprktp.d.mts} +0 -0
- /package/dist/{types-tgR4Pt8F.d.mts → types-DDyTPc6y.d.mts} +0 -0
- /package/dist/{websocket-CyJ1VIFI.d.mts → websocket-ChC2rqe1.d.mts} +0 -0
|
@@ -26,7 +26,7 @@ async function init(options = {}) {
|
|
|
26
26
|
`);
|
|
27
27
|
const config = await gatherConfig(options);
|
|
28
28
|
console.log(`\nCreating project: ${config.name}`);
|
|
29
|
-
console.log(` Adapter: ${config.adapter === "mongokit" ? "MongoKit (MongoDB)" : "Custom"}`);
|
|
29
|
+
console.log(` Adapter: ${config.adapter === "mongokit" ? "MongoKit (MongoDB)" : "Custom / Drizzle-ready"}`);
|
|
30
30
|
console.log(` Auth: ${config.auth === "better-auth" ? "Better Auth (recommended)" : "Arc JWT"}`);
|
|
31
31
|
console.log(` Tenant: ${config.tenant === "multi" ? "Multi-tenant" : "Single-tenant"}`);
|
|
32
32
|
console.log(` Language: ${config.typescript ? "TypeScript" : "JavaScript"}`);
|
|
@@ -87,42 +87,103 @@ function existsSync$1(filePath) {
|
|
|
87
87
|
}
|
|
88
88
|
}
|
|
89
89
|
/**
|
|
90
|
-
*
|
|
90
|
+
* Single source of truth for scaffolded project dependencies.
|
|
91
|
+
*
|
|
92
|
+
* Versions are pinned to the floor each subsystem requires — peer-dep
|
|
93
|
+
* minimums on Arc, kit minimums (mongokit ≥ 3.11, repo-core ≥ 0.2,
|
|
94
|
+
* mongoose ≥ 9.4.1), and major-version stable for the rest. The carets
|
|
95
|
+
* allow minor + patch upgrades without breaking arc's contract, while
|
|
96
|
+
* preventing the silent breakage of `@latest` on a kit floor bump.
|
|
97
|
+
*
|
|
98
|
+
* Used by both `packageJsonTemplate` (declares the deps in the generated
|
|
99
|
+
* `package.json` so `npm install` works without a pre-pass) and
|
|
100
|
+
* `installDependencies` (runs the package manager's `install` against
|
|
101
|
+
* the declared ranges). One source — no drift.
|
|
91
102
|
*/
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
"@classytic/arc
|
|
95
|
-
"fastify
|
|
96
|
-
"@fastify/
|
|
97
|
-
"@fastify/
|
|
98
|
-
"@fastify/
|
|
99
|
-
"@fastify/
|
|
100
|
-
"
|
|
101
|
-
"
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
103
|
+
const SCAFFOLD_DEP_VERSIONS = {
|
|
104
|
+
core: {
|
|
105
|
+
"@classytic/arc": "^2.11.3",
|
|
106
|
+
"@fastify/cors": "^11.0.0",
|
|
107
|
+
"@fastify/helmet": "^13.0.0",
|
|
108
|
+
"@fastify/rate-limit": "^10.0.0",
|
|
109
|
+
"@fastify/sensible": "^6.0.0",
|
|
110
|
+
"@fastify/under-pressure": "^9.0.0",
|
|
111
|
+
dotenv: "^17.0.0",
|
|
112
|
+
fastify: "^5.8.0"
|
|
113
|
+
},
|
|
114
|
+
authJwt: {
|
|
115
|
+
"@fastify/jwt": "^10.0.0",
|
|
116
|
+
bcryptjs: "^3.0.0"
|
|
117
|
+
},
|
|
118
|
+
authBetterAuth: {
|
|
119
|
+
"better-auth": "^1.6.0",
|
|
120
|
+
mongodb: "^6.10.0"
|
|
121
|
+
},
|
|
122
|
+
adapterMongokit: {
|
|
123
|
+
"@classytic/mongokit": "^3.11.0",
|
|
124
|
+
"@classytic/repo-core": "^0.2.0",
|
|
125
|
+
mongoose: "^9.4.1"
|
|
126
|
+
},
|
|
127
|
+
devCommon: {
|
|
128
|
+
"pino-pretty": "^13.0.0",
|
|
129
|
+
vitest: "^4.0.0"
|
|
130
|
+
},
|
|
131
|
+
devTypescript: {
|
|
132
|
+
"@types/node": "^22.0.0",
|
|
133
|
+
tsx: "^4.21.0",
|
|
134
|
+
typescript: "^5.6.0"
|
|
135
|
+
},
|
|
136
|
+
typesJwt: { "@types/bcryptjs": "^3.0.0" }
|
|
137
|
+
};
|
|
138
|
+
/**
|
|
139
|
+
* Resolve the dependency manifest for a scaffold configuration.
|
|
140
|
+
*
|
|
141
|
+
* Returns sorted records (alphabetical by package name) so the generated
|
|
142
|
+
* `package.json` is deterministic — diffs across re-runs stay clean.
|
|
143
|
+
*/
|
|
144
|
+
function resolveScaffoldDependencies(config) {
|
|
145
|
+
const dependencies = { ...SCAFFOLD_DEP_VERSIONS.core };
|
|
146
|
+
const devDependencies = { ...SCAFFOLD_DEP_VERSIONS.devCommon };
|
|
147
|
+
if (config.auth === "better-auth") Object.assign(dependencies, SCAFFOLD_DEP_VERSIONS.authBetterAuth);
|
|
148
|
+
else {
|
|
149
|
+
Object.assign(dependencies, SCAFFOLD_DEP_VERSIONS.authJwt);
|
|
150
|
+
if (config.typescript) Object.assign(devDependencies, SCAFFOLD_DEP_VERSIONS.typesJwt);
|
|
151
|
+
}
|
|
152
|
+
if (config.adapter === "mongokit") Object.assign(dependencies, SCAFFOLD_DEP_VERSIONS.adapterMongokit);
|
|
153
|
+
if (config.typescript) Object.assign(devDependencies, SCAFFOLD_DEP_VERSIONS.devTypescript);
|
|
154
|
+
return {
|
|
155
|
+
dependencies: sortByKey(dependencies),
|
|
156
|
+
devDependencies: sortByKey(devDependencies)
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* Sort a record alphabetically by key — package.json convention.
|
|
161
|
+
*/
|
|
162
|
+
function sortByKey(record) {
|
|
163
|
+
return Object.fromEntries(Object.entries(record).sort(([a], [b]) => a.localeCompare(b)));
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Install dependencies using the detected package manager.
|
|
167
|
+
*
|
|
168
|
+
* Dependencies are already declared in the generated `package.json` (see
|
|
169
|
+
* `packageJsonTemplate`), so a single plain `install` resolves the full
|
|
170
|
+
* tree. No two-pass `npm add` flow — the manifest is the source of truth.
|
|
171
|
+
*/
|
|
172
|
+
async function installDependencies(projectPath, _config, pm) {
|
|
110
173
|
console.log(` Installing dependencies...`);
|
|
111
|
-
await runCommand(
|
|
112
|
-
console.log(` Installing dev dependencies...`);
|
|
113
|
-
await runCommand(installDevCmd, projectPath);
|
|
174
|
+
await runCommand(getInstallCommand(pm), projectPath);
|
|
114
175
|
console.log(`\nDependencies installed successfully.`);
|
|
115
176
|
}
|
|
116
177
|
/**
|
|
117
|
-
* Get the install command for a package manager
|
|
178
|
+
* Get the plain `install` command for a package manager. Reads the declared
|
|
179
|
+
* dependencies from the project's `package.json`.
|
|
118
180
|
*/
|
|
119
|
-
function getInstallCommand(pm
|
|
120
|
-
const pkgList = packages.join(" ");
|
|
181
|
+
function getInstallCommand(pm) {
|
|
121
182
|
switch (pm) {
|
|
122
|
-
case "pnpm": return
|
|
123
|
-
case "yarn": return
|
|
124
|
-
case "bun": return
|
|
125
|
-
default: return
|
|
183
|
+
case "pnpm": return "pnpm install";
|
|
184
|
+
case "yarn": return "yarn install";
|
|
185
|
+
case "bun": return "bun install";
|
|
186
|
+
default: return "npm install";
|
|
126
187
|
}
|
|
127
188
|
}
|
|
128
189
|
/**
|
|
@@ -156,7 +217,7 @@ async function gatherConfig(options) {
|
|
|
156
217
|
try {
|
|
157
218
|
const name = options.name || await question("Project name: ") || "my-arc-app";
|
|
158
219
|
let adapter = options.adapter || "mongokit";
|
|
159
|
-
if (!options.adapter && !nonInteractive) adapter = await question("Database adapter [1=MongoKit (recommended), 2=Custom]: ") === "2" ? "custom" : "mongokit";
|
|
220
|
+
if (!options.adapter && !nonInteractive) adapter = await question("Database adapter [1=MongoKit (recommended), 2=Custom / Drizzle-ready]: ") === "2" ? "custom" : "mongokit";
|
|
160
221
|
let auth = options.auth || "better-auth";
|
|
161
222
|
if (!options.auth && !nonInteractive) auth = await question("Auth strategy [1=Better Auth (recommended), 2=Arc JWT]: ") === "2" ? "jwt" : "better-auth";
|
|
162
223
|
let tenant = options.tenant || "single";
|
|
@@ -171,12 +232,12 @@ async function gatherConfig(options) {
|
|
|
171
232
|
console.log(" MongoDB Atlas works with the raw driver (mongodb 6.15+ with nodejs_compat_v2),");
|
|
172
233
|
console.log(" but MongoKit depends on Mongoose. Options:");
|
|
173
234
|
console.log(" 1. Use AWS Lambda / Vercel Serverless (Node.js) — Mongoose works normally");
|
|
174
|
-
console.log(" 2. Use Cloudflare Hyperdrive + PostgreSQL (
|
|
235
|
+
console.log(" 2. Use Cloudflare Hyperdrive + PostgreSQL (wire sqlitekit/Drizzle via custom adapter)");
|
|
175
236
|
console.log(" 3. Continue with MongoKit — works on Lambda/Vercel, NOT on Cloudflare Workers");
|
|
176
237
|
console.log("");
|
|
177
238
|
if ((await question("Continue with MongoKit? [y/N]: ")).toLowerCase() !== "y") {
|
|
178
239
|
adapter = "custom";
|
|
179
|
-
console.log(" Switched to custom adapter.
|
|
240
|
+
console.log(" Switched to custom adapter. Wire sqlitekit/Drizzle here; Prisma remains experimental.");
|
|
180
241
|
}
|
|
181
242
|
}
|
|
182
243
|
return {
|
|
@@ -267,6 +328,7 @@ async function createProjectStructure(projectPath, config) {
|
|
|
267
328
|
}
|
|
268
329
|
}
|
|
269
330
|
function packageJsonTemplate(config) {
|
|
331
|
+
const { dependencies, devDependencies } = resolveScaffoldDependencies(config);
|
|
270
332
|
const scripts = config.typescript ? config.edge ? {
|
|
271
333
|
dev: "tsx watch src/index.ts",
|
|
272
334
|
build: "tsc",
|
|
@@ -309,6 +371,8 @@ function packageJsonTemplate(config) {
|
|
|
309
371
|
"#utils/*": "./src/utils/*"
|
|
310
372
|
},
|
|
311
373
|
scripts,
|
|
374
|
+
dependencies,
|
|
375
|
+
devDependencies,
|
|
312
376
|
engines: { node: ">=22" }
|
|
313
377
|
}, null, 2);
|
|
314
378
|
}
|
|
@@ -465,7 +529,7 @@ src/
|
|
|
465
529
|
│ ├── env.${ext} # Env loader (import first!)
|
|
466
530
|
│ └── index.${ext} # App config
|
|
467
531
|
├── shared/ # Shared utilities
|
|
468
|
-
│ ├── adapter.${ext} # ${config.adapter === "mongokit" ? "MongoKit adapter factory" : "Custom adapter"}
|
|
532
|
+
│ ├── adapter.${ext} # ${config.adapter === "mongokit" ? "MongoKit adapter factory" : "Custom / Drizzle-ready adapter"}
|
|
469
533
|
│ ├── permissions.${ext} # Permission helpers
|
|
470
534
|
│ └── presets/ # ${config.tenant === "multi" ? "Multi-tenant presets" : "Standard presets"}
|
|
471
535
|
├── plugins/ # App-specific plugins
|
|
@@ -995,29 +1059,31 @@ function customAdapterTemplate(config) {
|
|
|
995
1059
|
return `/**
|
|
996
1060
|
* Custom Adapter Factory
|
|
997
1061
|
*
|
|
998
|
-
*
|
|
1062
|
+
* Use this for sqlitekit/Drizzle, Prisma experiments, or any repository
|
|
1063
|
+
* that satisfies Arc's RepositoryLike contract.
|
|
999
1064
|
*/
|
|
1000
1065
|
|
|
1001
|
-
import {
|
|
1002
|
-
${ts ? "import type { Model } from 'mongoose';" : ""}
|
|
1066
|
+
${ts ? "import type { DataAdapter, RepositoryLike } from '@classytic/arc/adapters';" : ""}
|
|
1003
1067
|
|
|
1004
1068
|
/**
|
|
1005
|
-
* Create a custom adapter for a resource
|
|
1069
|
+
* Create a custom adapter for a resource.
|
|
1006
1070
|
*
|
|
1007
|
-
*
|
|
1008
|
-
* -
|
|
1009
|
-
*
|
|
1010
|
-
*
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1071
|
+
* Recommended SQL path:
|
|
1072
|
+
* - sqlitekit repository + Arc's createDrizzleAdapter for Drizzle tables
|
|
1073
|
+
*
|
|
1074
|
+
* Experimental path:
|
|
1075
|
+
* - Prisma can be wired with createPrismaAdapter, but keep it opt-in until
|
|
1076
|
+
* your app has integration coverage.
|
|
1077
|
+
*/
|
|
1078
|
+
export function createAdapter${ts ? "<TDoc = unknown>" : ""}(
|
|
1079
|
+
_source${ts ? ": unknown" : ""},
|
|
1080
|
+
repository${ts ? ": RepositoryLike<TDoc>" : ""}
|
|
1081
|
+
)${ts ? ": DataAdapter<TDoc>" : ""} {
|
|
1082
|
+
return {
|
|
1083
|
+
type: 'custom',
|
|
1084
|
+
name: 'custom-repository',
|
|
1019
1085
|
repository,
|
|
1020
|
-
}
|
|
1086
|
+
};
|
|
1021
1087
|
}
|
|
1022
1088
|
`;
|
|
1023
1089
|
}
|
|
@@ -1806,8 +1872,10 @@ describe('Example Resource', () => {
|
|
|
1806
1872
|
authMode: 'jwt',
|
|
1807
1873
|
${config.adapter === "mongokit" ? " connectMongoose: true,\n" : ""} });
|
|
1808
1874
|
|
|
1875
|
+
// Arc's permission engine reads singular user.role — string,
|
|
1876
|
+
// comma-separated string, or array all normalise via getUserRoles().
|
|
1809
1877
|
ctx.auth${ts ? "!" : ""}.register('admin', {
|
|
1810
|
-
user: { id: '1',
|
|
1878
|
+
user: { id: '1', role: 'admin' },
|
|
1811
1879
|
orgId: 'org-1',
|
|
1812
1880
|
});
|
|
1813
1881
|
});
|
|
@@ -1911,13 +1979,19 @@ userSchema.methods.removeOrganization = function(organizationId${ts ? ": Types.O
|
|
|
1911
1979
|
userSchema.index({ 'organizations.organizationId': 1 });
|
|
1912
1980
|
` : "";
|
|
1913
1981
|
const userType = ts ? `
|
|
1914
|
-
|
|
1982
|
+
const PLATFORM_ROLES = ['user', 'admin', 'superadmin'] as const;
|
|
1983
|
+
type PlatformRole = typeof PLATFORM_ROLES[number];
|
|
1915
1984
|
|
|
1985
|
+
/**
|
|
1986
|
+
* Comma-separated list of platform roles (Better Auth admin-plugin convention).
|
|
1987
|
+
* Single role: 'admin'. Multiple: 'admin,trainer'. Arc's permission engine
|
|
1988
|
+
* normalises both forms via getUserRoles() — see @classytic/arc/scope.
|
|
1989
|
+
*/
|
|
1916
1990
|
type User = {
|
|
1917
1991
|
name: string;
|
|
1918
1992
|
email: string;
|
|
1919
1993
|
password: string;
|
|
1920
|
-
|
|
1994
|
+
role: string;${config.tenant === "multi" ? `
|
|
1921
1995
|
organizations: UserOrganization[];` : ""}
|
|
1922
1996
|
resetPasswordToken?: string;
|
|
1923
1997
|
resetPasswordExpires?: Date;
|
|
@@ -1956,11 +2030,21 @@ const userSchema = new Schema${ts ? "<User, UserModel, UserMethods>" : ""}(
|
|
|
1956
2030
|
},
|
|
1957
2031
|
password: { type: String, required: true },
|
|
1958
2032
|
|
|
1959
|
-
// Platform
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
|
|
2033
|
+
// Platform role — singular field, matches Arc's permission engine
|
|
2034
|
+
// (req.user.role) and Better Auth's admin-plugin convention.
|
|
2035
|
+
// Comma-separated for multi-role users (e.g. 'admin,trainer');
|
|
2036
|
+
// getUserRoles() in @classytic/arc/scope normalises both forms.
|
|
2037
|
+
role: {
|
|
2038
|
+
type: String,
|
|
2039
|
+
required: true,
|
|
2040
|
+
default: 'user',
|
|
2041
|
+
index: true,
|
|
2042
|
+
validate: {
|
|
2043
|
+
validator: (v${ts ? ": string" : ""}) =>
|
|
2044
|
+
/^(user|admin|superadmin)(,(user|admin|superadmin))*$/.test(v),
|
|
2045
|
+
message: (props${ts ? ": { value: string }" : ""}) =>
|
|
2046
|
+
\`Invalid role "\${props.value}" — expected one or more of user|admin|superadmin\`,
|
|
2047
|
+
},
|
|
1964
2048
|
},
|
|
1965
2049
|
${orgSchema}
|
|
1966
2050
|
// Password reset
|
|
@@ -2379,7 +2463,7 @@ export async function register(request${ts ? ": FastifyRequest" : ""}, reply${ts
|
|
|
2379
2463
|
}
|
|
2380
2464
|
|
|
2381
2465
|
// Create user
|
|
2382
|
-
await userRepository.create({ name, email, password,
|
|
2466
|
+
await userRepository.create({ name, email, password, role: 'user' });
|
|
2383
2467
|
|
|
2384
2468
|
return reply.code(201).send({ success: true, message: 'User registered successfully' });
|
|
2385
2469
|
} catch (error) {
|
|
@@ -2504,10 +2588,10 @@ export async function updateUserProfile(request${ts ? ": FastifyRequest" : ""},
|
|
|
2504
2588
|
const userId = (request${ts ? " as any" : ""}).user?._id || (request${ts ? " as any" : ""}).user?.id;
|
|
2505
2589
|
const updates = { ...request.body${ts ? " as any" : ""} };
|
|
2506
2590
|
|
|
2507
|
-
// Prevent updating protected fields
|
|
2508
|
-
|
|
2509
|
-
|
|
2510
|
-
|
|
2591
|
+
// Prevent updating protected fields — auth-managed only
|
|
2592
|
+
delete updates.password;
|
|
2593
|
+
delete updates.role;
|
|
2594
|
+
delete updates.organizations;
|
|
2511
2595
|
|
|
2512
2596
|
const user = await userRepository.Model.findByIdAndUpdate(userId, updates, { new: true });
|
|
2513
2597
|
return reply.send({ success: true, data: user });
|
|
@@ -2596,7 +2680,7 @@ export const loginResponse = {
|
|
|
2596
2680
|
id: { type: 'string' },
|
|
2597
2681
|
name: { type: 'string' },
|
|
2598
2682
|
email: { type: 'string' },
|
|
2599
|
-
|
|
2683
|
+
role: { type: 'string' },
|
|
2600
2684
|
},
|
|
2601
2685
|
},
|
|
2602
2686
|
accessToken: { type: 'string' },
|
package/dist/context/index.mjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { t as requestContext } from "../requestContext-
|
|
1
|
+
import { t as requestContext } from "../requestContext-C5XeK3VA.mjs";
|
|
2
2
|
export { requestContext };
|
package/dist/core/index.d.mts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import { B as ResourceDefinition, Gt as TreeMixin, Ht as SoftDeleteExt, Jt as BulkExt, Kt as SlugExt, Ut as SoftDeleteMixin, V as defineResource, Vt as BaseController, Wt as TreeExt, Yt as BulkMixin, an as QueryResolverConfig, cn as AccessControl, in as QueryResolver, ln as AccessControlConfig, nn as BaseCrudController, on as BodySanitizer, qt as SlugMixin, rn as ListResult, sn as BodySanitizerConfig, tn as BaseControllerOptions } from "../index-
|
|
2
|
-
import { C as MAX_REGEX_LENGTH, D as RESERVED_QUERY_PARAMS, E as MutationOperation, O as SYSTEM_FIELDS, S as MAX_FILTER_DEPTH, T as MUTATION_OPERATIONS, _ as DEFAULT_UPDATE_METHOD, a as getControllerScope, b as HookOperation, c as createCrudRouter, d as CrudOperation, f as DEFAULT_ID_FIELD, g as DEFAULT_TENANT_FIELD, h as DEFAULT_SORT, i as getControllerContext, l as createPermissionMiddleware, m as DEFAULT_MAX_LIMIT, n as createFastifyHandler, o as sendControllerResponse, p as DEFAULT_LIMIT, r as createRequestContext, s as defineResourceVariants, t as createCrudHandlers, u as CRUD_OPERATIONS, v as HOOK_OPERATIONS, w as MAX_SEARCH_LENGTH, x as HookPhase, y as HOOK_PHASES } from "../index-
|
|
1
|
+
import { B as ResourceDefinition, Gt as TreeMixin, Ht as SoftDeleteExt, Jt as BulkExt, Kt as SlugExt, Ut as SoftDeleteMixin, V as defineResource, Vt as BaseController, Wt as TreeExt, Yt as BulkMixin, an as QueryResolverConfig, cn as AccessControl, in as QueryResolver, ln as AccessControlConfig, nn as BaseCrudController, on as BodySanitizer, qt as SlugMixin, rn as ListResult, sn as BodySanitizerConfig, tn as BaseControllerOptions } from "../index-CXXRbnf8.mjs";
|
|
2
|
+
import { C as MAX_REGEX_LENGTH, D as RESERVED_QUERY_PARAMS, E as MutationOperation, O as SYSTEM_FIELDS, S as MAX_FILTER_DEPTH, T as MUTATION_OPERATIONS, _ as DEFAULT_UPDATE_METHOD, a as getControllerScope, b as HookOperation, c as createCrudRouter, d as CrudOperation, f as DEFAULT_ID_FIELD, g as DEFAULT_TENANT_FIELD, h as DEFAULT_SORT, i as getControllerContext, l as createPermissionMiddleware, m as DEFAULT_MAX_LIMIT, n as createFastifyHandler, o as sendControllerResponse, p as DEFAULT_LIMIT, r as createRequestContext, s as defineResourceVariants, t as createCrudHandlers, u as CRUD_OPERATIONS, v as HOOK_OPERATIONS, w as MAX_SEARCH_LENGTH, x as HookPhase, y as HOOK_PHASES } from "../index-m8mOOlFW.mjs";
|
|
3
3
|
export { AccessControl, AccessControlConfig, BaseController, BaseControllerOptions, BaseCrudController, BodySanitizer, BodySanitizerConfig, BulkExt, BulkMixin, CRUD_OPERATIONS, CrudOperation, DEFAULT_ID_FIELD, DEFAULT_LIMIT, DEFAULT_MAX_LIMIT, DEFAULT_SORT, DEFAULT_TENANT_FIELD, DEFAULT_UPDATE_METHOD, HOOK_OPERATIONS, HOOK_PHASES, HookOperation, HookPhase, ListResult, MAX_FILTER_DEPTH, MAX_REGEX_LENGTH, MAX_SEARCH_LENGTH, MUTATION_OPERATIONS, MutationOperation, QueryResolver, QueryResolverConfig, RESERVED_QUERY_PARAMS, ResourceDefinition, SYSTEM_FIELDS, SlugExt, SlugMixin, SoftDeleteExt, SoftDeleteMixin, TreeExt, TreeMixin, createCrudHandlers, createCrudRouter, createFastifyHandler, createPermissionMiddleware, createRequestContext, defineResource, defineResourceVariants, getControllerContext, getControllerScope, sendControllerResponse };
|
package/dist/core/index.mjs
CHANGED
|
@@ -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-BhY1OHoH.mjs";
|
|
2
|
-
import { a as BulkMixin, c as BodySanitizer, i as SlugMixin, l as AccessControl, n as TreeMixin, o as BaseCrudController, r as SoftDeleteMixin, s as QueryResolver, t as BaseController } from "../BaseController-
|
|
3
|
-
import { _ as
|
|
4
|
-
import { a as createPermissionMiddleware, i as createCrudRouter, n as ResourceDefinition, r as defineResource, t as defineResourceVariants } from "../core-
|
|
2
|
+
import { a as BulkMixin, c as BodySanitizer, i as SlugMixin, l as AccessControl, n as TreeMixin, o as BaseCrudController, r as SoftDeleteMixin, s as QueryResolver, t as BaseController } from "../BaseController-swXruJ2_.mjs";
|
|
3
|
+
import { _ as getControllerContext, g as createRequestContext, h as createFastifyHandler, m as createCrudHandlers, v as getControllerScope, y as sendControllerResponse } from "../routerShared-BqLRb5l7.mjs";
|
|
4
|
+
import { a as createPermissionMiddleware, i as createCrudRouter, n as ResourceDefinition, r as defineResource, t as defineResourceVariants } from "../core-CbcQRIch.mjs";
|
|
5
5
|
export { AccessControl, BaseController, BaseCrudController, BodySanitizer, BulkMixin, 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, SlugMixin, SoftDeleteMixin, TreeMixin, createCrudHandlers, createCrudRouter, createFastifyHandler, createPermissionMiddleware, createRequestContext, defineResource, defineResourceVariants, getControllerContext, getControllerScope, sendControllerResponse };
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { s as DEFAULT_UPDATE_METHOD, t as CRUD_OPERATIONS } from "./constants-BhY1OHoH.mjs";
|
|
2
2
|
import { arcLog } from "./logger/index.mjs";
|
|
3
|
-
import { m as assertValidConfig, y as getDefaultCrudSchemas } from "./utils-
|
|
4
|
-
import { t as BaseController } from "./BaseController-
|
|
3
|
+
import { m as assertValidConfig, y as getDefaultCrudSchemas } from "./utils-CcYTj09l.mjs";
|
|
4
|
+
import { t as BaseController } from "./BaseController-swXruJ2_.mjs";
|
|
5
5
|
import { n as convertRouteSchema, t as convertOpenApiSchemas } from "./schemaConverter-B0oKLuqI.mjs";
|
|
6
|
-
import { c as buildPreHandlerChain, d as
|
|
7
|
-
import { t as applyPresets } from "./presets-
|
|
6
|
+
import { b as buildRequestScopeProjection, c as buildPreHandlerChain, d as resolveRoutePreHandlers, f as resolveRouterPluginMw, h as createFastifyHandler, i as buildAuthMiddleware, l as buildRateLimitConfig, m as createCrudHandlers, o as buildCrudPermissionMw, p as selectPluginMw, r as buildArcDecorator, s as buildPipelineHandler, u as resolvePipelineSteps } from "./routerShared-BqLRb5l7.mjs";
|
|
7
|
+
import { t as applyPresets } from "./presets-Z7P5w4gF.mjs";
|
|
8
8
|
import { t as hasEvents } from "./typeGuards-CcFZXgU7.mjs";
|
|
9
|
-
import { t as resolveActionPermission } from "./actionPermissions-
|
|
9
|
+
import { t as resolveActionPermission } from "./actionPermissions-sUUKDhtP.mjs";
|
|
10
10
|
//#region src/core/createCrudRouter.ts
|
|
11
11
|
/**
|
|
12
12
|
* Mount custom routes (from presets or user-defined `routes`) on Fastify.
|
|
@@ -39,7 +39,7 @@ function createCustomRoutes(fastify, routes, controller, options) {
|
|
|
39
39
|
...route.description ? { description: route.description } : {},
|
|
40
40
|
...convertedSchema ?? {}
|
|
41
41
|
};
|
|
42
|
-
const customPreHandlers =
|
|
42
|
+
const customPreHandlers = resolveRoutePreHandlers(route.preHandler, fastify, `${route.method} ${route.path}`);
|
|
43
43
|
const preHandler = buildPreHandlerChain({
|
|
44
44
|
preAuth: route.preAuth ?? [],
|
|
45
45
|
arcDecorator,
|
|
@@ -469,7 +469,24 @@ function resolveOrAutoCreateController(resolvedConfig, adapter, repository, hasC
|
|
|
469
469
|
if (typeof ctrl.setQueryParser === "function") ctrl.setQueryParser(resolvedConfig.queryParser);
|
|
470
470
|
else arcLog("defineResource").warn(`Resource "${resolvedConfig.name}" declares a custom \`queryParser\` but its controller does not expose \`setQueryParser(qp)\`. The parser will NOT be threaded into the controller's query resolution — operator filters (\`[contains]\`, \`[like]\`, etc.) may fall back to the controller's internal default. Extend \`BaseController\` / \`BaseCrudController\` (both implement \`setQueryParser\`) OR add the method to your custom controller to honor the resource-level parser.`);
|
|
471
471
|
}
|
|
472
|
-
if (controller
|
|
472
|
+
if (controller) {
|
|
473
|
+
const authorOptions = [];
|
|
474
|
+
if (resolvedConfig.tenantField !== void 0) authorOptions.push("tenantField");
|
|
475
|
+
if (resolvedConfig.schemaOptions !== void 0 && Object.keys(resolvedConfig.schemaOptions).length > 0) authorOptions.push("schemaOptions");
|
|
476
|
+
if (resolvedConfig.idField !== void 0) authorOptions.push("idField");
|
|
477
|
+
if (resolvedConfig.defaultSort !== void 0) authorOptions.push("defaultSort");
|
|
478
|
+
if (resolvedConfig.cache !== void 0) authorOptions.push("cache");
|
|
479
|
+
if (resolvedConfig.onFieldWriteDenied !== void 0) authorOptions.push("onFieldWriteDenied");
|
|
480
|
+
if (authorOptions.length > 0) arcLog("defineResource").warn(`Resource "${resolvedConfig.name}" declares a custom controller AND resource-level option(s) [${authorOptions.join(", ")}]. Arc only threads these when it auto-builds the controller — when you pass your own, they are dropped silently and the controller falls back to its own defaults (e.g. tenantField → 'organizationId'). Forward them to your controller's \`super(repo, { ... })\` call. Same root cause as the \`queryParser\` warn above.`);
|
|
481
|
+
if (resolvedConfig._controllerOptions !== void 0) {
|
|
482
|
+
const presetFields = [];
|
|
483
|
+
if (resolvedConfig._controllerOptions.slugField) presetFields.push("slugField");
|
|
484
|
+
if (resolvedConfig._controllerOptions.parentField) presetFields.push("parentField");
|
|
485
|
+
arcLog("defineResource").warn(`Resource "${resolvedConfig.name}" applies a preset that injects controller field(s) [${presetFields.join(", ") || "preset metadata"}] (e.g. slugLookup / softDelete / parent), but the resource also declares a custom controller. Preset metadata only reaches arc's auto-built BaseController — your custom controller will not see \`slugField\`/\`parentField\`/etc. Either (a) drop the preset on this resource (\`presets: [...]\` without it), or (b) extend \`BaseController\` / \`BaseCrudController\` so arc auto-builds the controller and threads the preset fields automatically.`);
|
|
486
|
+
}
|
|
487
|
+
return controller;
|
|
488
|
+
}
|
|
489
|
+
if (!hasCrudRoutes || !repository) return controller;
|
|
473
490
|
const qp = resolvedConfig.queryParser;
|
|
474
491
|
let maxLimitFromParser;
|
|
475
492
|
if (qp?.getQuerySchema) {
|
|
@@ -869,7 +886,7 @@ var ResourceDefinition = class {
|
|
|
869
886
|
fields: self.fields
|
|
870
887
|
});
|
|
871
888
|
if (self.actions && Object.keys(self.actions).length > 0) {
|
|
872
|
-
const { createActionRouter } = await import("./createActionRouter-
|
|
889
|
+
const { createActionRouter } = await import("./createActionRouter-CIKOcNA7.mjs").then((n) => n.n);
|
|
873
890
|
createActionRouter(typedInstance, {
|
|
874
891
|
...normalizeActionsToRouterConfig(self.actions, self.actionPermissions, self.tag, self.permissions, self.name, typedInstance.log),
|
|
875
892
|
resourceName: self.name,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { t as __exportAll } from "./chunk-BpYLSNr0.mjs";
|
|
2
|
-
import { a as buildAuthMiddlewareForPermissions, c as buildPreHandlerChain,
|
|
3
|
-
import { n as schemaIRToJsonSchemaBranch, t as normalizeSchemaIR } from "./schemaIR-
|
|
2
|
+
import { a as buildAuthMiddlewareForPermissions, c as buildPreHandlerChain, f as resolveRouterPluginMw, l as buildRateLimitConfig, n as buildActionPipelineHandler, p as selectPluginMw, r as buildArcDecorator, t as buildActionPermissionMw, u as resolvePipelineSteps, y as sendControllerResponse } from "./routerShared-BqLRb5l7.mjs";
|
|
3
|
+
import { n as schemaIRToJsonSchemaBranch, t as normalizeSchemaIR } from "./schemaIR-Dy2p4MxS.mjs";
|
|
4
4
|
//#region src/core/createActionRouter.ts
|
|
5
5
|
var createActionRouter_exports = /* @__PURE__ */ __exportAll({
|
|
6
6
|
buildActionBodySchema: () => buildActionBodySchema,
|
|
@@ -118,34 +118,94 @@ function createActionRouter(fastify, config) {
|
|
|
118
118
|
* {
|
|
119
119
|
* "type": "object",
|
|
120
120
|
* "required": ["action"],
|
|
121
|
+
* "properties": {
|
|
122
|
+
* "action": { "type": "string", "enum": ["dispatch", "approve"] },
|
|
123
|
+
* "carrier": { "type": "string" }
|
|
124
|
+
* },
|
|
121
125
|
* "oneOf": [
|
|
122
|
-
* {
|
|
123
|
-
*
|
|
126
|
+
* {
|
|
127
|
+
* "properties": {
|
|
128
|
+
* "action": { "const": "dispatch" },
|
|
129
|
+
* "carrier": { "type": "string" } // ← every branch lists the union
|
|
130
|
+
* },
|
|
131
|
+
* "required": ["action", "carrier"]
|
|
132
|
+
* },
|
|
133
|
+
* {
|
|
134
|
+
* "properties": {
|
|
135
|
+
* "action": { "const": "approve" },
|
|
136
|
+
* "carrier": { "type": "string" } // ← even though approve doesn't use it
|
|
137
|
+
* },
|
|
138
|
+
* "required": ["action"]
|
|
139
|
+
* }
|
|
124
140
|
* ]
|
|
125
141
|
* }
|
|
126
142
|
* ```
|
|
127
143
|
*
|
|
128
|
-
*
|
|
129
|
-
*
|
|
144
|
+
* **Why every branch carries the full property union.** AJV's
|
|
145
|
+
* `removeAdditional: 'all'` (Fastify's framework default) interacts badly
|
|
146
|
+
* with `oneOf`: when a branch's `properties` lacks a field, AJV strips it
|
|
147
|
+
* from the body during that branch's evaluation — *even if a different
|
|
148
|
+
* branch would have allowed it*. The strip mutates the body before
|
|
149
|
+
* `oneOf` finishes discriminating, so by the time the matching branch
|
|
150
|
+
* wins, the body has already lost fields. Concretely: `actions: { verify:
|
|
151
|
+
* {}, hold: { schema: z.object({ amount, reason }.optional()) } }` +
|
|
152
|
+
* `POST { action: 'hold', amount: 1, reason }` lands at the handler as
|
|
153
|
+
* `{ action: 'hold' }`. Empirically reproduced and locked at
|
|
154
|
+
* [tests/core/action-discriminator-strip.test.ts](../../tests/core/action-discriminator-strip.test.ts).
|
|
155
|
+
*
|
|
156
|
+
* Listing every action's properties on every branch makes per-branch
|
|
157
|
+
* removeAdditional walks see every caller field as "in this branch's
|
|
158
|
+
* properties," so nothing gets stripped during oneOf evaluation. The
|
|
159
|
+
* `required` array stays per-action, so the handler still gets called
|
|
160
|
+
* only when the matching branch's required-field contract is satisfied.
|
|
161
|
+
* Per-branch `additionalProperties: false` (Zod v4 default) carries
|
|
162
|
+
* through but, under host removeAdditional: 'all', it can no longer
|
|
163
|
+
* reject sibling-action fields — those become silently stripped at top
|
|
164
|
+
* level instead. That's the host's opt-in to stripping; arc's job is to
|
|
165
|
+
* stop accidentally losing the action's *own* declared fields.
|
|
166
|
+
*
|
|
167
|
+
* Under arc's own `createApp` (`removeAdditional: false`), strict-mode
|
|
168
|
+
* rejection still functions normally — see
|
|
169
|
+
* [tests/core/action-strict-schema-parity.test.ts](../../tests/core/action-strict-schema-parity.test.ts).
|
|
130
170
|
*
|
|
131
171
|
* Exported so OpenAPI generation and MCP tool generation can reuse the same
|
|
132
172
|
* schema shape (single source of truth).
|
|
133
173
|
*/
|
|
134
174
|
function buildActionBodySchema(actionEnum, actionSchemas = {}) {
|
|
135
|
-
const
|
|
175
|
+
const unionProperties = {};
|
|
176
|
+
const irs = [];
|
|
136
177
|
for (const actionName of actionEnum) {
|
|
137
178
|
const ir = normalizeSchemaIR(actionSchemas[actionName]);
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
required: ["action"]
|
|
144
|
-
}));
|
|
179
|
+
irs.push({
|
|
180
|
+
name: actionName,
|
|
181
|
+
ir
|
|
182
|
+
});
|
|
183
|
+
for (const [key, val] of Object.entries(ir.properties)) unionProperties[key] = val;
|
|
145
184
|
}
|
|
185
|
+
const branches = [];
|
|
186
|
+
for (const { name, ir } of irs) branches.push(schemaIRToJsonSchemaBranch({
|
|
187
|
+
...ir,
|
|
188
|
+
properties: {
|
|
189
|
+
...unionProperties,
|
|
190
|
+
...ir.properties
|
|
191
|
+
}
|
|
192
|
+
}, {
|
|
193
|
+
properties: { action: {
|
|
194
|
+
type: "string",
|
|
195
|
+
const: name
|
|
196
|
+
} },
|
|
197
|
+
required: ["action"]
|
|
198
|
+
}));
|
|
146
199
|
return {
|
|
147
200
|
type: "object",
|
|
148
201
|
required: ["action"],
|
|
202
|
+
properties: {
|
|
203
|
+
action: {
|
|
204
|
+
type: "string",
|
|
205
|
+
enum: [...actionEnum]
|
|
206
|
+
},
|
|
207
|
+
...unionProperties
|
|
208
|
+
},
|
|
149
209
|
oneOf: branches
|
|
150
210
|
};
|
|
151
211
|
}
|
|
@@ -117,10 +117,7 @@ const developmentPreset = {
|
|
|
117
117
|
]
|
|
118
118
|
},
|
|
119
119
|
rateLimit: false,
|
|
120
|
-
underPressure:
|
|
121
|
-
exposeStatusRoute: true,
|
|
122
|
-
maxEventLoopDelay: 5e3
|
|
123
|
-
}
|
|
120
|
+
underPressure: false
|
|
124
121
|
};
|
|
125
122
|
/**
|
|
126
123
|
* Testing preset - minimal setup, fast startup
|
|
@@ -204,7 +201,7 @@ async function registerArcCore(fastify, config, trackPlugin) {
|
|
|
204
201
|
await fastify.register(arcCorePlugin, { emitEvents: config.arcPlugins?.emitEvents !== false });
|
|
205
202
|
trackPlugin("arc-core");
|
|
206
203
|
if (config.arcPlugins?.events !== false) {
|
|
207
|
-
const { default: eventPlugin } = await import("./eventPlugin
|
|
204
|
+
const { default: eventPlugin } = await import("./eventPlugin-Cts2-Tfj.mjs").then((n) => n.n);
|
|
208
205
|
const eventOpts = typeof config.arcPlugins?.events === "object" ? config.arcPlugins.events : {};
|
|
209
206
|
await fastify.register(eventPlugin, {
|
|
210
207
|
...eventOpts,
|
|
@@ -605,7 +602,8 @@ async function registerSecurityPlugins(fastify, config) {
|
|
|
605
602
|
if (config.cors !== false) {
|
|
606
603
|
const cors = await loadPlugin("cors");
|
|
607
604
|
const corsOptions = { ...config.cors ?? {} };
|
|
608
|
-
|
|
605
|
+
const originDeclared = "origin" in corsOptions && corsOptions.origin !== void 0;
|
|
606
|
+
if (config.preset === "production" && !originDeclared) fastify.log.warn("CORS origin is not explicitly configured in production. Browser apps: set cors.origin to allowed domains (e.g. ['https://app.example.com']) with credentials: true. Server-to-server / API-key services: cors: { origin: '*', credentials: false } OR cors: false to disable. Tip: when wiring cors.origin from an env var, fail fast on missing (`if (!process.env.ALLOWED_ORIGINS) throw ...`) instead of letting `undefined` slip through.");
|
|
609
607
|
if (corsOptions.credentials && corsOptions.origin === "*") corsOptions.origin = true;
|
|
610
608
|
await fastify.register(cors, corsOptions);
|
|
611
609
|
fastify.log.debug("CORS enabled");
|