@atscript/moost-db 0.1.56 → 0.1.58

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.d.mts CHANGED
@@ -4,7 +4,8 @@ import { HttpError } from "@moostjs/event-http";
4
4
  import * as moost from "moost";
5
5
  import { Moost, TConsoleBase } from "moost";
6
6
  import * as _uniqu_url0 from "@uniqu/url";
7
- import { AtscriptDbReadable, AtscriptDbTable, FilterExpr, TDbActionInfo, TDbActionInfo as TDbActionInfo$1, TDbActionIntent, TDbActionIntent as TDbActionIntent$1, TDbActionLevel, TDbActionLevel as TDbActionLevel$1, TDbActionProcessor, TDbFieldMeta, TMetaResponse, Uniquery, UniqueryControls } from "@atscript/db";
7
+ import { AtscriptDbReadable, AtscriptDbTable, FilterExpr, TCrudOp, TCrudPermissions, TCrudPermissions as TCrudPermissions$1, TDbActionInfo, TDbActionInfo as TDbActionInfo$1, TDbActionIntent, TDbActionIntent as TDbActionIntent$1, TDbActionLevel, TDbActionLevel as TDbActionLevel$1, TDbActionProcessor, TDbFieldMeta, TMetaResponse, Uniquery, UniqueryControls } from "@atscript/db";
8
+ import * as _wooksjs_event_core0 from "@wooksjs/event-core";
8
9
 
9
10
  //#region src/as-readable.controller.d.ts
10
11
  /**
@@ -88,11 +89,6 @@ declare abstract class AsReadableController<T extends TAtscriptAnnotatedType = T
88
89
  * to customise.
89
90
  */
90
91
  protected getSerializeOptions(): TSerializeOptions;
91
- /**
92
- * Whether this controller is read-only (no write endpoints).
93
- * Returns `true` by default; {@link AsDbController} overrides to `false`.
94
- */
95
- protected _isReadOnly(): boolean;
96
92
  private _queryControlsValidator?;
97
93
  private _pagesControlsValidator?;
98
94
  private _getOneControlsValidator?;
@@ -111,28 +107,39 @@ declare abstract class AsReadableController<T extends TAtscriptAnnotatedType = T
111
107
  protected parseQueryString(url: string): _uniqu_url0.UrlQuery;
112
108
  protected returnOne(result: Promise<DataType | null>): Promise<DataType | HttpError>;
113
109
  /**
114
- * **GET /meta** — returns the bound interface's metadata envelope.
115
- *
116
- * Base implementation delegates to {@link buildMetaResponse}, which subclasses
117
- * override to add source-specific fields (relations, searchable flags, etc.).
118
- * The response is cached on the instance; async overrides must cache any
119
- * extra enrichment themselves.
110
+ * **GET /meta** — returns the bound interface's metadata envelope. The
111
+ * static envelope is cached; {@link applyMetaOverlay} runs per request so
112
+ * subclasses can prune the response by principal.
120
113
  */
121
114
  meta(): Promise<TMetaResponse>;
122
115
  /**
123
116
  * Builds the `/meta` payload. Override in subclasses to populate source-specific
124
- * fields. Defaults return a minimal envelope with the serialized type and the
125
- * read-only flag; value-help dicts populate their capability hints here.
126
- * Subclasses that fully replace the envelope must call {@link buildActions}
127
- * directly so `@DbAction*` decorators still surface.
117
+ * fields. Subclasses that fully replace the envelope must call
118
+ * {@link buildActions} and {@link buildCrud} directly so `@DbAction*`
119
+ * decorators and CRUD permissions still surface.
128
120
  */
129
- protected buildMetaResponse(): Promise<TMetaResponse>;
121
+ protected buildMetaResponse(): TMetaResponse;
130
122
  /**
131
123
  * Discovers `@DbAction*` and `@DbActions`-style class metadata on this
132
124
  * controller and produces the `actions` array. Returns `[]` for value-help
133
125
  * controllers — see {@link AsValueHelpController#buildMetaResponse}.
134
126
  */
135
127
  protected buildActions(): TDbActionInfo$1[];
128
+ /**
129
+ * Declares the built-in CRUD operations this controller exposes. Subclasses
130
+ * override to add their keys; the bare base only exposes `/meta`. See
131
+ * `docs/http/permissions.md` for the wire shape and overlay rules.
132
+ */
133
+ protected buildCrud(): TCrudPermissions$1;
134
+ /**
135
+ * Per-request overlay applied to the cached `/meta` envelope. Default no-op.
136
+ * Subclasses may shallow-clone and prune `crud` keys, `crud[op]` arrays, or
137
+ * `actions[]` based on the current request principal (read via Moost
138
+ * composables). The cached envelope MUST NOT be mutated — see
139
+ * `docs/http/permissions.md` for the full contract, including the
140
+ * "discoverability only" caveat.
141
+ */
142
+ protected applyMetaOverlay(meta: TMetaResponse): TMetaResponse | Promise<TMetaResponse>;
136
143
  }
137
144
  //#endregion
138
145
  //#region src/as-db-readable.controller.d.ts
@@ -204,7 +211,8 @@ declare class AsDbReadableController<T extends TAtscriptAnnotatedType = TAtscrip
204
211
  * vector-searchable flags, field-descriptor-derived filter/sort hints, and
205
212
  * the configured primary keys.
206
213
  */
207
- protected buildMetaResponse(): Promise<TMetaResponse>;
214
+ protected buildMetaResponse(): TMetaResponse;
215
+ protected buildCrud(): TCrudPermissions$1;
208
216
  }
209
217
  //#endregion
210
218
  //#region src/as-db.controller.d.ts
@@ -222,7 +230,7 @@ declare class AsDbController<T extends TAtscriptAnnotatedType = TAtscriptAnnotat
222
230
  /** Reference to the underlying table (typed for write access). */
223
231
  protected get table(): AtscriptDbTable<T>;
224
232
  constructor(table: AtscriptDbTable<T>, app: Moost);
225
- protected _isReadOnly(): boolean;
233
+ protected buildCrud(): TCrudPermissions$1;
226
234
  /**
227
235
  * Intercepts write operations. Return `undefined` to abort.
228
236
  * May be async (e.g. to enrich payloads from session / permissions).
@@ -344,8 +352,9 @@ declare abstract class AsValueHelpController<T extends TAtscriptAnnotatedType =
344
352
  * client picker UI (which controls to render); the server does not enforce
345
353
  * these flags at request time.
346
354
  */
347
- protected buildMetaResponse(): Promise<TMetaResponse>;
355
+ protected buildMetaResponse(): TMetaResponse;
348
356
  protected buildActions(): never[];
357
+ protected buildCrud(): TCrudPermissions$1;
349
358
  }
350
359
  //#endregion
351
360
  //#region src/as-json-value-help.controller.d.ts
@@ -452,15 +461,72 @@ declare const validationErrorTransform: () => moost.TInterceptorDef;
452
461
  declare const UseValidationErrorTransform: () => ClassDecorator & MethodDecorator;
453
462
  //#endregion
454
463
  //#region src/actions/types.d.ts
464
+ /** `'rows'`-level batch policy — controls whether failing rows reject or are filtered out. */
465
+ type TOnDisabledRows = "reject" | "skip";
455
466
  /**
456
467
  * Options accepted by `@DbAction(name, opts?)`. Structurally derived from
457
- * {@link TDbActionInfo} so every addition to the wire shape automatically
458
- * propagates here. Fields owned by the framework (`name`, `level`, `processor`,
459
- * `value`) are excluded `name` comes from the decorator argument, `level` is
460
- * inferred from `@DbActionPK*` usage, `processor` is fixed to `'backend'` for
461
- * method-decorator actions, and `value` is filled from the `@Post` path.
468
+ * {@link TDbActionInfo} so every wire-shape addition propagates here, EXCEPT
469
+ * `disabled` and `requiredFields` which differ in shape between decorator
470
+ * opts (function / dev-supplied) and the wire (string / forwarded verbatim).
471
+ *
472
+ * Fields owned by the framework (`name`, `level`, `processor`, `value`) are
473
+ * excluded — `name` comes from the decorator argument, `level` is inferred
474
+ * from `@DbActionPK*` / `@DbActionRow*` usage, `processor` is fixed to
475
+ * `'backend'` for method-decorator actions, and `value` is filled from the
476
+ * `@Post` path.
477
+ *
478
+ * Generic over `TRow` so the `disabled` predicate can be type-checked against
479
+ * the bound table's row shape. Note: TS decorators cannot infer `TRow` from
480
+ * the enclosing controller's class generic, so the dev MUST annotate the row
481
+ * arg explicitly (`(row: Order) => …`) to get type-checking.
462
482
  */
463
- type DbActionOpts = Partial<Omit<TDbActionInfo$1, "name" | "level" | "processor" | "value">>;
483
+ type DbActionOpts<TRow = unknown> = Partial<Omit<TDbActionInfo$1, "name" | "level" | "processor" | "value" | "disabled" | "requiredFields">> & {
484
+ /**
485
+ * Per-row gate predicate. Truthy → action is disabled for that row.
486
+ * Server enforces (via the gate interceptor); UI evaluates the same
487
+ * expression to grey-out / hide the button.
488
+ *
489
+ * The dev MUST annotate the row arg explicitly (`(row: Order) => …`) —
490
+ * TS decorators cannot infer `TRow` from the enclosing class generic.
491
+ */
492
+ disabled?: (row: TRow) => boolean;
493
+ /**
494
+ * Optional dot-notation field paths the UI should union into `$select`.
495
+ * Plain `string[]` in v1.
496
+ *
497
+ * TODO: upgrade to typed `PathOf<TRow>[]` in a follow-up — the recursive
498
+ * type pattern is finicky for nested objects/arrays/optionals and isn't
499
+ * blocking v1.
500
+ *
501
+ * When omitted, the UI parses the stringified `disabled` itself to extract
502
+ * row-property accesses. When present, the UI uses this list verbatim — the
503
+ * server does NOT auto-derive or merge.
504
+ */
505
+ requiredFields?: string[];
506
+ /**
507
+ * `'rows'`-level batch policy. Default `'reject'`.
508
+ *
509
+ * - `'reject'`: evaluate every row (FULL scan, NOT short-circuit) before
510
+ * throwing; if any row fails, the error body lists ALL failing PKs;
511
+ * handler not invoked.
512
+ * - `'skip'`: filter cached rows + cached PKs to passing-only;
513
+ * zero survivors → reject. Handler runs against the survivors.
514
+ *
515
+ * Ignored for `'row'` and `'table'` level actions.
516
+ */
517
+ onDisabledRows?: TOnDisabledRows;
518
+ /**
519
+ * Bound table reference. REQUIRED on non-`AsDbReadableController` classes
520
+ * when `disabled` is set OR a `@DbActionRow*` parameter is declared.
521
+ *
522
+ * Silently ignored on `AsDbReadableController` subclasses (which include
523
+ * `AsDbController`) — the bound table from the controller wins; the
524
+ * gate / thin interceptor probes `instanceof AsDbReadableController` and
525
+ * populates the bound-table slot from `controller.readable` before
526
+ * checking `opts.table`.
527
+ */
528
+ table?: AtscriptDbTable<any>;
529
+ };
464
530
  interface DbActionsEntryCommon {
465
531
  label: string;
466
532
  level: TDbActionLevel$1;
@@ -469,7 +535,28 @@ interface DbActionsEntryCommon {
469
535
  description?: string;
470
536
  order?: number;
471
537
  default?: boolean;
472
- promptText?: string;
538
+ /** Mirrors {@link TDbActionInfo.promptText} — singular/plural via tuple. */
539
+ promptText?: string | [string, string];
540
+ /** Mirrors {@link TDbActionInfo.shortcut} — single-character UI hint. */
541
+ shortcut?: string;
542
+ /**
543
+ * UI-only gate predicate (class-level dict entries do NOT register a
544
+ * server-side gate interceptor — the dict entry's `value` may point at an
545
+ * endpoint in another controller). The wire emits `fn.toString()` so the
546
+ * UI can grey-out / hide the button. For symmetric server enforcement at
547
+ * the actual `@Post`-bound handler, declare `@DbAction(name, { disabled })`
548
+ * on that handler too.
549
+ *
550
+ * Not generic over `TRow` — devs type the row arg explicitly.
551
+ */
552
+ disabled?: (row: any) => boolean;
553
+ /** Same as method-decorator `requiredFields`. UI hint, not server-derived. */
554
+ requiredFields?: string[];
555
+ /**
556
+ * Reserved for future API symmetry with method-decorator opts. Currently a
557
+ * no-op for class-level dict entries (no gate interceptor registers).
558
+ */
559
+ onDisabledRows?: TOnDisabledRows;
473
560
  }
474
561
  /**
475
562
  * Class-level dict entry. `value` semantics by processor:
@@ -500,21 +587,13 @@ type TDbActionsEntryUnpinned = DistributiveOmit<TDbActionsEntry, "level">;
500
587
  //#endregion
501
588
  //#region src/actions/db-action.decorator.d.ts
502
589
  /**
503
- * Mark a controller method as a database action surfaced via `/meta`.
504
- *
505
- * Metadata-only pair with `@Post(...)` for Moost to bind the route. The
506
- * meta builder reads this metadata plus the bound POST path lazily and
507
- * emits the action with `processor: 'backend'`. Order vs.
508
- * `@DbActionDefault()` does not matter — both merge into the same slot.
509
- *
510
- * @example
511
- * ```ts
512
- * @Post('actions/block')
513
- * @DbAction('block', { label: 'Block', icon: 'i-as-block', intent: 'negative' })
514
- * async blockUser(@DbActionPK() id: string) { ... }
515
- * ```
590
+ * Mark a controller method as a database action surfaced via `/meta`. Writes
591
+ * `MOOST_DB_ACTION` metadata and registers a Moost interceptor when needed
592
+ * (gate when `disabled` is set, thin bound-table injector when only
593
+ * `@DbActionRow*` is present). Stacking two `@DbAction` on the same method
594
+ * is undefined and emits a warning.
516
595
  */
517
- declare function DbAction(name: string, opts?: DbActionOpts): MethodDecorator;
596
+ declare function DbAction<TRow = unknown>(name: string, opts?: DbActionOpts<TRow>): MethodDecorator;
518
597
  //#endregion
519
598
  //#region src/actions/db-action-default.decorator.d.ts
520
599
  /**
@@ -537,6 +616,10 @@ declare function DbActionDefault(): MethodDecorator;
537
616
  *
538
617
  * Marks the param so {@link discoverActions} can infer the action's `level`
539
618
  * as `'row'`.
619
+ *
620
+ * Implementation note: the resolver is a thin reader of the cached PK wook
621
+ * — validation logic lives in the wook factory, which runs once per request
622
+ * regardless of how many readers consume the value.
540
623
  */
541
624
  declare function DbActionPK(): ParameterDecorator;
542
625
  //#endregion
@@ -550,9 +633,37 @@ declare function DbActionPK(): ParameterDecorator;
550
633
  *
551
634
  * Validation is strict — no type coercion. Marks the param so
552
635
  * {@link discoverActions} can infer the action's `level` as `'rows'`.
636
+ *
637
+ * In `'rows'` skip mode the resolved value reflects the gate interceptor's
638
+ * filtered subset (the cached PK slot is overwritten in place); see
639
+ * {@link dbActionPksSlot} for precedence details.
553
640
  */
554
641
  declare function DbActionPKs(): ParameterDecorator;
555
642
  //#endregion
643
+ //#region src/actions/db-action-row.decorator.d.ts
644
+ /**
645
+ * Parameter decorator that injects the row whose PK was supplied in the
646
+ * request body. Reads from the cached row wook (fetched once per request,
647
+ * shared with the gate interceptor when `disabled` is set).
648
+ *
649
+ * Marks the param so {@link discoverActions} infers the action's `level` as
650
+ * `'row'`. Co-occurrence with `@DbActionRows()` (or any multi-cardinality
651
+ * decorator) drops the action with a warning.
652
+ *
653
+ * In `'skip'` mode this returns the gate's filtered row; the original
654
+ * request-body row is not retrievable.
655
+ */
656
+ declare function DbActionRow(): ParameterDecorator;
657
+ /**
658
+ * Parameter decorator that injects the rows-array fetched by primary keys
659
+ * from the request body. Reads from the cached row-array wook.
660
+ *
661
+ * Marks the param so {@link discoverActions} infers the action's `level` as
662
+ * `'rows'`. In `'rows'` + `'skip'` mode the resolved value contains only the
663
+ * gate's surviving rows.
664
+ */
665
+ declare function DbActionRows(): ParameterDecorator;
666
+ //#endregion
556
667
  //#region src/actions/db-actions.decorator.d.ts
557
668
  /**
558
669
  * Declare class-level actions on a controller. Entries are flat dicts with
@@ -599,4 +710,65 @@ interface PkValidationSource {
599
710
  fieldDescriptors: readonly TDbFieldMeta[];
600
711
  }
601
712
  //#endregion
602
- export { AsDbController, AsDbReadableController, AsJsonValueHelpController, AsReadableController, AsValueHelpController, DbAction, DbActionDefault, type DbActionOpts, DbActionPK, DbActionPKs, DbActions, DbRowActions, DbRowsActions, DbTableActions, type PkValidationSource, READABLE_DEF, ReadableController, ReadableGates, TABLE_DEF, type TDbActionInfo, type TDbActionIntent, type TDbActionLevel, type TDbActionProcessor, type TDbActionsEntry, type TDbActionsEntryUnpinned, TableController, UseValidationErrorTransform, ValueHelpQuery, ViewController, discoverActions, validationErrorTransform };
713
+ //#region src/actions/pk-cache.d.ts
714
+ declare const useDbActionPk: _wooksjs_event_core0.WookComposable<{
715
+ load: () => Promise<unknown>;
716
+ }>;
717
+ declare const useDbActionPks: _wooksjs_event_core0.WookComposable<{
718
+ load: () => Promise<unknown[]>;
719
+ }>;
720
+ //#endregion
721
+ //#region src/actions/row-cache.d.ts
722
+ declare const useDbActionRow: _wooksjs_event_core0.WookComposable<{
723
+ load: () => Promise<unknown>;
724
+ }>;
725
+ declare const useDbActionRows: _wooksjs_event_core0.WookComposable<{
726
+ load: () => Promise<unknown[]>;
727
+ }>;
728
+ //#endregion
729
+ //#region src/actions/action-disabled-error.d.ts
730
+ /**
731
+ * Wire-body shape for server-side gate rejections. The `name` discriminator
732
+ * lets `@atscript/db-client` recognise the response and construct the typed
733
+ * `ActionDisabledError` subclass. The shape extends Moost's standard
734
+ * `ServerError` envelope (`{ message, statusCode, errors? }`) with three
735
+ * additional fields:
736
+ *
737
+ * - `name: 'ActionDisabledError'` — discriminator the client matches.
738
+ * - `action` — the `@DbAction` name that rejected the request.
739
+ * - `pk?` — present only for `'row'`-level rejections.
740
+ * - `pks?` — present only for `'rows'`-level rejections.
741
+ *
742
+ * `message` is populated with a human-readable string so generic
743
+ * `ClientError` consumers (which read `body.message`) still get something
744
+ * useful without typed-catch dispatch.
745
+ */
746
+ interface ActionDisabledErrorBody {
747
+ name: "ActionDisabledError";
748
+ message: string;
749
+ statusCode: 409;
750
+ action: string;
751
+ pk?: unknown;
752
+ pks?: unknown[];
753
+ }
754
+ /**
755
+ * Thrown by the gate interceptor when `disabled` returns truthy. Composes
756
+ * with Moost's existing error mapper to produce HTTP 409 with the wire body
757
+ * defined by {@link ActionDisabledErrorBody}.
758
+ *
759
+ * - `'row'`-level rejection: pass `(action, pk)` — the body emits `pk`.
760
+ * - `'rows'`-level rejection: pass `(action, undefined, pks)` — the body
761
+ * emits `pks` (the FULL list of failing PKs in reject mode; the FULL list
762
+ * of request PKs in skip mode with zero survivors).
763
+ */
764
+ declare class ActionDisabledError extends HttpError<ActionDisabledErrorBody> {
765
+ name: string;
766
+ constructor(action: string, pk?: unknown, pks?: unknown[]);
767
+ }
768
+ //#endregion
769
+ //#region src/permissions/crud-controls.d.ts
770
+ declare const QUERY_CONTROLS: readonly string[];
771
+ declare const PAGES_CONTROLS: readonly string[];
772
+ declare const ONE_CONTROLS: readonly string[];
773
+ //#endregion
774
+ export { ActionDisabledError, type ActionDisabledErrorBody, AsDbController, AsDbReadableController, AsJsonValueHelpController, AsReadableController, AsValueHelpController, DbAction, DbActionDefault, type DbActionOpts, DbActionPK, DbActionPKs, DbActionRow, DbActionRows, DbActions, DbRowActions, DbRowsActions, DbTableActions, ONE_CONTROLS, PAGES_CONTROLS, type PkValidationSource, QUERY_CONTROLS, READABLE_DEF, ReadableController, ReadableGates, TABLE_DEF, type TCrudOp, type TCrudPermissions, type TDbActionInfo, type TDbActionIntent, type TDbActionLevel, type TDbActionProcessor, type TDbActionsEntry, type TDbActionsEntryUnpinned, TableController, UseValidationErrorTransform, ValueHelpQuery, ViewController, discoverActions, useDbActionPk, useDbActionPks, useDbActionRow, useDbActionRows, validationErrorTransform };