@atscript/moost-db 0.1.53 → 0.1.55

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.cts CHANGED
@@ -6,29 +6,66 @@ import { HttpError } from "@moostjs/event-http";
6
6
  import * as moost from "moost";
7
7
  import { Moost, TConsoleBase } from "moost";
8
8
 
9
- //#region src/as-db-readable.controller.d.ts
9
+ //#region src/as-readable.controller.d.ts
10
10
  /**
11
- * Read-only database controller for Moost that works with any `AtscriptDbReadable`
12
- * (tables or views). Provides query, pages, getOne, and meta endpoints.
11
+ * Optional gate configuration for a single request. Each present entry enables
12
+ * the corresponding check; omitted entries skip that gate entirely.
13
+ */
14
+ interface ReadableGates {
15
+ filter?: {
16
+ predicate: (field: string) => boolean;
17
+ annotation: string;
18
+ };
19
+ sort?: {
20
+ predicate: (field: string) => boolean;
21
+ annotation: string;
22
+ };
23
+ search?: {
24
+ allowed: boolean;
25
+ rejectionMessage: string;
26
+ };
27
+ }
28
+ /**
29
+ * Abstract base class for read-only HTTP controllers over an Atscript interface.
13
30
  *
14
- * For write operations (insert, replace, update, delete), use {@link AsDbController}.
15
- * For views, use {@link AsDbViewController}.
31
+ * Shared responsibilities (implemented here):
32
+ * - Stamps `@db.http.path` on the bound interface's metadata at registration
33
+ * with the final public path (leading slash + Moost `globalPrefix`).
34
+ * - Lazily serializes the bound interface for the `/meta` endpoint
35
+ * (see {@link getSerializeOptions}).
36
+ * - Provides DTO-backed validators for the Uniquery controls DTOs and the
37
+ * helpers (`parseQueryString`, `returnOne`, `validateParsed`, etc.) that
38
+ * subclasses share.
39
+ * - Registers the `/meta` route. Subclasses override {@link buildMetaResponse}
40
+ * to shape the payload; DB-backed readables add relations/searchable flags,
41
+ * value-help controllers add their capability hints.
42
+ *
43
+ * Subclass responsibilities:
44
+ * - Pass the bound interface + logical name + (optional) kind tag through super().
45
+ * - Implement {@link hasField} so insights validation can reject unknown keys.
46
+ * - Register the `/query`, `/pages`, `/one(/:id)` routes with the concrete
47
+ * handlers that match the data source's contract (DB readables route into
48
+ * aggregate/vector/search; value-help controllers just filter/sort/paginate).
16
49
  */
17
- declare class AsDbReadableController<T extends TAtscriptAnnotatedType = TAtscriptAnnotatedType, DataType = TAtscriptDataType<T>> {
18
- /** Reference to the underlying readable (table or view). */
19
- protected readable: AtscriptDbReadable<T>;
50
+ declare abstract class AsReadableController<T extends TAtscriptAnnotatedType = TAtscriptAnnotatedType, DataType = TAtscriptDataType<T>> {
51
+ /** The Atscript interface this controller serves. */
52
+ protected readonly boundType: T;
53
+ /** Short human-readable name for logging (usually the table/source name). */
54
+ protected readonly controllerName: string;
20
55
  /** Application-scoped logger. */
21
56
  protected logger: TConsoleBase;
22
- /** Cached serialized type definition (lazy, computed on first access). */
23
- private _serializedType?;
24
57
  /** Moost application instance. */
25
58
  protected app: Moost;
59
+ /** Cached serialized type definition (lazy, computed on first access). */
60
+ private _serializedType?;
26
61
  /** Cached full meta response (computed lazily on first meta() call). */
27
62
  private _metaResponse?;
28
- constructor(readable: AtscriptDbReadable<T>, app: Moost);
63
+ constructor(boundType: T, controllerName: string, app: Moost, kindTag?: string);
64
+ /** Subclass contract: return `true` if `path` addresses a valid field on the bound source. */
65
+ protected abstract hasField(path: string): boolean;
29
66
  /** Sets @db.http.path on the type metadata from the controller's computed prefix. */
30
67
  private _resolveHttpPath;
31
- /** Lazily serializes the type (after all controllers have set their @db.http.path). */
68
+ /** Lazily serializes the bound type (after all controllers have set @db.http.path). */
32
69
  protected getSerializedType(): _atscript_typescript_utils0.TSerializedAnnotatedType;
33
70
  /**
34
71
  * One-time initialization hook. Override to seed data, register watchers, etc.
@@ -36,14 +73,24 @@ declare class AsDbReadableController<T extends TAtscriptAnnotatedType = TAtscrip
36
73
  protected init(): void | Promise<void>;
37
74
  /**
38
75
  * Returns serialization options for the `/meta` endpoint's type field.
39
- * Default: whitelist — keeps `meta.*`, `expect.*`, and `db.rel.*` annotations,
40
- * strips all other `db.*` annotations (table, column, index, default, etc.).
41
- * Override in subclass to customise what annotations are exposed to clients.
76
+ *
77
+ * `refDepth: 0.5` is intentionally static independent of `@db.depth.limit`
78
+ * (which is a security guard on nested writes, not a serialization policy).
79
+ * The shallow shape emits `{ field, type: { id, metadata } }` for every FK,
80
+ * which carries the target's `db.http.path` so clients can resolve value-help
81
+ * URLs and lazy-fetch target `/meta` when deeper structure is needed. Nav
82
+ * props (`@db.rel.from` / `@db.rel.to` / `@db.rel.via`) are not `.ref` nodes
83
+ * and always expand fully regardless of `refDepth` — the write-payload shape
84
+ * clients need is unaffected.
85
+ *
86
+ * Annotation whitelist: keeps `meta.*`, `expect.*`, and `db.rel.*`; strips
87
+ * other `db.*` (table, column, index, default, etc.). Override in subclass
88
+ * to customise.
42
89
  */
43
90
  protected getSerializeOptions(): TSerializeOptions;
44
91
  /**
45
92
  * Whether this controller is read-only (no write endpoints).
46
- * Returns `true` for readable/view controllers, overridden to `false` in AsDbController.
93
+ * Returns `true` by default; {@link AsDbController} overrides to `false`.
47
94
  */
48
95
  protected _isReadOnly(): boolean;
49
96
  private _queryControlsValidator?;
@@ -55,6 +102,49 @@ declare class AsDbReadableController<T extends TAtscriptAnnotatedType = TAtscrip
55
102
  protected validateControls(controls: Record<string, unknown>, type: "query" | "pages" | "getOne"): string | undefined;
56
103
  protected validateInsights(insights: Map<string, unknown>): string | undefined;
57
104
  protected validateParsed(parsed: Uniquery, type: "query" | "pages" | "getOne"): HttpError | undefined;
105
+ /**
106
+ * Shared filter/sort/search gate check. Subclasses assemble a {@link ReadableGates}
107
+ * config per request (or once in the constructor when static) and call this to
108
+ * get a uniform HTTP 400 response for any offending field/control.
109
+ */
110
+ protected checkGates(filter: FilterExpr | undefined, controls: Record<string, unknown>, gates: ReadableGates): HttpError | undefined;
111
+ protected parseQueryString(url: string): _uniqu_url0.UrlQuery;
112
+ protected returnOne(result: Promise<DataType | null>): Promise<DataType | HttpError>;
113
+ /**
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.
120
+ */
121
+ meta(): Promise<TMetaResponse>;
122
+ /**
123
+ * 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
+ */
127
+ protected buildMetaResponse(): Promise<TMetaResponse>;
128
+ }
129
+ //#endregion
130
+ //#region src/as-db-readable.controller.d.ts
131
+ /**
132
+ * Read-only database controller for Moost that works with any `AtscriptDbReadable`
133
+ * (tables or views). Provides query, pages, getOne, and meta endpoints.
134
+ *
135
+ * For write operations (insert, replace, update, delete), use {@link AsDbController}.
136
+ * For views, use {@link AsDbViewController}.
137
+ */
138
+ declare class AsDbReadableController<T extends TAtscriptAnnotatedType = TAtscriptAnnotatedType, DataType = TAtscriptDataType<T>> extends AsReadableController<T, DataType> {
139
+ /** Reference to the underlying readable (table or view). */
140
+ protected readable: AtscriptDbReadable<T>;
141
+ private readonly _gates;
142
+ constructor(readable: AtscriptDbReadable<T>, app: Moost);
143
+ private _buildGates;
144
+ private _collectAnnotated;
145
+ protected hasField(path: string): boolean;
146
+ /** Validates $with relations against the readable. */
147
+ protected validateParsed(parsed: Uniquery, type: "query" | "pages" | "getOne"): HttpError | undefined;
58
148
  /**
59
149
  * Compute an embedding vector from a search term.
60
150
  * Override in subclass to integrate with your embedding provider (OpenAI, etc.).
@@ -71,8 +161,6 @@ declare class AsDbReadableController<T extends TAtscriptAnnotatedType = TAtscrip
71
161
  * May return a Promise for async lookups.
72
162
  */
73
163
  protected transformProjection(projection?: UniqueryControls["$select"]): UniqueryControls["$select"] | undefined | Promise<UniqueryControls["$select"] | undefined>;
74
- protected parseQueryString(url: string): _uniqu_url0.UrlQuery;
75
- protected returnOne(result: Promise<DataType | null>): Promise<DataType | HttpError>;
76
164
  /**
77
165
  * Extracts a composite identifier object from query params.
78
166
  * Tries composite primary key first, then compound unique indexes.
@@ -104,12 +192,11 @@ declare class AsDbReadableController<T extends TAtscriptAnnotatedType = TAtscrip
104
192
  /**
105
193
  * **GET /meta** — returns table/view metadata for UI.
106
194
  *
107
- * The return type includes `Promise<...>` so subclasses can override with an
108
- * async implementation (e.g. to enrich the payload from an external source).
109
- * The base cache only covers the base payload — async overrides must cache
110
- * their own enrichment if needed.
195
+ * Overrides the base's minimal envelope to add relations, searchable flags,
196
+ * vector-searchable flags, field-descriptor-derived filter/sort hints, and
197
+ * the configured primary keys.
111
198
  */
112
- meta(): TMetaResponse | Promise<TMetaResponse>;
199
+ protected buildMetaResponse(): Promise<TMetaResponse>;
113
200
  }
114
201
  //#endregion
115
202
  //#region src/as-db.controller.d.ts
@@ -161,6 +248,132 @@ declare class AsDbController<T extends TAtscriptAnnotatedType = TAtscriptAnnotat
161
248
  removeComposite(query: Record<string, string>): Promise<unknown>;
162
249
  }
163
250
  //#endregion
251
+ //#region src/as-value-help.controller.d.ts
252
+ /**
253
+ * Parsed Uniquery controls with the `$search` field carved out for value-help
254
+ * use (the core DTO includes it but we narrow the type here so implementations
255
+ * can rely on the concrete shape).
256
+ */
257
+ interface ValueHelpQuery<T> {
258
+ filter: FilterExpr;
259
+ controls: {
260
+ $skip?: number;
261
+ $limit?: number;
262
+ $search?: string;
263
+ $select?: (keyof T | string)[];
264
+ $sort?: unknown;
265
+ [key: string]: unknown;
266
+ };
267
+ }
268
+ /**
269
+ * Abstract base class for read-only HTTP controllers serving a **value-help**
270
+ * source — an interface bound to a simple `/query` / `/pages` / `/one(/:id)` /
271
+ * `/meta` surface, not a full DB table. Value-help controllers drive the
272
+ * client-side picker UI on fields annotated `@db.rel.FK`.
273
+ *
274
+ * Subclass responsibilities:
275
+ * - Pass the bound interface + rows/backing-source through super().
276
+ * - Implement the abstract {@link query} and {@link getOne} methods.
277
+ *
278
+ * The bound interface's `@ui.dict.*` annotations are **client-side hints**
279
+ * consumed by the picker UI; the server does not gate filter / sort / search
280
+ * requests against them. Subclasses that need a backend gate should compose
281
+ * one of their own (see {@link AsDbReadableController} for the
282
+ * `@db.column.filterable` / `@db.column.sortable` pattern).
283
+ */
284
+ declare abstract class AsValueHelpController<T extends TAtscriptAnnotatedType = TAtscriptAnnotatedType, DataType = TAtscriptDataType<T>> extends AsReadableController<T, DataType> {
285
+ /** Per-prop metadata map of the bound interface; eagerly built once. */
286
+ protected readonly fieldMeta: Map<string, Map<string, unknown>>;
287
+ /**
288
+ * Fields that participate in `$search` by default. Populated from
289
+ * `@ui.dict.searchable`:
290
+ * - If any prop carries `@ui.dict.searchable`, only those props are here.
291
+ * - Else if the interface carries `@ui.dict.searchable`, every `string`-typed prop is here.
292
+ * - Else every `string`-typed prop is here (hint is absent — default to all strings).
293
+ */
294
+ protected readonly searchableFields: readonly string[];
295
+ /** The `@meta.id` field name on the bound interface, if any. */
296
+ protected readonly primaryKey: string | undefined;
297
+ constructor(boundType: T, controllerName: string, app: Moost);
298
+ /** Executes a value-help query against the backing source. */
299
+ protected abstract query(controls: ValueHelpQuery<DataType>): Promise<{
300
+ data: DataType[];
301
+ count: number;
302
+ }>;
303
+ /** Returns the row whose primary key matches `id`, or `null` on miss. */
304
+ protected abstract getOne(id: string | number): Promise<DataType | null>;
305
+ protected hasField(path: string): boolean;
306
+ /**
307
+ * **GET /query** — returns an array of matched rows (up to `$limit`).
308
+ */
309
+ runQuery(url: string): Promise<DataType[] | HttpError>;
310
+ /**
311
+ * **GET /pages** — paginated row window plus total count.
312
+ */
313
+ runPages(url: string): Promise<{
314
+ data: DataType[];
315
+ page: number;
316
+ itemsPerPage: number;
317
+ pages: number;
318
+ count: number;
319
+ } | HttpError>;
320
+ /**
321
+ * **GET /one/:id** — retrieves a single row by primary key.
322
+ */
323
+ runGetOne(id: string): Promise<DataType | HttpError>;
324
+ /**
325
+ * **GET /one?<pk>=<val>** — retrieves a single row by PK query param (fallback).
326
+ */
327
+ runGetOneComposite(query: Record<string, string>): Promise<DataType | HttpError>;
328
+ /**
329
+ * Meta response surfaces `@ui.dict.*` annotations as **hints** for the
330
+ * client picker UI (which controls to render); the server does not enforce
331
+ * these flags at request time.
332
+ */
333
+ protected buildMetaResponse(): Promise<TMetaResponse>;
334
+ }
335
+ //#endregion
336
+ //#region src/as-json-value-help.controller.d.ts
337
+ /**
338
+ * Concrete value-help controller backed by a static in-memory array. Provides
339
+ * filter/sort/search/paginate over the provided rows, respecting the
340
+ * `@ui.dict.*` capability annotations on the bound interface.
341
+ *
342
+ * **Semantics:**
343
+ * - Filter is interpreted as a subset of MongoDB-style comparison operators
344
+ * (`$eq`, `$ne`, `$in`, `$nin`, `$gt`, `$gte`, `$lt`, `$lte`, `$regex`) and
345
+ * logical combinators (`$and`, `$or`, `$not`, `$nor`). Unknown operators
346
+ * fall through to strict equality.
347
+ * - Sort is stable, multi-key, lexicographic. Direction via `-` prefix on the
348
+ * field name or `{ [field]: 'asc' | 'desc' }`.
349
+ * - Search is case-insensitive substring matching across every field listed in
350
+ * {@link searchableFields}.
351
+ * - Type coercion: comparisons compare raw JS values (no implicit string-to-
352
+ * number coercion); strings are compared case-insensitively only for
353
+ * `$search`, not for filter operators.
354
+ *
355
+ * **Constructor:**
356
+ * ```ts
357
+ * new AsJsonValueHelpController(StatusDict, [
358
+ * { id: 'active', label: 'Active' },
359
+ * { id: 'archived', label: 'Archived' },
360
+ * ], app);
361
+ * ```
362
+ *
363
+ * Register under a Moost path with `@Controller('/api/dicts/status')` or the
364
+ * equivalent composition decorator.
365
+ */
366
+ declare class AsJsonValueHelpController<T extends TAtscriptAnnotatedType = TAtscriptAnnotatedType, DataType = TAtscriptDataType<T>> extends AsValueHelpController<T, DataType> {
367
+ protected rows: DataType[];
368
+ private _pkIndex?;
369
+ constructor(boundType: T, rows: DataType[], app: Moost, controllerName?: string);
370
+ protected query(controls: ValueHelpQuery<DataType>): Promise<{
371
+ data: DataType[];
372
+ count: number;
373
+ }>;
374
+ protected getOne(id: string | number): Promise<DataType | null>;
375
+ }
376
+ //#endregion
164
377
  //#region src/decorators.d.ts
165
378
  /**
166
379
  * DI token under which the {@link AtscriptDbReadable} instance
@@ -223,4 +436,4 @@ declare const ViewController: (readable: AtscriptDbReadable, prefix?: string) =>
223
436
  declare const validationErrorTransform: () => moost.TInterceptorDef;
224
437
  declare const UseValidationErrorTransform: () => ClassDecorator & MethodDecorator;
225
438
  //#endregion
226
- export { AsDbController, AsDbReadableController, READABLE_DEF, ReadableController, TABLE_DEF, TableController, UseValidationErrorTransform, ViewController, validationErrorTransform };
439
+ export { AsDbController, AsDbReadableController, AsJsonValueHelpController, AsReadableController, AsValueHelpController, READABLE_DEF, ReadableController, ReadableGates, TABLE_DEF, TableController, UseValidationErrorTransform, ValueHelpQuery, ViewController, validationErrorTransform };
package/dist/index.d.mts CHANGED
@@ -6,29 +6,66 @@ import { Moost, TConsoleBase } from "moost";
6
6
  import * as _uniqu_url0 from "@uniqu/url";
7
7
  import { AtscriptDbReadable, AtscriptDbTable, FilterExpr, TMetaResponse, Uniquery, UniqueryControls } from "@atscript/db";
8
8
 
9
- //#region src/as-db-readable.controller.d.ts
9
+ //#region src/as-readable.controller.d.ts
10
10
  /**
11
- * Read-only database controller for Moost that works with any `AtscriptDbReadable`
12
- * (tables or views). Provides query, pages, getOne, and meta endpoints.
11
+ * Optional gate configuration for a single request. Each present entry enables
12
+ * the corresponding check; omitted entries skip that gate entirely.
13
+ */
14
+ interface ReadableGates {
15
+ filter?: {
16
+ predicate: (field: string) => boolean;
17
+ annotation: string;
18
+ };
19
+ sort?: {
20
+ predicate: (field: string) => boolean;
21
+ annotation: string;
22
+ };
23
+ search?: {
24
+ allowed: boolean;
25
+ rejectionMessage: string;
26
+ };
27
+ }
28
+ /**
29
+ * Abstract base class for read-only HTTP controllers over an Atscript interface.
13
30
  *
14
- * For write operations (insert, replace, update, delete), use {@link AsDbController}.
15
- * For views, use {@link AsDbViewController}.
31
+ * Shared responsibilities (implemented here):
32
+ * - Stamps `@db.http.path` on the bound interface's metadata at registration
33
+ * with the final public path (leading slash + Moost `globalPrefix`).
34
+ * - Lazily serializes the bound interface for the `/meta` endpoint
35
+ * (see {@link getSerializeOptions}).
36
+ * - Provides DTO-backed validators for the Uniquery controls DTOs and the
37
+ * helpers (`parseQueryString`, `returnOne`, `validateParsed`, etc.) that
38
+ * subclasses share.
39
+ * - Registers the `/meta` route. Subclasses override {@link buildMetaResponse}
40
+ * to shape the payload; DB-backed readables add relations/searchable flags,
41
+ * value-help controllers add their capability hints.
42
+ *
43
+ * Subclass responsibilities:
44
+ * - Pass the bound interface + logical name + (optional) kind tag through super().
45
+ * - Implement {@link hasField} so insights validation can reject unknown keys.
46
+ * - Register the `/query`, `/pages`, `/one(/:id)` routes with the concrete
47
+ * handlers that match the data source's contract (DB readables route into
48
+ * aggregate/vector/search; value-help controllers just filter/sort/paginate).
16
49
  */
17
- declare class AsDbReadableController<T extends TAtscriptAnnotatedType = TAtscriptAnnotatedType, DataType = TAtscriptDataType<T>> {
18
- /** Reference to the underlying readable (table or view). */
19
- protected readable: AtscriptDbReadable<T>;
50
+ declare abstract class AsReadableController<T extends TAtscriptAnnotatedType = TAtscriptAnnotatedType, DataType = TAtscriptDataType<T>> {
51
+ /** The Atscript interface this controller serves. */
52
+ protected readonly boundType: T;
53
+ /** Short human-readable name for logging (usually the table/source name). */
54
+ protected readonly controllerName: string;
20
55
  /** Application-scoped logger. */
21
56
  protected logger: TConsoleBase;
22
- /** Cached serialized type definition (lazy, computed on first access). */
23
- private _serializedType?;
24
57
  /** Moost application instance. */
25
58
  protected app: Moost;
59
+ /** Cached serialized type definition (lazy, computed on first access). */
60
+ private _serializedType?;
26
61
  /** Cached full meta response (computed lazily on first meta() call). */
27
62
  private _metaResponse?;
28
- constructor(readable: AtscriptDbReadable<T>, app: Moost);
63
+ constructor(boundType: T, controllerName: string, app: Moost, kindTag?: string);
64
+ /** Subclass contract: return `true` if `path` addresses a valid field on the bound source. */
65
+ protected abstract hasField(path: string): boolean;
29
66
  /** Sets @db.http.path on the type metadata from the controller's computed prefix. */
30
67
  private _resolveHttpPath;
31
- /** Lazily serializes the type (after all controllers have set their @db.http.path). */
68
+ /** Lazily serializes the bound type (after all controllers have set @db.http.path). */
32
69
  protected getSerializedType(): _atscript_typescript_utils0.TSerializedAnnotatedType;
33
70
  /**
34
71
  * One-time initialization hook. Override to seed data, register watchers, etc.
@@ -36,14 +73,24 @@ declare class AsDbReadableController<T extends TAtscriptAnnotatedType = TAtscrip
36
73
  protected init(): void | Promise<void>;
37
74
  /**
38
75
  * Returns serialization options for the `/meta` endpoint's type field.
39
- * Default: whitelist — keeps `meta.*`, `expect.*`, and `db.rel.*` annotations,
40
- * strips all other `db.*` annotations (table, column, index, default, etc.).
41
- * Override in subclass to customise what annotations are exposed to clients.
76
+ *
77
+ * `refDepth: 0.5` is intentionally static independent of `@db.depth.limit`
78
+ * (which is a security guard on nested writes, not a serialization policy).
79
+ * The shallow shape emits `{ field, type: { id, metadata } }` for every FK,
80
+ * which carries the target's `db.http.path` so clients can resolve value-help
81
+ * URLs and lazy-fetch target `/meta` when deeper structure is needed. Nav
82
+ * props (`@db.rel.from` / `@db.rel.to` / `@db.rel.via`) are not `.ref` nodes
83
+ * and always expand fully regardless of `refDepth` — the write-payload shape
84
+ * clients need is unaffected.
85
+ *
86
+ * Annotation whitelist: keeps `meta.*`, `expect.*`, and `db.rel.*`; strips
87
+ * other `db.*` (table, column, index, default, etc.). Override in subclass
88
+ * to customise.
42
89
  */
43
90
  protected getSerializeOptions(): TSerializeOptions;
44
91
  /**
45
92
  * Whether this controller is read-only (no write endpoints).
46
- * Returns `true` for readable/view controllers, overridden to `false` in AsDbController.
93
+ * Returns `true` by default; {@link AsDbController} overrides to `false`.
47
94
  */
48
95
  protected _isReadOnly(): boolean;
49
96
  private _queryControlsValidator?;
@@ -55,6 +102,49 @@ declare class AsDbReadableController<T extends TAtscriptAnnotatedType = TAtscrip
55
102
  protected validateControls(controls: Record<string, unknown>, type: "query" | "pages" | "getOne"): string | undefined;
56
103
  protected validateInsights(insights: Map<string, unknown>): string | undefined;
57
104
  protected validateParsed(parsed: Uniquery, type: "query" | "pages" | "getOne"): HttpError | undefined;
105
+ /**
106
+ * Shared filter/sort/search gate check. Subclasses assemble a {@link ReadableGates}
107
+ * config per request (or once in the constructor when static) and call this to
108
+ * get a uniform HTTP 400 response for any offending field/control.
109
+ */
110
+ protected checkGates(filter: FilterExpr | undefined, controls: Record<string, unknown>, gates: ReadableGates): HttpError | undefined;
111
+ protected parseQueryString(url: string): _uniqu_url0.UrlQuery;
112
+ protected returnOne(result: Promise<DataType | null>): Promise<DataType | HttpError>;
113
+ /**
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.
120
+ */
121
+ meta(): Promise<TMetaResponse>;
122
+ /**
123
+ * 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
+ */
127
+ protected buildMetaResponse(): Promise<TMetaResponse>;
128
+ }
129
+ //#endregion
130
+ //#region src/as-db-readable.controller.d.ts
131
+ /**
132
+ * Read-only database controller for Moost that works with any `AtscriptDbReadable`
133
+ * (tables or views). Provides query, pages, getOne, and meta endpoints.
134
+ *
135
+ * For write operations (insert, replace, update, delete), use {@link AsDbController}.
136
+ * For views, use {@link AsDbViewController}.
137
+ */
138
+ declare class AsDbReadableController<T extends TAtscriptAnnotatedType = TAtscriptAnnotatedType, DataType = TAtscriptDataType<T>> extends AsReadableController<T, DataType> {
139
+ /** Reference to the underlying readable (table or view). */
140
+ protected readable: AtscriptDbReadable<T>;
141
+ private readonly _gates;
142
+ constructor(readable: AtscriptDbReadable<T>, app: Moost);
143
+ private _buildGates;
144
+ private _collectAnnotated;
145
+ protected hasField(path: string): boolean;
146
+ /** Validates $with relations against the readable. */
147
+ protected validateParsed(parsed: Uniquery, type: "query" | "pages" | "getOne"): HttpError | undefined;
58
148
  /**
59
149
  * Compute an embedding vector from a search term.
60
150
  * Override in subclass to integrate with your embedding provider (OpenAI, etc.).
@@ -71,8 +161,6 @@ declare class AsDbReadableController<T extends TAtscriptAnnotatedType = TAtscrip
71
161
  * May return a Promise for async lookups.
72
162
  */
73
163
  protected transformProjection(projection?: UniqueryControls["$select"]): UniqueryControls["$select"] | undefined | Promise<UniqueryControls["$select"] | undefined>;
74
- protected parseQueryString(url: string): _uniqu_url0.UrlQuery;
75
- protected returnOne(result: Promise<DataType | null>): Promise<DataType | HttpError>;
76
164
  /**
77
165
  * Extracts a composite identifier object from query params.
78
166
  * Tries composite primary key first, then compound unique indexes.
@@ -104,12 +192,11 @@ declare class AsDbReadableController<T extends TAtscriptAnnotatedType = TAtscrip
104
192
  /**
105
193
  * **GET /meta** — returns table/view metadata for UI.
106
194
  *
107
- * The return type includes `Promise<...>` so subclasses can override with an
108
- * async implementation (e.g. to enrich the payload from an external source).
109
- * The base cache only covers the base payload — async overrides must cache
110
- * their own enrichment if needed.
195
+ * Overrides the base's minimal envelope to add relations, searchable flags,
196
+ * vector-searchable flags, field-descriptor-derived filter/sort hints, and
197
+ * the configured primary keys.
111
198
  */
112
- meta(): TMetaResponse | Promise<TMetaResponse>;
199
+ protected buildMetaResponse(): Promise<TMetaResponse>;
113
200
  }
114
201
  //#endregion
115
202
  //#region src/as-db.controller.d.ts
@@ -161,6 +248,132 @@ declare class AsDbController<T extends TAtscriptAnnotatedType = TAtscriptAnnotat
161
248
  removeComposite(query: Record<string, string>): Promise<unknown>;
162
249
  }
163
250
  //#endregion
251
+ //#region src/as-value-help.controller.d.ts
252
+ /**
253
+ * Parsed Uniquery controls with the `$search` field carved out for value-help
254
+ * use (the core DTO includes it but we narrow the type here so implementations
255
+ * can rely on the concrete shape).
256
+ */
257
+ interface ValueHelpQuery<T> {
258
+ filter: FilterExpr;
259
+ controls: {
260
+ $skip?: number;
261
+ $limit?: number;
262
+ $search?: string;
263
+ $select?: (keyof T | string)[];
264
+ $sort?: unknown;
265
+ [key: string]: unknown;
266
+ };
267
+ }
268
+ /**
269
+ * Abstract base class for read-only HTTP controllers serving a **value-help**
270
+ * source — an interface bound to a simple `/query` / `/pages` / `/one(/:id)` /
271
+ * `/meta` surface, not a full DB table. Value-help controllers drive the
272
+ * client-side picker UI on fields annotated `@db.rel.FK`.
273
+ *
274
+ * Subclass responsibilities:
275
+ * - Pass the bound interface + rows/backing-source through super().
276
+ * - Implement the abstract {@link query} and {@link getOne} methods.
277
+ *
278
+ * The bound interface's `@ui.dict.*` annotations are **client-side hints**
279
+ * consumed by the picker UI; the server does not gate filter / sort / search
280
+ * requests against them. Subclasses that need a backend gate should compose
281
+ * one of their own (see {@link AsDbReadableController} for the
282
+ * `@db.column.filterable` / `@db.column.sortable` pattern).
283
+ */
284
+ declare abstract class AsValueHelpController<T extends TAtscriptAnnotatedType = TAtscriptAnnotatedType, DataType = TAtscriptDataType<T>> extends AsReadableController<T, DataType> {
285
+ /** Per-prop metadata map of the bound interface; eagerly built once. */
286
+ protected readonly fieldMeta: Map<string, Map<string, unknown>>;
287
+ /**
288
+ * Fields that participate in `$search` by default. Populated from
289
+ * `@ui.dict.searchable`:
290
+ * - If any prop carries `@ui.dict.searchable`, only those props are here.
291
+ * - Else if the interface carries `@ui.dict.searchable`, every `string`-typed prop is here.
292
+ * - Else every `string`-typed prop is here (hint is absent — default to all strings).
293
+ */
294
+ protected readonly searchableFields: readonly string[];
295
+ /** The `@meta.id` field name on the bound interface, if any. */
296
+ protected readonly primaryKey: string | undefined;
297
+ constructor(boundType: T, controllerName: string, app: Moost);
298
+ /** Executes a value-help query against the backing source. */
299
+ protected abstract query(controls: ValueHelpQuery<DataType>): Promise<{
300
+ data: DataType[];
301
+ count: number;
302
+ }>;
303
+ /** Returns the row whose primary key matches `id`, or `null` on miss. */
304
+ protected abstract getOne(id: string | number): Promise<DataType | null>;
305
+ protected hasField(path: string): boolean;
306
+ /**
307
+ * **GET /query** — returns an array of matched rows (up to `$limit`).
308
+ */
309
+ runQuery(url: string): Promise<DataType[] | HttpError>;
310
+ /**
311
+ * **GET /pages** — paginated row window plus total count.
312
+ */
313
+ runPages(url: string): Promise<{
314
+ data: DataType[];
315
+ page: number;
316
+ itemsPerPage: number;
317
+ pages: number;
318
+ count: number;
319
+ } | HttpError>;
320
+ /**
321
+ * **GET /one/:id** — retrieves a single row by primary key.
322
+ */
323
+ runGetOne(id: string): Promise<DataType | HttpError>;
324
+ /**
325
+ * **GET /one?<pk>=<val>** — retrieves a single row by PK query param (fallback).
326
+ */
327
+ runGetOneComposite(query: Record<string, string>): Promise<DataType | HttpError>;
328
+ /**
329
+ * Meta response surfaces `@ui.dict.*` annotations as **hints** for the
330
+ * client picker UI (which controls to render); the server does not enforce
331
+ * these flags at request time.
332
+ */
333
+ protected buildMetaResponse(): Promise<TMetaResponse>;
334
+ }
335
+ //#endregion
336
+ //#region src/as-json-value-help.controller.d.ts
337
+ /**
338
+ * Concrete value-help controller backed by a static in-memory array. Provides
339
+ * filter/sort/search/paginate over the provided rows, respecting the
340
+ * `@ui.dict.*` capability annotations on the bound interface.
341
+ *
342
+ * **Semantics:**
343
+ * - Filter is interpreted as a subset of MongoDB-style comparison operators
344
+ * (`$eq`, `$ne`, `$in`, `$nin`, `$gt`, `$gte`, `$lt`, `$lte`, `$regex`) and
345
+ * logical combinators (`$and`, `$or`, `$not`, `$nor`). Unknown operators
346
+ * fall through to strict equality.
347
+ * - Sort is stable, multi-key, lexicographic. Direction via `-` prefix on the
348
+ * field name or `{ [field]: 'asc' | 'desc' }`.
349
+ * - Search is case-insensitive substring matching across every field listed in
350
+ * {@link searchableFields}.
351
+ * - Type coercion: comparisons compare raw JS values (no implicit string-to-
352
+ * number coercion); strings are compared case-insensitively only for
353
+ * `$search`, not for filter operators.
354
+ *
355
+ * **Constructor:**
356
+ * ```ts
357
+ * new AsJsonValueHelpController(StatusDict, [
358
+ * { id: 'active', label: 'Active' },
359
+ * { id: 'archived', label: 'Archived' },
360
+ * ], app);
361
+ * ```
362
+ *
363
+ * Register under a Moost path with `@Controller('/api/dicts/status')` or the
364
+ * equivalent composition decorator.
365
+ */
366
+ declare class AsJsonValueHelpController<T extends TAtscriptAnnotatedType = TAtscriptAnnotatedType, DataType = TAtscriptDataType<T>> extends AsValueHelpController<T, DataType> {
367
+ protected rows: DataType[];
368
+ private _pkIndex?;
369
+ constructor(boundType: T, rows: DataType[], app: Moost, controllerName?: string);
370
+ protected query(controls: ValueHelpQuery<DataType>): Promise<{
371
+ data: DataType[];
372
+ count: number;
373
+ }>;
374
+ protected getOne(id: string | number): Promise<DataType | null>;
375
+ }
376
+ //#endregion
164
377
  //#region src/decorators.d.ts
165
378
  /**
166
379
  * DI token under which the {@link AtscriptDbReadable} instance
@@ -223,4 +436,4 @@ declare const ViewController: (readable: AtscriptDbReadable, prefix?: string) =>
223
436
  declare const validationErrorTransform: () => moost.TInterceptorDef;
224
437
  declare const UseValidationErrorTransform: () => ClassDecorator & MethodDecorator;
225
438
  //#endregion
226
- export { AsDbController, AsDbReadableController, READABLE_DEF, ReadableController, TABLE_DEF, TableController, UseValidationErrorTransform, ViewController, validationErrorTransform };
439
+ export { AsDbController, AsDbReadableController, AsJsonValueHelpController, AsReadableController, AsValueHelpController, READABLE_DEF, ReadableController, ReadableGates, TABLE_DEF, TableController, UseValidationErrorTransform, ValueHelpQuery, ViewController, validationErrorTransform };