@atscript/moost-db 0.1.56 → 0.1.57

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 CHANGED
@@ -473,13 +473,6 @@ let AsReadableController = class AsReadableController {
473
473
  }
474
474
  };
475
475
  }
476
- /**
477
- * Whether this controller is read-only (no write endpoints).
478
- * Returns `true` by default; {@link AsDbController} overrides to `false`.
479
- */
480
- _isReadOnly() {
481
- return true;
482
- }
483
476
  _queryControlsValidator;
484
477
  _pagesControlsValidator;
485
478
  _getOneControlsValidator;
@@ -539,37 +532,31 @@ let AsReadableController = class AsReadableController {
539
532
  return item;
540
533
  }
541
534
  /**
542
- * **GET /meta** — returns the bound interface's metadata envelope.
543
- *
544
- * Base implementation delegates to {@link buildMetaResponse}, which subclasses
545
- * override to add source-specific fields (relations, searchable flags, etc.).
546
- * The response is cached on the instance; async overrides must cache any
547
- * extra enrichment themselves.
535
+ * **GET /meta** — returns the bound interface's metadata envelope. The
536
+ * static envelope is cached; {@link applyMetaOverlay} runs per request so
537
+ * subclasses can prune the response by principal.
548
538
  */
549
539
  async meta() {
550
- if (this._metaResponse) return this._metaResponse;
551
- const response = await this.buildMetaResponse();
552
- this._metaResponse = response;
553
- return response;
540
+ if (!this._metaResponse) this._metaResponse = this.buildMetaResponse();
541
+ return this.applyMetaOverlay(this._metaResponse);
554
542
  }
555
543
  /**
556
544
  * Builds the `/meta` payload. Override in subclasses to populate source-specific
557
- * fields. Defaults return a minimal envelope with the serialized type and the
558
- * read-only flag; value-help dicts populate their capability hints here.
559
- * Subclasses that fully replace the envelope must call {@link buildActions}
560
- * directly so `@DbAction*` decorators still surface.
545
+ * fields. Subclasses that fully replace the envelope must call
546
+ * {@link buildActions} and {@link buildCrud} directly so `@DbAction*`
547
+ * decorators and CRUD permissions still surface.
561
548
  */
562
- async buildMetaResponse() {
549
+ buildMetaResponse() {
563
550
  return {
564
551
  searchable: false,
565
552
  vectorSearchable: false,
566
553
  searchIndexes: [],
567
554
  primaryKeys: [],
568
- readOnly: this._isReadOnly(),
569
555
  relations: [],
570
556
  fields: {},
571
557
  type: this.getSerializedType(),
572
- actions: this.buildActions()
558
+ actions: this.buildActions(),
559
+ crud: this.buildCrud()
573
560
  };
574
561
  }
575
562
  /**
@@ -580,6 +567,25 @@ let AsReadableController = class AsReadableController {
580
567
  buildActions() {
581
568
  return discoverActions(this.constructor, this.app, this.logger);
582
569
  }
570
+ /**
571
+ * Declares the built-in CRUD operations this controller exposes. Subclasses
572
+ * override to add their keys; the bare base only exposes `/meta`. See
573
+ * `docs/http/permissions.md` for the wire shape and overlay rules.
574
+ */
575
+ buildCrud() {
576
+ return {};
577
+ }
578
+ /**
579
+ * Per-request overlay applied to the cached `/meta` envelope. Default no-op.
580
+ * Subclasses may shallow-clone and prune `crud` keys, `crud[op]` arrays, or
581
+ * `actions[]` based on the current request principal (read via Moost
582
+ * composables). The cached envelope MUST NOT be mutated — see
583
+ * `docs/http/permissions.md` for the full contract, including the
584
+ * "discoverability only" caveat.
585
+ */
586
+ applyMetaOverlay(meta) {
587
+ return meta;
588
+ }
583
589
  };
584
590
  __decorate([
585
591
  (0, _moostjs_event_http.Get)("meta"),
@@ -658,6 +664,23 @@ const ReadableController = (readable, prefix) => {
658
664
  */
659
665
  const ViewController = ReadableController;
660
666
  //#endregion
667
+ //#region src/permissions/crud-controls.ts
668
+ /**
669
+ * Static control whitelists per read op. Each list is the matching DTO's
670
+ * `$`-properties (stripped) plus the URL-grammar extras (`filter`,
671
+ * `insights`, `groupBy`) that bypass the DTO. The DTO is the single source of
672
+ * truth — adding a `$control` there auto-extends the whitelist here.
673
+ */
674
+ const dtoControls = (Dto) => [...Dto.type.props.keys()].map((k) => k.startsWith("$") ? k.slice(1) : k);
675
+ const QUERY_CONTROLS = [
676
+ "filter",
677
+ "insights",
678
+ ...dtoControls(QueryControlsDto),
679
+ "groupBy"
680
+ ];
681
+ const PAGES_CONTROLS = ["filter", ...dtoControls(PagesControlsDto)];
682
+ const ONE_CONTROLS = dtoControls(GetOneControlsDto);
683
+ //#endregion
661
684
  //#region \0@oxc-project+runtime@0.120.0/helpers/decorateParam.js
662
685
  function __decorateParam(paramIndex, decorator) {
663
686
  return function(target, key) {
@@ -907,7 +930,7 @@ let AsDbReadableController = class AsDbReadableController extends AsReadableCont
907
930
  * vector-searchable flags, field-descriptor-derived filter/sort hints, and
908
931
  * the configured primary keys.
909
932
  */
910
- async buildMetaResponse() {
933
+ buildMetaResponse() {
911
934
  const relations = [];
912
935
  for (const [name, rel] of this.readable.relations) relations.push({
913
936
  name,
@@ -932,11 +955,19 @@ let AsDbReadableController = class AsDbReadableController extends AsReadableCont
932
955
  vectorSearchable: this.readable.isVectorSearchable(),
933
956
  searchIndexes: this.readable.getSearchIndexes(),
934
957
  primaryKeys: [...this.readable.primaryKeys],
935
- readOnly: this._isReadOnly(),
936
958
  relations,
937
959
  fields,
938
960
  type: this.getSerializedType(),
939
- actions: this.buildActions()
961
+ actions: this.buildActions(),
962
+ crud: this.buildCrud()
963
+ };
964
+ }
965
+ buildCrud() {
966
+ return {
967
+ ...super.buildCrud(),
968
+ query: [...QUERY_CONTROLS],
969
+ pages: [...PAGES_CONTROLS],
970
+ one: [...ONE_CONTROLS]
940
971
  };
941
972
  }
942
973
  };
@@ -986,8 +1017,14 @@ let AsDbController = class AsDbController extends AsDbReadableController {
986
1017
  constructor(table, app) {
987
1018
  super(table, app);
988
1019
  }
989
- _isReadOnly() {
990
- return false;
1020
+ buildCrud() {
1021
+ return {
1022
+ ...super.buildCrud(),
1023
+ insert: [],
1024
+ update: [],
1025
+ replace: [],
1026
+ remove: []
1027
+ };
991
1028
  }
992
1029
  /**
993
1030
  * Intercepts write operations. Return `undefined` to abort.
@@ -1204,7 +1241,7 @@ let AsValueHelpController = class AsValueHelpController extends AsReadableContro
1204
1241
  * client picker UI (which controls to render); the server does not enforce
1205
1242
  * these flags at request time.
1206
1243
  */
1207
- async buildMetaResponse() {
1244
+ buildMetaResponse() {
1208
1245
  const fields = {};
1209
1246
  for (const [path, meta] of this.fieldMeta) fields[path] = {
1210
1247
  sortable: meta.has("ui.dict.sortable"),
@@ -1215,16 +1252,24 @@ let AsValueHelpController = class AsValueHelpController extends AsReadableContro
1215
1252
  vectorSearchable: false,
1216
1253
  searchIndexes: [],
1217
1254
  primaryKeys: this.primaryKey ? [this.primaryKey] : [],
1218
- readOnly: this._isReadOnly(),
1219
1255
  relations: [],
1220
1256
  fields,
1221
1257
  type: this.getSerializedType(),
1222
- actions: []
1258
+ actions: [],
1259
+ crud: this.buildCrud()
1223
1260
  };
1224
1261
  }
1225
1262
  buildActions() {
1226
1263
  return [];
1227
1264
  }
1265
+ buildCrud() {
1266
+ return {
1267
+ ...super.buildCrud(),
1268
+ query: [...QUERY_CONTROLS],
1269
+ pages: [...PAGES_CONTROLS],
1270
+ one: [...ONE_CONTROLS]
1271
+ };
1272
+ }
1228
1273
  };
1229
1274
  __decorate([
1230
1275
  (0, _moostjs_event_http.Get)("query"),
@@ -1705,6 +1750,9 @@ exports.DbActions = DbActions;
1705
1750
  exports.DbRowActions = DbRowActions;
1706
1751
  exports.DbRowsActions = DbRowsActions;
1707
1752
  exports.DbTableActions = DbTableActions;
1753
+ exports.ONE_CONTROLS = ONE_CONTROLS;
1754
+ exports.PAGES_CONTROLS = PAGES_CONTROLS;
1755
+ exports.QUERY_CONTROLS = QUERY_CONTROLS;
1708
1756
  exports.READABLE_DEF = READABLE_DEF;
1709
1757
  exports.ReadableController = ReadableController;
1710
1758
  exports.TABLE_DEF = TABLE_DEF;
package/dist/index.d.cts CHANGED
@@ -1,7 +1,7 @@
1
1
  import * as _atscript_typescript_utils0 from "@atscript/typescript/utils";
2
2
  import { TAtscriptAnnotatedType, TAtscriptDataType, TSerializeOptions, Validator } from "@atscript/typescript/utils";
3
3
  import * as _uniqu_url0 from "@uniqu/url";
4
- 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";
4
+ 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";
5
5
  import { HttpError } from "@moostjs/event-http";
6
6
  import * as moost from "moost";
7
7
  import { Moost, TConsoleBase } from "moost";
@@ -88,11 +88,6 @@ declare abstract class AsReadableController<T extends TAtscriptAnnotatedType = T
88
88
  * to customise.
89
89
  */
90
90
  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
91
  private _queryControlsValidator?;
97
92
  private _pagesControlsValidator?;
98
93
  private _getOneControlsValidator?;
@@ -111,28 +106,39 @@ declare abstract class AsReadableController<T extends TAtscriptAnnotatedType = T
111
106
  protected parseQueryString(url: string): _uniqu_url0.UrlQuery;
112
107
  protected returnOne(result: Promise<DataType | null>): Promise<DataType | HttpError>;
113
108
  /**
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.
109
+ * **GET /meta** — returns the bound interface's metadata envelope. The
110
+ * static envelope is cached; {@link applyMetaOverlay} runs per request so
111
+ * subclasses can prune the response by principal.
120
112
  */
121
113
  meta(): Promise<TMetaResponse>;
122
114
  /**
123
115
  * 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.
116
+ * fields. Subclasses that fully replace the envelope must call
117
+ * {@link buildActions} and {@link buildCrud} directly so `@DbAction*`
118
+ * decorators and CRUD permissions still surface.
128
119
  */
129
- protected buildMetaResponse(): Promise<TMetaResponse>;
120
+ protected buildMetaResponse(): TMetaResponse;
130
121
  /**
131
122
  * Discovers `@DbAction*` and `@DbActions`-style class metadata on this
132
123
  * controller and produces the `actions` array. Returns `[]` for value-help
133
124
  * controllers — see {@link AsValueHelpController#buildMetaResponse}.
134
125
  */
135
126
  protected buildActions(): TDbActionInfo$1[];
127
+ /**
128
+ * Declares the built-in CRUD operations this controller exposes. Subclasses
129
+ * override to add their keys; the bare base only exposes `/meta`. See
130
+ * `docs/http/permissions.md` for the wire shape and overlay rules.
131
+ */
132
+ protected buildCrud(): TCrudPermissions$1;
133
+ /**
134
+ * Per-request overlay applied to the cached `/meta` envelope. Default no-op.
135
+ * Subclasses may shallow-clone and prune `crud` keys, `crud[op]` arrays, or
136
+ * `actions[]` based on the current request principal (read via Moost
137
+ * composables). The cached envelope MUST NOT be mutated — see
138
+ * `docs/http/permissions.md` for the full contract, including the
139
+ * "discoverability only" caveat.
140
+ */
141
+ protected applyMetaOverlay(meta: TMetaResponse): TMetaResponse | Promise<TMetaResponse>;
136
142
  }
137
143
  //#endregion
138
144
  //#region src/as-db-readable.controller.d.ts
@@ -204,7 +210,8 @@ declare class AsDbReadableController<T extends TAtscriptAnnotatedType = TAtscrip
204
210
  * vector-searchable flags, field-descriptor-derived filter/sort hints, and
205
211
  * the configured primary keys.
206
212
  */
207
- protected buildMetaResponse(): Promise<TMetaResponse>;
213
+ protected buildMetaResponse(): TMetaResponse;
214
+ protected buildCrud(): TCrudPermissions$1;
208
215
  }
209
216
  //#endregion
210
217
  //#region src/as-db.controller.d.ts
@@ -222,7 +229,7 @@ declare class AsDbController<T extends TAtscriptAnnotatedType = TAtscriptAnnotat
222
229
  /** Reference to the underlying table (typed for write access). */
223
230
  protected get table(): AtscriptDbTable<T>;
224
231
  constructor(table: AtscriptDbTable<T>, app: Moost);
225
- protected _isReadOnly(): boolean;
232
+ protected buildCrud(): TCrudPermissions$1;
226
233
  /**
227
234
  * Intercepts write operations. Return `undefined` to abort.
228
235
  * May be async (e.g. to enrich payloads from session / permissions).
@@ -344,8 +351,9 @@ declare abstract class AsValueHelpController<T extends TAtscriptAnnotatedType =
344
351
  * client picker UI (which controls to render); the server does not enforce
345
352
  * these flags at request time.
346
353
  */
347
- protected buildMetaResponse(): Promise<TMetaResponse>;
354
+ protected buildMetaResponse(): TMetaResponse;
348
355
  protected buildActions(): never[];
356
+ protected buildCrud(): TCrudPermissions$1;
349
357
  }
350
358
  //#endregion
351
359
  //#region src/as-json-value-help.controller.d.ts
@@ -599,4 +607,9 @@ interface PkValidationSource {
599
607
  fieldDescriptors: readonly TDbFieldMeta[];
600
608
  }
601
609
  //#endregion
602
- export { AsDbController, AsDbReadableController, AsJsonValueHelpController, AsReadableController, AsValueHelpController, DbAction, DbActionDefault, DbActionOpts, DbActionPK, DbActionPKs, DbActions, DbRowActions, DbRowsActions, DbTableActions, PkValidationSource, READABLE_DEF, ReadableController, ReadableGates, TABLE_DEF, type TDbActionInfo, type TDbActionIntent, type TDbActionLevel, type TDbActionProcessor, TDbActionsEntry, TDbActionsEntryUnpinned, TableController, UseValidationErrorTransform, ValueHelpQuery, ViewController, discoverActions, validationErrorTransform };
610
+ //#region src/permissions/crud-controls.d.ts
611
+ declare const QUERY_CONTROLS: readonly string[];
612
+ declare const PAGES_CONTROLS: readonly string[];
613
+ declare const ONE_CONTROLS: readonly string[];
614
+ //#endregion
615
+ export { AsDbController, AsDbReadableController, AsJsonValueHelpController, AsReadableController, AsValueHelpController, DbAction, DbActionDefault, DbActionOpts, DbActionPK, DbActionPKs, DbActions, DbRowActions, DbRowsActions, DbTableActions, ONE_CONTROLS, PAGES_CONTROLS, PkValidationSource, QUERY_CONTROLS, READABLE_DEF, ReadableController, ReadableGates, TABLE_DEF, type TCrudOp, type TCrudPermissions, type TDbActionInfo, type TDbActionIntent, type TDbActionLevel, type TDbActionProcessor, TDbActionsEntry, TDbActionsEntryUnpinned, TableController, UseValidationErrorTransform, ValueHelpQuery, ViewController, discoverActions, validationErrorTransform };
package/dist/index.d.mts CHANGED
@@ -4,7 +4,7 @@ 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
8
 
9
9
  //#region src/as-readable.controller.d.ts
10
10
  /**
@@ -88,11 +88,6 @@ declare abstract class AsReadableController<T extends TAtscriptAnnotatedType = T
88
88
  * to customise.
89
89
  */
90
90
  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
91
  private _queryControlsValidator?;
97
92
  private _pagesControlsValidator?;
98
93
  private _getOneControlsValidator?;
@@ -111,28 +106,39 @@ declare abstract class AsReadableController<T extends TAtscriptAnnotatedType = T
111
106
  protected parseQueryString(url: string): _uniqu_url0.UrlQuery;
112
107
  protected returnOne(result: Promise<DataType | null>): Promise<DataType | HttpError>;
113
108
  /**
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.
109
+ * **GET /meta** — returns the bound interface's metadata envelope. The
110
+ * static envelope is cached; {@link applyMetaOverlay} runs per request so
111
+ * subclasses can prune the response by principal.
120
112
  */
121
113
  meta(): Promise<TMetaResponse>;
122
114
  /**
123
115
  * 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.
116
+ * fields. Subclasses that fully replace the envelope must call
117
+ * {@link buildActions} and {@link buildCrud} directly so `@DbAction*`
118
+ * decorators and CRUD permissions still surface.
128
119
  */
129
- protected buildMetaResponse(): Promise<TMetaResponse>;
120
+ protected buildMetaResponse(): TMetaResponse;
130
121
  /**
131
122
  * Discovers `@DbAction*` and `@DbActions`-style class metadata on this
132
123
  * controller and produces the `actions` array. Returns `[]` for value-help
133
124
  * controllers — see {@link AsValueHelpController#buildMetaResponse}.
134
125
  */
135
126
  protected buildActions(): TDbActionInfo$1[];
127
+ /**
128
+ * Declares the built-in CRUD operations this controller exposes. Subclasses
129
+ * override to add their keys; the bare base only exposes `/meta`. See
130
+ * `docs/http/permissions.md` for the wire shape and overlay rules.
131
+ */
132
+ protected buildCrud(): TCrudPermissions$1;
133
+ /**
134
+ * Per-request overlay applied to the cached `/meta` envelope. Default no-op.
135
+ * Subclasses may shallow-clone and prune `crud` keys, `crud[op]` arrays, or
136
+ * `actions[]` based on the current request principal (read via Moost
137
+ * composables). The cached envelope MUST NOT be mutated — see
138
+ * `docs/http/permissions.md` for the full contract, including the
139
+ * "discoverability only" caveat.
140
+ */
141
+ protected applyMetaOverlay(meta: TMetaResponse): TMetaResponse | Promise<TMetaResponse>;
136
142
  }
137
143
  //#endregion
138
144
  //#region src/as-db-readable.controller.d.ts
@@ -204,7 +210,8 @@ declare class AsDbReadableController<T extends TAtscriptAnnotatedType = TAtscrip
204
210
  * vector-searchable flags, field-descriptor-derived filter/sort hints, and
205
211
  * the configured primary keys.
206
212
  */
207
- protected buildMetaResponse(): Promise<TMetaResponse>;
213
+ protected buildMetaResponse(): TMetaResponse;
214
+ protected buildCrud(): TCrudPermissions$1;
208
215
  }
209
216
  //#endregion
210
217
  //#region src/as-db.controller.d.ts
@@ -222,7 +229,7 @@ declare class AsDbController<T extends TAtscriptAnnotatedType = TAtscriptAnnotat
222
229
  /** Reference to the underlying table (typed for write access). */
223
230
  protected get table(): AtscriptDbTable<T>;
224
231
  constructor(table: AtscriptDbTable<T>, app: Moost);
225
- protected _isReadOnly(): boolean;
232
+ protected buildCrud(): TCrudPermissions$1;
226
233
  /**
227
234
  * Intercepts write operations. Return `undefined` to abort.
228
235
  * May be async (e.g. to enrich payloads from session / permissions).
@@ -344,8 +351,9 @@ declare abstract class AsValueHelpController<T extends TAtscriptAnnotatedType =
344
351
  * client picker UI (which controls to render); the server does not enforce
345
352
  * these flags at request time.
346
353
  */
347
- protected buildMetaResponse(): Promise<TMetaResponse>;
354
+ protected buildMetaResponse(): TMetaResponse;
348
355
  protected buildActions(): never[];
356
+ protected buildCrud(): TCrudPermissions$1;
349
357
  }
350
358
  //#endregion
351
359
  //#region src/as-json-value-help.controller.d.ts
@@ -599,4 +607,9 @@ interface PkValidationSource {
599
607
  fieldDescriptors: readonly TDbFieldMeta[];
600
608
  }
601
609
  //#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 };
610
+ //#region src/permissions/crud-controls.d.ts
611
+ declare const QUERY_CONTROLS: readonly string[];
612
+ declare const PAGES_CONTROLS: readonly string[];
613
+ declare const ONE_CONTROLS: readonly string[];
614
+ //#endregion
615
+ export { AsDbController, AsDbReadableController, AsJsonValueHelpController, AsReadableController, AsValueHelpController, DbAction, DbActionDefault, type DbActionOpts, DbActionPK, DbActionPKs, 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, validationErrorTransform };
package/dist/index.mjs CHANGED
@@ -472,13 +472,6 @@ let AsReadableController = class AsReadableController {
472
472
  }
473
473
  };
474
474
  }
475
- /**
476
- * Whether this controller is read-only (no write endpoints).
477
- * Returns `true` by default; {@link AsDbController} overrides to `false`.
478
- */
479
- _isReadOnly() {
480
- return true;
481
- }
482
475
  _queryControlsValidator;
483
476
  _pagesControlsValidator;
484
477
  _getOneControlsValidator;
@@ -538,37 +531,31 @@ let AsReadableController = class AsReadableController {
538
531
  return item;
539
532
  }
540
533
  /**
541
- * **GET /meta** — returns the bound interface's metadata envelope.
542
- *
543
- * Base implementation delegates to {@link buildMetaResponse}, which subclasses
544
- * override to add source-specific fields (relations, searchable flags, etc.).
545
- * The response is cached on the instance; async overrides must cache any
546
- * extra enrichment themselves.
534
+ * **GET /meta** — returns the bound interface's metadata envelope. The
535
+ * static envelope is cached; {@link applyMetaOverlay} runs per request so
536
+ * subclasses can prune the response by principal.
547
537
  */
548
538
  async meta() {
549
- if (this._metaResponse) return this._metaResponse;
550
- const response = await this.buildMetaResponse();
551
- this._metaResponse = response;
552
- return response;
539
+ if (!this._metaResponse) this._metaResponse = this.buildMetaResponse();
540
+ return this.applyMetaOverlay(this._metaResponse);
553
541
  }
554
542
  /**
555
543
  * Builds the `/meta` payload. Override in subclasses to populate source-specific
556
- * fields. Defaults return a minimal envelope with the serialized type and the
557
- * read-only flag; value-help dicts populate their capability hints here.
558
- * Subclasses that fully replace the envelope must call {@link buildActions}
559
- * directly so `@DbAction*` decorators still surface.
544
+ * fields. Subclasses that fully replace the envelope must call
545
+ * {@link buildActions} and {@link buildCrud} directly so `@DbAction*`
546
+ * decorators and CRUD permissions still surface.
560
547
  */
561
- async buildMetaResponse() {
548
+ buildMetaResponse() {
562
549
  return {
563
550
  searchable: false,
564
551
  vectorSearchable: false,
565
552
  searchIndexes: [],
566
553
  primaryKeys: [],
567
- readOnly: this._isReadOnly(),
568
554
  relations: [],
569
555
  fields: {},
570
556
  type: this.getSerializedType(),
571
- actions: this.buildActions()
557
+ actions: this.buildActions(),
558
+ crud: this.buildCrud()
572
559
  };
573
560
  }
574
561
  /**
@@ -579,6 +566,25 @@ let AsReadableController = class AsReadableController {
579
566
  buildActions() {
580
567
  return discoverActions(this.constructor, this.app, this.logger);
581
568
  }
569
+ /**
570
+ * Declares the built-in CRUD operations this controller exposes. Subclasses
571
+ * override to add their keys; the bare base only exposes `/meta`. See
572
+ * `docs/http/permissions.md` for the wire shape and overlay rules.
573
+ */
574
+ buildCrud() {
575
+ return {};
576
+ }
577
+ /**
578
+ * Per-request overlay applied to the cached `/meta` envelope. Default no-op.
579
+ * Subclasses may shallow-clone and prune `crud` keys, `crud[op]` arrays, or
580
+ * `actions[]` based on the current request principal (read via Moost
581
+ * composables). The cached envelope MUST NOT be mutated — see
582
+ * `docs/http/permissions.md` for the full contract, including the
583
+ * "discoverability only" caveat.
584
+ */
585
+ applyMetaOverlay(meta) {
586
+ return meta;
587
+ }
582
588
  };
583
589
  __decorate([
584
590
  Get("meta"),
@@ -657,6 +663,23 @@ const ReadableController = (readable, prefix) => {
657
663
  */
658
664
  const ViewController = ReadableController;
659
665
  //#endregion
666
+ //#region src/permissions/crud-controls.ts
667
+ /**
668
+ * Static control whitelists per read op. Each list is the matching DTO's
669
+ * `$`-properties (stripped) plus the URL-grammar extras (`filter`,
670
+ * `insights`, `groupBy`) that bypass the DTO. The DTO is the single source of
671
+ * truth — adding a `$control` there auto-extends the whitelist here.
672
+ */
673
+ const dtoControls = (Dto) => [...Dto.type.props.keys()].map((k) => k.startsWith("$") ? k.slice(1) : k);
674
+ const QUERY_CONTROLS = [
675
+ "filter",
676
+ "insights",
677
+ ...dtoControls(QueryControlsDto),
678
+ "groupBy"
679
+ ];
680
+ const PAGES_CONTROLS = ["filter", ...dtoControls(PagesControlsDto)];
681
+ const ONE_CONTROLS = dtoControls(GetOneControlsDto);
682
+ //#endregion
660
683
  //#region \0@oxc-project+runtime@0.120.0/helpers/decorateParam.js
661
684
  function __decorateParam(paramIndex, decorator) {
662
685
  return function(target, key) {
@@ -906,7 +929,7 @@ let AsDbReadableController = class AsDbReadableController extends AsReadableCont
906
929
  * vector-searchable flags, field-descriptor-derived filter/sort hints, and
907
930
  * the configured primary keys.
908
931
  */
909
- async buildMetaResponse() {
932
+ buildMetaResponse() {
910
933
  const relations = [];
911
934
  for (const [name, rel] of this.readable.relations) relations.push({
912
935
  name,
@@ -931,11 +954,19 @@ let AsDbReadableController = class AsDbReadableController extends AsReadableCont
931
954
  vectorSearchable: this.readable.isVectorSearchable(),
932
955
  searchIndexes: this.readable.getSearchIndexes(),
933
956
  primaryKeys: [...this.readable.primaryKeys],
934
- readOnly: this._isReadOnly(),
935
957
  relations,
936
958
  fields,
937
959
  type: this.getSerializedType(),
938
- actions: this.buildActions()
960
+ actions: this.buildActions(),
961
+ crud: this.buildCrud()
962
+ };
963
+ }
964
+ buildCrud() {
965
+ return {
966
+ ...super.buildCrud(),
967
+ query: [...QUERY_CONTROLS],
968
+ pages: [...PAGES_CONTROLS],
969
+ one: [...ONE_CONTROLS]
939
970
  };
940
971
  }
941
972
  };
@@ -985,8 +1016,14 @@ let AsDbController = class AsDbController extends AsDbReadableController {
985
1016
  constructor(table, app) {
986
1017
  super(table, app);
987
1018
  }
988
- _isReadOnly() {
989
- return false;
1019
+ buildCrud() {
1020
+ return {
1021
+ ...super.buildCrud(),
1022
+ insert: [],
1023
+ update: [],
1024
+ replace: [],
1025
+ remove: []
1026
+ };
990
1027
  }
991
1028
  /**
992
1029
  * Intercepts write operations. Return `undefined` to abort.
@@ -1203,7 +1240,7 @@ let AsValueHelpController = class AsValueHelpController extends AsReadableContro
1203
1240
  * client picker UI (which controls to render); the server does not enforce
1204
1241
  * these flags at request time.
1205
1242
  */
1206
- async buildMetaResponse() {
1243
+ buildMetaResponse() {
1207
1244
  const fields = {};
1208
1245
  for (const [path, meta] of this.fieldMeta) fields[path] = {
1209
1246
  sortable: meta.has("ui.dict.sortable"),
@@ -1214,16 +1251,24 @@ let AsValueHelpController = class AsValueHelpController extends AsReadableContro
1214
1251
  vectorSearchable: false,
1215
1252
  searchIndexes: [],
1216
1253
  primaryKeys: this.primaryKey ? [this.primaryKey] : [],
1217
- readOnly: this._isReadOnly(),
1218
1254
  relations: [],
1219
1255
  fields,
1220
1256
  type: this.getSerializedType(),
1221
- actions: []
1257
+ actions: [],
1258
+ crud: this.buildCrud()
1222
1259
  };
1223
1260
  }
1224
1261
  buildActions() {
1225
1262
  return [];
1226
1263
  }
1264
+ buildCrud() {
1265
+ return {
1266
+ ...super.buildCrud(),
1267
+ query: [...QUERY_CONTROLS],
1268
+ pages: [...PAGES_CONTROLS],
1269
+ one: [...ONE_CONTROLS]
1270
+ };
1271
+ }
1227
1272
  };
1228
1273
  __decorate([
1229
1274
  Get("query"),
@@ -1666,4 +1711,4 @@ function classLevelActions(dict, forcedLevel) {
1666
1711
  });
1667
1712
  }
1668
1713
  //#endregion
1669
- export { AsDbController, AsDbReadableController, AsJsonValueHelpController, AsReadableController, AsValueHelpController, DbAction, DbActionDefault, DbActionPK, DbActionPKs, DbActions, DbRowActions, DbRowsActions, DbTableActions, READABLE_DEF, ReadableController, TABLE_DEF, TableController, UseValidationErrorTransform, ViewController, discoverActions, validationErrorTransform };
1714
+ export { AsDbController, AsDbReadableController, AsJsonValueHelpController, AsReadableController, AsValueHelpController, DbAction, DbActionDefault, DbActionPK, DbActionPKs, DbActions, DbRowActions, DbRowsActions, DbTableActions, ONE_CONTROLS, PAGES_CONTROLS, QUERY_CONTROLS, READABLE_DEF, ReadableController, TABLE_DEF, TableController, UseValidationErrorTransform, ViewController, discoverActions, validationErrorTransform };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atscript/moost-db",
3
- "version": "0.1.56",
3
+ "version": "0.1.57",
4
4
  "description": "Generic database controller for Moost with Atscript.",
5
5
  "keywords": [
6
6
  "annotations",
@@ -55,7 +55,7 @@
55
55
  "@uniqu/core": "^0.1.5",
56
56
  "@wooksjs/http-body": "^0.7.10",
57
57
  "moost": "^0.6.8",
58
- "@atscript/db": "^0.1.56"
58
+ "@atscript/db": "^0.1.57"
59
59
  },
60
60
  "scripts": {
61
61
  "postinstall": "asc -f dts",