@classytic/arc 2.1.3 → 2.2.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/README.md +6 -5
- package/bin/arc.js +1 -0
- package/dist/adapters/index.d.mts +2 -2
- package/dist/audit/index.d.mts +1 -1
- package/dist/audit/mongodb.d.mts +1 -1
- package/dist/auth/index.d.mts +1 -1
- package/dist/cli/commands/generate.d.mts +5 -0
- package/dist/cli/commands/generate.mjs +87 -55
- package/dist/cli/commands/init.mjs +20 -13
- package/dist/core/index.d.mts +2 -2
- package/dist/core/index.mjs +1 -1
- package/dist/{createApp-D2D5XXaV.mjs → createApp-BTHYuHNU.mjs} +8 -3
- package/dist/{defineResource-PXzSJ15_.mjs → defineResource-Bq_fXZtm.mjs} +16 -28
- package/dist/docs/index.d.mts +1 -1
- package/dist/factory/index.d.mts +1 -1
- package/dist/factory/index.mjs +1 -1
- package/dist/{fastifyAdapter-C8DlE0YH.d.mts → fastifyAdapter-DTOLNtjw.d.mts} +1 -1
- package/dist/hooks/index.d.mts +1 -1
- package/dist/index.d.mts +3 -3
- package/dist/index.mjs +1 -1
- package/dist/{interface-e9XfSsUV.d.mts → interface-Dm4-jnia.d.mts} +15 -6
- package/dist/org/index.d.mts +1 -1
- package/dist/plugins/index.d.mts +1 -1
- package/dist/presets/index.d.mts +1 -1
- package/dist/presets/multiTenant.d.mts +1 -1
- package/dist/{prisma-C3iornoK.d.mts → prisma-Bi9nxirN.d.mts} +1 -1
- package/dist/registry/index.d.mts +1 -1
- package/dist/testing/index.d.mts +13 -3
- package/dist/testing/index.mjs +38 -6
- package/dist/types/index.d.mts +2 -2
- package/dist/utils/index.d.mts +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -326,11 +326,12 @@ import { tracingPlugin } from '@classytic/arc/plugins/tracing';
|
|
|
326
326
|
## CLI
|
|
327
327
|
|
|
328
328
|
```bash
|
|
329
|
-
arc init my-api --mongokit --better-auth --ts # Scaffold project
|
|
330
|
-
arc generate resource product # Generate resource files
|
|
331
|
-
arc
|
|
332
|
-
arc
|
|
333
|
-
arc
|
|
329
|
+
npx @classytic/arc init my-api --mongokit --better-auth --ts # Scaffold project
|
|
330
|
+
npx @classytic/arc generate resource product # Generate resource files
|
|
331
|
+
npx @classytic/arc describe ./dist/index.js # Resource metadata (JSON)
|
|
332
|
+
npx @classytic/arc docs ./openapi.json --entry ./dist/index.js # Export OpenAPI
|
|
333
|
+
npx @classytic/arc introspect --entry ./dist/index.js # Show resources
|
|
334
|
+
npx @classytic/arc doctor # Health check
|
|
334
335
|
```
|
|
335
336
|
|
|
336
337
|
## Subpath Imports
|
package/bin/arc.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import "../elevation-DGo5shaX.mjs";
|
|
2
|
-
import { a as RepositoryLike, i as RelationMetadata, n as DataAdapter, o as SchemaMetadata, r as FieldMetadata, s as ValidationResult, t as AdapterFactory } from "../interface-
|
|
2
|
+
import { a as RepositoryLike, i as RelationMetadata, n as DataAdapter, o as SchemaMetadata, r as FieldMetadata, s as ValidationResult, t as AdapterFactory } from "../interface-Dm4-jnia.mjs";
|
|
3
3
|
import "../types-RLkFVgaw.mjs";
|
|
4
|
-
import { a as PrismaQueryParserOptions, c as MongooseAdapterOptions, i as PrismaQueryParser, l as createMongooseAdapter, n as PrismaAdapterOptions, o as createPrismaAdapter, r as PrismaQueryOptions, s as MongooseAdapter, t as PrismaAdapter } from "../prisma-
|
|
4
|
+
import { a as PrismaQueryParserOptions, c as MongooseAdapterOptions, i as PrismaQueryParser, l as createMongooseAdapter, n as PrismaAdapterOptions, o as createPrismaAdapter, r as PrismaQueryOptions, s as MongooseAdapter, t as PrismaAdapter } from "../prisma-Bi9nxirN.mjs";
|
|
5
5
|
export { type AdapterFactory, type DataAdapter, type FieldMetadata, MongooseAdapter, type MongooseAdapterOptions, PrismaAdapter, type PrismaAdapterOptions, type PrismaQueryOptions, PrismaQueryParser, type PrismaQueryParserOptions, type RelationMetadata, type RepositoryLike, type SchemaMetadata, type ValidationResult, createMongooseAdapter, createPrismaAdapter };
|
package/dist/audit/index.d.mts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import "../elevation-DGo5shaX.mjs";
|
|
2
|
-
import "../interface-
|
|
2
|
+
import "../interface-Dm4-jnia.mjs";
|
|
3
3
|
import "../types-RLkFVgaw.mjs";
|
|
4
4
|
import { a as AuditContext, c as AuditStore, i as AuditAction, l as AuditStoreOptions, n as MongoAuditStoreOptions, o as AuditEntry, r as MongoConnection, s as AuditQueryOptions, u as createAuditEntry } from "../mongodb-ClykrfGo.mjs";
|
|
5
5
|
import { FastifyPluginAsync } from "fastify";
|
package/dist/audit/mongodb.d.mts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import "../elevation-DGo5shaX.mjs";
|
|
2
|
-
import "../interface-
|
|
2
|
+
import "../interface-Dm4-jnia.mjs";
|
|
3
3
|
import "../types-RLkFVgaw.mjs";
|
|
4
4
|
import { n as MongoAuditStoreOptions, t as MongoAuditStore } from "../mongodb-ClykrfGo.mjs";
|
|
5
5
|
export { MongoAuditStore, type MongoAuditStoreOptions };
|
package/dist/auth/index.d.mts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import "../elevation-DGo5shaX.mjs";
|
|
2
|
-
import "../interface-
|
|
2
|
+
import "../interface-Dm4-jnia.mjs";
|
|
3
3
|
import { t as PermissionCheck } from "../types-RLkFVgaw.mjs";
|
|
4
4
|
import { AuthHelpers, AuthPluginOptions } from "../types/index.mjs";
|
|
5
5
|
import { t as ExternalOpenApiPaths } from "../externalPaths-SyPF2tgK.mjs";
|
|
@@ -8,6 +8,11 @@
|
|
|
8
8
|
* - src/resources/product/product.resource.ts
|
|
9
9
|
* - src/resources/product/product.controller.ts
|
|
10
10
|
* - src/resources/product/product.schemas.ts
|
|
11
|
+
*
|
|
12
|
+
* Handles kebab-case names: `arc g r org-profile` generates:
|
|
13
|
+
* - Class names: OrgProfile, OrgProfileRepository
|
|
14
|
+
* - Variable names: orgProfileSchema, orgProfileRepository
|
|
15
|
+
* - File names: org-profile.model.ts, org-profile.repository.ts
|
|
11
16
|
*/
|
|
12
17
|
/**
|
|
13
18
|
* Generate command handler
|
|
@@ -12,6 +12,11 @@ import { join } from "node:path";
|
|
|
12
12
|
* - src/resources/product/product.resource.ts
|
|
13
13
|
* - src/resources/product/product.controller.ts
|
|
14
14
|
* - src/resources/product/product.schemas.ts
|
|
15
|
+
*
|
|
16
|
+
* Handles kebab-case names: `arc g r org-profile` generates:
|
|
17
|
+
* - Class names: OrgProfile, OrgProfileRepository
|
|
18
|
+
* - Variable names: orgProfileSchema, orgProfileRepository
|
|
19
|
+
* - File names: org-profile.model.ts, org-profile.repository.ts
|
|
15
20
|
*/
|
|
16
21
|
function readProjectConfig() {
|
|
17
22
|
try {
|
|
@@ -24,10 +29,25 @@ function readProjectConfig() {
|
|
|
24
29
|
function isTypeScriptProject() {
|
|
25
30
|
return existsSync(join(process.cwd(), "tsconfig.json"));
|
|
26
31
|
}
|
|
32
|
+
/** Convert kebab-case to PascalCase: org-profile → OrgProfile */
|
|
33
|
+
function toPascalCase(name) {
|
|
34
|
+
return name.split("-").map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join("");
|
|
35
|
+
}
|
|
36
|
+
/** Convert PascalCase to camelCase: OrgProfile → orgProfile */
|
|
37
|
+
function toCamelCase(pascalName) {
|
|
38
|
+
return pascalName.charAt(0).toLowerCase() + pascalName.slice(1);
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Template functions accept:
|
|
42
|
+
* - name: PascalCase class name (e.g., OrgProfile)
|
|
43
|
+
* - fileName: kebab-case for file paths (e.g., org-profile)
|
|
44
|
+
*/
|
|
27
45
|
function getTemplates(ts, config = {}) {
|
|
28
46
|
const isMultiTenant = config.tenant === "multi";
|
|
29
47
|
return {
|
|
30
|
-
model: (name) =>
|
|
48
|
+
model: (name, fileName) => {
|
|
49
|
+
const camel = toCamelCase(name);
|
|
50
|
+
return `/**
|
|
31
51
|
* ${name} Model
|
|
32
52
|
* Generated by Arc CLI
|
|
33
53
|
*/
|
|
@@ -44,7 +64,7 @@ export interface I${name} {
|
|
|
44
64
|
|
|
45
65
|
export type ${name}Document = HydratedDocument<I${name}>;
|
|
46
66
|
` : ""}
|
|
47
|
-
const ${
|
|
67
|
+
const ${camel}Schema = new Schema${ts ? `<I${name}>` : ""}(
|
|
48
68
|
{
|
|
49
69
|
name: { type: String, required: true, trim: true },
|
|
50
70
|
description: { type: String, trim: true },
|
|
@@ -54,13 +74,16 @@ const ${name.toLowerCase()}Schema = new Schema${ts ? `<I${name}>` : ""}(
|
|
|
54
74
|
);
|
|
55
75
|
|
|
56
76
|
// Indexes
|
|
57
|
-
${
|
|
58
|
-
${
|
|
77
|
+
${camel}Schema.index({ name: 1 });
|
|
78
|
+
${camel}Schema.index({ isActive: 1 });
|
|
59
79
|
|
|
60
|
-
const ${name} = mongoose.models.${name} || mongoose.model('${name}', ${
|
|
80
|
+
const ${name} = mongoose.models.${name} || mongoose.model('${name}', ${camel}Schema);
|
|
61
81
|
export default ${name};
|
|
62
|
-
|
|
63
|
-
|
|
82
|
+
`;
|
|
83
|
+
},
|
|
84
|
+
repository: (name, fileName) => {
|
|
85
|
+
const camel = toCamelCase(name);
|
|
86
|
+
return `/**
|
|
64
87
|
* ${name} Repository
|
|
65
88
|
* Generated by Arc CLI
|
|
66
89
|
*/
|
|
@@ -71,7 +94,7 @@ import {
|
|
|
71
94
|
softDeletePlugin,
|
|
72
95
|
mongoOperationsPlugin,
|
|
73
96
|
} from '@classytic/mongokit';
|
|
74
|
-
import ${name} from './${
|
|
97
|
+
import ${name} from './${fileName}.model.js';
|
|
75
98
|
|
|
76
99
|
class ${name}Repository extends Repository {
|
|
77
100
|
constructor() {
|
|
@@ -90,11 +113,14 @@ class ${name}Repository extends Repository {
|
|
|
90
113
|
}
|
|
91
114
|
}
|
|
92
115
|
|
|
93
|
-
const ${
|
|
94
|
-
export default ${
|
|
116
|
+
const ${camel}Repository = new ${name}Repository();
|
|
117
|
+
export default ${camel}Repository;
|
|
95
118
|
export { ${name}Repository };
|
|
96
|
-
|
|
97
|
-
|
|
119
|
+
`;
|
|
120
|
+
},
|
|
121
|
+
controller: (name, fileName) => {
|
|
122
|
+
const camel = toCamelCase(name);
|
|
123
|
+
return `/**
|
|
98
124
|
* ${name} Controller
|
|
99
125
|
* Generated by Arc CLI
|
|
100
126
|
*
|
|
@@ -103,27 +129,28 @@ export { ${name}Repository };
|
|
|
103
129
|
*/
|
|
104
130
|
|
|
105
131
|
import { BaseController } from '@classytic/arc';
|
|
106
|
-
import ${
|
|
132
|
+
import ${camel}Repository from './${fileName}.repository.js';
|
|
107
133
|
|
|
108
134
|
class ${name}Controller extends BaseController {
|
|
109
135
|
constructor() {
|
|
110
|
-
super(${
|
|
111
|
-
resourceName: '${
|
|
136
|
+
super(${camel}Repository, {
|
|
137
|
+
resourceName: '${fileName}',
|
|
112
138
|
});
|
|
113
139
|
}
|
|
114
140
|
|
|
115
141
|
// Add custom controller methods here
|
|
116
142
|
}
|
|
117
143
|
|
|
118
|
-
const ${
|
|
119
|
-
export default ${
|
|
120
|
-
|
|
121
|
-
|
|
144
|
+
const ${camel}Controller = new ${name}Controller();
|
|
145
|
+
export default ${camel}Controller;
|
|
146
|
+
`;
|
|
147
|
+
},
|
|
148
|
+
schemas: (name, fileName) => `/**
|
|
122
149
|
* ${name} Schemas
|
|
123
150
|
* Generated by Arc CLI
|
|
124
151
|
*/
|
|
125
152
|
|
|
126
|
-
import ${name} from './${
|
|
153
|
+
import ${name} from './${fileName}.model.js';
|
|
127
154
|
import { buildCrudSchemasFromModel } from '@classytic/mongokit/utils';
|
|
128
155
|
|
|
129
156
|
/**
|
|
@@ -145,7 +172,8 @@ const crudSchemas = buildCrudSchemasFromModel(${name}, {
|
|
|
145
172
|
|
|
146
173
|
export default crudSchemas;
|
|
147
174
|
`,
|
|
148
|
-
resource: (name) => {
|
|
175
|
+
resource: (name, fileName) => {
|
|
176
|
+
const camel = toCamelCase(name);
|
|
149
177
|
const useMongoKit = config.adapter === "mongokit" || !config.adapter;
|
|
150
178
|
const queryParserImport = useMongoKit ? `\nimport { QueryParser } from '@classytic/mongokit';\n\nconst queryParser = new QueryParser();\n` : "";
|
|
151
179
|
const queryParserConfig = useMongoKit ? `\n queryParser,` : "";
|
|
@@ -155,24 +183,24 @@ export default crudSchemas;
|
|
|
155
183
|
*/
|
|
156
184
|
|
|
157
185
|
import { defineResource, createMongooseAdapter } from '@classytic/arc';
|
|
158
|
-
import {
|
|
159
|
-
import ${name}${ts ? `, { type I${name} }` : ""} from './${
|
|
160
|
-
import ${
|
|
186
|
+
import { requireAuth, requireRoles } from '@classytic/arc/permissions';
|
|
187
|
+
import ${name}${ts ? `, { type I${name} }` : ""} from './${fileName}.model.js';
|
|
188
|
+
import ${camel}Repository from './${fileName}.repository.js';${queryParserImport}
|
|
161
189
|
|
|
162
|
-
const ${
|
|
163
|
-
name: '${
|
|
164
|
-
adapter: createMongooseAdapter(${name}, ${
|
|
190
|
+
const ${camel}Resource = defineResource${ts ? `<I${name}>` : ""}({
|
|
191
|
+
name: '${fileName}',
|
|
192
|
+
adapter: createMongooseAdapter(${name}, ${camel}Repository),${queryParserConfig}
|
|
165
193
|
presets: ['softDelete'],
|
|
166
194
|
permissions: {
|
|
167
|
-
list:
|
|
168
|
-
get:
|
|
169
|
-
create:
|
|
170
|
-
update:
|
|
171
|
-
delete:
|
|
195
|
+
list: requireAuth(),
|
|
196
|
+
get: requireAuth(),
|
|
197
|
+
create: requireRoles(['admin']),
|
|
198
|
+
update: requireRoles(['admin']),
|
|
199
|
+
delete: requireRoles(['admin']),
|
|
172
200
|
},
|
|
173
201
|
});
|
|
174
202
|
|
|
175
|
-
export default ${
|
|
203
|
+
export default ${camel}Resource;
|
|
176
204
|
` : `/**
|
|
177
205
|
* ${name} Resource
|
|
178
206
|
* Generated by Arc CLI
|
|
@@ -180,12 +208,12 @@ export default ${name.toLowerCase()}Resource;
|
|
|
180
208
|
|
|
181
209
|
import { defineResource, createMongooseAdapter } from '@classytic/arc';
|
|
182
210
|
import { requireAuth, requireRoles } from '@classytic/arc/permissions';
|
|
183
|
-
import ${name}${ts ? `, { type I${name} }` : ""} from './${
|
|
184
|
-
import ${
|
|
211
|
+
import ${name}${ts ? `, { type I${name} }` : ""} from './${fileName}.model.js';
|
|
212
|
+
import ${camel}Repository from './${fileName}.repository.js';${queryParserImport}
|
|
185
213
|
|
|
186
|
-
const ${
|
|
187
|
-
name: '${
|
|
188
|
-
adapter: createMongooseAdapter(${name}, ${
|
|
214
|
+
const ${camel}Resource = defineResource${ts ? `<I${name}>` : ""}({
|
|
215
|
+
name: '${fileName}',
|
|
216
|
+
adapter: createMongooseAdapter(${name}, ${camel}Repository),${queryParserConfig}
|
|
189
217
|
presets: ['softDelete'],
|
|
190
218
|
permissions: {
|
|
191
219
|
list: requireAuth(),
|
|
@@ -196,10 +224,12 @@ const ${name.toLowerCase()}Resource = defineResource${ts ? `<I${name}>` : ""}({
|
|
|
196
224
|
},
|
|
197
225
|
});
|
|
198
226
|
|
|
199
|
-
export default ${
|
|
227
|
+
export default ${camel}Resource;
|
|
200
228
|
`;
|
|
201
229
|
},
|
|
202
|
-
test: (name) =>
|
|
230
|
+
test: (name, fileName) => {
|
|
231
|
+
const camel = toCamelCase(name);
|
|
232
|
+
return `/**
|
|
203
233
|
* ${name} Tests
|
|
204
234
|
* Generated by Arc CLI
|
|
205
235
|
*/
|
|
@@ -207,7 +237,7 @@ export default ${name.toLowerCase()}Resource;
|
|
|
207
237
|
import { describe, it, expect, beforeAll, afterAll } from 'vitest';
|
|
208
238
|
import mongoose from 'mongoose';
|
|
209
239
|
import { createMinimalTestApp } from '@classytic/arc/testing';
|
|
210
|
-
${ts ? "import type { FastifyInstance } from 'fastify';\n" : ""}import ${
|
|
240
|
+
${ts ? "import type { FastifyInstance } from 'fastify';\n" : ""}import ${camel}Resource from '../src/resources/${fileName}/${fileName}.resource.js';
|
|
211
241
|
|
|
212
242
|
describe('${name} Resource', () => {
|
|
213
243
|
let app${ts ? ": FastifyInstance" : ""};
|
|
@@ -217,7 +247,7 @@ describe('${name} Resource', () => {
|
|
|
217
247
|
await mongoose.connect(testDbUri);
|
|
218
248
|
|
|
219
249
|
app = createMinimalTestApp();
|
|
220
|
-
await app.register(${
|
|
250
|
+
await app.register(${camel}Resource.toPlugin());
|
|
221
251
|
await app.ready();
|
|
222
252
|
});
|
|
223
253
|
|
|
@@ -226,11 +256,11 @@ describe('${name} Resource', () => {
|
|
|
226
256
|
await mongoose.connection.close();
|
|
227
257
|
});
|
|
228
258
|
|
|
229
|
-
describe('GET /${pluralize(
|
|
259
|
+
describe('GET /${pluralize(fileName)}', () => {
|
|
230
260
|
it('should return a list', async () => {
|
|
231
261
|
const response = await app.inject({
|
|
232
262
|
method: 'GET',
|
|
233
|
-
url: '/${pluralize(
|
|
263
|
+
url: '/${pluralize(fileName)}',
|
|
234
264
|
});
|
|
235
265
|
|
|
236
266
|
expect(response.statusCode).toBe(200);
|
|
@@ -239,7 +269,8 @@ describe('${name} Resource', () => {
|
|
|
239
269
|
});
|
|
240
270
|
});
|
|
241
271
|
});
|
|
242
|
-
|
|
272
|
+
`;
|
|
273
|
+
}
|
|
243
274
|
};
|
|
244
275
|
}
|
|
245
276
|
/**
|
|
@@ -249,7 +280,7 @@ async function generate(type, args) {
|
|
|
249
280
|
if (!type) throw new Error("Missing type argument\nUsage: arc generate <resource|controller|model|repository|schemas> <name>");
|
|
250
281
|
const [name] = args;
|
|
251
282
|
if (!name) throw new Error("Missing name argument\nUsage: arc generate <type> <name>\nExample: arc generate resource product");
|
|
252
|
-
const capitalizedName =
|
|
283
|
+
const capitalizedName = toPascalCase(name);
|
|
253
284
|
const lowerName = name.toLowerCase();
|
|
254
285
|
const projectConfig = readProjectConfig();
|
|
255
286
|
const ts = projectConfig.typescript ?? isTypeScriptProject();
|
|
@@ -290,9 +321,9 @@ async function generateResource(name, lowerName, resourcePath, templates, ext) {
|
|
|
290
321
|
console.log(` + Created: src/resources/${lowerName}/`);
|
|
291
322
|
}
|
|
292
323
|
const files = {
|
|
293
|
-
[`${lowerName}.model.${ext}`]: templates.model(name),
|
|
294
|
-
[`${lowerName}.repository.${ext}`]: templates.repository(name),
|
|
295
|
-
[`${lowerName}.resource.${ext}`]: templates.resource(name)
|
|
324
|
+
[`${lowerName}.model.${ext}`]: templates.model(name, lowerName),
|
|
325
|
+
[`${lowerName}.repository.${ext}`]: templates.repository(name, lowerName),
|
|
326
|
+
[`${lowerName}.resource.${ext}`]: templates.resource(name, lowerName)
|
|
296
327
|
};
|
|
297
328
|
for (const [filename, content] of Object.entries(files)) {
|
|
298
329
|
const filepath = join(resourcePath, filename);
|
|
@@ -306,9 +337,10 @@ async function generateResource(name, lowerName, resourcePath, templates, ext) {
|
|
|
306
337
|
if (!existsSync(testsDir)) mkdirSync(testsDir, { recursive: true });
|
|
307
338
|
const testPath = join(testsDir, `${lowerName}.test.${ext}`);
|
|
308
339
|
if (!existsSync(testPath)) {
|
|
309
|
-
writeFileSync(testPath, templates.test(name));
|
|
340
|
+
writeFileSync(testPath, templates.test(name, lowerName));
|
|
310
341
|
console.log(` + Created: tests/${lowerName}.test.${ext}`);
|
|
311
342
|
}
|
|
343
|
+
const camel = toCamelCase(name);
|
|
312
344
|
const isMultiTenant = readProjectConfig().tenant === "multi";
|
|
313
345
|
console.log(`
|
|
314
346
|
╔═══════════════════════════════════════════════════════════════╗
|
|
@@ -318,19 +350,19 @@ async function generateResource(name, lowerName, resourcePath, templates, ext) {
|
|
|
318
350
|
Next steps:
|
|
319
351
|
|
|
320
352
|
1. Register in src/resources/index.${ext}:
|
|
321
|
-
import ${
|
|
353
|
+
import ${camel}Resource from './${lowerName}/${lowerName}.resource.js';
|
|
322
354
|
|
|
323
355
|
export const resources = [
|
|
324
356
|
// ... existing resources
|
|
325
|
-
${
|
|
357
|
+
${camel}Resource,
|
|
326
358
|
];
|
|
327
359
|
|
|
328
360
|
2. Customize the model schema in:
|
|
329
361
|
src/resources/${lowerName}/${lowerName}.model.${ext}
|
|
330
362
|
|
|
331
363
|
3. Adjust permissions in ${lowerName}.resource.${ext}:
|
|
332
|
-
${isMultiTenant ? ` -
|
|
333
|
-
-
|
|
364
|
+
${isMultiTenant ? ` - requireAuth() → any authenticated user
|
|
365
|
+
- requireRoles(['admin']) → specific platform roles` : ` - requireAuth() → any authenticated user
|
|
334
366
|
- requireRoles(['admin']) → specific platform roles`}
|
|
335
367
|
|
|
336
368
|
4. Run tests:
|
|
@@ -349,7 +381,7 @@ async function generateFile(name, lowerName, resourcePath, fileType, template, e
|
|
|
349
381
|
const filename = `${lowerName}.${fileType}.${ext}`;
|
|
350
382
|
const filepath = join(resourcePath, filename);
|
|
351
383
|
if (existsSync(filepath)) throw new Error(`${filename} already exists. Remove it first or use a different name.`);
|
|
352
|
-
writeFileSync(filepath, template(name));
|
|
384
|
+
writeFileSync(filepath, template(name, lowerName));
|
|
353
385
|
console.log(` + Created: ${filename}`);
|
|
354
386
|
}
|
|
355
387
|
|
|
@@ -260,23 +260,20 @@ function packageJsonTemplate(config) {
|
|
|
260
260
|
test: "vitest run",
|
|
261
261
|
"test:watch": "vitest"
|
|
262
262
|
};
|
|
263
|
-
const imports = config.typescript ? {
|
|
264
|
-
"#config/*": "./dist/config/*",
|
|
265
|
-
"#shared/*": "./dist/shared/*",
|
|
266
|
-
"#resources/*": "./dist/resources/*",
|
|
267
|
-
"#plugins/*": "./dist/plugins/*"
|
|
268
|
-
} : {
|
|
269
|
-
"#config/*": "./src/config/*",
|
|
270
|
-
"#shared/*": "./src/shared/*",
|
|
271
|
-
"#resources/*": "./src/resources/*",
|
|
272
|
-
"#plugins/*": "./src/plugins/*"
|
|
273
|
-
};
|
|
274
263
|
return JSON.stringify({
|
|
275
264
|
name: config.name,
|
|
276
265
|
version: "1.0.0",
|
|
277
266
|
type: "module",
|
|
278
267
|
main: config.typescript ? "dist/index.js" : "src/index.js",
|
|
279
|
-
imports
|
|
268
|
+
imports: {
|
|
269
|
+
"#config/*": "./src/config/*",
|
|
270
|
+
"#shared/*": "./src/shared/*",
|
|
271
|
+
"#resources/*": "./src/resources/*",
|
|
272
|
+
"#plugins/*": "./src/plugins/*",
|
|
273
|
+
"#services/*": "./src/services/*",
|
|
274
|
+
"#lib/*": "./src/lib/*",
|
|
275
|
+
"#utils/*": "./src/utils/*"
|
|
276
|
+
},
|
|
280
277
|
scripts,
|
|
281
278
|
engines: { node: ">=20" }
|
|
282
279
|
}, null, 2);
|
|
@@ -2177,7 +2174,17 @@ ${orgPluginUsage}
|
|
|
2177
2174
|
enabled: process.env.NODE_ENV === 'production',
|
|
2178
2175
|
},
|
|
2179
2176
|
});
|
|
2180
|
-
|
|
2177
|
+
${config.adapter === "mongokit" ? `
|
|
2178
|
+
// Register stub Mongoose models for Better Auth collections.
|
|
2179
|
+
// BA uses the raw MongoDB driver, so no Mongoose models exist by default.
|
|
2180
|
+
// These stubs (strict: false) enable populate() on refs like 'user', 'organization', etc.
|
|
2181
|
+
const baCollections = ['user', 'organization', 'member', 'invitation', 'session', 'account'];
|
|
2182
|
+
for (const name of baCollections) {
|
|
2183
|
+
if (!mongoose.models[name]) {
|
|
2184
|
+
mongoose.model(name, new mongoose.Schema({}, { strict: false, collection: name }));
|
|
2185
|
+
}
|
|
2186
|
+
}
|
|
2187
|
+
` : ""} }
|
|
2181
2188
|
|
|
2182
2189
|
return _auth;
|
|
2183
2190
|
}
|
package/dist/core/index.d.mts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import "../elevation-DGo5shaX.mjs";
|
|
2
|
-
import { E as defineResource, T as ResourceDefinition, c as BaseController, d as QueryResolverConfig, f as BodySanitizer, h as AccessControlConfig, l as BaseControllerOptions, m as AccessControl, p as BodySanitizerConfig, u as QueryResolver } from "../interface-
|
|
2
|
+
import { E as defineResource, T as ResourceDefinition, c as BaseController, d as QueryResolverConfig, f as BodySanitizer, h as AccessControlConfig, l as BaseControllerOptions, m as AccessControl, p as BodySanitizerConfig, u as QueryResolver } from "../interface-Dm4-jnia.mjs";
|
|
3
3
|
import "../types-RLkFVgaw.mjs";
|
|
4
|
-
import { A as createCrudRouter, C as MutationOperation, D as ActionRouterConfig, E as ActionHandler, O as IdempotencyService, S as MUTATION_OPERATIONS, T as SYSTEM_FIELDS, _ as HookOperation, a as getControllerScope, b as MAX_REGEX_LENGTH, c as CrudOperation, d as DEFAULT_MAX_LIMIT, f as DEFAULT_SORT, g as HOOK_PHASES, h as HOOK_OPERATIONS, i as getControllerContext, j as createPermissionMiddleware, k as createActionRouter, l as DEFAULT_ID_FIELD, m as DEFAULT_UPDATE_METHOD, n as createFastifyHandler, o as sendControllerResponse, p as DEFAULT_TENANT_FIELD, r as createRequestContext, s as CRUD_OPERATIONS, t as createCrudHandlers, u as DEFAULT_LIMIT, v as HookPhase, w as RESERVED_QUERY_PARAMS, x as MAX_SEARCH_LENGTH, y as MAX_FILTER_DEPTH } from "../fastifyAdapter-
|
|
4
|
+
import { A as createCrudRouter, C as MutationOperation, D as ActionRouterConfig, E as ActionHandler, O as IdempotencyService, S as MUTATION_OPERATIONS, T as SYSTEM_FIELDS, _ as HookOperation, a as getControllerScope, b as MAX_REGEX_LENGTH, c as CrudOperation, d as DEFAULT_MAX_LIMIT, f as DEFAULT_SORT, g as HOOK_PHASES, h as HOOK_OPERATIONS, i as getControllerContext, j as createPermissionMiddleware, k as createActionRouter, l as DEFAULT_ID_FIELD, m as DEFAULT_UPDATE_METHOD, n as createFastifyHandler, o as sendControllerResponse, p as DEFAULT_TENANT_FIELD, r as createRequestContext, s as CRUD_OPERATIONS, t as createCrudHandlers, u as DEFAULT_LIMIT, v as HookPhase, w as RESERVED_QUERY_PARAMS, x as MAX_SEARCH_LENGTH, y as MAX_FILTER_DEPTH } from "../fastifyAdapter-DTOLNtjw.mjs";
|
|
5
5
|
export { AccessControl, type AccessControlConfig, type ActionHandler, type ActionRouterConfig, BaseController, type BaseControllerOptions, BodySanitizer, type BodySanitizerConfig, 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, type IdempotencyService, MAX_FILTER_DEPTH, MAX_REGEX_LENGTH, MAX_SEARCH_LENGTH, MUTATION_OPERATIONS, MutationOperation, QueryResolver, type QueryResolverConfig, RESERVED_QUERY_PARAMS, ResourceDefinition, SYSTEM_FIELDS, createActionRouter, createCrudHandlers, createCrudRouter, createFastifyHandler, createPermissionMiddleware, createRequestContext, defineResource, getControllerContext, getControllerScope, sendControllerResponse };
|
package/dist/core/index.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
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-DdXFXQtN.mjs";
|
|
2
|
-
import { _ as QueryResolver, c as createPermissionMiddleware, d as createFastifyHandler, f as createRequestContext, g as BaseController, h as sendControllerResponse, m as getControllerScope, n as defineResource, o as createActionRouter, p as getControllerContext, s as createCrudRouter, t as ResourceDefinition, u as createCrudHandlers, v as BodySanitizer, y as AccessControl } from "../defineResource-
|
|
2
|
+
import { _ as QueryResolver, c as createPermissionMiddleware, d as createFastifyHandler, f as createRequestContext, g as BaseController, h as sendControllerResponse, m as getControllerScope, n as defineResource, o as createActionRouter, p as getControllerContext, s as createCrudRouter, t as ResourceDefinition, u as createCrudHandlers, v as BodySanitizer, y as AccessControl } from "../defineResource-Bq_fXZtm.mjs";
|
|
3
3
|
|
|
4
4
|
export { AccessControl, BaseController, BodySanitizer, 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, createActionRouter, createCrudHandlers, createCrudRouter, createFastifyHandler, createPermissionMiddleware, createRequestContext, defineResource, getControllerContext, getControllerScope, sendControllerResponse };
|
|
@@ -50,7 +50,9 @@ const productionPreset = {
|
|
|
50
50
|
allowedHeaders: [
|
|
51
51
|
"Content-Type",
|
|
52
52
|
"Authorization",
|
|
53
|
-
"Accept"
|
|
53
|
+
"Accept",
|
|
54
|
+
"x-organization-id",
|
|
55
|
+
"x-request-id"
|
|
54
56
|
]
|
|
55
57
|
},
|
|
56
58
|
rateLimit: {
|
|
@@ -95,7 +97,9 @@ const developmentPreset = {
|
|
|
95
97
|
allowedHeaders: [
|
|
96
98
|
"Content-Type",
|
|
97
99
|
"Authorization",
|
|
98
|
-
"Accept"
|
|
100
|
+
"Accept",
|
|
101
|
+
"x-organization-id",
|
|
102
|
+
"x-request-id"
|
|
99
103
|
]
|
|
100
104
|
},
|
|
101
105
|
rateLimit: {
|
|
@@ -337,8 +341,9 @@ async function createApp(options) {
|
|
|
337
341
|
} else fastify.log.warn("Helmet disabled - security headers not applied");
|
|
338
342
|
if (config.cors !== false) {
|
|
339
343
|
const cors = await loadPlugin("cors");
|
|
340
|
-
const corsOptions = config.cors ?? {};
|
|
344
|
+
const corsOptions = { ...config.cors ?? {} };
|
|
341
345
|
if (config.preset === "production" && (!corsOptions || !("origin" in corsOptions))) throw new Error("CORS origin must be explicitly configured in production.\nSet cors.origin to allowed domains or set cors: false to disable.\nExample: cors: { origin: ['https://yourdomain.com'] }\nDocs: https://github.com/classytic/arc#security");
|
|
346
|
+
if (corsOptions.credentials && corsOptions.origin === "*") corsOptions.origin = true;
|
|
342
347
|
await fastify.register(cors, corsOptions);
|
|
343
348
|
fastify.log.debug("CORS enabled");
|
|
344
349
|
} else fastify.log.warn("CORS disabled");
|
|
@@ -41,7 +41,7 @@ var AccessControl = class AccessControl {
|
|
|
41
41
|
if (policyFilters) Object.assign(filter, policyFilters);
|
|
42
42
|
const scope = arcContext?._scope;
|
|
43
43
|
const orgId = scope ? getOrgId(scope) : void 0;
|
|
44
|
-
if (orgId && !policyFilters?.[this.tenantField]) filter[this.tenantField] = orgId;
|
|
44
|
+
if (this.tenantField && orgId && !policyFilters?.[this.tenantField]) filter[this.tenantField] = orgId;
|
|
45
45
|
return filter;
|
|
46
46
|
}
|
|
47
47
|
/**
|
|
@@ -65,6 +65,7 @@ var AccessControl = class AccessControl {
|
|
|
65
65
|
* unscoped records from leaking across tenants.
|
|
66
66
|
*/
|
|
67
67
|
checkOrgScope(item, arcContext) {
|
|
68
|
+
if (!this.tenantField) return true;
|
|
68
69
|
const scope = arcContext?._scope;
|
|
69
70
|
const orgId = scope ? getOrgId(scope) : void 0;
|
|
70
71
|
if (!item || !orgId) return true;
|
|
@@ -259,7 +260,7 @@ var QueryResolver = class {
|
|
|
259
260
|
this.defaultLimit = config.defaultLimit ?? DEFAULT_LIMIT;
|
|
260
261
|
this.defaultSort = config.defaultSort ?? DEFAULT_SORT;
|
|
261
262
|
this.schemaOptions = config.schemaOptions ?? {};
|
|
262
|
-
this.tenantField = config.tenantField
|
|
263
|
+
this.tenantField = config.tenantField !== void 0 ? config.tenantField : DEFAULT_TENANT_FIELD;
|
|
263
264
|
}
|
|
264
265
|
/**
|
|
265
266
|
* Resolve a request into parsed query options -- ONE parse per request.
|
|
@@ -278,7 +279,7 @@ var QueryResolver = class {
|
|
|
278
279
|
if (policyFilters) Object.assign(filters, policyFilters);
|
|
279
280
|
const scope = arcContext?._scope;
|
|
280
281
|
const orgId = scope ? getOrgId(scope) : void 0;
|
|
281
|
-
if (orgId && !policyFilters?.[this.tenantField]) filters[this.tenantField] = orgId;
|
|
282
|
+
if (this.tenantField && orgId && !policyFilters?.[this.tenantField]) filters[this.tenantField] = orgId;
|
|
282
283
|
return {
|
|
283
284
|
page,
|
|
284
285
|
limit,
|
|
@@ -371,7 +372,7 @@ var BaseController = class {
|
|
|
371
372
|
this.defaultLimit = options.defaultLimit ?? DEFAULT_LIMIT;
|
|
372
373
|
this.defaultSort = options.defaultSort ?? DEFAULT_SORT;
|
|
373
374
|
this.resourceName = options.resourceName;
|
|
374
|
-
this.tenantField = options.tenantField
|
|
375
|
+
this.tenantField = options.tenantField !== void 0 ? options.tenantField : DEFAULT_TENANT_FIELD;
|
|
375
376
|
this.idField = options.idField ?? DEFAULT_ID_FIELD;
|
|
376
377
|
this._matchesFilter = options.matchesFilter;
|
|
377
378
|
if (options.cache) this._cacheConfig = options.cache;
|
|
@@ -396,6 +397,16 @@ var BaseController = class {
|
|
|
396
397
|
this.update = this.update.bind(this);
|
|
397
398
|
this.delete = this.delete.bind(this);
|
|
398
399
|
}
|
|
400
|
+
/**
|
|
401
|
+
* Get the tenant field name if multi-tenant scoping is enabled.
|
|
402
|
+
* Returns `undefined` when `tenantField` is `false` (platform-universal mode).
|
|
403
|
+
*
|
|
404
|
+
* Use this in subclass overrides instead of accessing `this.tenantField` directly
|
|
405
|
+
* to avoid TypeScript indexing errors with `string | false`.
|
|
406
|
+
*/
|
|
407
|
+
getTenantField() {
|
|
408
|
+
return this.tenantField || void 0;
|
|
409
|
+
}
|
|
399
410
|
/** Extract typed Arc internal metadata from request */
|
|
400
411
|
meta(req) {
|
|
401
412
|
return req.metadata;
|
|
@@ -570,7 +581,7 @@ var BaseController = class {
|
|
|
570
581
|
const data = this.bodySanitizer.sanitize(req.body ?? {}, "create", req, arcContext);
|
|
571
582
|
const scope = arcContext?._scope;
|
|
572
583
|
const createOrgId = scope ? getOrgId(scope) : void 0;
|
|
573
|
-
if (createOrgId) data[this.tenantField] = createOrgId;
|
|
584
|
+
if (this.tenantField && createOrgId) data[this.tenantField] = createOrgId;
|
|
574
585
|
const userId = getUserId(req.user);
|
|
575
586
|
if (userId) data.createdBy = userId;
|
|
576
587
|
const hooks = this.getHooks(req);
|
|
@@ -1571,29 +1582,6 @@ function createActionRouter(fastify, config) {
|
|
|
1571
1582
|
type: "object",
|
|
1572
1583
|
properties: bodyProperties,
|
|
1573
1584
|
required: ["action"]
|
|
1574
|
-
},
|
|
1575
|
-
response: {
|
|
1576
|
-
200: {
|
|
1577
|
-
type: "object",
|
|
1578
|
-
properties: {
|
|
1579
|
-
success: { type: "boolean" },
|
|
1580
|
-
data: { type: "object" }
|
|
1581
|
-
}
|
|
1582
|
-
},
|
|
1583
|
-
400: {
|
|
1584
|
-
type: "object",
|
|
1585
|
-
properties: {
|
|
1586
|
-
success: { type: "boolean" },
|
|
1587
|
-
error: { type: "string" }
|
|
1588
|
-
}
|
|
1589
|
-
},
|
|
1590
|
-
403: {
|
|
1591
|
-
type: "object",
|
|
1592
|
-
properties: {
|
|
1593
|
-
success: { type: "boolean" },
|
|
1594
|
-
error: { type: "string" }
|
|
1595
|
-
}
|
|
1596
|
-
}
|
|
1597
1585
|
}
|
|
1598
1586
|
};
|
|
1599
1587
|
const preHandler = [];
|
package/dist/docs/index.d.mts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import "../elevation-DGo5shaX.mjs";
|
|
2
|
-
import "../interface-
|
|
2
|
+
import "../interface-Dm4-jnia.mjs";
|
|
3
3
|
import "../types-RLkFVgaw.mjs";
|
|
4
4
|
import { RegistryEntry } from "../types/index.mjs";
|
|
5
5
|
import { t as ExternalOpenApiPaths } from "../externalPaths-SyPF2tgK.mjs";
|
package/dist/factory/index.d.mts
CHANGED
package/dist/factory/index.mjs
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import { a as getPreset, i as developmentPreset, n as createApp, o as productionPreset, s as testingPreset, t as ArcFactory } from "../createApp-
|
|
1
|
+
import { a as getPreset, i as developmentPreset, n as createApp, o as productionPreset, s as testingPreset, t as ArcFactory } from "../createApp-BTHYuHNU.mjs";
|
|
2
2
|
|
|
3
3
|
export { ArcFactory, createApp, developmentPreset, getPreset, productionPreset, testingPreset };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { s as RequestScope } from "./elevation-DGo5shaX.mjs";
|
|
2
|
-
import { b as IControllerResponse, x as IRequestContext, y as IController } from "./interface-
|
|
2
|
+
import { b as IControllerResponse, x as IRequestContext, y as IController } from "./interface-Dm4-jnia.mjs";
|
|
3
3
|
import { t as PermissionCheck } from "./types-RLkFVgaw.mjs";
|
|
4
4
|
import { CrudController, CrudRouterOptions, FastifyWithDecorators, RequestContext, RequestWithExtras } from "./types/index.mjs";
|
|
5
5
|
import { FastifyInstance, FastifyReply, FastifyRequest, RouteHandlerMethod } from "fastify";
|
package/dist/hooks/index.d.mts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
import "../elevation-DGo5shaX.mjs";
|
|
2
|
-
import { $ as beforeUpdate, B as DefineHookOptions, G as HookRegistration, H as HookHandler, J as afterCreate, K as HookSystem, Q as beforeDelete, U as HookOperation, V as HookContext, W as HookPhase, X as afterUpdate, Y as afterDelete, Z as beforeCreate, et as createHookSystem, q as HookSystemOptions, tt as defineHook } from "../interface-
|
|
2
|
+
import { $ as beforeUpdate, B as DefineHookOptions, G as HookRegistration, H as HookHandler, J as afterCreate, K as HookSystem, Q as beforeDelete, U as HookOperation, V as HookContext, W as HookPhase, X as afterUpdate, Y as afterDelete, Z as beforeCreate, et as createHookSystem, q as HookSystemOptions, tt as defineHook } from "../interface-Dm4-jnia.mjs";
|
|
3
3
|
import "../types-RLkFVgaw.mjs";
|
|
4
4
|
export { type DefineHookOptions, type HookContext, type HookHandler, type HookOperation, type HookPhase, type HookRegistration, HookSystem, type HookSystemOptions, afterCreate, afterDelete, afterUpdate, beforeCreate, beforeDelete, beforeUpdate, createHookSystem, defineHook };
|
package/dist/index.d.mts
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import "./elevation-DGo5shaX.mjs";
|
|
2
|
-
import { D as CrudRepository, E as defineResource, F as OperationFilter, I as PipelineConfig, L as PipelineContext, M as Guard, N as Interceptor, P as NextFunction, R as PipelineStep, S as RouteHandler, T as ResourceDefinition, _ as ControllerLike, a as RepositoryLike, b as IControllerResponse, c as BaseController, i as RelationMetadata, j as QueryOptions, k as PaginatedResult, l as BaseControllerOptions, n as DataAdapter, o as SchemaMetadata, r as FieldMetadata, s as ValidationResult, x as IRequestContext, y as IController, z as Transform } from "./interface-
|
|
2
|
+
import { D as CrudRepository, E as defineResource, F as OperationFilter, I as PipelineConfig, L as PipelineContext, M as Guard, N as Interceptor, P as NextFunction, R as PipelineStep, S as RouteHandler, T as ResourceDefinition, _ as ControllerLike, a as RepositoryLike, b as IControllerResponse, c as BaseController, i as RelationMetadata, j as QueryOptions, k as PaginatedResult, l as BaseControllerOptions, n as DataAdapter, o as SchemaMetadata, r as FieldMetadata, s as ValidationResult, x as IRequestContext, y as IController, z as Transform } from "./interface-Dm4-jnia.mjs";
|
|
3
3
|
import { a as applyFieldWritePermissions, i as applyFieldReadPermissions, n as FieldPermissionMap, o as fields, t as FieldPermission } from "./fields-Bi_AVKSo.mjs";
|
|
4
4
|
import { i as UserBase, n as PermissionContext, r as PermissionResult, t as PermissionCheck } from "./types-RLkFVgaw.mjs";
|
|
5
5
|
import { AdditionalRoute, AnyRecord, ApiResponse, ArcInternalMetadata, AuthPluginOptions, ConfigError, CrudController, CrudRouteKey, CrudRouterOptions, CrudSchemas, EventDefinition, FastifyRequestExtras, FastifyWithAuth, FastifyWithDecorators, FieldRule, GracefulShutdownOptions, HealthCheck, HealthOptions, InferAdapterDoc, InferDocType, InferResourceDoc, IntrospectionData, IntrospectionPluginOptions, JWTPayload, MiddlewareConfig, MiddlewareHandler, OwnershipCheck, PresetFunction, PresetResult, RateLimitConfig, RegistryEntry, RegistryStats, RequestContext, RequestIdOptions, RequestWithExtras, ResourceConfig, ResourceMetadata, RouteHandlerMethod, RouteSchemaOptions, ServiceContext, TypedController, TypedRepository, TypedResourceConfig, UserOrganization, ValidateOptions, ValidationResult as ValidationResult$1 } from "./types/index.mjs";
|
|
6
|
-
import { c as MongooseAdapterOptions, l as createMongooseAdapter, n as PrismaAdapterOptions, o as createPrismaAdapter, s as MongooseAdapter, t as PrismaAdapter } from "./prisma-
|
|
6
|
+
import { c as MongooseAdapterOptions, l as createMongooseAdapter, n as PrismaAdapterOptions, o as createPrismaAdapter, s as MongooseAdapter, t as PrismaAdapter } from "./prisma-Bi9nxirN.mjs";
|
|
7
7
|
import "./adapters/index.mjs";
|
|
8
|
-
import { C as MutationOperation, S as MUTATION_OPERATIONS, T as SYSTEM_FIELDS, _ as HookOperation, a as getControllerScope, b as MAX_REGEX_LENGTH, c as CrudOperation, d as DEFAULT_MAX_LIMIT, f as DEFAULT_SORT, g as HOOK_PHASES, h as HOOK_OPERATIONS, l as DEFAULT_ID_FIELD, m as DEFAULT_UPDATE_METHOD, p as DEFAULT_TENANT_FIELD, s as CRUD_OPERATIONS, u as DEFAULT_LIMIT, v as HookPhase, w as RESERVED_QUERY_PARAMS, x as MAX_SEARCH_LENGTH, y as MAX_FILTER_DEPTH } from "./fastifyAdapter-
|
|
8
|
+
import { C as MutationOperation, S as MUTATION_OPERATIONS, T as SYSTEM_FIELDS, _ as HookOperation, a as getControllerScope, b as MAX_REGEX_LENGTH, c as CrudOperation, d as DEFAULT_MAX_LIMIT, f as DEFAULT_SORT, g as HOOK_PHASES, h as HOOK_OPERATIONS, l as DEFAULT_ID_FIELD, m as DEFAULT_UPDATE_METHOD, p as DEFAULT_TENANT_FIELD, s as CRUD_OPERATIONS, u as DEFAULT_LIMIT, v as HookPhase, w as RESERVED_QUERY_PARAMS, x as MAX_SEARCH_LENGTH, y as MAX_FILTER_DEPTH } from "./fastifyAdapter-DTOLNtjw.mjs";
|
|
9
9
|
import "./core/index.mjs";
|
|
10
10
|
import { a as NotFoundError, d as ValidationError, i as ForbiddenError, t as ArcError, u as UnauthorizedError } from "./errors-DAWRdiYP.mjs";
|
|
11
11
|
import { a as presets_d_exports, c as readOnly, i as ownerWithAdminBypass, n as authenticated, o as publicRead, r as fullPublic, s as publicReadAdminWrite, t as adminOnly } from "./presets-BTeYbw7h.mjs";
|
package/dist/index.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
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-DdXFXQtN.mjs";
|
|
2
2
|
import { a as createMongooseAdapter, i as MongooseAdapter, r as createPrismaAdapter, t as PrismaAdapter } from "./prisma-DJbMt3yf.mjs";
|
|
3
|
-
import { a as validateResourceConfig, g as BaseController, i as formatValidationErrors, l as pipe, m as getControllerScope, n as defineResource, r as assertValidConfig, t as ResourceDefinition } from "./defineResource-
|
|
3
|
+
import { a as validateResourceConfig, g as BaseController, i as formatValidationErrors, l as pipe, m as getControllerScope, n as defineResource, r as assertValidConfig, t as ResourceDefinition } from "./defineResource-Bq_fXZtm.mjs";
|
|
4
4
|
import { n as applyFieldWritePermissions, r as fields, t as applyFieldReadPermissions } from "./fields-CTd_CrKr.mjs";
|
|
5
5
|
import { i as NotFoundError, l as UnauthorizedError, r as ForbiddenError, t as ArcError, u as ValidationError } from "./errors-DBANPbGr.mjs";
|
|
6
6
|
import { t as requestContext } from "./requestContext-xi6OKBL-.mjs";
|
|
@@ -759,8 +759,8 @@ interface ControllerLike {
|
|
|
759
759
|
//#endregion
|
|
760
760
|
//#region src/core/AccessControl.d.ts
|
|
761
761
|
interface AccessControlConfig {
|
|
762
|
-
/** Field name used for multi-tenant scoping (default: 'organizationId') */
|
|
763
|
-
tenantField: string;
|
|
762
|
+
/** Field name used for multi-tenant scoping (default: 'organizationId'). Set to `false` to disable org filtering. */
|
|
763
|
+
tenantField: string | false;
|
|
764
764
|
/** Primary key field name (default: '_id') */
|
|
765
765
|
idField: string;
|
|
766
766
|
/**
|
|
@@ -876,8 +876,8 @@ interface QueryResolverConfig {
|
|
|
876
876
|
defaultSort?: string;
|
|
877
877
|
/** Schema options for field sanitization */
|
|
878
878
|
schemaOptions?: RouteSchemaOptions;
|
|
879
|
-
/** Field name used for multi-tenant scoping (default: 'organizationId') */
|
|
880
|
-
tenantField?: string;
|
|
879
|
+
/** Field name used for multi-tenant scoping (default: 'organizationId'). Set to `false` to disable. */
|
|
880
|
+
tenantField?: string | false;
|
|
881
881
|
}
|
|
882
882
|
declare class QueryResolver {
|
|
883
883
|
private queryParser;
|
|
@@ -926,8 +926,9 @@ interface BaseControllerOptions {
|
|
|
926
926
|
/**
|
|
927
927
|
* Field name used for multi-tenant scoping (default: 'organizationId').
|
|
928
928
|
* Override to match your schema: 'workspaceId', 'tenantId', 'teamId', etc.
|
|
929
|
+
* Set to `false` to disable org filtering for platform-universal resources.
|
|
929
930
|
*/
|
|
930
|
-
tenantField?: string;
|
|
931
|
+
tenantField?: string | false;
|
|
931
932
|
/**
|
|
932
933
|
* Primary key field name (default: '_id').
|
|
933
934
|
* Override for non-MongoDB adapters (e.g., 'id' for SQL databases).
|
|
@@ -965,7 +966,7 @@ declare class BaseController<TDoc = AnyRecord, TRepository extends RepositoryLik
|
|
|
965
966
|
protected defaultLimit: number;
|
|
966
967
|
protected defaultSort: string;
|
|
967
968
|
protected resourceName?: string;
|
|
968
|
-
protected tenantField: string;
|
|
969
|
+
protected tenantField: string | false;
|
|
969
970
|
protected idField: string;
|
|
970
971
|
/** Composable access control (ID filtering, policy checks, org scope, ownership) */
|
|
971
972
|
readonly accessControl: AccessControl;
|
|
@@ -977,6 +978,14 @@ declare class BaseController<TDoc = AnyRecord, TRepository extends RepositoryLik
|
|
|
977
978
|
private _presetFields;
|
|
978
979
|
private _cacheConfig?;
|
|
979
980
|
constructor(repository: TRepository, options?: BaseControllerOptions);
|
|
981
|
+
/**
|
|
982
|
+
* Get the tenant field name if multi-tenant scoping is enabled.
|
|
983
|
+
* Returns `undefined` when `tenantField` is `false` (platform-universal mode).
|
|
984
|
+
*
|
|
985
|
+
* Use this in subclass overrides instead of accessing `this.tenantField` directly
|
|
986
|
+
* to avoid TypeScript indexing errors with `string | false`.
|
|
987
|
+
*/
|
|
988
|
+
protected getTenantField(): string | undefined;
|
|
980
989
|
/** Extract typed Arc internal metadata from request */
|
|
981
990
|
private meta;
|
|
982
991
|
/** Get hook system from request context (instance-scoped) */
|
package/dist/org/index.d.mts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import "../elevation-DGo5shaX.mjs";
|
|
2
|
-
import { S as RouteHandler } from "../interface-
|
|
2
|
+
import { S as RouteHandler } from "../interface-Dm4-jnia.mjs";
|
|
3
3
|
import { i as UserBase } from "../types-RLkFVgaw.mjs";
|
|
4
4
|
import "../types/index.mjs";
|
|
5
5
|
import { InvitationAdapter, InvitationDoc, MemberDoc, OrgAdapter, OrgDoc, OrgPermissionStatement, OrgRole, OrganizationPluginOptions } from "./types.mjs";
|
package/dist/plugins/index.d.mts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import "../elevation-DGo5shaX.mjs";
|
|
2
|
-
import { K as HookSystem, w as ResourceRegistry } from "../interface-
|
|
2
|
+
import { K as HookSystem, w as ResourceRegistry } from "../interface-Dm4-jnia.mjs";
|
|
3
3
|
import "../types-RLkFVgaw.mjs";
|
|
4
4
|
import { AdditionalRoute, AnyRecord, MiddlewareConfig, PresetHook, RouteSchemaOptions } from "../types/index.mjs";
|
|
5
5
|
import { t as ExternalOpenApiPaths } from "../externalPaths-SyPF2tgK.mjs";
|
package/dist/presets/index.d.mts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import "../elevation-DGo5shaX.mjs";
|
|
2
|
-
import { b as IControllerResponse, k as PaginatedResult, x as IRequestContext } from "../interface-
|
|
2
|
+
import { b as IControllerResponse, k as PaginatedResult, x as IRequestContext } from "../interface-Dm4-jnia.mjs";
|
|
3
3
|
import "../types-RLkFVgaw.mjs";
|
|
4
4
|
import { AnyRecord, PresetResult, ResourceConfig } from "../types/index.mjs";
|
|
5
5
|
import multiTenantPreset, { MultiTenantOptions } from "./multiTenant.mjs";
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { D as CrudRepository, a as RepositoryLike, n as DataAdapter, o as SchemaMetadata, s as ValidationResult } from "./interface-
|
|
1
|
+
import { D as CrudRepository, a as RepositoryLike, n as DataAdapter, o as SchemaMetadata, s as ValidationResult } from "./interface-Dm4-jnia.mjs";
|
|
2
2
|
import { OpenApiSchemas, ParsedQuery, QueryParserInterface, RouteSchemaOptions } from "./types/index.mjs";
|
|
3
3
|
import { Model } from "mongoose";
|
|
4
4
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import "../elevation-DGo5shaX.mjs";
|
|
2
|
-
import { C as RegisterOptions, w as ResourceRegistry } from "../interface-
|
|
2
|
+
import { C as RegisterOptions, w as ResourceRegistry } from "../interface-Dm4-jnia.mjs";
|
|
3
3
|
import "../types-RLkFVgaw.mjs";
|
|
4
4
|
import { IntrospectionPluginOptions } from "../types/index.mjs";
|
|
5
5
|
import { FastifyPluginAsync } from "fastify";
|
package/dist/testing/index.d.mts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import "../elevation-DGo5shaX.mjs";
|
|
2
|
-
import { D as CrudRepository, T as ResourceDefinition } from "../interface-
|
|
2
|
+
import { D as CrudRepository, T as ResourceDefinition } from "../interface-Dm4-jnia.mjs";
|
|
3
3
|
import "../types-RLkFVgaw.mjs";
|
|
4
4
|
import { AnyRecord } from "../types/index.mjs";
|
|
5
5
|
import "../queryCachePlugin-Q6SYuHZ6.mjs";
|
|
@@ -649,7 +649,7 @@ interface HttpTestHarnessOptions<T = unknown> {
|
|
|
649
649
|
};
|
|
650
650
|
/** Auth provider for generating request headers */
|
|
651
651
|
auth: AuthProvider;
|
|
652
|
-
/** API path prefix (default: '/api') */
|
|
652
|
+
/** API path prefix (default: '/api' for eager, '' for deferred) */
|
|
653
653
|
apiPrefix?: string;
|
|
654
654
|
}
|
|
655
655
|
/** Options can be passed directly or as a getter for deferred resolution */
|
|
@@ -666,12 +666,21 @@ type OptionsOrGetter<T> = HttpTestHarnessOptions<T> | (() => HttpTestHarnessOpti
|
|
|
666
666
|
declare class HttpTestHarness<T = unknown> {
|
|
667
667
|
private resource;
|
|
668
668
|
private optionsOrGetter;
|
|
669
|
-
private
|
|
669
|
+
private eagerBaseUrl;
|
|
670
670
|
private enabledRoutes;
|
|
671
671
|
private updateMethod;
|
|
672
672
|
constructor(resource: ResourceDefinition<unknown>, optionsOrGetter: OptionsOrGetter<T>);
|
|
673
673
|
/** Resolve options (supports both direct and deferred) */
|
|
674
674
|
private getOptions;
|
|
675
|
+
/**
|
|
676
|
+
* Resolve the base URL for requests.
|
|
677
|
+
*
|
|
678
|
+
* - Eager mode: uses pre-computed baseUrl from constructor
|
|
679
|
+
* - Deferred mode: reads apiPrefix from the getter options at runtime
|
|
680
|
+
*
|
|
681
|
+
* Must only be called inside it()/afterAll() callbacks (after beforeAll has run).
|
|
682
|
+
*/
|
|
683
|
+
private getBaseUrl;
|
|
675
684
|
/**
|
|
676
685
|
* Run all test suites: CRUD + permissions + validation
|
|
677
686
|
*/
|
|
@@ -715,6 +724,7 @@ declare class HttpTestHarness<T = unknown> {
|
|
|
715
724
|
*
|
|
716
725
|
* createHttpTestHarness(jobResource, () => ({
|
|
717
726
|
* app: ctx.app,
|
|
727
|
+
* apiPrefix: '',
|
|
718
728
|
* fixtures: { valid: { title: 'Test' } },
|
|
719
729
|
* auth: createBetterAuthProvider({ ... }),
|
|
720
730
|
* })).runAll();
|
package/dist/testing/index.mjs
CHANGED
|
@@ -1000,7 +1000,7 @@ var DatabaseSnapshot = class {
|
|
|
1000
1000
|
* ```
|
|
1001
1001
|
*/
|
|
1002
1002
|
async function createTestApp(options = {}) {
|
|
1003
|
-
const { createApp } = await import("../createApp-
|
|
1003
|
+
const { createApp } = await import("../createApp-BTHYuHNU.mjs").then((n) => n.r);
|
|
1004
1004
|
const { useInMemoryDb = true, mongoUri: providedMongoUri, ...appOptions } = options;
|
|
1005
1005
|
const defaultAuth = {
|
|
1006
1006
|
type: "jwt",
|
|
@@ -1606,6 +1606,7 @@ async function setupBetterAuthOrg(options) {
|
|
|
1606
1606
|
*
|
|
1607
1607
|
* const harness = createHttpTestHarness(jobResource, () => ({
|
|
1608
1608
|
* app: ctx.app,
|
|
1609
|
+
* apiPrefix: '',
|
|
1609
1610
|
* fixtures: { valid: { title: 'Test' } },
|
|
1610
1611
|
* auth: createBetterAuthProvider({ tokens: { admin: ctx.users.admin.token }, orgId: ctx.orgId, adminRole: 'admin' }),
|
|
1611
1612
|
* }));
|
|
@@ -1687,13 +1688,14 @@ function createBetterAuthProvider(options) {
|
|
|
1687
1688
|
var HttpTestHarness = class {
|
|
1688
1689
|
resource;
|
|
1689
1690
|
optionsOrGetter;
|
|
1690
|
-
|
|
1691
|
+
eagerBaseUrl;
|
|
1691
1692
|
enabledRoutes;
|
|
1692
1693
|
updateMethod;
|
|
1693
1694
|
constructor(resource, optionsOrGetter) {
|
|
1694
1695
|
this.resource = resource;
|
|
1695
1696
|
this.optionsOrGetter = optionsOrGetter;
|
|
1696
|
-
|
|
1697
|
+
if (typeof optionsOrGetter === "function") this.eagerBaseUrl = null;
|
|
1698
|
+
else this.eagerBaseUrl = `${optionsOrGetter.apiPrefix ?? "/api"}${resource.prefix}`;
|
|
1697
1699
|
const disabled = new Set(resource.disabledRoutes ?? []);
|
|
1698
1700
|
this.enabledRoutes = new Set(resource.disableDefaultRoutes ? [] : CRUD_OPERATIONS.filter((op) => !disabled.has(op)));
|
|
1699
1701
|
this.updateMethod = resource.updateMethod === "PUT" ? "PUT" : "PATCH";
|
|
@@ -1703,6 +1705,18 @@ var HttpTestHarness = class {
|
|
|
1703
1705
|
return typeof this.optionsOrGetter === "function" ? this.optionsOrGetter() : this.optionsOrGetter;
|
|
1704
1706
|
}
|
|
1705
1707
|
/**
|
|
1708
|
+
* Resolve the base URL for requests.
|
|
1709
|
+
*
|
|
1710
|
+
* - Eager mode: uses pre-computed baseUrl from constructor
|
|
1711
|
+
* - Deferred mode: reads apiPrefix from the getter options at runtime
|
|
1712
|
+
*
|
|
1713
|
+
* Must only be called inside it()/afterAll() callbacks (after beforeAll has run).
|
|
1714
|
+
*/
|
|
1715
|
+
getBaseUrl() {
|
|
1716
|
+
if (this.eagerBaseUrl !== null) return this.eagerBaseUrl;
|
|
1717
|
+
return `${this.getOptions().apiPrefix ?? ""}${this.resource.prefix}`;
|
|
1718
|
+
}
|
|
1719
|
+
/**
|
|
1706
1720
|
* Run all test suites: CRUD + permissions + validation
|
|
1707
1721
|
*/
|
|
1708
1722
|
runAll() {
|
|
@@ -1722,12 +1736,13 @@ var HttpTestHarness = class {
|
|
|
1722
1736
|
* - GET /:id with non-existent ID → 404
|
|
1723
1737
|
*/
|
|
1724
1738
|
runCrud() {
|
|
1725
|
-
const { resource,
|
|
1739
|
+
const { resource, enabledRoutes, updateMethod } = this;
|
|
1726
1740
|
let createdId = null;
|
|
1727
1741
|
describe(`${resource.displayName} HTTP CRUD`, () => {
|
|
1728
1742
|
afterAll(async () => {
|
|
1729
1743
|
if (createdId && enabledRoutes.has("delete")) {
|
|
1730
1744
|
const { app, auth } = this.getOptions();
|
|
1745
|
+
const baseUrl = this.getBaseUrl();
|
|
1731
1746
|
await app.inject({
|
|
1732
1747
|
method: "DELETE",
|
|
1733
1748
|
url: `${baseUrl}/${createdId}`,
|
|
@@ -1737,6 +1752,7 @@ var HttpTestHarness = class {
|
|
|
1737
1752
|
});
|
|
1738
1753
|
if (enabledRoutes.has("create")) it("POST should create a resource", async () => {
|
|
1739
1754
|
const { app, auth, fixtures } = this.getOptions();
|
|
1755
|
+
const baseUrl = this.getBaseUrl();
|
|
1740
1756
|
const adminHeaders = auth.getHeaders(auth.adminRole);
|
|
1741
1757
|
const res = await app.inject({
|
|
1742
1758
|
method: "POST",
|
|
@@ -1753,6 +1769,7 @@ var HttpTestHarness = class {
|
|
|
1753
1769
|
});
|
|
1754
1770
|
if (enabledRoutes.has("list")) it("GET should list resources", async () => {
|
|
1755
1771
|
const { app, auth } = this.getOptions();
|
|
1772
|
+
const baseUrl = this.getBaseUrl();
|
|
1756
1773
|
const res = await app.inject({
|
|
1757
1774
|
method: "GET",
|
|
1758
1775
|
url: baseUrl,
|
|
@@ -1769,6 +1786,7 @@ var HttpTestHarness = class {
|
|
|
1769
1786
|
it("GET /:id should return the resource", async () => {
|
|
1770
1787
|
if (!createdId) return;
|
|
1771
1788
|
const { app, auth } = this.getOptions();
|
|
1789
|
+
const baseUrl = this.getBaseUrl();
|
|
1772
1790
|
const res = await app.inject({
|
|
1773
1791
|
method: "GET",
|
|
1774
1792
|
url: `${baseUrl}/${createdId}`,
|
|
@@ -1782,6 +1800,7 @@ var HttpTestHarness = class {
|
|
|
1782
1800
|
});
|
|
1783
1801
|
it("GET /:id with non-existent ID should return 404", async () => {
|
|
1784
1802
|
const { app, auth } = this.getOptions();
|
|
1803
|
+
const baseUrl = this.getBaseUrl();
|
|
1785
1804
|
const res = await app.inject({
|
|
1786
1805
|
method: "GET",
|
|
1787
1806
|
url: `${baseUrl}/000000000000000000000000`,
|
|
@@ -1795,6 +1814,7 @@ var HttpTestHarness = class {
|
|
|
1795
1814
|
it(`${updateMethod} /:id should update the resource`, async () => {
|
|
1796
1815
|
if (!createdId) return;
|
|
1797
1816
|
const { app, auth, fixtures } = this.getOptions();
|
|
1817
|
+
const baseUrl = this.getBaseUrl();
|
|
1798
1818
|
const updatePayload = fixtures.update || fixtures.valid;
|
|
1799
1819
|
const res = await app.inject({
|
|
1800
1820
|
method: updateMethod,
|
|
@@ -1809,6 +1829,7 @@ var HttpTestHarness = class {
|
|
|
1809
1829
|
});
|
|
1810
1830
|
it(`${updateMethod} /:id with non-existent ID should return 404`, async () => {
|
|
1811
1831
|
const { app, auth, fixtures } = this.getOptions();
|
|
1832
|
+
const baseUrl = this.getBaseUrl();
|
|
1812
1833
|
expect((await app.inject({
|
|
1813
1834
|
method: updateMethod,
|
|
1814
1835
|
url: `${baseUrl}/000000000000000000000000`,
|
|
@@ -1820,6 +1841,7 @@ var HttpTestHarness = class {
|
|
|
1820
1841
|
if (enabledRoutes.has("delete")) {
|
|
1821
1842
|
it("DELETE /:id should delete the resource", async () => {
|
|
1822
1843
|
const { app, auth, fixtures } = this.getOptions();
|
|
1844
|
+
const baseUrl = this.getBaseUrl();
|
|
1823
1845
|
const adminHeaders = auth.getHeaders(auth.adminRole);
|
|
1824
1846
|
let deleteId;
|
|
1825
1847
|
if (enabledRoutes.has("create")) {
|
|
@@ -1845,6 +1867,7 @@ var HttpTestHarness = class {
|
|
|
1845
1867
|
});
|
|
1846
1868
|
it("DELETE /:id with non-existent ID should return 404", async () => {
|
|
1847
1869
|
const { app, auth } = this.getOptions();
|
|
1870
|
+
const baseUrl = this.getBaseUrl();
|
|
1848
1871
|
expect((await app.inject({
|
|
1849
1872
|
method: "DELETE",
|
|
1850
1873
|
url: `${baseUrl}/000000000000000000000000`,
|
|
@@ -1862,10 +1885,11 @@ var HttpTestHarness = class {
|
|
|
1862
1885
|
* - Admin role gets 2xx for all operations
|
|
1863
1886
|
*/
|
|
1864
1887
|
runPermissions() {
|
|
1865
|
-
const { resource,
|
|
1888
|
+
const { resource, enabledRoutes, updateMethod } = this;
|
|
1866
1889
|
describe(`${resource.displayName} HTTP Permissions`, () => {
|
|
1867
1890
|
if (enabledRoutes.has("list")) it("GET list without auth should return 401", async () => {
|
|
1868
1891
|
const { app } = this.getOptions();
|
|
1892
|
+
const baseUrl = this.getBaseUrl();
|
|
1869
1893
|
expect((await app.inject({
|
|
1870
1894
|
method: "GET",
|
|
1871
1895
|
url: baseUrl
|
|
@@ -1873,6 +1897,7 @@ var HttpTestHarness = class {
|
|
|
1873
1897
|
});
|
|
1874
1898
|
if (enabledRoutes.has("get")) it("GET get without auth should return 401", async () => {
|
|
1875
1899
|
const { app } = this.getOptions();
|
|
1900
|
+
const baseUrl = this.getBaseUrl();
|
|
1876
1901
|
expect((await app.inject({
|
|
1877
1902
|
method: "GET",
|
|
1878
1903
|
url: `${baseUrl}/000000000000000000000000`
|
|
@@ -1880,6 +1905,7 @@ var HttpTestHarness = class {
|
|
|
1880
1905
|
});
|
|
1881
1906
|
if (enabledRoutes.has("create")) it("POST create without auth should return 401", async () => {
|
|
1882
1907
|
const { app, fixtures } = this.getOptions();
|
|
1908
|
+
const baseUrl = this.getBaseUrl();
|
|
1883
1909
|
expect((await app.inject({
|
|
1884
1910
|
method: "POST",
|
|
1885
1911
|
url: baseUrl,
|
|
@@ -1888,6 +1914,7 @@ var HttpTestHarness = class {
|
|
|
1888
1914
|
});
|
|
1889
1915
|
if (enabledRoutes.has("update")) it(`${updateMethod} update without auth should return 401`, async () => {
|
|
1890
1916
|
const { app, fixtures } = this.getOptions();
|
|
1917
|
+
const baseUrl = this.getBaseUrl();
|
|
1891
1918
|
expect((await app.inject({
|
|
1892
1919
|
method: updateMethod,
|
|
1893
1920
|
url: `${baseUrl}/000000000000000000000000`,
|
|
@@ -1896,6 +1923,7 @@ var HttpTestHarness = class {
|
|
|
1896
1923
|
});
|
|
1897
1924
|
if (enabledRoutes.has("delete")) it("DELETE delete without auth should return 401", async () => {
|
|
1898
1925
|
const { app } = this.getOptions();
|
|
1926
|
+
const baseUrl = this.getBaseUrl();
|
|
1899
1927
|
expect((await app.inject({
|
|
1900
1928
|
method: "DELETE",
|
|
1901
1929
|
url: `${baseUrl}/000000000000000000000000`
|
|
@@ -1903,6 +1931,7 @@ var HttpTestHarness = class {
|
|
|
1903
1931
|
});
|
|
1904
1932
|
if (enabledRoutes.has("list")) it("admin should access list endpoint", async () => {
|
|
1905
1933
|
const { app, auth } = this.getOptions();
|
|
1934
|
+
const baseUrl = this.getBaseUrl();
|
|
1906
1935
|
expect((await app.inject({
|
|
1907
1936
|
method: "GET",
|
|
1908
1937
|
url: baseUrl,
|
|
@@ -1911,6 +1940,7 @@ var HttpTestHarness = class {
|
|
|
1911
1940
|
});
|
|
1912
1941
|
if (enabledRoutes.has("create")) it("admin should access create endpoint", async () => {
|
|
1913
1942
|
const { app, auth, fixtures } = this.getOptions();
|
|
1943
|
+
const baseUrl = this.getBaseUrl();
|
|
1914
1944
|
const res = await app.inject({
|
|
1915
1945
|
method: "POST",
|
|
1916
1946
|
url: baseUrl,
|
|
@@ -1933,11 +1963,12 @@ var HttpTestHarness = class {
|
|
|
1933
1963
|
* Tests that invalid payloads return 400.
|
|
1934
1964
|
*/
|
|
1935
1965
|
runValidation() {
|
|
1936
|
-
const { resource,
|
|
1966
|
+
const { resource, enabledRoutes } = this;
|
|
1937
1967
|
if (!enabledRoutes.has("create")) return;
|
|
1938
1968
|
describe(`${resource.displayName} HTTP Validation`, () => {
|
|
1939
1969
|
it("POST with invalid payload should not return 2xx", async () => {
|
|
1940
1970
|
const { app, auth, fixtures } = this.getOptions();
|
|
1971
|
+
const baseUrl = this.getBaseUrl();
|
|
1941
1972
|
if (!fixtures.invalid) return;
|
|
1942
1973
|
const res = await app.inject({
|
|
1943
1974
|
method: "POST",
|
|
@@ -1963,6 +1994,7 @@ var HttpTestHarness = class {
|
|
|
1963
1994
|
*
|
|
1964
1995
|
* createHttpTestHarness(jobResource, () => ({
|
|
1965
1996
|
* app: ctx.app,
|
|
1997
|
+
* apiPrefix: '',
|
|
1966
1998
|
* fixtures: { valid: { title: 'Test' } },
|
|
1967
1999
|
* auth: createBetterAuthProvider({ ... }),
|
|
1968
2000
|
* })).runAll();
|
package/dist/types/index.d.mts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { a as AUTHENTICATED_SCOPE, c as getOrgId, d as hasOrgAccess, f as isAuthenticated, l as getOrgRoles, m as isMember, n as ElevationOptions, o as PUBLIC_SCOPE, p as isElevated, s as RequestScope, t as ElevationEvent, u as getTeamId } from "../elevation-DGo5shaX.mjs";
|
|
2
|
-
import { A as PaginationParams, D as CrudRepository, I as PipelineConfig, K as HookSystem, O as InferDoc, S as RouteHandler, _ as ControllerLike, b as IControllerResponse, g as ControllerHandler, j as QueryOptions, k as PaginatedResult, l as BaseControllerOptions, n as DataAdapter, v as FastifyHandler, w as ResourceRegistry, x as IRequestContext, y as IController } from "../interface-
|
|
2
|
+
import { A as PaginationParams, D as CrudRepository, I as PipelineConfig, K as HookSystem, O as InferDoc, S as RouteHandler, _ as ControllerLike, b as IControllerResponse, g as ControllerHandler, j as QueryOptions, k as PaginatedResult, l as BaseControllerOptions, n as DataAdapter, v as FastifyHandler, w as ResourceRegistry, x as IRequestContext, y as IController } from "../interface-Dm4-jnia.mjs";
|
|
3
3
|
import { n as FieldPermissionMap } from "../fields-Bi_AVKSo.mjs";
|
|
4
4
|
import { i as UserBase, n as PermissionContext, r as PermissionResult, t as PermissionCheck } from "../types-RLkFVgaw.mjs";
|
|
5
5
|
import { FastifyInstance, FastifyReply, FastifyRequest, RouteHandlerMethod, RouteHandlerMethod as RouteHandlerMethod$1 } from "fastify";
|
|
@@ -363,7 +363,7 @@ interface ResourceConfig<TDoc = AnyRecord> {
|
|
|
363
363
|
* Override to match your schema: 'workspaceId', 'tenantId', 'teamId', etc.
|
|
364
364
|
* Takes effect when org context is present (via multiTenant preset).
|
|
365
365
|
*/
|
|
366
|
-
tenantField?: string;
|
|
366
|
+
tenantField?: string | false;
|
|
367
367
|
/**
|
|
368
368
|
* Primary key field name (default: '_id').
|
|
369
369
|
* Override for non-MongoDB adapters (e.g., 'id' for SQL databases).
|
package/dist/utils/index.d.mts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import "../elevation-DGo5shaX.mjs";
|
|
2
|
-
import "../interface-
|
|
2
|
+
import "../interface-Dm4-jnia.mjs";
|
|
3
3
|
import "../types-RLkFVgaw.mjs";
|
|
4
4
|
import { AnyRecord, OpenApiSchemas, ParsedQuery, QueryParserInterface } from "../types/index.mjs";
|
|
5
5
|
import { a as NotFoundError, c as RateLimitError, d as ValidationError, f as createError, i as ForbiddenError, l as ServiceUnavailableError, n as ConflictError, o as OrgAccessDeniedError, p as isArcError, r as ErrorDetails, s as OrgRequiredError, t as ArcError, u as UnauthorizedError } from "../errors-DAWRdiYP.mjs";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@classytic/arc",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.2.0",
|
|
4
4
|
"description": "Resource-oriented backend framework for Fastify — clean, minimal, powerful, tree-shakable",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -191,7 +191,7 @@
|
|
|
191
191
|
"node": ">=22"
|
|
192
192
|
},
|
|
193
193
|
"peerDependencies": {
|
|
194
|
-
"@classytic/mongokit": "^3.2.
|
|
194
|
+
"@classytic/mongokit": "^3.2.4",
|
|
195
195
|
"@classytic/streamline": ">=1.0.0",
|
|
196
196
|
"@fastify/cors": "^11.0.0",
|
|
197
197
|
"@fastify/helmet": "^13.0.0",
|