@classytic/arc 2.11.3 → 2.13.1
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/README.md +27 -18
- package/dist/{BaseController-swXruJ2_.mjs → BaseController-DX_T-bDB.mjs} +388 -423
- package/dist/EventTransport-CT_52aWU.d.mts +34 -0
- package/dist/EventTransport-DLWoUMHy.mjs +103 -0
- package/dist/{QueryCache-DOBNHBE0.d.mts → QueryCache-D41bfdBB.d.mts} +1 -1
- package/dist/{ResourceRegistry-DkAeAuTX.mjs → ResourceRegistry-CTERg_2x.mjs} +139 -66
- package/dist/audit/index.d.mts +2 -2
- package/dist/audit/index.mjs +1 -1
- package/dist/auth/audit.d.mts +199 -0
- package/dist/auth/audit.mjs +288 -0
- package/dist/auth/index.d.mts +5 -5
- package/dist/auth/index.mjs +117 -191
- package/dist/auth/redis-session.d.mts +1 -1
- package/dist/{betterAuthOpenApi-DwxtK3uG.mjs → betterAuthOpenApi--M_i87dQ.mjs} +1 -1
- package/dist/buildHandler-olo-gt94.mjs +610 -0
- package/dist/cache/index.d.mts +3 -3
- package/dist/cache/index.mjs +3 -3
- package/dist/cli/commands/describe.d.mts +89 -13
- package/dist/cli/commands/describe.mjs +56 -2
- package/dist/cli/commands/docs.mjs +2 -2
- package/dist/cli/commands/generate.mjs +147 -48
- package/dist/cli/commands/init.d.mts +13 -0
- package/dist/cli/commands/init.mjs +237 -112
- package/dist/cli/commands/introspect.mjs +8 -1
- package/dist/context/index.mjs +1 -1
- package/dist/core/index.d.mts +3 -3
- package/dist/core/index.mjs +5 -5
- package/dist/core-D72ia0EH.mjs +1399 -0
- package/dist/{createActionRouter-u3ql2EDo.mjs → createActionRouter-CEvzKcy8.mjs} +7 -20
- package/dist/createAggregationRouter-CyecOxnO.mjs +114 -0
- package/dist/{createApp-BFxtdKy6.mjs → createApp-XX2-N0Yd.mjs} +31 -27
- package/dist/defineEvent-D5h7EvAx.mjs +188 -0
- package/dist/docs/index.d.mts +2 -2
- package/dist/docs/index.mjs +2 -2
- package/dist/{elevation-DOFoxoDs.mjs → elevation-DgoeTyfX.mjs} +1 -1
- package/dist/errorHandler-Bk-AGhkU.mjs +174 -0
- package/dist/errorHandler-DFr45ZG4.d.mts +45 -0
- package/dist/errors-j4aJm1Wg.mjs +184 -0
- package/dist/{eventPlugin-KrFIQ097.mjs → eventPlugin-CaKTYkYM.mjs} +35 -137
- package/dist/{eventPlugin-CUNjYYRY.d.mts → eventPlugin-qXpqTebY.d.mts} +57 -7
- package/dist/events/index.d.mts +164 -5
- package/dist/events/index.mjs +133 -209
- 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 +2 -2
- package/dist/factory/index.mjs +2 -2
- package/dist/{fields-C8Y0XLAu.d.mts → fields-COhcH3fk.d.mts} +23 -2
- package/dist/hooks/index.d.mts +1 -1
- package/dist/hooks/index.mjs +1 -1
- package/dist/idempotency/index.d.mts +3 -3
- package/dist/idempotency/index.mjs +1 -20
- package/dist/idempotency/redis.d.mts +1 -1
- package/dist/idempotency/redis.mjs +1 -1
- package/dist/{index-BYCqHCVu.d.mts → index-BTqLEvhu.d.mts} +164 -4
- package/dist/{index-6u4_Gg6G.d.mts → index-BtW7qYwa.d.mts} +661 -281
- package/dist/{index-BdXnTPRj.d.mts → index-Ds61mrJE.d.mts} +50 -4
- package/dist/{index-DdQ3O9Pg.d.mts → index-Dz5IKsrE.d.mts} +360 -219
- package/dist/index.d.mts +6 -7
- package/dist/index.mjs +9 -10
- package/dist/integrations/event-gateway.d.mts +2 -2
- package/dist/integrations/event-gateway.mjs +1 -1
- 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/streamline.d.mts +60 -11
- package/dist/integrations/streamline.mjs +75 -85
- package/dist/integrations/websocket-redis.d.mts +1 -1
- package/dist/integrations/websocket.d.mts +1 -1
- package/dist/integrations/websocket.mjs +2 -8
- package/dist/middleware/index.d.mts +1 -1
- package/dist/middleware/index.mjs +2 -2
- package/dist/migrations/index.d.mts +23 -3
- package/dist/migrations/index.mjs +0 -7
- package/dist/{multipartBody-CvTR1Un6.mjs → multipartBody-BOvVSVCD.mjs} +11 -8
- package/dist/{openapi-BGUn7Ki1.mjs → openapi-CiOMVW1p.mjs} +143 -13
- package/dist/org/index.d.mts +2 -2
- package/dist/org/index.mjs +1 -1
- package/dist/permissions/index.d.mts +3 -3
- package/dist/permissions/index.mjs +3 -3
- package/dist/{permissions-gd_aUWrR.mjs → permissions-ohQyv50e.mjs} +404 -176
- package/dist/{pipe-DVoIheVC.mjs → pipe-Zr0KXjQe.mjs} +1 -1
- package/dist/pipeline/index.d.mts +1 -1
- package/dist/pipeline/index.mjs +1 -1
- package/dist/plugins/index.d.mts +18 -33
- package/dist/plugins/index.mjs +33 -13
- package/dist/plugins/response-cache.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 +5 -5
- package/dist/presets/filesUpload.mjs +6 -9
- 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/multiTenant.mjs +2 -2
- package/dist/presets/search.d.mts +2 -2
- package/dist/presets/search.mjs +6 -8
- package/dist/{presets-Z7P5w4gF.mjs → presets-BbkjdPeH.mjs} +6 -28
- package/dist/{queryCachePlugin-BUXBSm4F.d.mts → queryCachePlugin-CqMdLI2-.d.mts} +2 -2
- package/dist/{queryCachePlugin-Bq6bO6vc.mjs → queryCachePlugin-m1XsgAIJ.mjs} +3 -3
- package/dist/{redis-Cm1gnRDf.d.mts → redis-DiMkdHEl.d.mts} +1 -1
- package/dist/redis-stream-D6HzR1Z_.d.mts +232 -0
- package/dist/registry/index.d.mts +1 -1
- package/dist/registry/index.mjs +2 -2
- package/dist/{replyHelpers-ByllIXXV.mjs → replyHelpers-CK-FNO8E.mjs} +3 -21
- package/dist/{resourceToTools-ByZpgjeH.mjs → resourceToTools-C5coh64w.mjs} +224 -71
- package/dist/{routerShared-BqLRb5l7.mjs → routerShared-D6_fEGHh.mjs} +40 -36
- package/dist/{schemaIR-BlG9bY7v.mjs → schemaIR-7Vl611Qs.mjs} +1 -1
- package/dist/schemas/index.d.mts +100 -30
- package/dist/schemas/index.mjs +86 -29
- package/dist/scim/index.d.mts +264 -0
- package/dist/scim/index.mjs +963 -0
- package/dist/scope/index.d.mts +3 -3
- package/dist/scope/index.mjs +4 -4
- package/dist/{sse-V7aXc3bW.mjs → sse-Bz-5ZeTt.mjs} +1 -1
- package/dist/{store-helpers-BhrzxvyQ.mjs → store-helpers-BkIN9-vu.mjs} +1 -1
- package/dist/testing/index.d.mts +2 -8
- package/dist/testing/index.mjs +16 -24
- 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-BH7dEGvU.d.mts → types-BvqwCCSx.d.mts} +77 -29
- package/dist/{types-tgR4Pt8F.d.mts → types-CTYvcwHe.d.mts} +195 -1
- package/dist/{types-AOD8fxIw.mjs → types-C_s5moIu.mjs} +117 -1
- package/dist/{types-9beEMe25.d.mts → types-DQHFc8PM.d.mts} +1 -1
- package/dist/utils/index.d.mts +2 -2
- package/dist/utils/index.mjs +5 -5
- package/dist/{utils-CcYTj09l.mjs → utils-_h9B3c57.mjs} +1269 -1334
- package/dist/{versioning-M9lNLhO8.d.mts → versioning-DTTvc80y.d.mts} +1 -1
- package/package.json +24 -34
- package/skills/arc/SKILL.md +521 -785
- package/skills/arc/references/agent-auth.md +238 -0
- package/skills/arc/references/api-reference.md +187 -0
- package/skills/arc/references/auth.md +354 -7
- package/skills/arc/references/enterprise-auth.md +94 -0
- package/skills/arc/references/events.md +8 -6
- package/skills/arc/references/mcp.md +2 -2
- package/skills/arc/references/multi-tenancy.md +11 -2
- package/skills/arc/references/production.md +10 -9
- package/skills/arc/references/scim.md +247 -0
- package/skills/arc/references/testing.md +1 -1
- package/skills/arc-code-review/SKILL.md +141 -0
- package/skills/arc-code-review/references/anti-patterns.md +911 -0
- package/skills/arc-code-review/references/arc-cheatsheet.md +380 -0
- package/skills/arc-code-review/references/migration-recipes.md +700 -0
- package/skills/arc-code-review/references/mongokit-migration.md +386 -0
- package/skills/arc-code-review/references/scaffolding.md +230 -0
- package/skills/arc-code-review/references/severity.md +127 -0
- package/dist/EventTransport-CfVEGaEl.d.mts +0 -293
- package/dist/adapters/index.d.mts +0 -3
- package/dist/adapters/index.mjs +0 -2
- package/dist/adapters-D0tT2Tyo.mjs +0 -949
- package/dist/auth/mongoose.d.mts +0 -191
- package/dist/auth/mongoose.mjs +0 -73
- package/dist/core-DnUsRpuX.mjs +0 -1049
- package/dist/errorHandler-BQm8ZxTK.mjs +0 -173
- package/dist/errorHandler-Co3lnVmJ.d.mts +0 -114
- package/dist/errors-D5c-5BJL.mjs +0 -232
- package/dist/index-BbMrcvGp.d.mts +0 -362
- package/dist/redis-stream-CM8TXTix.d.mts +0 -110
- /package/dist/{HookSystem-CGsMd6oK.mjs → HookSystem-Iiebom92.mjs} +0 -0
- /package/dist/{actionPermissions-sUUKDhtP.mjs → actionPermissions-CyUkQu6O.mjs} +0 -0
- /package/dist/{caching-CheW3m-S.mjs → caching-SM8gghN6.mjs} +0 -0
- /package/dist/{constants-BhY1OHoH.mjs → constants-Cxde4rpC.mjs} +0 -0
- /package/dist/{elevation-s5ykdNHr.d.mts → elevation-BXOWoGCF.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/{keys-CARyUjiR.mjs → keys-CGcCbNyu.mjs} +0 -0
- /package/dist/{loadResources-CPpkyKfM.mjs → loadResources-DBMQg_Aj.mjs} +0 -0
- /package/dist/{memory-DikHSvWa.mjs → memory-UBydS5ku.mjs} +0 -0
- /package/dist/{metrics-Csh4nsvv.mjs → metrics-Qnvwc-LQ.mjs} +0 -0
- /package/dist/{pluralize-BneOJkpi.mjs → pluralize-DQgqgifU.mjs} +0 -0
- /package/dist/{registry-D63ee7fl.mjs → registry-I-ogLgL9.mjs} +0 -0
- /package/dist/{requestContext-C5XeK3VA.mjs → requestContext-SSaaTgW8.mjs} +0 -0
- /package/dist/{schemaConverter-B0oKLuqI.mjs → schemaConverter-De34B1ZG.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/{tracing-DokiEsuz.d.mts → tracing-QJVprktp.d.mts} +0 -0
- /package/dist/{typeGuards-CcFZXgU7.mjs → typeGuards-BzkXkvVv.mjs} +0 -0
- /package/dist/{types-DV9WDfeg.mjs → types-D57iXYb8.mjs} +0 -0
- /package/dist/{versioning-CGPjkqAg.mjs → versioning-BUrT5aP4.mjs} +0 -0
- /package/dist/{websocket-CyJ1VIFI.d.mts → websocket-ChC2rqe1.d.mts} +0 -0
|
@@ -28,6 +28,10 @@ async function init(options = {}) {
|
|
|
28
28
|
console.log(`\nCreating project: ${config.name}`);
|
|
29
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
|
+
if (config.auth === "better-auth") {
|
|
32
|
+
console.log(` Session: ${config.session === "cookie" ? "Cookie" : config.session === "bearer" ? "Bearer token" : "Cookie + Bearer"}`);
|
|
33
|
+
if (config.apiKey) console.log(` API keys: enabled (@better-auth/api-key)`);
|
|
34
|
+
}
|
|
31
35
|
console.log(` Tenant: ${config.tenant === "multi" ? "Multi-tenant" : "Single-tenant"}`);
|
|
32
36
|
console.log(` Language: ${config.typescript ? "TypeScript" : "JavaScript"}`);
|
|
33
37
|
console.log(` Target: ${config.edge ? "Edge/Serverless" : "Node.js Server"}\n`);
|
|
@@ -87,42 +91,108 @@ function existsSync$1(filePath) {
|
|
|
87
91
|
}
|
|
88
92
|
}
|
|
89
93
|
/**
|
|
90
|
-
*
|
|
94
|
+
* Single source of truth for scaffolded project dependencies.
|
|
95
|
+
*
|
|
96
|
+
* Versions are pinned to the floor each subsystem requires — peer-dep
|
|
97
|
+
* minimums on Arc, kit minimums (mongokit ≥ 3.11, repo-core ≥ 0.2,
|
|
98
|
+
* mongoose ≥ 9.4.1), and major-version stable for the rest. The carets
|
|
99
|
+
* allow minor + patch upgrades without breaking arc's contract, while
|
|
100
|
+
* preventing the silent breakage of `@latest` on a kit floor bump.
|
|
101
|
+
*
|
|
102
|
+
* Used by both `packageJsonTemplate` (declares the deps in the generated
|
|
103
|
+
* `package.json` so `npm install` works without a pre-pass) and
|
|
104
|
+
* `installDependencies` (runs the package manager's `install` against
|
|
105
|
+
* the declared ranges). One source — no drift.
|
|
91
106
|
*/
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
"@classytic/arc
|
|
95
|
-
"
|
|
96
|
-
"@
|
|
97
|
-
"@fastify/
|
|
98
|
-
"@fastify/
|
|
99
|
-
"@fastify/
|
|
100
|
-
"@fastify/
|
|
101
|
-
"
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
107
|
+
const SCAFFOLD_DEP_VERSIONS = {
|
|
108
|
+
core: {
|
|
109
|
+
"@classytic/arc": "^2.13.0",
|
|
110
|
+
"@classytic/primitives": "^0.3.0",
|
|
111
|
+
"@classytic/repo-core": "^0.4.0",
|
|
112
|
+
"@fastify/cors": "^11.2.0",
|
|
113
|
+
"@fastify/helmet": "^13.0.2",
|
|
114
|
+
"@fastify/rate-limit": "^10.3.0",
|
|
115
|
+
"@fastify/sensible": "^6.0.4",
|
|
116
|
+
"@fastify/under-pressure": "^9.0.3",
|
|
117
|
+
dotenv: "^17.4.2",
|
|
118
|
+
fastify: "^5.8.5"
|
|
119
|
+
},
|
|
120
|
+
authJwt: {
|
|
121
|
+
"@fastify/jwt": "^10.0.0",
|
|
122
|
+
bcryptjs: "^3.0.0"
|
|
123
|
+
},
|
|
124
|
+
authBetterAuth: {
|
|
125
|
+
"better-auth": "^1.6.9",
|
|
126
|
+
mongodb: "^7.1.0"
|
|
127
|
+
},
|
|
128
|
+
authBetterAuthApiKey: { "@better-auth/api-key": "^1.6.9" },
|
|
129
|
+
adapterMongokit: {
|
|
130
|
+
"@classytic/mongokit": "^3.13.0",
|
|
131
|
+
mongoose: "^9.6.1"
|
|
132
|
+
},
|
|
133
|
+
devCommon: {
|
|
134
|
+
"mongodb-memory-server": "^11.1.0",
|
|
135
|
+
"pino-pretty": "^13.0.0",
|
|
136
|
+
vitest: "^4.1.5"
|
|
137
|
+
},
|
|
138
|
+
devTypescript: {
|
|
139
|
+
"@types/node": "^22.10.0",
|
|
140
|
+
tsx: "^4.21.0",
|
|
141
|
+
typescript: "^5.7.2"
|
|
142
|
+
},
|
|
143
|
+
typesJwt: { "@types/bcryptjs": "^3.0.0" }
|
|
144
|
+
};
|
|
145
|
+
/**
|
|
146
|
+
* Resolve the dependency manifest for a scaffold configuration.
|
|
147
|
+
*
|
|
148
|
+
* Returns sorted records (alphabetical by package name) so the generated
|
|
149
|
+
* `package.json` is deterministic — diffs across re-runs stay clean.
|
|
150
|
+
*/
|
|
151
|
+
function resolveScaffoldDependencies(config) {
|
|
152
|
+
const dependencies = { ...SCAFFOLD_DEP_VERSIONS.core };
|
|
153
|
+
const devDependencies = { ...SCAFFOLD_DEP_VERSIONS.devCommon };
|
|
154
|
+
if (config.auth === "better-auth") {
|
|
155
|
+
Object.assign(dependencies, SCAFFOLD_DEP_VERSIONS.authBetterAuth);
|
|
156
|
+
if (config.apiKey) Object.assign(dependencies, SCAFFOLD_DEP_VERSIONS.authBetterAuthApiKey);
|
|
157
|
+
} else {
|
|
158
|
+
Object.assign(dependencies, SCAFFOLD_DEP_VERSIONS.authJwt);
|
|
159
|
+
if (config.typescript) Object.assign(devDependencies, SCAFFOLD_DEP_VERSIONS.typesJwt);
|
|
160
|
+
}
|
|
161
|
+
if (config.adapter === "mongokit") Object.assign(dependencies, SCAFFOLD_DEP_VERSIONS.adapterMongokit);
|
|
162
|
+
if (config.typescript) Object.assign(devDependencies, SCAFFOLD_DEP_VERSIONS.devTypescript);
|
|
163
|
+
return {
|
|
164
|
+
dependencies: sortByKey(dependencies),
|
|
165
|
+
devDependencies: sortByKey(devDependencies)
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* Sort a record alphabetically by key — package.json convention.
|
|
170
|
+
*/
|
|
171
|
+
function sortByKey(record) {
|
|
172
|
+
return Object.fromEntries(Object.entries(record).sort(([a], [b]) => a.localeCompare(b)));
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* Install dependencies using the detected package manager.
|
|
176
|
+
*
|
|
177
|
+
* Dependencies are already declared in the generated `package.json` (see
|
|
178
|
+
* `packageJsonTemplate`), so a single plain `install` resolves the full
|
|
179
|
+
* tree. No two-pass `npm add` flow — the manifest is the source of truth.
|
|
180
|
+
*/
|
|
181
|
+
async function installDependencies(projectPath, _config, pm) {
|
|
110
182
|
console.log(` Installing dependencies...`);
|
|
111
|
-
await runCommand(
|
|
112
|
-
console.log(` Installing dev dependencies...`);
|
|
113
|
-
await runCommand(installDevCmd, projectPath);
|
|
183
|
+
await runCommand(getInstallCommand(pm), projectPath);
|
|
114
184
|
console.log(`\nDependencies installed successfully.`);
|
|
115
185
|
}
|
|
116
186
|
/**
|
|
117
|
-
* Get the install command for a package manager
|
|
187
|
+
* Get the plain `install` command for a package manager. Reads the declared
|
|
188
|
+
* dependencies from the project's `package.json`.
|
|
118
189
|
*/
|
|
119
|
-
function getInstallCommand(pm
|
|
120
|
-
const pkgList = packages.join(" ");
|
|
190
|
+
function getInstallCommand(pm) {
|
|
121
191
|
switch (pm) {
|
|
122
|
-
case "pnpm": return
|
|
123
|
-
case "yarn": return
|
|
124
|
-
case "bun": return
|
|
125
|
-
default: return
|
|
192
|
+
case "pnpm": return "pnpm install";
|
|
193
|
+
case "yarn": return "yarn install";
|
|
194
|
+
case "bun": return "bun install";
|
|
195
|
+
default: return "npm install";
|
|
126
196
|
}
|
|
127
197
|
}
|
|
128
198
|
/**
|
|
@@ -159,6 +229,15 @@ async function gatherConfig(options) {
|
|
|
159
229
|
if (!options.adapter && !nonInteractive) adapter = await question("Database adapter [1=MongoKit (recommended), 2=Custom / Drizzle-ready]: ") === "2" ? "custom" : "mongokit";
|
|
160
230
|
let auth = options.auth || "better-auth";
|
|
161
231
|
if (!options.auth && !nonInteractive) auth = await question("Auth strategy [1=Better Auth (recommended), 2=Arc JWT]: ") === "2" ? "jwt" : "better-auth";
|
|
232
|
+
let session = options.session ?? "cookie";
|
|
233
|
+
let apiKey = options.apiKey ?? false;
|
|
234
|
+
if (auth === "better-auth" && !nonInteractive) {
|
|
235
|
+
if (options.session === void 0) {
|
|
236
|
+
const sessionChoice = await question("Session strategy [1=Cookie (web app, default), 2=Bearer token (mobile/SPA), 3=Both]: ");
|
|
237
|
+
session = sessionChoice === "2" ? "bearer" : sessionChoice === "3" ? "both" : "cookie";
|
|
238
|
+
}
|
|
239
|
+
if (options.apiKey === void 0) apiKey = (await question("Enable API key plugin (machine-to-machine auth via @better-auth/api-key)? [y/N]: ")).toLowerCase() === "y";
|
|
240
|
+
}
|
|
162
241
|
let tenant = options.tenant || "single";
|
|
163
242
|
if (!options.tenant && !nonInteractive) tenant = await question("Tenant mode [1=Single-tenant, 2=Multi-tenant]: ") === "2" ? "multi" : "single";
|
|
164
243
|
let typescript = options.typescript ?? true;
|
|
@@ -176,7 +255,7 @@ async function gatherConfig(options) {
|
|
|
176
255
|
console.log("");
|
|
177
256
|
if ((await question("Continue with MongoKit? [y/N]: ")).toLowerCase() !== "y") {
|
|
178
257
|
adapter = "custom";
|
|
179
|
-
console.log(" Switched to custom adapter. Wire sqlitekit/Drizzle
|
|
258
|
+
console.log(" Switched to custom adapter. Wire sqlitekit/Drizzle, prismakit, or any RepositoryLike-conforming repo.");
|
|
180
259
|
}
|
|
181
260
|
}
|
|
182
261
|
return {
|
|
@@ -184,6 +263,8 @@ async function gatherConfig(options) {
|
|
|
184
263
|
adapter,
|
|
185
264
|
auth,
|
|
186
265
|
tenant,
|
|
266
|
+
apiKey,
|
|
267
|
+
session,
|
|
187
268
|
typescript,
|
|
188
269
|
edge
|
|
189
270
|
};
|
|
@@ -267,6 +348,7 @@ async function createProjectStructure(projectPath, config) {
|
|
|
267
348
|
}
|
|
268
349
|
}
|
|
269
350
|
function packageJsonTemplate(config) {
|
|
351
|
+
const { dependencies, devDependencies } = resolveScaffoldDependencies(config);
|
|
270
352
|
const scripts = config.typescript ? config.edge ? {
|
|
271
353
|
dev: "tsx watch src/index.ts",
|
|
272
354
|
build: "tsc",
|
|
@@ -309,6 +391,8 @@ function packageJsonTemplate(config) {
|
|
|
309
391
|
"#utils/*": "./src/utils/*"
|
|
310
392
|
},
|
|
311
393
|
scripts,
|
|
394
|
+
dependencies,
|
|
395
|
+
devDependencies,
|
|
312
396
|
engines: { node: ">=22" }
|
|
313
397
|
}, null, 2);
|
|
314
398
|
}
|
|
@@ -557,7 +641,7 @@ Short forms also supported: \`.env.prod\`, \`.env.dev\`, \`.env.test\`
|
|
|
557
641
|
|
|
558
642
|
API documentation is available via Scalar UI:
|
|
559
643
|
|
|
560
|
-
- **Interactive UI**: [http://localhost:8040/docs](http://localhost:8040/
|
|
644
|
+
- **Interactive UI**: [http://localhost:8040/docs](http://localhost:8040/data)
|
|
561
645
|
- **OpenAPI Spec**: [http://localhost:8040/_docs/openapi.json](http://localhost:8040/_docs/openapi.json)
|
|
562
646
|
|
|
563
647
|
## API Endpoints
|
|
@@ -678,7 +762,10 @@ function appTemplate(config) {
|
|
|
678
762
|
const betterAuthImport = config.auth === "better-auth" ? `import { createBetterAuthAdapter } from '@classytic/arc/auth';
|
|
679
763
|
import { getAuth } from './auth.js';
|
|
680
764
|
` : "";
|
|
681
|
-
const authConfig = config.auth === "better-auth" ?
|
|
765
|
+
const authConfig = config.auth === "better-auth" ? `auth: {
|
|
766
|
+
type: 'betterAuth',
|
|
767
|
+
betterAuth: createBetterAuthAdapter({ auth: getAuth(), orgContext: true }),
|
|
768
|
+
},` : `auth: {
|
|
682
769
|
type: 'jwt',
|
|
683
770
|
jwt: { secret: config.jwt.secret },
|
|
684
771
|
},`;
|
|
@@ -708,10 +795,13 @@ import { resources, registerResources } from '#resources/index.js';
|
|
|
708
795
|
* @returns Configured Fastify instance ready to use
|
|
709
796
|
*/
|
|
710
797
|
export async function createAppInstance()${ts ? ": Promise<FastifyInstance>" : ""} {
|
|
711
|
-
// Create Arc app with resources and base configuration
|
|
798
|
+
// Create Arc app with resources and base configuration. \`resourcePrefix\`
|
|
799
|
+
// mounts every resource under \`/api\`, matching the \`apiPrefix\` the
|
|
800
|
+
// OpenAPI plugin advertises — keeps docs and routes in agreement.
|
|
712
801
|
const app = await createApp({
|
|
713
802
|
preset: config.env === 'production' ? (${config.edge ? "'edge'" : "'production'"}) : 'development',
|
|
714
803
|
resources,
|
|
804
|
+
resourcePrefix: '/api',
|
|
715
805
|
${authConfig}
|
|
716
806
|
cors: {
|
|
717
807
|
origin: config.cors.origins,
|
|
@@ -952,7 +1042,8 @@ function sharedIndexTemplate(_config) {
|
|
|
952
1042
|
export { createAdapter } from './adapter.js';
|
|
953
1043
|
|
|
954
1044
|
// Core Arc exports
|
|
955
|
-
export {
|
|
1045
|
+
export { defineResource } from '@classytic/arc';
|
|
1046
|
+
export { createMongooseAdapter } from '@classytic/mongokit/adapter';
|
|
956
1047
|
|
|
957
1048
|
// Permission helpers (core + application-level)
|
|
958
1049
|
export * from './permissions.js';
|
|
@@ -970,14 +1061,17 @@ function createAdapterTemplate(config) {
|
|
|
970
1061
|
* The repository handles query parsing via MongoKit's built-in QueryParser.
|
|
971
1062
|
*/
|
|
972
1063
|
|
|
973
|
-
import { createMongooseAdapter } from '@classytic/
|
|
1064
|
+
import { createMongooseAdapter } from '@classytic/mongokit/adapter';
|
|
1065
|
+
import { buildCrudSchemasFromModel } from '@classytic/mongokit';
|
|
974
1066
|
${ts ? "import type { Model } from 'mongoose';\nimport type { Repository } from '@classytic/mongokit';" : ""}
|
|
975
1067
|
|
|
976
1068
|
/**
|
|
977
|
-
* Create a MongoKit-powered adapter for a resource
|
|
1069
|
+
* Create a MongoKit-powered adapter for a resource.
|
|
978
1070
|
*
|
|
979
1071
|
* Note: Query parsing is handled by MongoKit's Repository class.
|
|
980
|
-
*
|
|
1072
|
+
* \`buildCrudSchemasFromModel\` is the canonical OpenAPI schema generator
|
|
1073
|
+
* for arc + Mongoose (arc 2.12+ no longer ships a built-in fallback —
|
|
1074
|
+
* passing it explicitly is required for OpenAPI auto-generation).
|
|
981
1075
|
*/
|
|
982
1076
|
export function createAdapter${ts ? "<TDoc = any>" : ""}(
|
|
983
1077
|
model${ts ? ": Model<TDoc>" : ""},
|
|
@@ -986,6 +1080,7 @@ export function createAdapter${ts ? "<TDoc = any>" : ""}(
|
|
|
986
1080
|
return createMongooseAdapter({
|
|
987
1081
|
model,
|
|
988
1082
|
repository,
|
|
1083
|
+
schemaGenerator: buildCrudSchemasFromModel,
|
|
989
1084
|
});
|
|
990
1085
|
}
|
|
991
1086
|
`;
|
|
@@ -995,21 +1090,21 @@ function customAdapterTemplate(config) {
|
|
|
995
1090
|
return `/**
|
|
996
1091
|
* Custom Adapter Factory
|
|
997
1092
|
*
|
|
998
|
-
* Use this for
|
|
999
|
-
*
|
|
1093
|
+
* Use this for the bring-your-own-repository path — any object that
|
|
1094
|
+
* satisfies the \`RepositoryLike\` contract from
|
|
1095
|
+
* \`@classytic/repo-core/adapter\` plugs in here. Each classytic kit
|
|
1096
|
+
* also ships its own \`/adapter\` subpath; if one of those fits, import
|
|
1097
|
+
* its factory directly instead.
|
|
1000
1098
|
*/
|
|
1001
1099
|
|
|
1002
|
-
${ts ? "import type { DataAdapter, RepositoryLike } from '@classytic/
|
|
1100
|
+
${ts ? "import type { DataAdapter, RepositoryLike } from '@classytic/repo-core/adapter';" : ""}
|
|
1003
1101
|
|
|
1004
1102
|
/**
|
|
1005
1103
|
* Create a custom adapter for a resource.
|
|
1006
1104
|
*
|
|
1007
|
-
*
|
|
1008
|
-
*
|
|
1009
|
-
*
|
|
1010
|
-
* Experimental path:
|
|
1011
|
-
* - Prisma can be wired with createPrismaAdapter, but keep it opt-in until
|
|
1012
|
-
* your app has integration coverage.
|
|
1105
|
+
* Pass any object satisfying \`RepositoryLike<TDoc>\` (a 5-method floor:
|
|
1106
|
+
* \`getAll\` / \`getById\` / \`create\` / \`update\` / \`delete\`, plus any
|
|
1107
|
+
* optional \`StandardRepo\` methods you implement).
|
|
1013
1108
|
*/
|
|
1014
1109
|
export function createAdapter${ts ? "<TDoc = unknown>" : ""}(
|
|
1015
1110
|
_source${ts ? ": unknown" : ""},
|
|
@@ -1222,9 +1317,8 @@ function createTenantInjection(tenantField${ts ? ": string" : ""}) {
|
|
|
1222
1317
|
// Fail-closed: Require orgId for create operations
|
|
1223
1318
|
if (!orgId) {
|
|
1224
1319
|
return reply.code(403).send({
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
message: 'Organization context required to create resources',
|
|
1320
|
+
error: 'Organization context required to create resources',
|
|
1321
|
+
code: 'arc.org_required',
|
|
1228
1322
|
});
|
|
1229
1323
|
}
|
|
1230
1324
|
|
|
@@ -1808,8 +1902,10 @@ describe('Example Resource', () => {
|
|
|
1808
1902
|
authMode: 'jwt',
|
|
1809
1903
|
${config.adapter === "mongokit" ? " connectMongoose: true,\n" : ""} });
|
|
1810
1904
|
|
|
1905
|
+
// Arc's permission engine reads singular user.role — string,
|
|
1906
|
+
// comma-separated string, or array all normalise via getUserRoles().
|
|
1811
1907
|
ctx.auth${ts ? "!" : ""}.register('admin', {
|
|
1812
|
-
user: { id: '1',
|
|
1908
|
+
user: { id: '1', role: 'admin' },
|
|
1813
1909
|
orgId: 'org-1',
|
|
1814
1910
|
});
|
|
1815
1911
|
});
|
|
@@ -1913,13 +2009,19 @@ userSchema.methods.removeOrganization = function(organizationId${ts ? ": Types.O
|
|
|
1913
2009
|
userSchema.index({ 'organizations.organizationId': 1 });
|
|
1914
2010
|
` : "";
|
|
1915
2011
|
const userType = ts ? `
|
|
1916
|
-
|
|
2012
|
+
const PLATFORM_ROLES = ['user', 'admin', 'superadmin'] as const;
|
|
2013
|
+
type PlatformRole = typeof PLATFORM_ROLES[number];
|
|
1917
2014
|
|
|
2015
|
+
/**
|
|
2016
|
+
* Comma-separated list of platform roles (Better Auth admin-plugin convention).
|
|
2017
|
+
* Single role: 'admin'. Multiple: 'admin,trainer'. Arc's permission engine
|
|
2018
|
+
* normalises both forms via getUserRoles() — see @classytic/arc/scope.
|
|
2019
|
+
*/
|
|
1918
2020
|
type User = {
|
|
1919
2021
|
name: string;
|
|
1920
2022
|
email: string;
|
|
1921
2023
|
password: string;
|
|
1922
|
-
|
|
2024
|
+
role: string;${config.tenant === "multi" ? `
|
|
1923
2025
|
organizations: UserOrganization[];` : ""}
|
|
1924
2026
|
resetPasswordToken?: string;
|
|
1925
2027
|
resetPasswordExpires?: Date;
|
|
@@ -1958,11 +2060,21 @@ const userSchema = new Schema${ts ? "<User, UserModel, UserMethods>" : ""}(
|
|
|
1958
2060
|
},
|
|
1959
2061
|
password: { type: String, required: true },
|
|
1960
2062
|
|
|
1961
|
-
// Platform
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
|
|
2063
|
+
// Platform role — singular field, matches Arc's permission engine
|
|
2064
|
+
// (req.user.role) and Better Auth's admin-plugin convention.
|
|
2065
|
+
// Comma-separated for multi-role users (e.g. 'admin,trainer');
|
|
2066
|
+
// getUserRoles() in @classytic/arc/scope normalises both forms.
|
|
2067
|
+
role: {
|
|
2068
|
+
type: String,
|
|
2069
|
+
required: true,
|
|
2070
|
+
default: 'user',
|
|
2071
|
+
index: true,
|
|
2072
|
+
validate: {
|
|
2073
|
+
validator: (v${ts ? ": string" : ""}) =>
|
|
2074
|
+
/^(user|admin|superadmin)(,(user|admin|superadmin))*$/.test(v),
|
|
2075
|
+
message: (props${ts ? ": { value: string }" : ""}) =>
|
|
2076
|
+
\`Invalid role "\${props.value}" — expected one or more of user|admin|superadmin\`,
|
|
2077
|
+
},
|
|
1966
2078
|
},
|
|
1967
2079
|
${orgSchema}
|
|
1968
2080
|
// Password reset
|
|
@@ -2237,39 +2349,58 @@ export default authResource;
|
|
|
2237
2349
|
}
|
|
2238
2350
|
function betterAuthSetupTemplate(config) {
|
|
2239
2351
|
const ts = config.typescript;
|
|
2240
|
-
const
|
|
2241
|
-
|
|
2242
|
-
const
|
|
2352
|
+
const useMongo = config.adapter === "mongokit";
|
|
2353
|
+
const useStubs = useMongo;
|
|
2354
|
+
const useOrg = config.tenant === "multi";
|
|
2355
|
+
const useBearer = config.session === "bearer" || config.session === "both";
|
|
2356
|
+
const useApiKey = config.apiKey;
|
|
2357
|
+
const mongoImport = useMongo ? `import mongoose from 'mongoose';
|
|
2358
|
+
import { mongodbAdapter } from '@better-auth/mongo-adapter';` : "";
|
|
2359
|
+
const stubsImport = useStubs ? `import { registerBetterAuthStubs } from '@classytic/mongokit/better-auth';` : "";
|
|
2360
|
+
const pluginImports = [
|
|
2361
|
+
useOrg ? `import { organization } from 'better-auth/plugins';` : "",
|
|
2362
|
+
useBearer ? `import { bearer } from 'better-auth/plugins';` : "",
|
|
2363
|
+
useApiKey ? `import { apiKey } from '@better-auth/api-key';` : ""
|
|
2364
|
+
].filter(Boolean).join("\n");
|
|
2365
|
+
const dbAdapter = useMongo ? config.typescript ? `database: mongodbAdapter(mongoose.connection.getClient().db() as any),` : `database: mongodbAdapter(mongoose.connection.getClient().db()),` : `// Configure your database adapter here
|
|
2243
2366
|
// See: https://www.better-auth.com/docs/concepts/database`;
|
|
2244
|
-
const
|
|
2245
|
-
|
|
2246
|
-
|
|
2247
|
-
|
|
2248
|
-
|
|
2249
|
-
|
|
2250
|
-
organization({
|
|
2367
|
+
const pluginEntries = [
|
|
2368
|
+
useBearer ? ` bearer(),` : "",
|
|
2369
|
+
useApiKey ? ` apiKey({
|
|
2370
|
+
enableMetadata: true,
|
|
2371
|
+
rateLimit: { enabled: true, timeWindow: 1000 * 60 * 60 * 24, maxRequests: 10 },
|
|
2372
|
+
}),` : "",
|
|
2373
|
+
useOrg ? ` organization({
|
|
2251
2374
|
allowUserToCreateOrganization: true,
|
|
2252
2375
|
creatorRole: 'owner',
|
|
2253
|
-
teams: {
|
|
2254
|
-
|
|
2255
|
-
|
|
2256
|
-
|
|
2376
|
+
teams: { enabled: true },
|
|
2377
|
+
}),` : ""
|
|
2378
|
+
].filter(Boolean);
|
|
2379
|
+
const orgPluginUsage = pluginEntries.length > 0 ? `
|
|
2380
|
+
plugins: [
|
|
2381
|
+
${pluginEntries.join("\n")}
|
|
2257
2382
|
],` : "";
|
|
2383
|
+
const stubPluginsList = [useOrg ? `'organization'` : ""].filter(Boolean).join(", ");
|
|
2384
|
+
const stubExtras = useApiKey ? `, extraCollections: ['apikey']` : "";
|
|
2258
2385
|
return `/**
|
|
2259
2386
|
* Better Auth Configuration
|
|
2260
2387
|
* Generated by Arc CLI
|
|
2261
2388
|
*
|
|
2262
|
-
*
|
|
2263
|
-
*
|
|
2389
|
+
* Better Auth owns auth flows + writes its own tables. Arc reads them as
|
|
2390
|
+
* resources via \`@classytic/mongokit/better-auth\`'s overlay (full pagination,
|
|
2391
|
+
* queryparser, OpenAPI, audit) without re-implementing CRUD.
|
|
2264
2392
|
*
|
|
2265
|
-
*
|
|
2266
|
-
* - user, session, account${config.tenant === "multi" ? ", organization, member, invitation, team, teamMember" : ""}
|
|
2393
|
+
* BA-managed tables: user, session, account, verification${useOrg ? ", organization, member, invitation, team, teamMember" : ""}${useApiKey ? ", apikey" : ""}
|
|
2267
2394
|
*
|
|
2268
2395
|
* @see https://www.better-auth.com/docs
|
|
2269
2396
|
*/
|
|
2270
2397
|
|
|
2271
2398
|
import { betterAuth } from 'better-auth';
|
|
2272
|
-
${
|
|
2399
|
+
${[
|
|
2400
|
+
mongoImport,
|
|
2401
|
+
pluginImports,
|
|
2402
|
+
stubsImport
|
|
2403
|
+
].filter(Boolean).join("\n")}
|
|
2273
2404
|
import config from '#config/index.js';
|
|
2274
2405
|
|
|
2275
2406
|
let _auth${ts ? ": ReturnType<typeof betterAuth> | null" : ""} = null;
|
|
@@ -2333,16 +2464,11 @@ ${orgPluginUsage}
|
|
|
2333
2464
|
enabled: process.env.NODE_ENV === 'production',
|
|
2334
2465
|
},
|
|
2335
2466
|
});
|
|
2336
|
-
${
|
|
2337
|
-
// Register stub Mongoose models for Better Auth collections
|
|
2338
|
-
//
|
|
2339
|
-
//
|
|
2340
|
-
|
|
2341
|
-
for (const name of baCollections) {
|
|
2342
|
-
if (!mongoose.models[name]) {
|
|
2343
|
-
mongoose.model(name, new mongoose.Schema({}, { strict: false, collection: name }));
|
|
2344
|
-
}
|
|
2345
|
-
}
|
|
2467
|
+
${useStubs ? `
|
|
2468
|
+
// Register stub Mongoose models for Better Auth's collections so
|
|
2469
|
+
// \`.populate('user')\` / \`ref: 'organization'\` resolve against BA-owned
|
|
2470
|
+
// documents app-wide. Idempotent, strict:false, plugin-aware.
|
|
2471
|
+
registerBetterAuthStubs(mongoose, {${stubPluginsList ? ` plugins: [${stubPluginsList}]` : ""}${stubExtras} });
|
|
2346
2472
|
` : ""} }
|
|
2347
2473
|
|
|
2348
2474
|
return _auth;
|
|
@@ -2377,16 +2503,16 @@ export async function register(request${ts ? ": FastifyRequest" : ""}, reply${ts
|
|
|
2377
2503
|
|
|
2378
2504
|
// Check if email exists
|
|
2379
2505
|
if (await userRepository.emailExists(email)) {
|
|
2380
|
-
return reply.code(400).send({
|
|
2506
|
+
return reply.code(400).send({ error: 'Email already registered', code: 'arc.email_exists' });
|
|
2381
2507
|
}
|
|
2382
2508
|
|
|
2383
2509
|
// Create user
|
|
2384
|
-
await userRepository.create({ name, email, password,
|
|
2510
|
+
await userRepository.create({ name, email, password, role: 'user' });
|
|
2385
2511
|
|
|
2386
|
-
return reply.code(201).send({
|
|
2512
|
+
return reply.code(201).send({ message: 'User registered successfully' });
|
|
2387
2513
|
} catch (error) {
|
|
2388
2514
|
request.log.error({ err: error }, 'Register error');
|
|
2389
|
-
return reply.code(500).send({
|
|
2515
|
+
return reply.code(500).send({ error: 'Registration failed', code: 'arc.internal' });
|
|
2390
2516
|
}
|
|
2391
2517
|
}
|
|
2392
2518
|
|
|
@@ -2399,19 +2525,18 @@ export async function login(request${ts ? ": FastifyRequest" : ""}, reply${ts ?
|
|
|
2399
2525
|
|
|
2400
2526
|
const user = await userRepository.findByEmail(email);
|
|
2401
2527
|
if (!user || !(await user.matchPassword(password))) {
|
|
2402
|
-
return reply.code(401).send({
|
|
2528
|
+
return reply.code(401).send({ error: 'Invalid credentials', code: 'arc.unauthorized' });
|
|
2403
2529
|
}
|
|
2404
2530
|
|
|
2405
2531
|
const tokens = request.server.auth.issueTokens({ id: user._id.toString(), role: user.role });
|
|
2406
2532
|
|
|
2407
2533
|
return reply.send({
|
|
2408
|
-
success: true,
|
|
2409
2534
|
user: { id: user._id, name: user.name, email: user.email, role: user.role },
|
|
2410
2535
|
...tokens,
|
|
2411
2536
|
});
|
|
2412
2537
|
} catch (error) {
|
|
2413
2538
|
request.log.error({ err: error }, 'Login error');
|
|
2414
|
-
return reply.code(500).send({
|
|
2539
|
+
return reply.code(500).send({ error: 'Login failed', code: 'arc.internal' });
|
|
2415
2540
|
}
|
|
2416
2541
|
}
|
|
2417
2542
|
|
|
@@ -2422,15 +2547,15 @@ export async function refreshToken(request${ts ? ": FastifyRequest" : ""}, reply
|
|
|
2422
2547
|
try {
|
|
2423
2548
|
const { token } = request.body${ts ? " as any" : ""};
|
|
2424
2549
|
if (!token) {
|
|
2425
|
-
return reply.code(401).send({
|
|
2550
|
+
return reply.code(401).send({ error: 'Refresh token required', code: 'arc.unauthorized' });
|
|
2426
2551
|
}
|
|
2427
2552
|
|
|
2428
2553
|
const decoded = request.server.auth.verifyRefreshToken(token)${ts ? " as { id: string }" : ""};
|
|
2429
2554
|
const tokens = request.server.auth.issueTokens({ id: decoded.id });
|
|
2430
2555
|
|
|
2431
|
-
return reply.send(
|
|
2556
|
+
return reply.send(tokens);
|
|
2432
2557
|
} catch {
|
|
2433
|
-
return reply.code(401).send({
|
|
2558
|
+
return reply.code(401).send({ error: 'Invalid refresh token', code: 'arc.unauthorized' });
|
|
2434
2559
|
}
|
|
2435
2560
|
}
|
|
2436
2561
|
|
|
@@ -2451,11 +2576,11 @@ export async function forgotPassword(request${ts ? ": FastifyRequest" : ""}, rep
|
|
|
2451
2576
|
request.log.info(\`Password reset requested for \${email}\`);
|
|
2452
2577
|
}
|
|
2453
2578
|
|
|
2454
|
-
// Always
|
|
2455
|
-
return reply.send({
|
|
2579
|
+
// Always 200 to prevent email enumeration
|
|
2580
|
+
return reply.send({ message: 'If email exists, reset link sent' });
|
|
2456
2581
|
} catch (error) {
|
|
2457
2582
|
request.log.error({ err: error }, 'Forgot password error');
|
|
2458
|
-
return reply.code(500).send({
|
|
2583
|
+
return reply.code(500).send({ error: 'Failed to process request', code: 'arc.internal' });
|
|
2459
2584
|
}
|
|
2460
2585
|
}
|
|
2461
2586
|
|
|
@@ -2468,14 +2593,14 @@ export async function resetPassword(request${ts ? ": FastifyRequest" : ""}, repl
|
|
|
2468
2593
|
const user = await userRepository.findByResetToken(token);
|
|
2469
2594
|
|
|
2470
2595
|
if (!user) {
|
|
2471
|
-
return reply.code(400).send({
|
|
2596
|
+
return reply.code(400).send({ error: 'Invalid or expired token', code: 'arc.bad_request' });
|
|
2472
2597
|
}
|
|
2473
2598
|
|
|
2474
2599
|
await userRepository.updatePassword(user._id, newPassword);
|
|
2475
|
-
return reply.send({
|
|
2600
|
+
return reply.send({ message: 'Password has been reset' });
|
|
2476
2601
|
} catch (error) {
|
|
2477
2602
|
request.log.error({ err: error }, 'Reset password error');
|
|
2478
|
-
return reply.code(500).send({
|
|
2603
|
+
return reply.code(500).send({ error: 'Failed to reset password', code: 'arc.internal' });
|
|
2479
2604
|
}
|
|
2480
2605
|
}
|
|
2481
2606
|
|
|
@@ -2488,13 +2613,13 @@ export async function getUserProfile(request${ts ? ": FastifyRequest" : ""}, rep
|
|
|
2488
2613
|
const user = await userRepository.getById(userId);
|
|
2489
2614
|
|
|
2490
2615
|
if (!user) {
|
|
2491
|
-
return reply.code(404).send({
|
|
2616
|
+
return reply.code(404).send({ error: 'User not found', code: 'arc.not_found' });
|
|
2492
2617
|
}
|
|
2493
2618
|
|
|
2494
|
-
return reply.send(
|
|
2619
|
+
return reply.send(user);
|
|
2495
2620
|
} catch (error) {
|
|
2496
2621
|
request.log.error({ err: error }, 'Get profile error');
|
|
2497
|
-
return reply.code(500).send({
|
|
2622
|
+
return reply.code(500).send({ error: 'Failed to get profile', code: 'arc.internal' });
|
|
2498
2623
|
}
|
|
2499
2624
|
}
|
|
2500
2625
|
|
|
@@ -2506,16 +2631,16 @@ export async function updateUserProfile(request${ts ? ": FastifyRequest" : ""},
|
|
|
2506
2631
|
const userId = (request${ts ? " as any" : ""}).user?._id || (request${ts ? " as any" : ""}).user?.id;
|
|
2507
2632
|
const updates = { ...request.body${ts ? " as any" : ""} };
|
|
2508
2633
|
|
|
2509
|
-
// Prevent updating protected fields
|
|
2510
|
-
|
|
2511
|
-
|
|
2512
|
-
|
|
2634
|
+
// Prevent updating protected fields — auth-managed only
|
|
2635
|
+
delete updates.password;
|
|
2636
|
+
delete updates.role;
|
|
2637
|
+
delete updates.organizations;
|
|
2513
2638
|
|
|
2514
2639
|
const user = await userRepository.Model.findByIdAndUpdate(userId, updates, { new: true });
|
|
2515
|
-
return reply.send(
|
|
2640
|
+
return reply.send(user);
|
|
2516
2641
|
} catch (error) {
|
|
2517
2642
|
request.log.error({ err: error }, 'Update profile error');
|
|
2518
|
-
return reply.code(500).send({
|
|
2643
|
+
return reply.code(500).send({ error: 'Failed to update profile', code: 'arc.internal' });
|
|
2519
2644
|
}
|
|
2520
2645
|
}
|
|
2521
2646
|
`;
|
|
@@ -2598,7 +2723,7 @@ export const loginResponse = {
|
|
|
2598
2723
|
id: { type: 'string' },
|
|
2599
2724
|
name: { type: 'string' },
|
|
2600
2725
|
email: { type: 'string' },
|
|
2601
|
-
|
|
2726
|
+
role: { type: 'string' },
|
|
2602
2727
|
},
|
|
2603
2728
|
},
|
|
2604
2729
|
accessToken: { type: 'string' },
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { t as ResourceRegistry } from "../../ResourceRegistry-
|
|
1
|
+
import { t as ResourceRegistry } from "../../ResourceRegistry-CTERg_2x.mjs";
|
|
2
2
|
import { resolve } from "node:path";
|
|
3
3
|
import { pathToFileURL } from "node:url";
|
|
4
4
|
//#region src/cli/commands/introspect.ts
|
|
@@ -57,6 +57,11 @@ async function introspect(args) {
|
|
|
57
57
|
}
|
|
58
58
|
if (resource.presets && resource.presets.length > 0) console.log(` Presets: ${resource.presets.join(", ")}`);
|
|
59
59
|
if (resource.customRoutes && resource.customRoutes.length > 0) console.log(` Additional Routes: ${resource.customRoutes.length}`);
|
|
60
|
+
if (resource.actions && resource.actions.length > 0) {
|
|
61
|
+
const fallback = resource.actionPermissions ? ` (fallback: ${describePermission(resource.actionPermissions)})` : "";
|
|
62
|
+
console.log(` Actions: ${resource.actions.map((a) => a.name).join(", ")}${fallback}`);
|
|
63
|
+
}
|
|
64
|
+
if (resource.aggregations && resource.aggregations.length > 0) console.log(` Aggregations: ${resource.aggregations.map((a) => a.name).join(", ")}`);
|
|
60
65
|
console.log("");
|
|
61
66
|
});
|
|
62
67
|
const stats = registry.getStats();
|
|
@@ -64,6 +69,8 @@ async function introspect(args) {
|
|
|
64
69
|
console.log(` Total Resources: ${stats.totalResources}`);
|
|
65
70
|
console.log(` With Presets: ${resources.filter((r) => r.presets?.length > 0).length}`);
|
|
66
71
|
console.log(` With Custom Routes: ${resources.filter((r) => r.customRoutes && r.customRoutes.length > 0).length}`);
|
|
72
|
+
console.log(` With Actions: ${resources.filter((r) => r.actions && r.actions.length > 0).length}`);
|
|
73
|
+
console.log(` With Aggregations: ${resources.filter((r) => r.aggregations && r.aggregations.length > 0).length}`);
|
|
67
74
|
} catch (error) {
|
|
68
75
|
if (error instanceof Error) throw error;
|
|
69
76
|
throw new Error(String(error));
|
package/dist/context/index.mjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { t as requestContext } from "../requestContext-
|
|
1
|
+
import { t as requestContext } from "../requestContext-SSaaTgW8.mjs";
|
|
2
2
|
export { requestContext };
|