@atscript/moost-db 0.1.55 → 0.1.56

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -22,7 +22,9 @@ Generic database controller for the [Moost](https://moost.org) framework. Expose
22
22
  pnpm add @atscript/moost-db
23
23
  ```
24
24
 
25
- Peer dependencies: `moost`, `@moostjs/event-http`, `@atscript/db`, `@atscript/typescript`.
25
+ Peer dependencies: `moost`, `@moostjs/event-http`, `@wooksjs/http-body`, `@atscript/db`, `@atscript/typescript`.
26
+
27
+ `@wooksjs/http-body` is required by the `@DbActionPK` / `@DbActionPKs` parameter resolvers (used by the action layer) to read the parsed JSON request body.
26
28
 
27
29
  ## Quick Start
28
30
 
package/dist/index.cjs CHANGED
@@ -4,6 +4,7 @@ let _moostjs_event_http = require("@moostjs/event-http");
4
4
  let moost = require("moost");
5
5
  let _uniqu_url = require("@uniqu/url");
6
6
  let _atscript_db = require("@atscript/db");
7
+ let _wooksjs_http_body = require("@wooksjs/http-body");
7
8
  //#region src/validation-interceptor.ts
8
9
  const dbErrorCodeToStatus = { CONFLICT: 409 };
9
10
  function transformValidationError(error, reply) {
@@ -180,6 +181,196 @@ function findSortOffender(sort, isAllowed) {
180
181
  }
181
182
  }
182
183
  //#endregion
184
+ //#region src/actions/keys.ts
185
+ /** Method-level metadata key — written by `@DbAction(name, opts)`. */
186
+ const MOOST_DB_ACTION = "atscript_db_action";
187
+ /** Class-level metadata key — written by `@DbActions` and the level-pinned shortcuts. Stored as an array; decorators accumulate. */
188
+ const MOOST_DB_ACTIONS = "atscript_db_actions";
189
+ /** Param-level metadata key — written by `@DbActionPK()` / `@DbActionPKs()`. Drives level inference. */
190
+ const MOOST_DB_ACTION_PARAM = "atscript_db_action_param";
191
+ /**
192
+ * Shared method-decorator update used by `@DbAction` and `@DbActionDefault`:
193
+ * read the existing `MOOST_DB_ACTION` slot, merge the patch (later-applied
194
+ * fields win), and write it back. `name` is empty until `@DbAction` provides
195
+ * one — `discoverActions` warns and drops actions with no name.
196
+ */
197
+ function mergeActionMeta(current, patch) {
198
+ const existing = current[MOOST_DB_ACTION];
199
+ return {
200
+ name: patch.name ?? existing?.name ?? "",
201
+ opts: {
202
+ ...existing?.opts,
203
+ ...patch.opts
204
+ }
205
+ };
206
+ }
207
+ //#endregion
208
+ //#region src/actions/discover.ts
209
+ /** Optional fields shared between method opts and class-level entries. */
210
+ const OPTIONAL_FIELDS = [
211
+ "icon",
212
+ "intent",
213
+ "description",
214
+ "order",
215
+ "default",
216
+ "promptText"
217
+ ];
218
+ const WARN_PREFIX = "[moost-db actions]";
219
+ const actionsCache = /* @__PURE__ */ new WeakMap();
220
+ /**
221
+ * Discover all actions declared on a controller and produce the `/meta` array.
222
+ * Reads class + method metadata via `getMoostMate()` and resolves bound POST
223
+ * paths through the Moost controller overview.
224
+ *
225
+ * Result is memoized per controller constructor — discovery walks every
226
+ * handler entry and reads decorator metadata, which is wasted work to repeat
227
+ * across instances.
228
+ */
229
+ function discoverActions(controllerCtor, app, logger) {
230
+ const cached = actionsCache.get(controllerCtor);
231
+ if (cached) return cached;
232
+ const overview = app.getControllersOverview?.()?.find((o) => o.type === controllerCtor);
233
+ const out = [];
234
+ collectMethodActions(controllerCtor, overview, logger, out);
235
+ collectClassActions(controllerCtor, logger, out);
236
+ applyDefaultPerLevel(out, logger);
237
+ actionsCache.set(controllerCtor, out);
238
+ return out;
239
+ }
240
+ function collectMethodActions(ctor, overview, logger, out) {
241
+ if (!overview) return;
242
+ const byMethod = /* @__PURE__ */ new Map();
243
+ for (const h of overview.handlers) {
244
+ const list = byMethod.get(h.method);
245
+ if (list) list.push(h);
246
+ else byMethod.set(h.method, [h]);
247
+ }
248
+ for (const [methodName, handlers] of byMethod) {
249
+ const methodMeta = handlers[0].meta;
250
+ const action = methodMeta[MOOST_DB_ACTION];
251
+ if (!action) continue;
252
+ if (!action.name) {
253
+ logger.warn(`${WARN_PREFIX} method "${methodName}" has @DbActionDefault() but no @DbAction(name) — dropping`);
254
+ continue;
255
+ }
256
+ const levelInfer = inferMethodLevel(methodMeta.params ?? [], action.name, logger);
257
+ if (!levelInfer) continue;
258
+ if (levelInfer.bodyConflict) {
259
+ logger.warn(`${WARN_PREFIX} action "${action.name}" cannot mix @DbActionPK*/@DbActionPKs with @Body() — dropping`);
260
+ continue;
261
+ }
262
+ const postEntry = handlers.find((h) => h.handler.type === "HTTP" && h.handler.method === "POST");
263
+ if (!postEntry) {
264
+ logger.warn(`${WARN_PREFIX} action "${action.name}" requires @Post(...); no POST handler bound to ${methodName} — dropping`);
265
+ continue;
266
+ }
267
+ const path = postEntry.registeredAs[0]?.path;
268
+ if (!path) {
269
+ logger.warn(`${WARN_PREFIX} action "${action.name}" — POST handler ${methodName} has no registered path — dropping`);
270
+ continue;
271
+ }
272
+ const label = action.opts.label ?? methodMeta.label;
273
+ if (!label) {
274
+ logger.warn(`${WARN_PREFIX} action "${action.name}" requires a label (opts.label or @Label) — dropping`);
275
+ continue;
276
+ }
277
+ const info = {
278
+ name: action.name,
279
+ label,
280
+ level: levelInfer.level,
281
+ processor: "backend",
282
+ value: path
283
+ };
284
+ copyOptionalFields(info, action.opts);
285
+ out.push(info);
286
+ }
287
+ }
288
+ function inferMethodLevel(params, actionName, logger) {
289
+ let hasPk = false;
290
+ let hasPks = false;
291
+ let hasBody = false;
292
+ for (const p of params) {
293
+ const kind = p[MOOST_DB_ACTION_PARAM];
294
+ if (kind === "pk") hasPk = true;
295
+ else if (kind === "pks") hasPks = true;
296
+ if (p.paramSource === "BODY") hasBody = true;
297
+ }
298
+ if (hasPk && hasPks) {
299
+ logger.warn(`${WARN_PREFIX} action "${actionName}" has both @DbActionPK and @DbActionPKs — dropping`);
300
+ return null;
301
+ }
302
+ const level = hasPk ? "row" : hasPks ? "rows" : "table";
303
+ return {
304
+ level,
305
+ bodyConflict: hasBody && level !== "table"
306
+ };
307
+ }
308
+ function collectClassActions(ctor, logger, out) {
309
+ const list = (0, moost.getMoostMate)().read(ctor)?.[MOOST_DB_ACTIONS];
310
+ if (!list) return;
311
+ for (const { name, entry, forcedLevel } of list) {
312
+ const built = buildClassEntry(name, entry, forcedLevel, logger);
313
+ if (built) out.push(built);
314
+ }
315
+ }
316
+ function buildClassEntry(name, entry, forcedLevel, logger) {
317
+ const level = forcedLevel ?? entry.level;
318
+ if (!level) {
319
+ logger.warn(`${WARN_PREFIX} class-level action "${name}" requires a level — dropping. Use @DbTableActions/@DbRowActions/@DbRowsActions or set "level" explicitly.`);
320
+ return null;
321
+ }
322
+ if (!entry.label) {
323
+ logger.warn(`${WARN_PREFIX} class-level action "${name}" requires a label — dropping`);
324
+ return null;
325
+ }
326
+ const processor = entry.processor;
327
+ let value;
328
+ if (processor === "navigate" || processor === "backend") {
329
+ const v = entry.value;
330
+ if (typeof v !== "string" || v === "") {
331
+ logger.warn(`${WARN_PREFIX} class-level action "${name}" with processor "${processor}" requires a non-empty "value" — dropping`);
332
+ return null;
333
+ }
334
+ value = v;
335
+ } else if (processor === "custom") {
336
+ const v = entry.value;
337
+ if (v !== void 0 && v !== null) {
338
+ logger.warn(`${WARN_PREFIX} class-level action "${name}" with processor "custom" forbids "value" (always derived from the dict key) — dropping`);
339
+ return null;
340
+ }
341
+ value = name;
342
+ } else {
343
+ logger.warn(`${WARN_PREFIX} class-level action "${name}" has unknown processor "${String(processor)}" — dropping`);
344
+ return null;
345
+ }
346
+ const info = {
347
+ name,
348
+ label: entry.label,
349
+ level,
350
+ processor,
351
+ value
352
+ };
353
+ copyOptionalFields(info, entry);
354
+ return info;
355
+ }
356
+ function applyDefaultPerLevel(actions, logger) {
357
+ const winners = /* @__PURE__ */ new Map();
358
+ for (const a of actions) {
359
+ if (!a.default) continue;
360
+ const existing = winners.get(a.level);
361
+ if (existing) {
362
+ a.default = false;
363
+ logger.warn(`${WARN_PREFIX} duplicate default action at level "${a.level}": "${existing}" wins, "${a.name}" demoted`);
364
+ } else winners.set(a.level, a.name);
365
+ }
366
+ }
367
+ function copyOptionalFields(info, source) {
368
+ for (const key of OPTIONAL_FIELDS) {
369
+ const value = source[key];
370
+ if (value !== void 0) info[key] = value;
371
+ }
372
+ }
373
+ //#endregion
183
374
  //#region \0@oxc-project+runtime@0.120.0/helpers/decorateMetadata.js
184
375
  function __decorateMetadata(k, v) {
185
376
  if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
@@ -365,6 +556,8 @@ let AsReadableController = class AsReadableController {
365
556
  * Builds the `/meta` payload. Override in subclasses to populate source-specific
366
557
  * fields. Defaults return a minimal envelope with the serialized type and the
367
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.
368
561
  */
369
562
  async buildMetaResponse() {
370
563
  return {
@@ -375,9 +568,18 @@ let AsReadableController = class AsReadableController {
375
568
  readOnly: this._isReadOnly(),
376
569
  relations: [],
377
570
  fields: {},
378
- type: this.getSerializedType()
571
+ type: this.getSerializedType(),
572
+ actions: this.buildActions()
379
573
  };
380
574
  }
575
+ /**
576
+ * Discovers `@DbAction*` and `@DbActions`-style class metadata on this
577
+ * controller and produces the `actions` array. Returns `[]` for value-help
578
+ * controllers — see {@link AsValueHelpController#buildMetaResponse}.
579
+ */
580
+ buildActions() {
581
+ return discoverActions(this.constructor, this.app, this.logger);
582
+ }
381
583
  };
382
584
  __decorate([
383
585
  (0, _moostjs_event_http.Get)("meta"),
@@ -733,7 +935,8 @@ let AsDbReadableController = class AsDbReadableController extends AsReadableCont
733
935
  readOnly: this._isReadOnly(),
734
936
  relations,
735
937
  fields,
736
- type: this.getSerializedType()
938
+ type: this.getSerializedType(),
939
+ actions: this.buildActions()
737
940
  };
738
941
  }
739
942
  };
@@ -1015,9 +1218,13 @@ let AsValueHelpController = class AsValueHelpController extends AsReadableContro
1015
1218
  readOnly: this._isReadOnly(),
1016
1219
  relations: [],
1017
1220
  fields,
1018
- type: this.getSerializedType()
1221
+ type: this.getSerializedType(),
1222
+ actions: []
1019
1223
  };
1020
1224
  }
1225
+ buildActions() {
1226
+ return [];
1227
+ }
1021
1228
  };
1022
1229
  __decorate([
1023
1230
  (0, _moostjs_event_http.Get)("query"),
@@ -1212,6 +1419,254 @@ function applySelect(rows, select) {
1212
1419
  });
1213
1420
  }
1214
1421
  //#endregion
1422
+ //#region src/actions/db-action.decorator.ts
1423
+ /**
1424
+ * Mark a controller method as a database action surfaced via `/meta`.
1425
+ *
1426
+ * Metadata-only — pair with `@Post(...)` for Moost to bind the route. The
1427
+ * meta builder reads this metadata plus the bound POST path lazily and
1428
+ * emits the action with `processor: 'backend'`. Order vs.
1429
+ * `@DbActionDefault()` does not matter — both merge into the same slot.
1430
+ *
1431
+ * @example
1432
+ * ```ts
1433
+ * @Post('actions/block')
1434
+ * @DbAction('block', { label: 'Block', icon: 'i-as-block', intent: 'negative' })
1435
+ * async blockUser(@DbActionPK() id: string) { ... }
1436
+ * ```
1437
+ */
1438
+ function DbAction(name, opts = {}) {
1439
+ return (0, moost.getMoostMate)().decorate((current) => {
1440
+ const meta = current;
1441
+ return {
1442
+ ...current,
1443
+ [MOOST_DB_ACTION]: mergeActionMeta(meta, {
1444
+ name,
1445
+ opts
1446
+ })
1447
+ };
1448
+ });
1449
+ }
1450
+ //#endregion
1451
+ //#region src/actions/db-action-default.decorator.ts
1452
+ /**
1453
+ * Sugar that flips `default: true` on the same method's `@DbAction` metadata.
1454
+ * Equivalent to passing `opts.default = true`. Decorator order does not matter.
1455
+ */
1456
+ function DbActionDefault() {
1457
+ return (0, moost.getMoostMate)().decorate((current) => {
1458
+ const meta = current;
1459
+ return {
1460
+ ...current,
1461
+ [MOOST_DB_ACTION]: mergeActionMeta(meta, { opts: { default: true } })
1462
+ };
1463
+ });
1464
+ }
1465
+ //#endregion
1466
+ //#region src/actions/pk-source.ts
1467
+ /**
1468
+ * Extract the PK validation source from a controller instance. Looks for
1469
+ * `readable` (set by {@link AsDbReadableController}) or `table` (set by
1470
+ * {@link AsDbController}).
1471
+ *
1472
+ * If the controller has no typed table attached (e.g. a value-help
1473
+ * controller, or a plain Moost controller without `@TableController`),
1474
+ * throws an HTTP 500 — this is a **server misconfiguration**, not a client
1475
+ * error. The body parser has nothing to validate against, so the request
1476
+ * cannot proceed. Use `@Body()` and parse the PK manually if you need to
1477
+ * accept PK-shaped bodies on a controller without an attached table.
1478
+ */
1479
+ function resolvePkSource(controller) {
1480
+ const c = controller;
1481
+ const candidate = c.readable ?? c.table;
1482
+ if (!isPkValidationSource(candidate)) throw new _moostjs_event_http.HttpError(500, "@DbActionPK/@DbActionPKs requires a controller with an attached table (via @TableController / @ReadableController). Use @Body() instead if your controller has no typed table.");
1483
+ return candidate;
1484
+ }
1485
+ function isPkValidationSource(value) {
1486
+ if (!value || typeof value !== "object") return false;
1487
+ const v = value;
1488
+ return Array.isArray(v.primaryKeys) && Array.isArray(v.fieldDescriptors);
1489
+ }
1490
+ /**
1491
+ * Build a parameter decorator that parses the JSON request body, validates
1492
+ * it against the bound table's PK schema with `validate`, and tags the param
1493
+ * so {@link discoverActions} can infer the action's `level`.
1494
+ */
1495
+ function createPkParamDecorator(kind, validate, resolverName) {
1496
+ return (0, moost.ApplyDecorators)((0, moost.getMoostMate)().decorate(MOOST_DB_ACTION_PARAM, kind), (0, moost.Resolve)(async () => {
1497
+ const body = await (0, _wooksjs_http_body.useBody)().parseBody();
1498
+ validate(body, resolvePkSource((0, moost.useControllerContext)().getController()));
1499
+ return body;
1500
+ }, resolverName));
1501
+ }
1502
+ //#endregion
1503
+ //#region src/actions/pk-validation.ts
1504
+ /**
1505
+ * Validate a JSON-decoded body against a single-row PK shape (scalar or
1506
+ * composite). Throws {@link ValidatorError} with structured `errors` so the
1507
+ * existing validation interceptor returns HTTP 400.
1508
+ */
1509
+ function validateSinglePk(body, source, path = "") {
1510
+ const errors = collectPkErrors(body, source, path);
1511
+ if (errors.length > 0) throw new _atscript_typescript_utils.ValidatorError(errors);
1512
+ }
1513
+ /**
1514
+ * Validate a JSON-decoded body against an array of PK shapes (`@DbActionPKs`).
1515
+ * The body MUST be an array; each element is validated against the PK schema.
1516
+ */
1517
+ function validateMultiPk(body, source) {
1518
+ if (!Array.isArray(body)) throw new _atscript_typescript_utils.ValidatorError([{
1519
+ path: "",
1520
+ message: "Expected JSON array of primary keys",
1521
+ details: []
1522
+ }]);
1523
+ const errors = [];
1524
+ for (let i = 0; i < body.length; i++) errors.push(...collectPkErrors(body[i], source, `[${i}]`));
1525
+ if (errors.length > 0) throw new _atscript_typescript_utils.ValidatorError(errors);
1526
+ }
1527
+ function collectPkErrors(value, source, pathPrefix) {
1528
+ const pkFields = source.primaryKeys;
1529
+ if (pkFields.length === 0) return [{
1530
+ path: pathPrefix,
1531
+ message: "Table has no primary key configured",
1532
+ details: []
1533
+ }];
1534
+ const errors = [];
1535
+ if (pkFields.length === 1) {
1536
+ const err = checkScalar(value, findFieldDescriptor(source, pkFields[0]), pathPrefix);
1537
+ if (err) errors.push(err);
1538
+ return errors;
1539
+ }
1540
+ if (!isPlainObject(value)) {
1541
+ errors.push({
1542
+ path: pathPrefix,
1543
+ message: "Expected JSON object for composite primary key",
1544
+ details: []
1545
+ });
1546
+ return errors;
1547
+ }
1548
+ for (const fieldName of pkFields) {
1549
+ const sub = pathPrefix ? `${pathPrefix}.${fieldName}` : fieldName;
1550
+ if (!(fieldName in value)) {
1551
+ errors.push({
1552
+ path: sub,
1553
+ message: `Missing primary-key field "${fieldName}"`,
1554
+ details: []
1555
+ });
1556
+ continue;
1557
+ }
1558
+ const fd = findFieldDescriptor(source, fieldName);
1559
+ const err = checkScalar(value[fieldName], fd, sub);
1560
+ if (err) errors.push(err);
1561
+ }
1562
+ return errors;
1563
+ }
1564
+ function findFieldDescriptor(source, name) {
1565
+ for (const fd of source.fieldDescriptors) if (fd.path === name) return fd;
1566
+ }
1567
+ function checkScalar(value, fd, path) {
1568
+ const expected = fd?.designType ?? "string";
1569
+ if (expected === "string" && typeof value !== "string") return scalarMismatch(path, expected, value);
1570
+ if (expected === "number" && typeof value !== "number") return scalarMismatch(path, expected, value);
1571
+ if (expected === "boolean" && typeof value !== "boolean") return scalarMismatch(path, expected, value);
1572
+ }
1573
+ function scalarMismatch(path, expected, value) {
1574
+ return {
1575
+ path,
1576
+ message: `Expected primary-key value to be ${expected}, got ${describe(value)}`,
1577
+ details: []
1578
+ };
1579
+ }
1580
+ function describe(value) {
1581
+ if (value === null) return "null";
1582
+ if (Array.isArray(value)) return "array";
1583
+ return typeof value;
1584
+ }
1585
+ function isPlainObject(value) {
1586
+ return typeof value === "object" && value !== null && !Array.isArray(value);
1587
+ }
1588
+ //#endregion
1589
+ //#region src/actions/db-action-pk.decorator.ts
1590
+ /**
1591
+ * Parameter resolver that reads the primary key from the JSON request body
1592
+ * and validates it against the bound table's PK schema.
1593
+ *
1594
+ * - Single-field PK → JSON-encoded scalar (`"abc"`, `42`, `true`).
1595
+ * - Composite PK → JSON object with all PK fields.
1596
+ *
1597
+ * Validation is strict — no type coercion. Mismatches throw a
1598
+ * `ValidatorError` which the existing validation interceptor surfaces as
1599
+ * HTTP 400 with the same envelope as DTO failures.
1600
+ *
1601
+ * Marks the param so {@link discoverActions} can infer the action's `level`
1602
+ * as `'row'`.
1603
+ */
1604
+ function DbActionPK() {
1605
+ return createPkParamDecorator("pk", validateSinglePk, "dbActionPk");
1606
+ }
1607
+ //#endregion
1608
+ //#region src/actions/db-action-pks.decorator.ts
1609
+ /**
1610
+ * Parameter resolver that reads a JSON array of primary keys from the request
1611
+ * body and validates each entry against the bound table's PK schema.
1612
+ *
1613
+ * - Scalar PK → JSON array of scalars (`["a","b","c"]`).
1614
+ * - Composite PK → JSON array of objects.
1615
+ *
1616
+ * Validation is strict — no type coercion. Marks the param so
1617
+ * {@link discoverActions} can infer the action's `level` as `'rows'`.
1618
+ */
1619
+ function DbActionPKs() {
1620
+ return createPkParamDecorator("pks", validateMultiPk, "dbActionPks");
1621
+ }
1622
+ //#endregion
1623
+ //#region src/actions/db-actions.decorator.ts
1624
+ /**
1625
+ * Declare class-level actions on a controller. Entries are flat dicts with
1626
+ * `processor: 'navigate' | 'custom' | 'backend'` matching the `/meta` wire
1627
+ * shape (see {@link TDbActionsEntry}). Each entry MUST specify `level`. Use
1628
+ * the level-pinned shortcuts (`@DbTableActions`, `@DbRowActions`,
1629
+ * `@DbRowsActions`) to avoid repeating `level`.
1630
+ *
1631
+ * The dictionary key serves as the action `name`. Entries do NOT bind any
1632
+ * HTTP route — the meta builder surfaces them in `/meta` only. For
1633
+ * `processor: 'backend'`, the dev-supplied `value` MUST point to a real
1634
+ * `@Post`-bound endpoint accepting the level-determined body shape.
1635
+ *
1636
+ * Multiple `@DbActions` (and shortcut) decorators on the same class
1637
+ * accumulate.
1638
+ */
1639
+ function DbActions(dict) {
1640
+ return classLevelActions(dict);
1641
+ }
1642
+ /** Sugar for `@DbActions` with `level: 'table'` injected into each entry. */
1643
+ function DbTableActions(dict) {
1644
+ return classLevelActions(dict, "table");
1645
+ }
1646
+ /** Sugar for `@DbActions` with `level: 'row'` injected into each entry. */
1647
+ function DbRowActions(dict) {
1648
+ return classLevelActions(dict, "row");
1649
+ }
1650
+ /** Sugar for `@DbActions` with `level: 'rows'` injected into each entry. */
1651
+ function DbRowsActions(dict) {
1652
+ return classLevelActions(dict, "rows");
1653
+ }
1654
+ function classLevelActions(dict, forcedLevel) {
1655
+ const entries = [];
1656
+ for (const [name, entry] of Object.entries(dict)) entries.push({
1657
+ name,
1658
+ entry,
1659
+ forcedLevel
1660
+ });
1661
+ return (0, moost.getMoostMate)().decorate((current) => {
1662
+ const existing = current["atscript_db_actions"] ?? [];
1663
+ return {
1664
+ ...current,
1665
+ [MOOST_DB_ACTIONS]: [...existing, ...entries]
1666
+ };
1667
+ });
1668
+ }
1669
+ //#endregion
1215
1670
  Object.defineProperty(exports, "AsDbController", {
1216
1671
  enumerable: true,
1217
1672
  get: function() {
@@ -1242,10 +1697,19 @@ Object.defineProperty(exports, "AsValueHelpController", {
1242
1697
  return AsValueHelpController;
1243
1698
  }
1244
1699
  });
1700
+ exports.DbAction = DbAction;
1701
+ exports.DbActionDefault = DbActionDefault;
1702
+ exports.DbActionPK = DbActionPK;
1703
+ exports.DbActionPKs = DbActionPKs;
1704
+ exports.DbActions = DbActions;
1705
+ exports.DbRowActions = DbRowActions;
1706
+ exports.DbRowsActions = DbRowsActions;
1707
+ exports.DbTableActions = DbTableActions;
1245
1708
  exports.READABLE_DEF = READABLE_DEF;
1246
1709
  exports.ReadableController = ReadableController;
1247
1710
  exports.TABLE_DEF = TABLE_DEF;
1248
1711
  exports.TableController = TableController;
1249
1712
  exports.UseValidationErrorTransform = UseValidationErrorTransform;
1250
1713
  exports.ViewController = ViewController;
1714
+ exports.discoverActions = discoverActions;
1251
1715
  exports.validationErrorTransform = validationErrorTransform;