@atscript/moost-db 0.1.119 → 0.1.121
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/index.cjs +74 -32
- package/dist/index.d.cts +42 -21
- package/dist/index.d.mts +42 -21
- package/dist/index.mjs +74 -32
- package/package.json +16 -16
package/dist/index.cjs
CHANGED
|
@@ -922,7 +922,7 @@ function normalizeOptions(prefixOrOptions) {
|
|
|
922
922
|
* Builds the shared binding metadata for a decorator invocation: classifies
|
|
923
923
|
* the binding form, computes the static route prefix, and packages a uniform
|
|
924
924
|
* `resolve()` used by both the DI provide factory and the base controller's
|
|
925
|
-
* `super(
|
|
925
|
+
* `super(app)` fallback.
|
|
926
926
|
*/
|
|
927
927
|
function buildBinding(binding, options, decoratorName) {
|
|
928
928
|
if ((0, _atscript_typescript_utils.isAnnotatedType)(binding)) {
|
|
@@ -1041,7 +1041,7 @@ function findReadableBinding(ctor) {
|
|
|
1041
1041
|
*
|
|
1042
1042
|
* Used by {@link AsDbReadableController}'s constructor when `readable` is
|
|
1043
1043
|
* `undefined` — i.e. a subclass with its own constructor called
|
|
1044
|
-
* `super(
|
|
1044
|
+
* `super(app)` instead of forwarding an injected instance.
|
|
1045
1045
|
*/
|
|
1046
1046
|
function resolveBoundReadable(ctor) {
|
|
1047
1047
|
const binding = findReadableBinding(ctor);
|
|
@@ -1096,7 +1096,7 @@ let AsDbReadableController = class AsDbReadableController extends AsReadableCont
|
|
|
1096
1096
|
_quantityRefByPath;
|
|
1097
1097
|
/** Paths the adapter vetoes for filtering (e.g. JSON storage on SQL). Symmetric with `/meta` `filterable: false`. */
|
|
1098
1098
|
_adapterNonFilterable;
|
|
1099
|
-
constructor(
|
|
1099
|
+
constructor(app, readable) {
|
|
1100
1100
|
const resolved = readable ?? resolveBoundReadable(new.target);
|
|
1101
1101
|
super(resolved.type, resolved.tableName, app, resolved.isView ? "view" : "table");
|
|
1102
1102
|
this.readable = resolved;
|
|
@@ -1758,9 +1758,9 @@ __decorate([
|
|
|
1758
1758
|
], AsDbReadableController.prototype, "getOneComposite", null);
|
|
1759
1759
|
AsDbReadableController = __decorate([
|
|
1760
1760
|
(0, moost.Inherit)(),
|
|
1761
|
-
__decorateParam(
|
|
1762
|
-
__decorateParam(
|
|
1763
|
-
__decorateMetadata("design:paramtypes", [
|
|
1761
|
+
__decorateParam(1, (0, moost.Inject)(READABLE_DEF)),
|
|
1762
|
+
__decorateParam(1, (0, moost.Optional)()),
|
|
1763
|
+
__decorateMetadata("design:paramtypes", [typeof moost.Moost === "undefined" ? Object : moost.Moost, Object])
|
|
1764
1764
|
], AsDbReadableController);
|
|
1765
1765
|
registerAsDbReadableController(AsDbReadableController);
|
|
1766
1766
|
//#endregion
|
|
@@ -1793,8 +1793,8 @@ let AsDbController = class AsDbController extends AsDbReadableController {
|
|
|
1793
1793
|
get table() {
|
|
1794
1794
|
return this.readable;
|
|
1795
1795
|
}
|
|
1796
|
-
constructor(
|
|
1797
|
-
super(
|
|
1796
|
+
constructor(app, table) {
|
|
1797
|
+
super(app, table);
|
|
1798
1798
|
}
|
|
1799
1799
|
buildCrud() {
|
|
1800
1800
|
return {
|
|
@@ -1956,9 +1956,9 @@ __decorate([
|
|
|
1956
1956
|
], AsDbController.prototype, "removeComposite", null);
|
|
1957
1957
|
AsDbController = __decorate([
|
|
1958
1958
|
(0, moost.Inherit)(),
|
|
1959
|
-
__decorateParam(
|
|
1960
|
-
__decorateParam(
|
|
1961
|
-
__decorateMetadata("design:paramtypes", [
|
|
1959
|
+
__decorateParam(1, (0, moost.Inject)(TABLE_DEF)),
|
|
1960
|
+
__decorateParam(1, (0, moost.Optional)()),
|
|
1961
|
+
__decorateMetadata("design:paramtypes", [typeof moost.Moost === "undefined" ? Object : moost.Moost, Object])
|
|
1962
1962
|
], AsDbController);
|
|
1963
1963
|
//#endregion
|
|
1964
1964
|
//#region src/as-value-help.controller.ts
|
|
@@ -2802,40 +2802,82 @@ function classLevelActions(dict, forcedLevel) {
|
|
|
2802
2802
|
}
|
|
2803
2803
|
//#endregion
|
|
2804
2804
|
//#region src/actions/db-action-input-form.decorator.ts
|
|
2805
|
+
/** Human-readable description of a rejected form candidate for the error hint. */
|
|
2806
|
+
function describeCandidate(candidate) {
|
|
2807
|
+
if (candidate === void 0) return "undefined — no reflected type (emitDecoratorMetadata off, or a circular import)";
|
|
2808
|
+
if (candidate === Object) return "Object — reflection lost the type (interface/union annotation, or the class was imported with `import type`)";
|
|
2809
|
+
if (typeof candidate === "function") return `class ${candidate.name ?? "<anonymous>"} — not a compiled .as interface`;
|
|
2810
|
+
return typeof candidate;
|
|
2811
|
+
}
|
|
2805
2812
|
/**
|
|
2806
2813
|
* Parameter decorator that injects the `input` field of the action request
|
|
2807
|
-
* envelope (`{ ids?, input? }`) into the handler
|
|
2814
|
+
* envelope (`{ ids?, input? }`) into the handler, **validated** against the
|
|
2815
|
+
* action's form.
|
|
2816
|
+
*
|
|
2817
|
+
* The form type may be passed explicitly — `@InputForm(MyForm)` — or inferred
|
|
2818
|
+
* from the parameter's reflected design type when omitted:
|
|
2819
|
+
*
|
|
2820
|
+
* ```ts
|
|
2821
|
+
* @Post("actions/comment")
|
|
2822
|
+
* @DbAction("comment", { label: "Comment" })
|
|
2823
|
+
* async comment(@InputForm() input: CommentForm) { ... }
|
|
2824
|
+
* ```
|
|
2808
2825
|
*
|
|
2809
|
-
*
|
|
2826
|
+
* Inference reads `design:paramtypes`, so it only works when the parameter is
|
|
2827
|
+
* annotated with the compiled `.as` class through a VALUE import — an
|
|
2828
|
+
* `import type` elides the class and reflection yields `Object`. When the
|
|
2829
|
+
* reflected type is unusable, decoration **throws** (fail-loud, at import
|
|
2830
|
+
* time) instead of silently serving an action without a form. The explicit
|
|
2831
|
+
* argument sidesteps reflection entirely and always wins over the annotation.
|
|
2810
2832
|
*
|
|
2811
|
-
*
|
|
2812
|
-
* name, consumed by `discoverActions` to:
|
|
2813
|
-
* - emit `inputForm: FormType.name` on the action's `/meta` entry, and
|
|
2814
|
-
* - register the type in the controller's form registry so
|
|
2815
|
-
* `GET /meta/form/:name` can serve the serialized schema.
|
|
2816
|
-
* 2. `atscript_type` — just the type ref, providing a generic hook any
|
|
2817
|
-
* atscript-aware Moost pipe can read without knowing about the
|
|
2818
|
-
* moost-db-specific key.
|
|
2833
|
+
* The resolved form drives three things:
|
|
2819
2834
|
*
|
|
2820
|
-
*
|
|
2821
|
-
*
|
|
2822
|
-
*
|
|
2823
|
-
*
|
|
2835
|
+
* 1. `atscript_db_action_input_form` — `{ type, name }` param metadata,
|
|
2836
|
+
* consumed by `discoverActions` to emit `inputForm: <name>` on the
|
|
2837
|
+
* action's `/meta` entry and to register the schema for
|
|
2838
|
+
* `GET /meta/form/:name`.
|
|
2839
|
+
* 2. `atscript_type` — a generic hook any atscript-aware Moost pipe can read
|
|
2840
|
+
* without knowing the moost-db-specific key.
|
|
2841
|
+
* 3. **Request-time validation** — the resolver runs
|
|
2842
|
+
* `FormType.validator(opts).validate(input ?? {})` before the handler
|
|
2843
|
+
* fires. A failure throws `ValidatorError`, which the controllers' own
|
|
2844
|
+
* `validationErrorTransform` shapes into the same structured `400`
|
|
2845
|
+
* envelope as strict-`ids` failures. Absent `input` is validated as `{}`
|
|
2846
|
+
* (all-optional forms pass; required fields produce per-field errors) and
|
|
2847
|
+
* the handler always receives an object, never `undefined`. Pass
|
|
2848
|
+
* `validatorOpts` to tune validation; an app-level `validatorPipe()` may
|
|
2849
|
+
* re-validate the same value harmlessly.
|
|
2824
2850
|
*
|
|
2825
2851
|
* Only one `@InputForm()` per action is supported. To collect multiple
|
|
2826
2852
|
* structured inputs, compose them into a single `.as` interface and pass an
|
|
2827
2853
|
* array form on the field whose user-facing intent is "list of items".
|
|
2828
2854
|
*
|
|
2829
2855
|
* @param formType A compiled `.as` interface class (carries `.validator()`,
|
|
2830
|
-
* `.metadata`, etc.).
|
|
2856
|
+
* `.metadata`, etc.). Optional — inferred from the param's
|
|
2857
|
+
* reflected type when omitted.
|
|
2858
|
+
* @param validatorOpts Options forwarded to `formType.validator()`.
|
|
2831
2859
|
*/
|
|
2832
|
-
function InputForm(formType) {
|
|
2860
|
+
function InputForm(formType, validatorOpts) {
|
|
2833
2861
|
const mate = (0, moost.getMoostMate)();
|
|
2834
|
-
|
|
2835
|
-
|
|
2836
|
-
|
|
2837
|
-
|
|
2838
|
-
|
|
2862
|
+
let resolved;
|
|
2863
|
+
return (0, moost.ApplyDecorators)(mate.decorate((paramMeta) => {
|
|
2864
|
+
const candidate = formType ?? paramMeta.type;
|
|
2865
|
+
if (!(0, _atscript_typescript_utils.isAnnotatedType)(candidate) || typeof candidate.name !== "string" || typeof candidate.validator !== "function") throw new Error(`@InputForm(${formType ? "…" : ""}) could not resolve the form type: expected a compiled .as interface, got ${describeCandidate(candidate)}. Annotate the parameter with the compiled .as class via a VALUE import, or pass the form explicitly: @InputForm(MyForm).`);
|
|
2866
|
+
resolved = candidate;
|
|
2867
|
+
const meta = {
|
|
2868
|
+
type: resolved,
|
|
2869
|
+
name: resolved.name
|
|
2870
|
+
};
|
|
2871
|
+
return {
|
|
2872
|
+
...paramMeta,
|
|
2873
|
+
atscript_db_action_input_form: meta,
|
|
2874
|
+
atscript_type: resolved
|
|
2875
|
+
};
|
|
2876
|
+
}), (0, moost.Resolve)(async () => {
|
|
2877
|
+
const input = await (0, _wooksjs_event_core.current)().get(dbActionInputSlot) ?? {};
|
|
2878
|
+
if (resolved) resolved.validator(validatorOpts).validate(input);
|
|
2879
|
+
return input;
|
|
2880
|
+
}, "dbActionInputForm"));
|
|
2839
2881
|
}
|
|
2840
2882
|
//#endregion
|
|
2841
2883
|
//#region src/actions/per-row.ts
|
package/dist/index.d.cts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { i as resolveDbSpace, n as clearDbSpaces, r as provideDbSpace, t as DEFAULT_DB_SPACE } from "./db-space-registry-CWpYwZ4R.cjs";
|
|
2
|
-
import { TAtscriptAnnotatedType, TAtscriptDataType, TSerializeOptions, TSerializedAnnotatedType, Validator } from "@atscript/typescript/utils";
|
|
2
|
+
import { TAtscriptAnnotatedType, TAtscriptDataType, TSerializeOptions, TSerializedAnnotatedType, TValidatorOptions, Validator } from "@atscript/typescript/utils";
|
|
3
3
|
import { AtscriptDbReadable, AtscriptDbTable, FilterExpr, FlatOf, TCrudOp, TCrudPermissions, TCrudPermissions as TCrudPermissions$1, TDbActionInfo, TDbActionInfo as TDbActionInfo$1, TDbActionIntent, TDbActionIntent as TDbActionIntent$1, TDbActionLevel, TDbActionLevel as TDbActionLevel$1, TDbActionProcessor, TDbFieldMeta, TIdentification, TMetaResponse, Uniquery, UniqueryControls } from "@atscript/db";
|
|
4
4
|
import { HttpError } from "@moostjs/event-http";
|
|
5
5
|
import { Mate, Moost, TConsoleBase, TMateParamMeta, TMoostMetadata } from "moost";
|
|
@@ -181,7 +181,7 @@ declare class AsDbReadableController<T extends TAtscriptAnnotatedType = TAtscrip
|
|
|
181
181
|
private readonly _quantityRefByPath;
|
|
182
182
|
/** Paths the adapter vetoes for filtering (e.g. JSON storage on SQL). Symmetric with `/meta` `filterable: false`. */
|
|
183
183
|
private readonly _adapterNonFilterable;
|
|
184
|
-
constructor(
|
|
184
|
+
constructor(app: Moost, readable?: AtscriptDbReadable<T>);
|
|
185
185
|
private _collectAdapterNonFilterable;
|
|
186
186
|
private _collectQuantityRefs;
|
|
187
187
|
private _buildGates;
|
|
@@ -323,7 +323,7 @@ declare class AsDbReadableController<T extends TAtscriptAnnotatedType = TAtscrip
|
|
|
323
323
|
declare class AsDbController<T extends TAtscriptAnnotatedType = TAtscriptAnnotatedType, DataType = TAtscriptDataType<T>> extends AsDbReadableController<T, DataType> {
|
|
324
324
|
/** Reference to the underlying table (typed for write access). */
|
|
325
325
|
protected get table(): AtscriptDbTable<T>;
|
|
326
|
-
constructor(
|
|
326
|
+
constructor(app: Moost, table?: AtscriptDbTable<T>);
|
|
327
327
|
protected buildCrud(): TCrudPermissions$1;
|
|
328
328
|
/**
|
|
329
329
|
* Intercepts write operations. Return `undefined` to abort.
|
|
@@ -699,7 +699,7 @@ declare module "moost" {
|
|
|
699
699
|
* Class-level readable-binding descriptor written by `@TableController` /
|
|
700
700
|
* `@ReadableController` / `@ViewController`. One uniform `resolve()` backs
|
|
701
701
|
* both the DI provide factory and the base controller's
|
|
702
|
-
* `super(
|
|
702
|
+
* `super(app)` fallback; `model` additionally feeds
|
|
703
703
|
* `assertExposed()`.
|
|
704
704
|
*/
|
|
705
705
|
interface TReadableBindingMeta {
|
|
@@ -903,7 +903,7 @@ declare function findReadableBinding(ctor: Function | undefined): TReadableBindi
|
|
|
903
903
|
*
|
|
904
904
|
* Used by {@link AsDbReadableController}'s constructor when `readable` is
|
|
905
905
|
* `undefined` — i.e. a subclass with its own constructor called
|
|
906
|
-
* `super(
|
|
906
|
+
* `super(app)` instead of forwarding an injected instance.
|
|
907
907
|
*/
|
|
908
908
|
declare function resolveBoundReadable(ctor: Function | undefined): AtscriptDbReadable<any>;
|
|
909
909
|
//#endregion
|
|
@@ -1056,34 +1056,55 @@ declare function DbRowsActions<TRow = unknown, const D extends Record<string, un
|
|
|
1056
1056
|
//#region src/actions/db-action-input-form.decorator.d.ts
|
|
1057
1057
|
/**
|
|
1058
1058
|
* Parameter decorator that injects the `input` field of the action request
|
|
1059
|
-
* envelope (`{ ids?, input? }`) into the handler
|
|
1059
|
+
* envelope (`{ ids?, input? }`) into the handler, **validated** against the
|
|
1060
|
+
* action's form.
|
|
1060
1061
|
*
|
|
1061
|
-
*
|
|
1062
|
+
* The form type may be passed explicitly — `@InputForm(MyForm)` — or inferred
|
|
1063
|
+
* from the parameter's reflected design type when omitted:
|
|
1062
1064
|
*
|
|
1063
|
-
*
|
|
1064
|
-
*
|
|
1065
|
-
*
|
|
1066
|
-
*
|
|
1067
|
-
*
|
|
1068
|
-
*
|
|
1069
|
-
*
|
|
1070
|
-
*
|
|
1065
|
+
* ```ts
|
|
1066
|
+
* @Post("actions/comment")
|
|
1067
|
+
* @DbAction("comment", { label: "Comment" })
|
|
1068
|
+
* async comment(@InputForm() input: CommentForm) { ... }
|
|
1069
|
+
* ```
|
|
1070
|
+
*
|
|
1071
|
+
* Inference reads `design:paramtypes`, so it only works when the parameter is
|
|
1072
|
+
* annotated with the compiled `.as` class through a VALUE import — an
|
|
1073
|
+
* `import type` elides the class and reflection yields `Object`. When the
|
|
1074
|
+
* reflected type is unusable, decoration **throws** (fail-loud, at import
|
|
1075
|
+
* time) instead of silently serving an action without a form. The explicit
|
|
1076
|
+
* argument sidesteps reflection entirely and always wins over the annotation.
|
|
1077
|
+
*
|
|
1078
|
+
* The resolved form drives three things:
|
|
1071
1079
|
*
|
|
1072
|
-
*
|
|
1073
|
-
*
|
|
1074
|
-
*
|
|
1075
|
-
*
|
|
1080
|
+
* 1. `atscript_db_action_input_form` — `{ type, name }` param metadata,
|
|
1081
|
+
* consumed by `discoverActions` to emit `inputForm: <name>` on the
|
|
1082
|
+
* action's `/meta` entry and to register the schema for
|
|
1083
|
+
* `GET /meta/form/:name`.
|
|
1084
|
+
* 2. `atscript_type` — a generic hook any atscript-aware Moost pipe can read
|
|
1085
|
+
* without knowing the moost-db-specific key.
|
|
1086
|
+
* 3. **Request-time validation** — the resolver runs
|
|
1087
|
+
* `FormType.validator(opts).validate(input ?? {})` before the handler
|
|
1088
|
+
* fires. A failure throws `ValidatorError`, which the controllers' own
|
|
1089
|
+
* `validationErrorTransform` shapes into the same structured `400`
|
|
1090
|
+
* envelope as strict-`ids` failures. Absent `input` is validated as `{}`
|
|
1091
|
+
* (all-optional forms pass; required fields produce per-field errors) and
|
|
1092
|
+
* the handler always receives an object, never `undefined`. Pass
|
|
1093
|
+
* `validatorOpts` to tune validation; an app-level `validatorPipe()` may
|
|
1094
|
+
* re-validate the same value harmlessly.
|
|
1076
1095
|
*
|
|
1077
1096
|
* Only one `@InputForm()` per action is supported. To collect multiple
|
|
1078
1097
|
* structured inputs, compose them into a single `.as` interface and pass an
|
|
1079
1098
|
* array form on the field whose user-facing intent is "list of items".
|
|
1080
1099
|
*
|
|
1081
1100
|
* @param formType A compiled `.as` interface class (carries `.validator()`,
|
|
1082
|
-
* `.metadata`, etc.).
|
|
1101
|
+
* `.metadata`, etc.). Optional — inferred from the param's
|
|
1102
|
+
* reflected type when omitted.
|
|
1103
|
+
* @param validatorOpts Options forwarded to `formType.validator()`.
|
|
1083
1104
|
*/
|
|
1084
1105
|
declare function InputForm<T extends TAtscriptAnnotatedType & {
|
|
1085
1106
|
readonly name: string;
|
|
1086
|
-
}>(formType
|
|
1107
|
+
}>(formType?: T, validatorOpts?: Partial<TValidatorOptions>): ParameterDecorator;
|
|
1087
1108
|
//#endregion
|
|
1088
1109
|
//#region src/actions/discover.d.ts
|
|
1089
1110
|
/**
|
package/dist/index.d.mts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { i as resolveDbSpace, n as clearDbSpaces, r as provideDbSpace, t as DEFAULT_DB_SPACE } from "./db-space-registry-CKR6G-hi.mjs";
|
|
2
|
-
import { TAtscriptAnnotatedType, TAtscriptDataType, TSerializeOptions, TSerializedAnnotatedType, Validator } from "@atscript/typescript/utils";
|
|
2
|
+
import { TAtscriptAnnotatedType, TAtscriptDataType, TSerializeOptions, TSerializedAnnotatedType, TValidatorOptions, Validator } from "@atscript/typescript/utils";
|
|
3
3
|
import { HttpError } from "@moostjs/event-http";
|
|
4
4
|
import { Mate, Moost, TConsoleBase, TMateParamMeta, TMoostMetadata } from "moost";
|
|
5
5
|
import { parseUrl } from "@uniqu/url";
|
|
@@ -181,7 +181,7 @@ declare class AsDbReadableController<T extends TAtscriptAnnotatedType = TAtscrip
|
|
|
181
181
|
private readonly _quantityRefByPath;
|
|
182
182
|
/** Paths the adapter vetoes for filtering (e.g. JSON storage on SQL). Symmetric with `/meta` `filterable: false`. */
|
|
183
183
|
private readonly _adapterNonFilterable;
|
|
184
|
-
constructor(
|
|
184
|
+
constructor(app: Moost, readable?: AtscriptDbReadable<T>);
|
|
185
185
|
private _collectAdapterNonFilterable;
|
|
186
186
|
private _collectQuantityRefs;
|
|
187
187
|
private _buildGates;
|
|
@@ -323,7 +323,7 @@ declare class AsDbReadableController<T extends TAtscriptAnnotatedType = TAtscrip
|
|
|
323
323
|
declare class AsDbController<T extends TAtscriptAnnotatedType = TAtscriptAnnotatedType, DataType = TAtscriptDataType<T>> extends AsDbReadableController<T, DataType> {
|
|
324
324
|
/** Reference to the underlying table (typed for write access). */
|
|
325
325
|
protected get table(): AtscriptDbTable<T>;
|
|
326
|
-
constructor(
|
|
326
|
+
constructor(app: Moost, table?: AtscriptDbTable<T>);
|
|
327
327
|
protected buildCrud(): TCrudPermissions$1;
|
|
328
328
|
/**
|
|
329
329
|
* Intercepts write operations. Return `undefined` to abort.
|
|
@@ -699,7 +699,7 @@ declare module "moost" {
|
|
|
699
699
|
* Class-level readable-binding descriptor written by `@TableController` /
|
|
700
700
|
* `@ReadableController` / `@ViewController`. One uniform `resolve()` backs
|
|
701
701
|
* both the DI provide factory and the base controller's
|
|
702
|
-
* `super(
|
|
702
|
+
* `super(app)` fallback; `model` additionally feeds
|
|
703
703
|
* `assertExposed()`.
|
|
704
704
|
*/
|
|
705
705
|
interface TReadableBindingMeta {
|
|
@@ -903,7 +903,7 @@ declare function findReadableBinding(ctor: Function | undefined): TReadableBindi
|
|
|
903
903
|
*
|
|
904
904
|
* Used by {@link AsDbReadableController}'s constructor when `readable` is
|
|
905
905
|
* `undefined` — i.e. a subclass with its own constructor called
|
|
906
|
-
* `super(
|
|
906
|
+
* `super(app)` instead of forwarding an injected instance.
|
|
907
907
|
*/
|
|
908
908
|
declare function resolveBoundReadable(ctor: Function | undefined): AtscriptDbReadable<any>;
|
|
909
909
|
//#endregion
|
|
@@ -1056,34 +1056,55 @@ declare function DbRowsActions<TRow = unknown, const D extends Record<string, un
|
|
|
1056
1056
|
//#region src/actions/db-action-input-form.decorator.d.ts
|
|
1057
1057
|
/**
|
|
1058
1058
|
* Parameter decorator that injects the `input` field of the action request
|
|
1059
|
-
* envelope (`{ ids?, input? }`) into the handler
|
|
1059
|
+
* envelope (`{ ids?, input? }`) into the handler, **validated** against the
|
|
1060
|
+
* action's form.
|
|
1060
1061
|
*
|
|
1061
|
-
*
|
|
1062
|
+
* The form type may be passed explicitly — `@InputForm(MyForm)` — or inferred
|
|
1063
|
+
* from the parameter's reflected design type when omitted:
|
|
1062
1064
|
*
|
|
1063
|
-
*
|
|
1064
|
-
*
|
|
1065
|
-
*
|
|
1066
|
-
*
|
|
1067
|
-
*
|
|
1068
|
-
*
|
|
1069
|
-
*
|
|
1070
|
-
*
|
|
1065
|
+
* ```ts
|
|
1066
|
+
* @Post("actions/comment")
|
|
1067
|
+
* @DbAction("comment", { label: "Comment" })
|
|
1068
|
+
* async comment(@InputForm() input: CommentForm) { ... }
|
|
1069
|
+
* ```
|
|
1070
|
+
*
|
|
1071
|
+
* Inference reads `design:paramtypes`, so it only works when the parameter is
|
|
1072
|
+
* annotated with the compiled `.as` class through a VALUE import — an
|
|
1073
|
+
* `import type` elides the class and reflection yields `Object`. When the
|
|
1074
|
+
* reflected type is unusable, decoration **throws** (fail-loud, at import
|
|
1075
|
+
* time) instead of silently serving an action without a form. The explicit
|
|
1076
|
+
* argument sidesteps reflection entirely and always wins over the annotation.
|
|
1077
|
+
*
|
|
1078
|
+
* The resolved form drives three things:
|
|
1071
1079
|
*
|
|
1072
|
-
*
|
|
1073
|
-
*
|
|
1074
|
-
*
|
|
1075
|
-
*
|
|
1080
|
+
* 1. `atscript_db_action_input_form` — `{ type, name }` param metadata,
|
|
1081
|
+
* consumed by `discoverActions` to emit `inputForm: <name>` on the
|
|
1082
|
+
* action's `/meta` entry and to register the schema for
|
|
1083
|
+
* `GET /meta/form/:name`.
|
|
1084
|
+
* 2. `atscript_type` — a generic hook any atscript-aware Moost pipe can read
|
|
1085
|
+
* without knowing the moost-db-specific key.
|
|
1086
|
+
* 3. **Request-time validation** — the resolver runs
|
|
1087
|
+
* `FormType.validator(opts).validate(input ?? {})` before the handler
|
|
1088
|
+
* fires. A failure throws `ValidatorError`, which the controllers' own
|
|
1089
|
+
* `validationErrorTransform` shapes into the same structured `400`
|
|
1090
|
+
* envelope as strict-`ids` failures. Absent `input` is validated as `{}`
|
|
1091
|
+
* (all-optional forms pass; required fields produce per-field errors) and
|
|
1092
|
+
* the handler always receives an object, never `undefined`. Pass
|
|
1093
|
+
* `validatorOpts` to tune validation; an app-level `validatorPipe()` may
|
|
1094
|
+
* re-validate the same value harmlessly.
|
|
1076
1095
|
*
|
|
1077
1096
|
* Only one `@InputForm()` per action is supported. To collect multiple
|
|
1078
1097
|
* structured inputs, compose them into a single `.as` interface and pass an
|
|
1079
1098
|
* array form on the field whose user-facing intent is "list of items".
|
|
1080
1099
|
*
|
|
1081
1100
|
* @param formType A compiled `.as` interface class (carries `.validator()`,
|
|
1082
|
-
* `.metadata`, etc.).
|
|
1101
|
+
* `.metadata`, etc.). Optional — inferred from the param's
|
|
1102
|
+
* reflected type when omitted.
|
|
1103
|
+
* @param validatorOpts Options forwarded to `formType.validator()`.
|
|
1083
1104
|
*/
|
|
1084
1105
|
declare function InputForm<T extends TAtscriptAnnotatedType & {
|
|
1085
1106
|
readonly name: string;
|
|
1086
|
-
}>(formType
|
|
1107
|
+
}>(formType?: T, validatorOpts?: Partial<TValidatorOptions>): ParameterDecorator;
|
|
1087
1108
|
//#endregion
|
|
1088
1109
|
//#region src/actions/discover.d.ts
|
|
1089
1110
|
/**
|
package/dist/index.mjs
CHANGED
|
@@ -921,7 +921,7 @@ function normalizeOptions(prefixOrOptions) {
|
|
|
921
921
|
* Builds the shared binding metadata for a decorator invocation: classifies
|
|
922
922
|
* the binding form, computes the static route prefix, and packages a uniform
|
|
923
923
|
* `resolve()` used by both the DI provide factory and the base controller's
|
|
924
|
-
* `super(
|
|
924
|
+
* `super(app)` fallback.
|
|
925
925
|
*/
|
|
926
926
|
function buildBinding(binding, options, decoratorName) {
|
|
927
927
|
if (isAnnotatedType(binding)) {
|
|
@@ -1040,7 +1040,7 @@ function findReadableBinding(ctor) {
|
|
|
1040
1040
|
*
|
|
1041
1041
|
* Used by {@link AsDbReadableController}'s constructor when `readable` is
|
|
1042
1042
|
* `undefined` — i.e. a subclass with its own constructor called
|
|
1043
|
-
* `super(
|
|
1043
|
+
* `super(app)` instead of forwarding an injected instance.
|
|
1044
1044
|
*/
|
|
1045
1045
|
function resolveBoundReadable(ctor) {
|
|
1046
1046
|
const binding = findReadableBinding(ctor);
|
|
@@ -1095,7 +1095,7 @@ let AsDbReadableController = class AsDbReadableController extends AsReadableCont
|
|
|
1095
1095
|
_quantityRefByPath;
|
|
1096
1096
|
/** Paths the adapter vetoes for filtering (e.g. JSON storage on SQL). Symmetric with `/meta` `filterable: false`. */
|
|
1097
1097
|
_adapterNonFilterable;
|
|
1098
|
-
constructor(
|
|
1098
|
+
constructor(app, readable) {
|
|
1099
1099
|
const resolved = readable ?? resolveBoundReadable(new.target);
|
|
1100
1100
|
super(resolved.type, resolved.tableName, app, resolved.isView ? "view" : "table");
|
|
1101
1101
|
this.readable = resolved;
|
|
@@ -1757,9 +1757,9 @@ __decorate([
|
|
|
1757
1757
|
], AsDbReadableController.prototype, "getOneComposite", null);
|
|
1758
1758
|
AsDbReadableController = __decorate([
|
|
1759
1759
|
Inherit(),
|
|
1760
|
-
__decorateParam(
|
|
1761
|
-
__decorateParam(
|
|
1762
|
-
__decorateMetadata("design:paramtypes", [
|
|
1760
|
+
__decorateParam(1, Inject(READABLE_DEF)),
|
|
1761
|
+
__decorateParam(1, Optional()),
|
|
1762
|
+
__decorateMetadata("design:paramtypes", [typeof Moost === "undefined" ? Object : Moost, Object])
|
|
1763
1763
|
], AsDbReadableController);
|
|
1764
1764
|
registerAsDbReadableController(AsDbReadableController);
|
|
1765
1765
|
//#endregion
|
|
@@ -1792,8 +1792,8 @@ let AsDbController = class AsDbController extends AsDbReadableController {
|
|
|
1792
1792
|
get table() {
|
|
1793
1793
|
return this.readable;
|
|
1794
1794
|
}
|
|
1795
|
-
constructor(
|
|
1796
|
-
super(
|
|
1795
|
+
constructor(app, table) {
|
|
1796
|
+
super(app, table);
|
|
1797
1797
|
}
|
|
1798
1798
|
buildCrud() {
|
|
1799
1799
|
return {
|
|
@@ -1955,9 +1955,9 @@ __decorate([
|
|
|
1955
1955
|
], AsDbController.prototype, "removeComposite", null);
|
|
1956
1956
|
AsDbController = __decorate([
|
|
1957
1957
|
Inherit(),
|
|
1958
|
-
__decorateParam(
|
|
1959
|
-
__decorateParam(
|
|
1960
|
-
__decorateMetadata("design:paramtypes", [
|
|
1958
|
+
__decorateParam(1, Inject(TABLE_DEF)),
|
|
1959
|
+
__decorateParam(1, Optional()),
|
|
1960
|
+
__decorateMetadata("design:paramtypes", [typeof Moost === "undefined" ? Object : Moost, Object])
|
|
1961
1961
|
], AsDbController);
|
|
1962
1962
|
//#endregion
|
|
1963
1963
|
//#region src/as-value-help.controller.ts
|
|
@@ -2801,40 +2801,82 @@ function classLevelActions(dict, forcedLevel) {
|
|
|
2801
2801
|
}
|
|
2802
2802
|
//#endregion
|
|
2803
2803
|
//#region src/actions/db-action-input-form.decorator.ts
|
|
2804
|
+
/** Human-readable description of a rejected form candidate for the error hint. */
|
|
2805
|
+
function describeCandidate(candidate) {
|
|
2806
|
+
if (candidate === void 0) return "undefined — no reflected type (emitDecoratorMetadata off, or a circular import)";
|
|
2807
|
+
if (candidate === Object) return "Object — reflection lost the type (interface/union annotation, or the class was imported with `import type`)";
|
|
2808
|
+
if (typeof candidate === "function") return `class ${candidate.name ?? "<anonymous>"} — not a compiled .as interface`;
|
|
2809
|
+
return typeof candidate;
|
|
2810
|
+
}
|
|
2804
2811
|
/**
|
|
2805
2812
|
* Parameter decorator that injects the `input` field of the action request
|
|
2806
|
-
* envelope (`{ ids?, input? }`) into the handler
|
|
2813
|
+
* envelope (`{ ids?, input? }`) into the handler, **validated** against the
|
|
2814
|
+
* action's form.
|
|
2815
|
+
*
|
|
2816
|
+
* The form type may be passed explicitly — `@InputForm(MyForm)` — or inferred
|
|
2817
|
+
* from the parameter's reflected design type when omitted:
|
|
2818
|
+
*
|
|
2819
|
+
* ```ts
|
|
2820
|
+
* @Post("actions/comment")
|
|
2821
|
+
* @DbAction("comment", { label: "Comment" })
|
|
2822
|
+
* async comment(@InputForm() input: CommentForm) { ... }
|
|
2823
|
+
* ```
|
|
2807
2824
|
*
|
|
2808
|
-
*
|
|
2825
|
+
* Inference reads `design:paramtypes`, so it only works when the parameter is
|
|
2826
|
+
* annotated with the compiled `.as` class through a VALUE import — an
|
|
2827
|
+
* `import type` elides the class and reflection yields `Object`. When the
|
|
2828
|
+
* reflected type is unusable, decoration **throws** (fail-loud, at import
|
|
2829
|
+
* time) instead of silently serving an action without a form. The explicit
|
|
2830
|
+
* argument sidesteps reflection entirely and always wins over the annotation.
|
|
2809
2831
|
*
|
|
2810
|
-
*
|
|
2811
|
-
* name, consumed by `discoverActions` to:
|
|
2812
|
-
* - emit `inputForm: FormType.name` on the action's `/meta` entry, and
|
|
2813
|
-
* - register the type in the controller's form registry so
|
|
2814
|
-
* `GET /meta/form/:name` can serve the serialized schema.
|
|
2815
|
-
* 2. `atscript_type` — just the type ref, providing a generic hook any
|
|
2816
|
-
* atscript-aware Moost pipe can read without knowing about the
|
|
2817
|
-
* moost-db-specific key.
|
|
2832
|
+
* The resolved form drives three things:
|
|
2818
2833
|
*
|
|
2819
|
-
*
|
|
2820
|
-
*
|
|
2821
|
-
*
|
|
2822
|
-
*
|
|
2834
|
+
* 1. `atscript_db_action_input_form` — `{ type, name }` param metadata,
|
|
2835
|
+
* consumed by `discoverActions` to emit `inputForm: <name>` on the
|
|
2836
|
+
* action's `/meta` entry and to register the schema for
|
|
2837
|
+
* `GET /meta/form/:name`.
|
|
2838
|
+
* 2. `atscript_type` — a generic hook any atscript-aware Moost pipe can read
|
|
2839
|
+
* without knowing the moost-db-specific key.
|
|
2840
|
+
* 3. **Request-time validation** — the resolver runs
|
|
2841
|
+
* `FormType.validator(opts).validate(input ?? {})` before the handler
|
|
2842
|
+
* fires. A failure throws `ValidatorError`, which the controllers' own
|
|
2843
|
+
* `validationErrorTransform` shapes into the same structured `400`
|
|
2844
|
+
* envelope as strict-`ids` failures. Absent `input` is validated as `{}`
|
|
2845
|
+
* (all-optional forms pass; required fields produce per-field errors) and
|
|
2846
|
+
* the handler always receives an object, never `undefined`. Pass
|
|
2847
|
+
* `validatorOpts` to tune validation; an app-level `validatorPipe()` may
|
|
2848
|
+
* re-validate the same value harmlessly.
|
|
2823
2849
|
*
|
|
2824
2850
|
* Only one `@InputForm()` per action is supported. To collect multiple
|
|
2825
2851
|
* structured inputs, compose them into a single `.as` interface and pass an
|
|
2826
2852
|
* array form on the field whose user-facing intent is "list of items".
|
|
2827
2853
|
*
|
|
2828
2854
|
* @param formType A compiled `.as` interface class (carries `.validator()`,
|
|
2829
|
-
* `.metadata`, etc.).
|
|
2855
|
+
* `.metadata`, etc.). Optional — inferred from the param's
|
|
2856
|
+
* reflected type when omitted.
|
|
2857
|
+
* @param validatorOpts Options forwarded to `formType.validator()`.
|
|
2830
2858
|
*/
|
|
2831
|
-
function InputForm(formType) {
|
|
2859
|
+
function InputForm(formType, validatorOpts) {
|
|
2832
2860
|
const mate = getMoostMate();
|
|
2833
|
-
|
|
2834
|
-
|
|
2835
|
-
|
|
2836
|
-
|
|
2837
|
-
|
|
2861
|
+
let resolved;
|
|
2862
|
+
return ApplyDecorators(mate.decorate((paramMeta) => {
|
|
2863
|
+
const candidate = formType ?? paramMeta.type;
|
|
2864
|
+
if (!isAnnotatedType(candidate) || typeof candidate.name !== "string" || typeof candidate.validator !== "function") throw new Error(`@InputForm(${formType ? "…" : ""}) could not resolve the form type: expected a compiled .as interface, got ${describeCandidate(candidate)}. Annotate the parameter with the compiled .as class via a VALUE import, or pass the form explicitly: @InputForm(MyForm).`);
|
|
2865
|
+
resolved = candidate;
|
|
2866
|
+
const meta = {
|
|
2867
|
+
type: resolved,
|
|
2868
|
+
name: resolved.name
|
|
2869
|
+
};
|
|
2870
|
+
return {
|
|
2871
|
+
...paramMeta,
|
|
2872
|
+
atscript_db_action_input_form: meta,
|
|
2873
|
+
atscript_type: resolved
|
|
2874
|
+
};
|
|
2875
|
+
}), Resolve(async () => {
|
|
2876
|
+
const input = await current().get(dbActionInputSlot) ?? {};
|
|
2877
|
+
if (resolved) resolved.validator(validatorOpts).validate(input);
|
|
2878
|
+
return input;
|
|
2879
|
+
}, "dbActionInputForm"));
|
|
2838
2880
|
}
|
|
2839
2881
|
//#endregion
|
|
2840
2882
|
//#region src/actions/per-row.ts
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@atscript/moost-db",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.121",
|
|
4
4
|
"description": "Generic database controller for Moost with Atscript.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"annotations",
|
|
@@ -44,27 +44,27 @@
|
|
|
44
44
|
},
|
|
45
45
|
"dependencies": {
|
|
46
46
|
"@uniqu/url": "^0.1.7",
|
|
47
|
-
"@atscript/db-memory": "^0.1.
|
|
47
|
+
"@atscript/db-memory": "^0.1.121"
|
|
48
48
|
},
|
|
49
49
|
"devDependencies": {
|
|
50
|
-
"@atscript/core": "^0.1.
|
|
51
|
-
"@atscript/typescript": "^0.1.
|
|
52
|
-
"@moostjs/event-http": "^0.6.
|
|
50
|
+
"@atscript/core": "^0.1.85",
|
|
51
|
+
"@atscript/typescript": "^0.1.85",
|
|
52
|
+
"@moostjs/event-http": "^0.6.33",
|
|
53
53
|
"@uniqu/core": "^0.1.7",
|
|
54
|
-
"@wooksjs/event-core": "^0.7.
|
|
55
|
-
"@wooksjs/event-http": "^0.7.
|
|
56
|
-
"@wooksjs/http-body": "^0.7.
|
|
57
|
-
"moost": "^0.6.
|
|
58
|
-
"unplugin-atscript": "^0.1.
|
|
54
|
+
"@wooksjs/event-core": "^0.7.21",
|
|
55
|
+
"@wooksjs/event-http": "^0.7.21",
|
|
56
|
+
"@wooksjs/http-body": "^0.7.21",
|
|
57
|
+
"moost": "^0.6.33",
|
|
58
|
+
"unplugin-atscript": "^0.1.85"
|
|
59
59
|
},
|
|
60
60
|
"peerDependencies": {
|
|
61
|
-
"@atscript/typescript": "^0.1.
|
|
62
|
-
"@moostjs/event-http": "^0.6.
|
|
61
|
+
"@atscript/typescript": "^0.1.85",
|
|
62
|
+
"@moostjs/event-http": "^0.6.33",
|
|
63
63
|
"@uniqu/core": "^0.1.7",
|
|
64
|
-
"@wooksjs/event-core": "^0.7.
|
|
65
|
-
"@wooksjs/http-body": "^0.7.
|
|
66
|
-
"moost": "^0.6.
|
|
67
|
-
"@atscript/db": "^0.1.
|
|
64
|
+
"@wooksjs/event-core": "^0.7.21",
|
|
65
|
+
"@wooksjs/http-body": "^0.7.21",
|
|
66
|
+
"moost": "^0.6.33",
|
|
67
|
+
"@atscript/db": "^0.1.121"
|
|
68
68
|
},
|
|
69
69
|
"scripts": {
|
|
70
70
|
"postinstall": "asc -f dts",
|