@rvoh/psychic 3.4.0 → 3.4.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/dist/cjs/src/bin/helpers/OpenApiSpecDiff.js +1 -0
- package/dist/cjs/src/cli/index.js +0 -2
- package/dist/cjs/src/controller/index.js +7 -48
- package/dist/cjs/src/generate/controller.js +1 -2
- package/dist/cjs/src/generate/helpers/generateControllerContent.js +27 -31
- package/dist/cjs/src/generate/resource.js +0 -9
- package/dist/cjs/src/server/params.js +0 -19
- package/dist/esm/src/bin/helpers/OpenApiSpecDiff.js +1 -0
- package/dist/esm/src/cli/index.js +0 -2
- package/dist/esm/src/controller/index.js +7 -48
- package/dist/esm/src/generate/controller.js +1 -2
- package/dist/esm/src/generate/helpers/generateControllerContent.js +27 -31
- package/dist/esm/src/generate/resource.js +0 -9
- package/dist/esm/src/server/params.js +0 -19
- package/dist/types/src/bin/index.d.ts +0 -2
- package/dist/types/src/cli/index.d.ts +0 -2
- package/dist/types/src/controller/index.d.ts +7 -46
- package/dist/types/src/generate/controller.d.ts +1 -3
- package/dist/types/src/generate/helpers/generateControllerContent.d.ts +1 -3
- package/dist/types/src/generate/resource.d.ts +0 -2
- package/dist/types/src/server/params.d.ts +3 -22
- package/package.json +5 -5
|
@@ -150,8 +150,6 @@ ${INDENT}Example:
|
|
|
150
150
|
${INDENT} pnpm psy g:resource --model-name=GroupDanceLesson v1/lessons/dance/groups Lesson/Dance/Group
|
|
151
151
|
${INDENT} # model is named GroupDanceLesson instead of LessonDanceGroup`)
|
|
152
152
|
.option('--no-soft-delete', `skip generating the @SoftDelete() decorator and the corresponding nullable \`deleted_at\` column. By default, generated models use soft-delete semantics (rows are marked deleted via \`deleted_at\` instead of being removed from the database). Pass this flag when you want records to be hard-deleted.`)
|
|
153
|
-
.option('--with-extract-params', `override the default scaffold to emit \`this.extractParams(Model, [...])\` (explicit allowlist). Only valid for admin-namespaced resources, which otherwise default to \`this.extractImplicitParams(Model)\`. Mutually exclusive with --with-extract-implicit-params.`, false)
|
|
154
|
-
.option('--with-extract-implicit-params', `override the default scaffold to emit \`this.extractImplicitParams(Model)\` (model-declared allowlist). Only useful for non-admin-namespaced resources, which otherwise default to \`this.extractParams(Model, [...])\`. Mutually exclusive with --with-extract-params.`, false)
|
|
155
153
|
.argument('<path>', `The URL path for this resource's routes, relative to the root domain. Use \`\\{\\}\` as a placeholder for a parent resource's ID parameter when nesting.
|
|
156
154
|
${INDENT}
|
|
157
155
|
${INDENT}The path determines the controller namespace hierarchy. Paths that begin with "admin" and "internal" remove the \`currentUser\` scoping of queries (\`--owning-model\` may be provided to apply query scoping). Each segment maps to a directory level in the controllers folder.
|
|
@@ -403,13 +403,13 @@ export default class PsychicController {
|
|
|
403
403
|
return this._castParam(keys, nestedParams, expectedType, opts);
|
|
404
404
|
}
|
|
405
405
|
/**
|
|
406
|
-
* @deprecated Prefer {@link extractParams}
|
|
407
|
-
*
|
|
408
|
-
*
|
|
409
|
-
* `
|
|
410
|
-
*
|
|
411
|
-
*
|
|
412
|
-
*
|
|
406
|
+
* @deprecated Prefer {@link extractParams} — the explicit-allowlist form
|
|
407
|
+
* keeps the permitted columns visible at the call site. Security-relevant:
|
|
408
|
+
* on models with permission-bearing fields, `paramsFor(Model)` without
|
|
409
|
+
* `only` extracts every column in `paramSafeColumnsOrFallback()` — which
|
|
410
|
+
* may be too broad unless the model declares `paramSafeColumns` explicitly.
|
|
411
|
+
* `extractParams` makes the allowlist visible to reviewers at the call
|
|
412
|
+
* site.
|
|
413
413
|
*
|
|
414
414
|
* Captures and validates parameters for the provided Dream model. Will
|
|
415
415
|
* exclude parameters that are not considered "safe" by default (based on
|
|
@@ -429,11 +429,7 @@ export default class PsychicController {
|
|
|
429
429
|
* ```ts
|
|
430
430
|
* class MyController extends ApplicationController {
|
|
431
431
|
* public create() {
|
|
432
|
-
* // Preferred: explicit allowlist via extractParams
|
|
433
432
|
* const safe = this.extractParams(User, ['email', 'name'])
|
|
434
|
-
*
|
|
435
|
-
* // Equivalent of the legacy `this.paramsFor(User)` under the new name:
|
|
436
|
-
* const viaModel = this.extractImplicitParams(User)
|
|
437
433
|
* }
|
|
438
434
|
* }
|
|
439
435
|
* ```
|
|
@@ -458,10 +454,6 @@ export default class PsychicController {
|
|
|
458
454
|
* `paramSafeColumnsOrFallback()` further strips anything a caller bypasses
|
|
459
455
|
* the type system to include.
|
|
460
456
|
*
|
|
461
|
-
* Use {@link extractImplicitParams} when the model's declared
|
|
462
|
-
* `paramSafeColumns` is the canonical allowlist and duplicating it at each
|
|
463
|
-
* call site would create drift.
|
|
464
|
-
*
|
|
465
457
|
* @param dreamClass - The Dream model class to retrieve params for
|
|
466
458
|
* @param allowed - Required. The columns permitted from the request.
|
|
467
459
|
* @param opts - Optional configuration
|
|
@@ -485,39 +477,6 @@ export default class PsychicController {
|
|
|
485
477
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
486
478
|
return Params.extract(source, dreamClass, allowed, opts);
|
|
487
479
|
}
|
|
488
|
-
/**
|
|
489
|
-
* Captures and validates parameters for the provided Dream model using the
|
|
490
|
-
* model's declared `paramSafeColumns` (or, when undeclared, the framework's
|
|
491
|
-
* default-safe fallback from `paramSafeColumnsOrFallback()`).
|
|
492
|
-
*
|
|
493
|
-
* Prefer {@link extractParams} when the caller can enumerate the allowed
|
|
494
|
-
* columns at the call site — explicit allowlists are more visible to
|
|
495
|
-
* reviewers. Reach for `extractImplicitParams` when the model-level
|
|
496
|
-
* declaration is the canonical allowlist and you want to avoid duplicating
|
|
497
|
-
* it at every call site.
|
|
498
|
-
*
|
|
499
|
-
* @param dreamClass - The Dream model class to retrieve params for
|
|
500
|
-
* @param opts - Optional configuration
|
|
501
|
-
* @param opts.key - Extract params from a nested key in the params object instead of root level
|
|
502
|
-
* @param opts.array - If true, expects and returns an array of param objects
|
|
503
|
-
* @returns A typed object containing the validated and casted params
|
|
504
|
-
* @throws {ParamValidationError} When any parameter validation fails
|
|
505
|
-
*
|
|
506
|
-
* @example
|
|
507
|
-
* ```ts
|
|
508
|
-
* class AdminBalloonsController extends ApplicationController {
|
|
509
|
-
* public create() {
|
|
510
|
-
* const params = this.extractImplicitParams(Balloon)
|
|
511
|
-
* const balloon = await Balloon.create(params)
|
|
512
|
-
* }
|
|
513
|
-
* }
|
|
514
|
-
* ```
|
|
515
|
-
*/
|
|
516
|
-
extractImplicitParams(dreamClass, opts) {
|
|
517
|
-
const source = opts?.key ? this.params[opts.key] || {} : this.params;
|
|
518
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
519
|
-
return Params.extractImplicit(source, dreamClass, opts);
|
|
520
|
-
}
|
|
521
480
|
/**
|
|
522
481
|
* Gets a cookie value from the request and casts it to the specified type.
|
|
523
482
|
*
|
|
@@ -9,7 +9,7 @@ import psychicPath from '../helpers/path/psychicPath.js';
|
|
|
9
9
|
import generateControllerContent from './helpers/generateControllerContent.js';
|
|
10
10
|
import generateControllerSpecContent from './helpers/generateControllerSpecContent.js';
|
|
11
11
|
import generateResourceControllerSpecContent from './helpers/generateResourceControllerSpecContent.js';
|
|
12
|
-
export default async function generateController({ fullyQualifiedControllerName, fullyQualifiedModelName, actions, columnsWithTypes = [], resourceSpecs = false, owningModel, singular,
|
|
12
|
+
export default async function generateController({ fullyQualifiedControllerName, fullyQualifiedModelName, actions, columnsWithTypes = [], resourceSpecs = false, owningModel, singular, }) {
|
|
13
13
|
fullyQualifiedModelName = fullyQualifiedModelName
|
|
14
14
|
? DreamApp.system.standardizeFullyQualifiedModelName(fullyQualifiedModelName)
|
|
15
15
|
: fullyQualifiedModelName;
|
|
@@ -71,7 +71,6 @@ export default async function generateController({ fullyQualifiedControllerName,
|
|
|
71
71
|
forInternal,
|
|
72
72
|
singular,
|
|
73
73
|
columnsWithTypes,
|
|
74
|
-
paramExtractionStrategy,
|
|
75
74
|
}));
|
|
76
75
|
}
|
|
77
76
|
catch (error) {
|
|
@@ -2,36 +2,14 @@ import { DreamApp } from '@rvoh/dream';
|
|
|
2
2
|
import { camelize, hyphenize } from '@rvoh/dream/utils';
|
|
3
3
|
import pluralize from 'pluralize-esm';
|
|
4
4
|
import paramSafeColumnNamesFromCliTokens from './paramSafeColumnNamesFromCliTokens.js';
|
|
5
|
-
export default function generateControllerContent({ ancestorName, ancestorImportStatement, fullyQualifiedControllerName, fullyQualifiedModelName, actions = [], omitOpenApi = false, owningModel, forAdmin, forInternal = false, singular, columnsWithTypes = [],
|
|
6
|
-
//
|
|
7
|
-
//
|
|
8
|
-
//
|
|
9
|
-
//
|
|
10
|
-
//
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
* Returns the `this.extract*Params(...)` expression that replaces the legacy
|
|
14
|
-
* `this.paramsFor(Model)` in the scaffold's commented hints. Does NOT include
|
|
15
|
-
* the outer closing paren that the surrounding call expects (e.g. the one
|
|
16
|
-
* closing `update(...)` or `create(...)` or `createAssociation(...)`); the
|
|
17
|
-
* caller appends that as part of its own template.
|
|
18
|
-
*/
|
|
19
|
-
const extractCallExpression = (modelClass) => {
|
|
20
|
-
if (resolvedExtractionStrategy === 'implicit') {
|
|
21
|
-
return `this.extractImplicitParams(${modelClass})`;
|
|
22
|
-
}
|
|
23
|
-
const safeColumns = paramSafeColumnNamesFromCliTokens(columnsWithTypes);
|
|
24
|
-
const serializedSafeColumns = safeColumns.length
|
|
25
|
-
? `[${safeColumns.map(name => `'${name}'`).join(', ')}]`
|
|
26
|
-
: '[]';
|
|
27
|
-
// The emitted list contains every implicitly-allowed column. When
|
|
28
|
-
// uncommenting the action body, the developer or agent is responsible
|
|
29
|
-
// for narrowing it down to only the columns this action should actually
|
|
30
|
-
// accept.
|
|
31
|
-
return `this.extractParams(${modelClass},
|
|
32
|
-
// ${serializedSafeColumns},
|
|
33
|
-
// )`;
|
|
34
|
-
};
|
|
5
|
+
export default function generateControllerContent({ ancestorName, ancestorImportStatement, fullyQualifiedControllerName, fullyQualifiedModelName, actions = [], omitOpenApi = false, owningModel, forAdmin, forInternal = false, singular, columnsWithTypes = [], }) {
|
|
6
|
+
// The scaffold emits a `paramSafeColumns` const at the top of the file
|
|
7
|
+
// (alongside `openApiTags`) and references it from both the `create` and
|
|
8
|
+
// `update` action hints. The list contains every implicitly-allowed column;
|
|
9
|
+
// when uncommenting the action body, the developer or agent is responsible
|
|
10
|
+
// for narrowing the const down to only the columns the actions should
|
|
11
|
+
// actually accept.
|
|
12
|
+
const extractCallExpression = (modelClass) => `this.extractParams(${modelClass}, paramSafeColumns)`;
|
|
35
13
|
fullyQualifiedControllerName = DreamApp.system.standardizeFullyQualifiedModelName(fullyQualifiedControllerName);
|
|
36
14
|
const additionalImports = [];
|
|
37
15
|
const controllerClassName = DreamApp.system.globalClassNameFromFullyQualifiedModelName(fullyQualifiedControllerName);
|
|
@@ -70,6 +48,9 @@ export default function generateControllerContent({ ancestorName, ancestorImport
|
|
|
70
48
|
tags: openApiTags,
|
|
71
49
|
description: 'Create ${aOrAnDreamModelName(modelClassName)}',${defaultOpenapiSerializerKeyProperty}
|
|
72
50
|
fastJsonStringify: true,
|
|
51
|
+
requestBody: {
|
|
52
|
+
only: paramSafeColumns,
|
|
53
|
+
},
|
|
73
54
|
})
|
|
74
55
|
public async create() {
|
|
75
56
|
// let ${modelAttributeName} = await ${useDirectModelAccess ? `${modelClassName}.create(` : `this.${owningModelProperty}.createAssociation('${pluralizedModelAttributeName}', `}${extractCallExpression(modelClassName)})
|
|
@@ -146,6 +127,9 @@ export default function generateControllerContent({ ancestorName, ancestorImport
|
|
|
146
127
|
tags: openApiTags,
|
|
147
128
|
description: 'Update ${aOrAnDreamModelName(modelClassName)}',
|
|
148
129
|
fastJsonStringify: true,
|
|
130
|
+
requestBody: {
|
|
131
|
+
only: paramSafeColumns,
|
|
132
|
+
},
|
|
149
133
|
})
|
|
150
134
|
public async update() {
|
|
151
135
|
// const ${modelAttributeName} = await this.${modelAttributeName}()
|
|
@@ -213,8 +197,20 @@ export default function generateControllerContent({ ancestorName, ancestorImport
|
|
|
213
197
|
});
|
|
214
198
|
const openApiImport = `import { OpenAPI } from '@rvoh/psychic'`;
|
|
215
199
|
const openApiTags = `const openApiTags = ['${hyphenize(pluralizedModelAttributeName || controllerClassName.replace(/Controller$/, ''))}']`;
|
|
200
|
+
const emitParamSafeColumns = !!modelClassName && actions.some(action => action === 'create' || action === 'update');
|
|
201
|
+
const safeColumns = emitParamSafeColumns ? paramSafeColumnNamesFromCliTokens(columnsWithTypes) : [];
|
|
202
|
+
// The const is typed against the model's safe-column names rather than
|
|
203
|
+
// `as const`, so editing the array literal gives the developer (or agent)
|
|
204
|
+
// autocomplete of valid columns and a compile error on anything that is
|
|
205
|
+
// not param-safe — directly in the assignment, no call-site round-trip.
|
|
206
|
+
const paramSafeColumnsDecl = !emitParamSafeColumns
|
|
207
|
+
? ''
|
|
208
|
+
: `\n\nconst paramSafeColumns: DreamParamSafeColumnNames<${modelClassName}>[] = [${safeColumns.map(name => `'${name}'`).join(', ')}]`;
|
|
209
|
+
const dreamTypesImport = emitParamSafeColumns
|
|
210
|
+
? `import { DreamParamSafeColumnNames } from '@rvoh/dream/types'\n`
|
|
211
|
+
: '';
|
|
216
212
|
return `\
|
|
217
|
-
${omitOpenApi ? '' : openApiImport + '\n'}${ancestorImportStatement}${additionalImports.length ? '\n' + additionalImports.join('\n') : ''}${omitOpenApi ? '' : '\n\n' + openApiTags}
|
|
213
|
+
${omitOpenApi ? '' : openApiImport + '\n' + dreamTypesImport}${ancestorImportStatement}${additionalImports.length ? '\n' + additionalImports.join('\n') : ''}${omitOpenApi ? '' : '\n\n' + openApiTags}${paramSafeColumnsDecl}
|
|
218
214
|
|
|
219
215
|
export default class ${controllerClassName} extends ${ancestorName} {
|
|
220
216
|
${methodDefs.join('\n\n')}${modelClassName ? privateMethods(forAdmin, forInternal, modelClassName, actions, loadQueryBase, singular) : ''}
|
|
@@ -32,14 +32,6 @@ export default async function generateResource({ route, fullyQualifiedModelName,
|
|
|
32
32
|
softDelete: options.softDelete,
|
|
33
33
|
},
|
|
34
34
|
});
|
|
35
|
-
if (options.withExtractParams && options.withExtractImplicitParams) {
|
|
36
|
-
throw new Error('--with-extract-params and --with-extract-implicit-params are mutually exclusive; pass only one.');
|
|
37
|
-
}
|
|
38
|
-
const paramExtractionStrategy = options.withExtractParams
|
|
39
|
-
? 'explicit'
|
|
40
|
-
: options.withExtractImplicitParams
|
|
41
|
-
? 'implicit'
|
|
42
|
-
: undefined;
|
|
43
35
|
await generateController({
|
|
44
36
|
fullyQualifiedControllerName,
|
|
45
37
|
fullyQualifiedModelName,
|
|
@@ -50,7 +42,6 @@ export default async function generateResource({ route, fullyQualifiedModelName,
|
|
|
50
42
|
resourceSpecs: true,
|
|
51
43
|
singular: options.singular,
|
|
52
44
|
owningModel: options.owningModel,
|
|
53
|
-
paramExtractionStrategy,
|
|
54
45
|
});
|
|
55
46
|
await addResourceToRoutes(route, {
|
|
56
47
|
singular: options.singular,
|
|
@@ -108,25 +108,6 @@ export default class Params {
|
|
|
108
108
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
109
109
|
return Params.for(params, dreamClass, { ...(opts ?? {}), only: allowed });
|
|
110
110
|
}
|
|
111
|
-
/**
|
|
112
|
-
* ### .extractImplicit
|
|
113
|
-
*
|
|
114
|
-
* Implicit-allowlist extraction driven by the model's `paramSafeColumns`
|
|
115
|
-
* declaration (or, when undeclared, the framework's default-safe fallback
|
|
116
|
-
* from `paramSafeColumnsOrFallback()`). Equivalent to {@link Params.for}
|
|
117
|
-
* without `only`. Use from a controller via
|
|
118
|
-
* {@link PsychicController.extractImplicitParams}.
|
|
119
|
-
*
|
|
120
|
-
* Prefer {@link Params.extract} when the caller can enumerate the allowed
|
|
121
|
-
* columns at the call site — it makes the allowlist visible to reviewers
|
|
122
|
-
* at the point of use. Reach for this method when the model-level
|
|
123
|
-
* `paramSafeColumns` declaration is the canonical allowlist and duplicating
|
|
124
|
-
* it at each call site would create maintenance drift.
|
|
125
|
-
*/
|
|
126
|
-
static extractImplicit(params, dreamClass, opts) {
|
|
127
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
128
|
-
return Params.for(params, dreamClass, (opts ?? {}));
|
|
129
|
-
}
|
|
130
111
|
static restrict(params, allowed) {
|
|
131
112
|
if (params === null || params === undefined)
|
|
132
113
|
return {};
|
|
@@ -150,8 +150,6 @@ ${INDENT}Example:
|
|
|
150
150
|
${INDENT} pnpm psy g:resource --model-name=GroupDanceLesson v1/lessons/dance/groups Lesson/Dance/Group
|
|
151
151
|
${INDENT} # model is named GroupDanceLesson instead of LessonDanceGroup`)
|
|
152
152
|
.option('--no-soft-delete', `skip generating the @SoftDelete() decorator and the corresponding nullable \`deleted_at\` column. By default, generated models use soft-delete semantics (rows are marked deleted via \`deleted_at\` instead of being removed from the database). Pass this flag when you want records to be hard-deleted.`)
|
|
153
|
-
.option('--with-extract-params', `override the default scaffold to emit \`this.extractParams(Model, [...])\` (explicit allowlist). Only valid for admin-namespaced resources, which otherwise default to \`this.extractImplicitParams(Model)\`. Mutually exclusive with --with-extract-implicit-params.`, false)
|
|
154
|
-
.option('--with-extract-implicit-params', `override the default scaffold to emit \`this.extractImplicitParams(Model)\` (model-declared allowlist). Only useful for non-admin-namespaced resources, which otherwise default to \`this.extractParams(Model, [...])\`. Mutually exclusive with --with-extract-params.`, false)
|
|
155
153
|
.argument('<path>', `The URL path for this resource's routes, relative to the root domain. Use \`\\{\\}\` as a placeholder for a parent resource's ID parameter when nesting.
|
|
156
154
|
${INDENT}
|
|
157
155
|
${INDENT}The path determines the controller namespace hierarchy. Paths that begin with "admin" and "internal" remove the \`currentUser\` scoping of queries (\`--owning-model\` may be provided to apply query scoping). Each segment maps to a directory level in the controllers folder.
|
|
@@ -403,13 +403,13 @@ export default class PsychicController {
|
|
|
403
403
|
return this._castParam(keys, nestedParams, expectedType, opts);
|
|
404
404
|
}
|
|
405
405
|
/**
|
|
406
|
-
* @deprecated Prefer {@link extractParams}
|
|
407
|
-
*
|
|
408
|
-
*
|
|
409
|
-
* `
|
|
410
|
-
*
|
|
411
|
-
*
|
|
412
|
-
*
|
|
406
|
+
* @deprecated Prefer {@link extractParams} — the explicit-allowlist form
|
|
407
|
+
* keeps the permitted columns visible at the call site. Security-relevant:
|
|
408
|
+
* on models with permission-bearing fields, `paramsFor(Model)` without
|
|
409
|
+
* `only` extracts every column in `paramSafeColumnsOrFallback()` — which
|
|
410
|
+
* may be too broad unless the model declares `paramSafeColumns` explicitly.
|
|
411
|
+
* `extractParams` makes the allowlist visible to reviewers at the call
|
|
412
|
+
* site.
|
|
413
413
|
*
|
|
414
414
|
* Captures and validates parameters for the provided Dream model. Will
|
|
415
415
|
* exclude parameters that are not considered "safe" by default (based on
|
|
@@ -429,11 +429,7 @@ export default class PsychicController {
|
|
|
429
429
|
* ```ts
|
|
430
430
|
* class MyController extends ApplicationController {
|
|
431
431
|
* public create() {
|
|
432
|
-
* // Preferred: explicit allowlist via extractParams
|
|
433
432
|
* const safe = this.extractParams(User, ['email', 'name'])
|
|
434
|
-
*
|
|
435
|
-
* // Equivalent of the legacy `this.paramsFor(User)` under the new name:
|
|
436
|
-
* const viaModel = this.extractImplicitParams(User)
|
|
437
433
|
* }
|
|
438
434
|
* }
|
|
439
435
|
* ```
|
|
@@ -458,10 +454,6 @@ export default class PsychicController {
|
|
|
458
454
|
* `paramSafeColumnsOrFallback()` further strips anything a caller bypasses
|
|
459
455
|
* the type system to include.
|
|
460
456
|
*
|
|
461
|
-
* Use {@link extractImplicitParams} when the model's declared
|
|
462
|
-
* `paramSafeColumns` is the canonical allowlist and duplicating it at each
|
|
463
|
-
* call site would create drift.
|
|
464
|
-
*
|
|
465
457
|
* @param dreamClass - The Dream model class to retrieve params for
|
|
466
458
|
* @param allowed - Required. The columns permitted from the request.
|
|
467
459
|
* @param opts - Optional configuration
|
|
@@ -485,39 +477,6 @@ export default class PsychicController {
|
|
|
485
477
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
486
478
|
return Params.extract(source, dreamClass, allowed, opts);
|
|
487
479
|
}
|
|
488
|
-
/**
|
|
489
|
-
* Captures and validates parameters for the provided Dream model using the
|
|
490
|
-
* model's declared `paramSafeColumns` (or, when undeclared, the framework's
|
|
491
|
-
* default-safe fallback from `paramSafeColumnsOrFallback()`).
|
|
492
|
-
*
|
|
493
|
-
* Prefer {@link extractParams} when the caller can enumerate the allowed
|
|
494
|
-
* columns at the call site — explicit allowlists are more visible to
|
|
495
|
-
* reviewers. Reach for `extractImplicitParams` when the model-level
|
|
496
|
-
* declaration is the canonical allowlist and you want to avoid duplicating
|
|
497
|
-
* it at every call site.
|
|
498
|
-
*
|
|
499
|
-
* @param dreamClass - The Dream model class to retrieve params for
|
|
500
|
-
* @param opts - Optional configuration
|
|
501
|
-
* @param opts.key - Extract params from a nested key in the params object instead of root level
|
|
502
|
-
* @param opts.array - If true, expects and returns an array of param objects
|
|
503
|
-
* @returns A typed object containing the validated and casted params
|
|
504
|
-
* @throws {ParamValidationError} When any parameter validation fails
|
|
505
|
-
*
|
|
506
|
-
* @example
|
|
507
|
-
* ```ts
|
|
508
|
-
* class AdminBalloonsController extends ApplicationController {
|
|
509
|
-
* public create() {
|
|
510
|
-
* const params = this.extractImplicitParams(Balloon)
|
|
511
|
-
* const balloon = await Balloon.create(params)
|
|
512
|
-
* }
|
|
513
|
-
* }
|
|
514
|
-
* ```
|
|
515
|
-
*/
|
|
516
|
-
extractImplicitParams(dreamClass, opts) {
|
|
517
|
-
const source = opts?.key ? this.params[opts.key] || {} : this.params;
|
|
518
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
519
|
-
return Params.extractImplicit(source, dreamClass, opts);
|
|
520
|
-
}
|
|
521
480
|
/**
|
|
522
481
|
* Gets a cookie value from the request and casts it to the specified type.
|
|
523
482
|
*
|
|
@@ -9,7 +9,7 @@ import psychicPath from '../helpers/path/psychicPath.js';
|
|
|
9
9
|
import generateControllerContent from './helpers/generateControllerContent.js';
|
|
10
10
|
import generateControllerSpecContent from './helpers/generateControllerSpecContent.js';
|
|
11
11
|
import generateResourceControllerSpecContent from './helpers/generateResourceControllerSpecContent.js';
|
|
12
|
-
export default async function generateController({ fullyQualifiedControllerName, fullyQualifiedModelName, actions, columnsWithTypes = [], resourceSpecs = false, owningModel, singular,
|
|
12
|
+
export default async function generateController({ fullyQualifiedControllerName, fullyQualifiedModelName, actions, columnsWithTypes = [], resourceSpecs = false, owningModel, singular, }) {
|
|
13
13
|
fullyQualifiedModelName = fullyQualifiedModelName
|
|
14
14
|
? DreamApp.system.standardizeFullyQualifiedModelName(fullyQualifiedModelName)
|
|
15
15
|
: fullyQualifiedModelName;
|
|
@@ -71,7 +71,6 @@ export default async function generateController({ fullyQualifiedControllerName,
|
|
|
71
71
|
forInternal,
|
|
72
72
|
singular,
|
|
73
73
|
columnsWithTypes,
|
|
74
|
-
paramExtractionStrategy,
|
|
75
74
|
}));
|
|
76
75
|
}
|
|
77
76
|
catch (error) {
|
|
@@ -2,36 +2,14 @@ import { DreamApp } from '@rvoh/dream';
|
|
|
2
2
|
import { camelize, hyphenize } from '@rvoh/dream/utils';
|
|
3
3
|
import pluralize from 'pluralize-esm';
|
|
4
4
|
import paramSafeColumnNamesFromCliTokens from './paramSafeColumnNamesFromCliTokens.js';
|
|
5
|
-
export default function generateControllerContent({ ancestorName, ancestorImportStatement, fullyQualifiedControllerName, fullyQualifiedModelName, actions = [], omitOpenApi = false, owningModel, forAdmin, forInternal = false, singular, columnsWithTypes = [],
|
|
6
|
-
//
|
|
7
|
-
//
|
|
8
|
-
//
|
|
9
|
-
//
|
|
10
|
-
//
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
* Returns the `this.extract*Params(...)` expression that replaces the legacy
|
|
14
|
-
* `this.paramsFor(Model)` in the scaffold's commented hints. Does NOT include
|
|
15
|
-
* the outer closing paren that the surrounding call expects (e.g. the one
|
|
16
|
-
* closing `update(...)` or `create(...)` or `createAssociation(...)`); the
|
|
17
|
-
* caller appends that as part of its own template.
|
|
18
|
-
*/
|
|
19
|
-
const extractCallExpression = (modelClass) => {
|
|
20
|
-
if (resolvedExtractionStrategy === 'implicit') {
|
|
21
|
-
return `this.extractImplicitParams(${modelClass})`;
|
|
22
|
-
}
|
|
23
|
-
const safeColumns = paramSafeColumnNamesFromCliTokens(columnsWithTypes);
|
|
24
|
-
const serializedSafeColumns = safeColumns.length
|
|
25
|
-
? `[${safeColumns.map(name => `'${name}'`).join(', ')}]`
|
|
26
|
-
: '[]';
|
|
27
|
-
// The emitted list contains every implicitly-allowed column. When
|
|
28
|
-
// uncommenting the action body, the developer or agent is responsible
|
|
29
|
-
// for narrowing it down to only the columns this action should actually
|
|
30
|
-
// accept.
|
|
31
|
-
return `this.extractParams(${modelClass},
|
|
32
|
-
// ${serializedSafeColumns},
|
|
33
|
-
// )`;
|
|
34
|
-
};
|
|
5
|
+
export default function generateControllerContent({ ancestorName, ancestorImportStatement, fullyQualifiedControllerName, fullyQualifiedModelName, actions = [], omitOpenApi = false, owningModel, forAdmin, forInternal = false, singular, columnsWithTypes = [], }) {
|
|
6
|
+
// The scaffold emits a `paramSafeColumns` const at the top of the file
|
|
7
|
+
// (alongside `openApiTags`) and references it from both the `create` and
|
|
8
|
+
// `update` action hints. The list contains every implicitly-allowed column;
|
|
9
|
+
// when uncommenting the action body, the developer or agent is responsible
|
|
10
|
+
// for narrowing the const down to only the columns the actions should
|
|
11
|
+
// actually accept.
|
|
12
|
+
const extractCallExpression = (modelClass) => `this.extractParams(${modelClass}, paramSafeColumns)`;
|
|
35
13
|
fullyQualifiedControllerName = DreamApp.system.standardizeFullyQualifiedModelName(fullyQualifiedControllerName);
|
|
36
14
|
const additionalImports = [];
|
|
37
15
|
const controllerClassName = DreamApp.system.globalClassNameFromFullyQualifiedModelName(fullyQualifiedControllerName);
|
|
@@ -70,6 +48,9 @@ export default function generateControllerContent({ ancestorName, ancestorImport
|
|
|
70
48
|
tags: openApiTags,
|
|
71
49
|
description: 'Create ${aOrAnDreamModelName(modelClassName)}',${defaultOpenapiSerializerKeyProperty}
|
|
72
50
|
fastJsonStringify: true,
|
|
51
|
+
requestBody: {
|
|
52
|
+
only: paramSafeColumns,
|
|
53
|
+
},
|
|
73
54
|
})
|
|
74
55
|
public async create() {
|
|
75
56
|
// let ${modelAttributeName} = await ${useDirectModelAccess ? `${modelClassName}.create(` : `this.${owningModelProperty}.createAssociation('${pluralizedModelAttributeName}', `}${extractCallExpression(modelClassName)})
|
|
@@ -146,6 +127,9 @@ export default function generateControllerContent({ ancestorName, ancestorImport
|
|
|
146
127
|
tags: openApiTags,
|
|
147
128
|
description: 'Update ${aOrAnDreamModelName(modelClassName)}',
|
|
148
129
|
fastJsonStringify: true,
|
|
130
|
+
requestBody: {
|
|
131
|
+
only: paramSafeColumns,
|
|
132
|
+
},
|
|
149
133
|
})
|
|
150
134
|
public async update() {
|
|
151
135
|
// const ${modelAttributeName} = await this.${modelAttributeName}()
|
|
@@ -213,8 +197,20 @@ export default function generateControllerContent({ ancestorName, ancestorImport
|
|
|
213
197
|
});
|
|
214
198
|
const openApiImport = `import { OpenAPI } from '@rvoh/psychic'`;
|
|
215
199
|
const openApiTags = `const openApiTags = ['${hyphenize(pluralizedModelAttributeName || controllerClassName.replace(/Controller$/, ''))}']`;
|
|
200
|
+
const emitParamSafeColumns = !!modelClassName && actions.some(action => action === 'create' || action === 'update');
|
|
201
|
+
const safeColumns = emitParamSafeColumns ? paramSafeColumnNamesFromCliTokens(columnsWithTypes) : [];
|
|
202
|
+
// The const is typed against the model's safe-column names rather than
|
|
203
|
+
// `as const`, so editing the array literal gives the developer (or agent)
|
|
204
|
+
// autocomplete of valid columns and a compile error on anything that is
|
|
205
|
+
// not param-safe — directly in the assignment, no call-site round-trip.
|
|
206
|
+
const paramSafeColumnsDecl = !emitParamSafeColumns
|
|
207
|
+
? ''
|
|
208
|
+
: `\n\nconst paramSafeColumns: DreamParamSafeColumnNames<${modelClassName}>[] = [${safeColumns.map(name => `'${name}'`).join(', ')}]`;
|
|
209
|
+
const dreamTypesImport = emitParamSafeColumns
|
|
210
|
+
? `import { DreamParamSafeColumnNames } from '@rvoh/dream/types'\n`
|
|
211
|
+
: '';
|
|
216
212
|
return `\
|
|
217
|
-
${omitOpenApi ? '' : openApiImport + '\n'}${ancestorImportStatement}${additionalImports.length ? '\n' + additionalImports.join('\n') : ''}${omitOpenApi ? '' : '\n\n' + openApiTags}
|
|
213
|
+
${omitOpenApi ? '' : openApiImport + '\n' + dreamTypesImport}${ancestorImportStatement}${additionalImports.length ? '\n' + additionalImports.join('\n') : ''}${omitOpenApi ? '' : '\n\n' + openApiTags}${paramSafeColumnsDecl}
|
|
218
214
|
|
|
219
215
|
export default class ${controllerClassName} extends ${ancestorName} {
|
|
220
216
|
${methodDefs.join('\n\n')}${modelClassName ? privateMethods(forAdmin, forInternal, modelClassName, actions, loadQueryBase, singular) : ''}
|
|
@@ -32,14 +32,6 @@ export default async function generateResource({ route, fullyQualifiedModelName,
|
|
|
32
32
|
softDelete: options.softDelete,
|
|
33
33
|
},
|
|
34
34
|
});
|
|
35
|
-
if (options.withExtractParams && options.withExtractImplicitParams) {
|
|
36
|
-
throw new Error('--with-extract-params and --with-extract-implicit-params are mutually exclusive; pass only one.');
|
|
37
|
-
}
|
|
38
|
-
const paramExtractionStrategy = options.withExtractParams
|
|
39
|
-
? 'explicit'
|
|
40
|
-
: options.withExtractImplicitParams
|
|
41
|
-
? 'implicit'
|
|
42
|
-
: undefined;
|
|
43
35
|
await generateController({
|
|
44
36
|
fullyQualifiedControllerName,
|
|
45
37
|
fullyQualifiedModelName,
|
|
@@ -50,7 +42,6 @@ export default async function generateResource({ route, fullyQualifiedModelName,
|
|
|
50
42
|
resourceSpecs: true,
|
|
51
43
|
singular: options.singular,
|
|
52
44
|
owningModel: options.owningModel,
|
|
53
|
-
paramExtractionStrategy,
|
|
54
45
|
});
|
|
55
46
|
await addResourceToRoutes(route, {
|
|
56
47
|
singular: options.singular,
|
|
@@ -108,25 +108,6 @@ export default class Params {
|
|
|
108
108
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
109
109
|
return Params.for(params, dreamClass, { ...(opts ?? {}), only: allowed });
|
|
110
110
|
}
|
|
111
|
-
/**
|
|
112
|
-
* ### .extractImplicit
|
|
113
|
-
*
|
|
114
|
-
* Implicit-allowlist extraction driven by the model's `paramSafeColumns`
|
|
115
|
-
* declaration (or, when undeclared, the framework's default-safe fallback
|
|
116
|
-
* from `paramSafeColumnsOrFallback()`). Equivalent to {@link Params.for}
|
|
117
|
-
* without `only`. Use from a controller via
|
|
118
|
-
* {@link PsychicController.extractImplicitParams}.
|
|
119
|
-
*
|
|
120
|
-
* Prefer {@link Params.extract} when the caller can enumerate the allowed
|
|
121
|
-
* columns at the call site — it makes the allowlist visible to reviewers
|
|
122
|
-
* at the point of use. Reach for this method when the model-level
|
|
123
|
-
* `paramSafeColumns` declaration is the canonical allowlist and duplicating
|
|
124
|
-
* it at each call site would create maintenance drift.
|
|
125
|
-
*/
|
|
126
|
-
static extractImplicit(params, dreamClass, opts) {
|
|
127
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
128
|
-
return Params.for(params, dreamClass, (opts ?? {}));
|
|
129
|
-
}
|
|
130
111
|
static restrict(params, allowed) {
|
|
131
112
|
if (params === null || params === undefined)
|
|
132
113
|
return {};
|
|
@@ -10,8 +10,6 @@ export default class PsychicBin {
|
|
|
10
10
|
modelName?: string;
|
|
11
11
|
tableName?: string;
|
|
12
12
|
softDelete: boolean;
|
|
13
|
-
withExtractParams?: boolean;
|
|
14
|
-
withExtractImplicitParams?: boolean;
|
|
15
13
|
}): Promise<void>;
|
|
16
14
|
static printRoutes(): void;
|
|
17
15
|
static printControllerHierarchy(controllersPath?: string): void;
|
|
@@ -251,13 +251,13 @@ export default class PsychicController {
|
|
|
251
251
|
castParam<const EnumType extends readonly string[], OptsType extends StrictInterface<OptsType, ParamsCastOptions<EnumType>>, const ExpectedType extends PsychicParamsPrimitiveLiteral | RegExp | OpenapiSchemaBody>(key: string, expectedType: ExpectedType, opts?: OptsType): ValidatedAllowsNull<ExpectedType, OptsType> extends infer T ? T extends ValidatedAllowsNull<ExpectedType, OptsType> ? T extends true ? ValidatedReturnType<ExpectedType, OptsType> | null | undefined : ValidatedReturnType<ExpectedType, OptsType> : never : never;
|
|
252
252
|
private _castParam;
|
|
253
253
|
/**
|
|
254
|
-
* @deprecated Prefer {@link extractParams}
|
|
255
|
-
*
|
|
256
|
-
*
|
|
257
|
-
* `
|
|
258
|
-
*
|
|
259
|
-
*
|
|
260
|
-
*
|
|
254
|
+
* @deprecated Prefer {@link extractParams} — the explicit-allowlist form
|
|
255
|
+
* keeps the permitted columns visible at the call site. Security-relevant:
|
|
256
|
+
* on models with permission-bearing fields, `paramsFor(Model)` without
|
|
257
|
+
* `only` extracts every column in `paramSafeColumnsOrFallback()` — which
|
|
258
|
+
* may be too broad unless the model declares `paramSafeColumns` explicitly.
|
|
259
|
+
* `extractParams` makes the allowlist visible to reviewers at the call
|
|
260
|
+
* site.
|
|
261
261
|
*
|
|
262
262
|
* Captures and validates parameters for the provided Dream model. Will
|
|
263
263
|
* exclude parameters that are not considered "safe" by default (based on
|
|
@@ -277,11 +277,7 @@ export default class PsychicController {
|
|
|
277
277
|
* ```ts
|
|
278
278
|
* class MyController extends ApplicationController {
|
|
279
279
|
* public create() {
|
|
280
|
-
* // Preferred: explicit allowlist via extractParams
|
|
281
280
|
* const safe = this.extractParams(User, ['email', 'name'])
|
|
282
|
-
*
|
|
283
|
-
* // Equivalent of the legacy `this.paramsFor(User)` under the new name:
|
|
284
|
-
* const viaModel = this.extractImplicitParams(User)
|
|
285
281
|
* }
|
|
286
282
|
* }
|
|
287
283
|
* ```
|
|
@@ -306,10 +302,6 @@ export default class PsychicController {
|
|
|
306
302
|
* `paramSafeColumnsOrFallback()` further strips anything a caller bypasses
|
|
307
303
|
* the type system to include.
|
|
308
304
|
*
|
|
309
|
-
* Use {@link extractImplicitParams} when the model's declared
|
|
310
|
-
* `paramSafeColumns` is the canonical allowlist and duplicating it at each
|
|
311
|
-
* call site would create drift.
|
|
312
|
-
*
|
|
313
305
|
* @param dreamClass - The Dream model class to retrieve params for
|
|
314
306
|
* @param allowed - Required. The columns permitted from the request.
|
|
315
307
|
* @param opts - Optional configuration
|
|
@@ -331,37 +323,6 @@ export default class PsychicController {
|
|
|
331
323
|
extractParams<T extends typeof Dream, I extends InstanceType<T>, const AllowedArray extends readonly (keyof DreamParamSafeAttributes<I>)[], OptsType extends StrictInterface<OptsType, ExtractParamsOpts>, ParamSafeAttrs extends DreamParamSafeAttributes<I>, ReturnPartial extends Partial<{
|
|
332
324
|
[K in AllowedArray[number] & keyof ParamSafeAttrs]: ParamSafeAttrs[K & keyof ParamSafeAttrs];
|
|
333
325
|
}>, ReturnPayload extends OptsType['array'] extends true ? ReturnPartial[] : ReturnPartial>(this: PsychicController, dreamClass: T, allowed: AllowedArray, opts?: OptsType): ReturnPayload;
|
|
334
|
-
/**
|
|
335
|
-
* Captures and validates parameters for the provided Dream model using the
|
|
336
|
-
* model's declared `paramSafeColumns` (or, when undeclared, the framework's
|
|
337
|
-
* default-safe fallback from `paramSafeColumnsOrFallback()`).
|
|
338
|
-
*
|
|
339
|
-
* Prefer {@link extractParams} when the caller can enumerate the allowed
|
|
340
|
-
* columns at the call site — explicit allowlists are more visible to
|
|
341
|
-
* reviewers. Reach for `extractImplicitParams` when the model-level
|
|
342
|
-
* declaration is the canonical allowlist and you want to avoid duplicating
|
|
343
|
-
* it at every call site.
|
|
344
|
-
*
|
|
345
|
-
* @param dreamClass - The Dream model class to retrieve params for
|
|
346
|
-
* @param opts - Optional configuration
|
|
347
|
-
* @param opts.key - Extract params from a nested key in the params object instead of root level
|
|
348
|
-
* @param opts.array - If true, expects and returns an array of param objects
|
|
349
|
-
* @returns A typed object containing the validated and casted params
|
|
350
|
-
* @throws {ParamValidationError} When any parameter validation fails
|
|
351
|
-
*
|
|
352
|
-
* @example
|
|
353
|
-
* ```ts
|
|
354
|
-
* class AdminBalloonsController extends ApplicationController {
|
|
355
|
-
* public create() {
|
|
356
|
-
* const params = this.extractImplicitParams(Balloon)
|
|
357
|
-
* const balloon = await Balloon.create(params)
|
|
358
|
-
* }
|
|
359
|
-
* }
|
|
360
|
-
* ```
|
|
361
|
-
*/
|
|
362
|
-
extractImplicitParams<T extends typeof Dream, I extends InstanceType<T>, OptsType extends StrictInterface<OptsType, ExtractParamsOpts>, ParamSafeColumnsOverride extends I['paramSafeColumns' & keyof I] extends never ? undefined : I['paramSafeColumns' & keyof I] & string[], ParamSafeColumns extends ParamSafeColumnsOverride extends string[] | Readonly<string[]> ? Extract<DreamParamSafeColumnNames<I>, ParamSafeColumnsOverride[number] & DreamParamSafeColumnNames<I>>[] : DreamParamSafeColumnNames<I>[], ParamSafeAttrs extends DreamParamSafeAttributes<I>, ReturnPartial extends Partial<{
|
|
363
|
-
[K in ParamSafeColumns[number & keyof ParamSafeColumns] & string]: ParamSafeAttrs[K & keyof ParamSafeAttrs];
|
|
364
|
-
}>, ReturnPayload extends OptsType['array'] extends true ? ReturnPartial[] : ReturnPartial>(this: PsychicController, dreamClass: T, opts?: OptsType): ReturnPayload;
|
|
365
326
|
/**
|
|
366
327
|
* Gets a cookie value from the request and casts it to the specified type.
|
|
367
328
|
*
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
export default function generateController({ fullyQualifiedControllerName, fullyQualifiedModelName, actions, columnsWithTypes, resourceSpecs, owningModel, singular, paramExtractionStrategy, }: {
|
|
1
|
+
export default function generateController({ fullyQualifiedControllerName, fullyQualifiedModelName, actions, columnsWithTypes, resourceSpecs, owningModel, singular, }: {
|
|
3
2
|
fullyQualifiedControllerName: string;
|
|
4
3
|
fullyQualifiedModelName?: string;
|
|
5
4
|
actions: string[];
|
|
@@ -7,5 +6,4 @@ export default function generateController({ fullyQualifiedControllerName, fully
|
|
|
7
6
|
resourceSpecs?: boolean;
|
|
8
7
|
owningModel?: string | undefined;
|
|
9
8
|
singular: boolean;
|
|
10
|
-
paramExtractionStrategy?: ParamExtractionStrategy | undefined;
|
|
11
9
|
}): Promise<void>;
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
export
|
|
2
|
-
export default function generateControllerContent({ ancestorName, ancestorImportStatement, fullyQualifiedControllerName, fullyQualifiedModelName, actions, omitOpenApi, owningModel, forAdmin, forInternal, singular, columnsWithTypes, paramExtractionStrategy, }: {
|
|
1
|
+
export default function generateControllerContent({ ancestorName, ancestorImportStatement, fullyQualifiedControllerName, fullyQualifiedModelName, actions, omitOpenApi, owningModel, forAdmin, forInternal, singular, columnsWithTypes, }: {
|
|
3
2
|
ancestorName: string;
|
|
4
3
|
ancestorImportStatement: string;
|
|
5
4
|
fullyQualifiedControllerName: string;
|
|
@@ -11,5 +10,4 @@ export default function generateControllerContent({ ancestorName, ancestorImport
|
|
|
11
10
|
forInternal?: boolean;
|
|
12
11
|
singular: boolean;
|
|
13
12
|
columnsWithTypes?: string[];
|
|
14
|
-
paramExtractionStrategy?: ParamExtractionStrategy | undefined;
|
|
15
13
|
}): string;
|
|
@@ -12,8 +12,6 @@ export default function generateResource({ route, fullyQualifiedModelName, optio
|
|
|
12
12
|
tableName?: string;
|
|
13
13
|
modelName?: string;
|
|
14
14
|
softDelete: boolean;
|
|
15
|
-
withExtractParams?: boolean;
|
|
16
|
-
withExtractImplicitParams?: boolean;
|
|
17
15
|
};
|
|
18
16
|
columnsWithTypes: string[];
|
|
19
17
|
}): Promise<void>;
|
|
@@ -42,24 +42,6 @@ export default class Params {
|
|
|
42
42
|
static extract<T extends typeof Dream, I extends InstanceType<T>, const AllowedArray extends readonly (keyof DreamParamSafeAttributes<I>)[], OptsType extends StrictInterface<OptsType, ExtractParamsOpts>, ParamSafeAttrs extends DreamParamSafeAttributes<I>, ReturnPartial extends Partial<{
|
|
43
43
|
[K in AllowedArray[number] & keyof ParamSafeAttrs]: ParamSafeAttrs[K & keyof ParamSafeAttrs];
|
|
44
44
|
}>, ReturnPayload extends OptsType['array'] extends true ? ReturnPartial[] : ReturnPartial>(params: object, dreamClass: T, allowed: AllowedArray, opts?: OptsType): ReturnPayload;
|
|
45
|
-
/**
|
|
46
|
-
* ### .extractImplicit
|
|
47
|
-
*
|
|
48
|
-
* Implicit-allowlist extraction driven by the model's `paramSafeColumns`
|
|
49
|
-
* declaration (or, when undeclared, the framework's default-safe fallback
|
|
50
|
-
* from `paramSafeColumnsOrFallback()`). Equivalent to {@link Params.for}
|
|
51
|
-
* without `only`. Use from a controller via
|
|
52
|
-
* {@link PsychicController.extractImplicitParams}.
|
|
53
|
-
*
|
|
54
|
-
* Prefer {@link Params.extract} when the caller can enumerate the allowed
|
|
55
|
-
* columns at the call site — it makes the allowlist visible to reviewers
|
|
56
|
-
* at the point of use. Reach for this method when the model-level
|
|
57
|
-
* `paramSafeColumns` declaration is the canonical allowlist and duplicating
|
|
58
|
-
* it at each call site would create maintenance drift.
|
|
59
|
-
*/
|
|
60
|
-
static extractImplicit<T extends typeof Dream, I extends InstanceType<T>, OptsType extends StrictInterface<OptsType, ExtractParamsOpts>, ParamSafeColumnsOverride extends I['paramSafeColumns' & keyof I] extends never ? undefined : I['paramSafeColumns' & keyof I] & string[], ParamSafeColumns extends ParamSafeColumnsOverride extends string[] | Readonly<string[]> ? Extract<DreamParamSafeColumnNames<I>, ParamSafeColumnsOverride[number] & DreamParamSafeColumnNames<I>>[] : DreamParamSafeColumnNames<I>[], ParamSafeAttrs extends DreamParamSafeAttributes<I>, ReturnPartial extends Partial<{
|
|
61
|
-
[K in ParamSafeColumns[number & keyof ParamSafeColumns] & string]: ParamSafeAttrs[K & keyof ParamSafeAttrs];
|
|
62
|
-
}>, ReturnPayload extends OptsType['array'] extends true ? ReturnPartial[] : ReturnPartial>(params: object, dreamClass: T, opts?: OptsType): ReturnPayload;
|
|
63
45
|
static restrict<T extends typeof Params>(this: T, params: PsychicParamsPrimitive | PsychicParamsDictionary | PsychicParamsDictionary[], allowed: string[]): PsychicParamsDictionary;
|
|
64
46
|
/**
|
|
65
47
|
* ### .cast
|
|
@@ -139,10 +121,9 @@ export interface ParamsForOpts<OnlyArray> extends ParamsForOptsBase<OnlyArray> {
|
|
|
139
121
|
key?: string;
|
|
140
122
|
}
|
|
141
123
|
/**
|
|
142
|
-
* Options for {@link Params.extract}
|
|
143
|
-
*
|
|
144
|
-
*
|
|
145
|
-
* `extractParams`, and `extractImplicitParams` has no allowlist argument.
|
|
124
|
+
* Options for {@link Params.extract} and the corresponding controller method.
|
|
125
|
+
* Mirrors {@link ParamsForOpts} minus `only` — the explicit allowlist moved
|
|
126
|
+
* to a required positional argument on `extractParams`.
|
|
146
127
|
*/
|
|
147
128
|
export interface ExtractParamsOpts {
|
|
148
129
|
array?: boolean;
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"type": "module",
|
|
3
3
|
"name": "@rvoh/psychic",
|
|
4
4
|
"description": "Typescript web framework",
|
|
5
|
-
"version": "3.4.
|
|
5
|
+
"version": "3.4.1",
|
|
6
6
|
"author": "RVOHealth",
|
|
7
7
|
"repository": {
|
|
8
8
|
"type": "git",
|
|
@@ -75,7 +75,7 @@
|
|
|
75
75
|
"@koa/cors": "^5.0.0",
|
|
76
76
|
"@koa/etag": "^5.0.2",
|
|
77
77
|
"@koa/router": "^15.3.0",
|
|
78
|
-
"@rvoh/dream": "^2.
|
|
78
|
+
"@rvoh/dream": "^2.10.0",
|
|
79
79
|
"@types/koa": "^3.0.1",
|
|
80
80
|
"@types/koa-bodyparser": "^4.3.12",
|
|
81
81
|
"@types/koa-conditional-get": "^2.0.3",
|
|
@@ -92,7 +92,7 @@
|
|
|
92
92
|
"@koa/cors": "^5.0.0",
|
|
93
93
|
"@koa/etag": "^5.0.2",
|
|
94
94
|
"@koa/router": "^15.3.1",
|
|
95
|
-
"@rvoh/dream": "^2.
|
|
95
|
+
"@rvoh/dream": "^2.10.0",
|
|
96
96
|
"@rvoh/dream-spec-helpers": "^2.1.1",
|
|
97
97
|
"@rvoh/psychic-spec-helpers": "3.0.0",
|
|
98
98
|
"@types/koa": "^3.0.1",
|
|
@@ -118,8 +118,8 @@
|
|
|
118
118
|
"koa-conditional-get": "^3.0.0",
|
|
119
119
|
"koa-passport": "^6.0.0",
|
|
120
120
|
"koa-session": "^7.0.2",
|
|
121
|
-
"kysely": "^0.
|
|
122
|
-
"kysely-codegen": "~0.
|
|
121
|
+
"kysely": "^0.29.0",
|
|
122
|
+
"kysely-codegen": "~0.20.0",
|
|
123
123
|
"luxon-jest-matchers": "^0.1.14",
|
|
124
124
|
"nodemon": "^3.1.11",
|
|
125
125
|
"openapi-typescript": "^7.13.0",
|