@metaobjectsdev/runtime-ts 1.1.1-rc.1 → 1.1.2
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/decimal-wire.d.ts +16 -6
- package/dist/decimal-wire.d.ts.map +1 -1
- package/dist/decimal-wire.js +25 -8
- package/dist/decimal-wire.js.map +1 -1
- package/dist/drizzle-fastify/index.d.ts +5 -9
- package/dist/drizzle-fastify/index.d.ts.map +1 -1
- package/dist/drizzle-fastify/index.js +5 -4
- package/dist/drizzle-fastify/index.js.map +1 -1
- package/dist/drizzle-fastify/mount-m2m.d.ts +2 -3
- package/dist/drizzle-fastify/mount-m2m.d.ts.map +1 -1
- package/dist/drizzle-fastify/mount-m2m.js +2 -2
- package/dist/drizzle-fastify/mount-m2m.js.map +1 -1
- package/dist/drizzle-fastify/mount-read-only.d.ts +2 -9
- package/dist/drizzle-fastify/mount-read-only.d.ts.map +1 -1
- package/dist/drizzle-fastify/mount-read-only.js +2 -2
- package/dist/drizzle-fastify/mount-read-only.js.map +1 -1
- package/dist/hono/index.d.ts +5 -9
- package/dist/hono/index.d.ts.map +1 -1
- package/dist/hono/index.js +5 -4
- package/dist/hono/index.js.map +1 -1
- package/dist/hono/mount-read-only.d.ts +2 -9
- package/dist/hono/mount-read-only.d.ts.map +1 -1
- package/dist/hono/mount-read-only.js +2 -2
- package/dist/hono/mount-read-only.js.map +1 -1
- package/dist/wire-format.d.ts +35 -0
- package/dist/wire-format.d.ts.map +1 -0
- package/dist/wire-format.js +77 -0
- package/dist/wire-format.js.map +1 -0
- package/package.json +3 -3
- package/src/decimal-wire.ts +26 -6
- package/src/drizzle-fastify/index.ts +7 -12
- package/src/drizzle-fastify/mount-m2m.ts +3 -5
- package/src/drizzle-fastify/mount-read-only.ts +3 -11
- package/src/hono/index.ts +7 -12
- package/src/hono/mount-read-only.ts +3 -11
- package/src/wire-format.ts +103 -0
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { type SqlDialect } from "../sql-dialect.js";
|
|
2
2
|
import type { Hono } from "hono";
|
|
3
3
|
import type { FilterAllowlist, SortAllowlist } from "../drizzle-fastify/filter-allowlist.js";
|
|
4
|
+
import { type WireOptions } from "../wire-format.js";
|
|
4
5
|
type AnyView = any;
|
|
5
6
|
type AnyHono = Hono<any, any, any>;
|
|
6
|
-
export interface MountReadOnlyOptions {
|
|
7
|
+
export interface MountReadOnlyOptions extends WireOptions {
|
|
7
8
|
readonly app: AnyHono;
|
|
8
9
|
readonly path: string;
|
|
9
10
|
readonly db: any;
|
|
@@ -29,14 +30,6 @@ export interface MountReadOnlyOptions {
|
|
|
29
30
|
readonly itemRoutes?: boolean;
|
|
30
31
|
/** The noun in the 405 message, which is free prose. Default "projection". */
|
|
31
32
|
readonly resource?: "projection" | "report";
|
|
32
|
-
/**
|
|
33
|
-
* The names of the view's `field.decimal` fields. A decimal is a canonical string on the wire
|
|
34
|
-
* on every dialect (ADR-0019 Amendment 1): Postgres and MySQL return the column's scale
|
|
35
|
-
* (`"100.5000"`), and SQLite hands back a number, a REAL for a computed one (a report's ratio,
|
|
36
|
-
* average or sum). Each row is rewritten under these keys. The generated route passes them;
|
|
37
|
-
* absent, rows are sent as read. Default none.
|
|
38
|
-
*/
|
|
39
|
-
readonly decimalColumns?: readonly string[];
|
|
40
33
|
}
|
|
41
34
|
export declare function mountReadOnlyCrudRoutes(opts: MountReadOnlyOptions): void;
|
|
42
35
|
export {};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mount-read-only.d.ts","sourceRoot":"","sources":["../../src/hono/mount-read-only.ts"],"names":[],"mappings":"AAIA,OAAO,EAAc,KAAK,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAChE,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAIjC,OAAO,KAAK,EACV,eAAe,EACf,aAAa,EACd,MAAM,wCAAwC,CAAC;
|
|
1
|
+
{"version":3,"file":"mount-read-only.d.ts","sourceRoot":"","sources":["../../src/hono/mount-read-only.ts"],"names":[],"mappings":"AAIA,OAAO,EAAc,KAAK,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAChE,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAIjC,OAAO,KAAK,EACV,eAAe,EACf,aAAa,EACd,MAAM,wCAAwC,CAAC;AAGhD,OAAO,EAAY,KAAK,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAK/D,KAAK,OAAO,GAAG,GAAG,CAAC;AAEnB,KAAK,OAAO,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,CAAC,CAAC;AAEnC,MAAM,WAAW,oBAAqB,SAAQ,WAAW;IACvD,QAAQ,CAAC,GAAG,EAAE,OAAO,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IAEtB,QAAQ,CAAC,EAAE,EAAE,GAAG,CAAC;IACjB,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;IACvB,QAAQ,CAAC,eAAe,EAAE,eAAe,CAAC;IAC1C,QAAQ,CAAC,aAAa,EAAE,aAAa,CAAC;IACtC,QAAQ,CAAC,OAAO,EAAE,UAAU,CAAC;IAC7B;;;;;;;;OAQG;IACH,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAC3B;;;;OAIG;IACH,QAAQ,CAAC,UAAU,CAAC,EAAE,OAAO,CAAC;IAC9B,8EAA8E;IAC9E,QAAQ,CAAC,QAAQ,CAAC,EAAE,YAAY,GAAG,QAAQ,CAAC;CAC7C;AAyED,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,oBAAoB,GAAG,IAAI,CAoIxE"}
|
|
@@ -7,7 +7,7 @@ import qs from "qs";
|
|
|
7
7
|
import { parseFilterParams, parsePageBound, RAW_VIEW_MAX_LIMIT, FilterParseError } from "../drizzle-fastify/filter-parser.js";
|
|
8
8
|
import { isTruthyFlag, coerceIdForColumn, rawIdLiteral, contractErrorCode, viewBaseConfig } from "../drizzle-fastify/util.js";
|
|
9
9
|
import { timestampWire } from "../timestamp-wire.js";
|
|
10
|
-
import {
|
|
10
|
+
import { withWire } from "../wire-format.js";
|
|
11
11
|
// An unexpected error on a mounted route answers `500 { error: "internal" }`.
|
|
12
12
|
import { guardRoute } from "./route-guard.js";
|
|
13
13
|
function resolveViewName(view) {
|
|
@@ -84,7 +84,7 @@ export function mountReadOnlyCrudRoutes(opts) {
|
|
|
84
84
|
const useRawSql = isEmptyColumnView(view) && !!viewName;
|
|
85
85
|
// The raw-SQL branch has no declared columns, so nothing names a timestamp there.
|
|
86
86
|
const timestamps = timestampWire(view);
|
|
87
|
-
const toWire =
|
|
87
|
+
const toWire = withWire(timestamps, opts);
|
|
88
88
|
// ── List ──────────────────────────────────────────────────────────────────
|
|
89
89
|
app.get(path, guardRoute(async (c) => {
|
|
90
90
|
try {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mount-read-only.js","sourceRoot":"","sources":["../../src/hono/mount-read-only.ts"],"names":[],"mappings":"AAAA,uEAAuE;AACvE,8EAA8E;AAC9E,4EAA4E;AAE5E,OAAO,EAAE,UAAU,EAAmB,MAAM,mBAAmB,CAAC;AAEhE,OAAO,EAAE,GAAG,EAAE,EAAE,EAAE,GAAG,EAAE,KAAK,EAAE,MAAM,aAAa,CAAC;AAClD,OAAO,EAAE,MAAM,IAAI,CAAC;AACpB,OAAO,EAAE,iBAAiB,EAAE,cAAc,EAAE,kBAAkB,EAAE,gBAAgB,EAAE,MAAM,qCAAqC,CAAC;AAK9H,OAAO,EAAE,YAAY,EAAE,iBAAiB,EAAE,YAAY,EAAE,iBAAiB,EAAE,cAAc,EAAE,MAAM,4BAA4B,CAAC;AAC9H,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AACrD,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"mount-read-only.js","sourceRoot":"","sources":["../../src/hono/mount-read-only.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { type DecimalWireMode } from "./decimal-wire.js";
|
|
2
|
+
/** `wire.nullMembers`. */
|
|
3
|
+
export type NullMembersMode = "native" | "emit" | "omit";
|
|
4
|
+
/**
|
|
5
|
+
* The declared shape of a value object held by a jsonb column: its member names, and the shape
|
|
6
|
+
* of any member that is itself a value object. `map` marks a `field.map` of value objects (the
|
|
7
|
+
* column's value is an object keyed by arbitrary text, each value a value object). An array
|
|
8
|
+
* (`isArray`) needs no flag: the walk reads the value's own shape.
|
|
9
|
+
*/
|
|
10
|
+
export interface ValueObjectShape {
|
|
11
|
+
readonly members: readonly string[];
|
|
12
|
+
readonly map?: boolean;
|
|
13
|
+
readonly nested?: Readonly<Record<string, ValueObjectShape>>;
|
|
14
|
+
}
|
|
15
|
+
/** The wire options every read-side mount takes beside its other options. */
|
|
16
|
+
export interface WireOptions {
|
|
17
|
+
/**
|
|
18
|
+
* The names of the entity's `field.decimal` fields. Each row sent is rewritten under these
|
|
19
|
+
* keys: `native` (default) leaves the string a driver returned and renders a SQLite number at
|
|
20
|
+
* 15 significant digits; `decimalWire: "string"` sends the canonical string (ADR-0019
|
|
21
|
+
* Amendment 1). The generated route passes them; absent, rows are sent as read.
|
|
22
|
+
*/
|
|
23
|
+
readonly decimalColumns?: readonly string[];
|
|
24
|
+
/** `wire.decimals`. Default `native`. */
|
|
25
|
+
readonly decimalWire?: DecimalWireMode;
|
|
26
|
+
/** `wire.nullMembers` for the members of the value objects under `valueObjects`. Default `native`. */
|
|
27
|
+
readonly nullMembers?: NullMembersMode;
|
|
28
|
+
/** The jsonb columns that hold a value object (or an array / map of them), by column name. */
|
|
29
|
+
readonly valueObjects?: Readonly<Record<string, ValueObjectShape>>;
|
|
30
|
+
}
|
|
31
|
+
/** Rewrite the value-object columns of a row per `mode`. `undefined` when there is nothing to do. */
|
|
32
|
+
export declare function nullMembersWire(shapes: Readonly<Record<string, ValueObjectShape>> | undefined, mode: NullMembersMode | undefined): ((row: unknown) => unknown) | undefined;
|
|
33
|
+
/** A wire mapper: `base` (timestamps), then the decimal rewrite, then the null-member rewrite. */
|
|
34
|
+
export declare function withWire(base: (row: unknown) => unknown, opts: WireOptions): (row: unknown) => unknown;
|
|
35
|
+
//# sourceMappingURL=wire-format.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"wire-format.d.ts","sourceRoot":"","sources":["../src/wire-format.ts"],"names":[],"mappings":"AAaA,OAAO,EAAgB,KAAK,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAEvE,0BAA0B;AAC1B,MAAM,MAAM,eAAe,GAAG,QAAQ,GAAG,MAAM,GAAG,MAAM,CAAC;AAEzD;;;;;GAKG;AACH,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;IACpC,QAAQ,CAAC,GAAG,CAAC,EAAE,OAAO,CAAC;IACvB,QAAQ,CAAC,MAAM,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,gBAAgB,CAAC,CAAC,CAAC;CAC9D;AAED,6EAA6E;AAC7E,MAAM,WAAW,WAAW;IAC1B;;;;;OAKG;IACH,QAAQ,CAAC,cAAc,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAC5C,yCAAyC;IACzC,QAAQ,CAAC,WAAW,CAAC,EAAE,eAAe,CAAC;IACvC,sGAAsG;IACtG,QAAQ,CAAC,WAAW,CAAC,EAAE,eAAe,CAAC;IACvC,8FAA8F;IAC9F,QAAQ,CAAC,YAAY,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,gBAAgB,CAAC,CAAC,CAAC;CACpE;AAkCD,qGAAqG;AACrG,wBAAgB,eAAe,CAC7B,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,gBAAgB,CAAC,CAAC,GAAG,SAAS,EAC9D,IAAI,EAAE,eAAe,GAAG,SAAS,GAChC,CAAC,CAAC,GAAG,EAAE,OAAO,KAAK,OAAO,CAAC,GAAG,SAAS,CAYzC;AAED,kGAAkG;AAClG,wBAAgB,QAAQ,CAAC,IAAI,EAAE,CAAC,GAAG,EAAE,OAAO,KAAK,OAAO,EAAE,IAAI,EAAE,WAAW,GAAG,CAAC,GAAG,EAAE,OAAO,KAAK,OAAO,CAItG"}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
// The OUTPUT form of a generated REST surface: the `wire` project setting (ADR-0019 Amendment 2).
|
|
2
|
+
// A generated route passes the options below to a mount helper; the helper rewrites each row it
|
|
3
|
+
// sends. Input rules do not follow the setting (decimal bodies are parsed by the generated write
|
|
4
|
+
// schemas, filter operands by the filter parser).
|
|
5
|
+
//
|
|
6
|
+
// decimals `native` (default): the string the TS type holds, not canonicalized
|
|
7
|
+
// (`decimalToNative`); `string`: the canonical W1 string (`decimalToWire`).
|
|
8
|
+
// nullMembers a value object's members. `native` (default): as stored. `omit`: a null member
|
|
9
|
+
// is dropped. `emit`: every DECLARED member is present, `null` when unset.
|
|
10
|
+
//
|
|
11
|
+
// Which columns are decimals and which hold value objects comes from the model, never from the
|
|
12
|
+
// shape of a value, so the generated route carries both as static data.
|
|
13
|
+
import { withDecimals } from "./decimal-wire.js";
|
|
14
|
+
function isPlainObject(v) {
|
|
15
|
+
return v !== null && typeof v === "object" && !Array.isArray(v);
|
|
16
|
+
}
|
|
17
|
+
function shapeMember(value, shape, mode) {
|
|
18
|
+
const out = {};
|
|
19
|
+
if (mode === "emit") {
|
|
20
|
+
// Every declared member, in declaration order, null when unset.
|
|
21
|
+
for (const m of shape.members)
|
|
22
|
+
out[m] = m in value && value[m] !== undefined ? value[m] : null;
|
|
23
|
+
for (const [k, v] of Object.entries(value))
|
|
24
|
+
if (!(k in out) && v !== undefined)
|
|
25
|
+
out[k] = v;
|
|
26
|
+
}
|
|
27
|
+
else {
|
|
28
|
+
for (const [k, v] of Object.entries(value))
|
|
29
|
+
if (v !== null && v !== undefined)
|
|
30
|
+
out[k] = v;
|
|
31
|
+
}
|
|
32
|
+
for (const [name, inner] of Object.entries(shape.nested ?? {})) {
|
|
33
|
+
if (name in out && out[name] !== null)
|
|
34
|
+
out[name] = shapeValue(out[name], inner, mode);
|
|
35
|
+
}
|
|
36
|
+
return out;
|
|
37
|
+
}
|
|
38
|
+
/** Apply `mode` to a stored value object, an array of them, or (for `shape.map`) a map of them. */
|
|
39
|
+
function shapeValue(value, shape, mode) {
|
|
40
|
+
if (Array.isArray(value))
|
|
41
|
+
return value.map((el) => shapeValue(el, shape, mode));
|
|
42
|
+
if (!isPlainObject(value))
|
|
43
|
+
return value;
|
|
44
|
+
if (shape.map === true) {
|
|
45
|
+
// Keys are arbitrary text, never compared with the value object's member names.
|
|
46
|
+
const out = {};
|
|
47
|
+
for (const [k, el] of Object.entries(value))
|
|
48
|
+
out[k] = isPlainObject(el) ? shapeMember(el, shape, mode) : el;
|
|
49
|
+
return out;
|
|
50
|
+
}
|
|
51
|
+
return shapeMember(value, shape, mode);
|
|
52
|
+
}
|
|
53
|
+
/** Rewrite the value-object columns of a row per `mode`. `undefined` when there is nothing to do. */
|
|
54
|
+
export function nullMembersWire(shapes, mode) {
|
|
55
|
+
if (shapes === undefined || mode === undefined || mode === "native")
|
|
56
|
+
return undefined;
|
|
57
|
+
const entries = Object.entries(shapes);
|
|
58
|
+
if (entries.length === 0)
|
|
59
|
+
return undefined;
|
|
60
|
+
return (row) => {
|
|
61
|
+
if (!isPlainObject(row))
|
|
62
|
+
return row;
|
|
63
|
+
const out = { ...row };
|
|
64
|
+
for (const [column, shape] of entries) {
|
|
65
|
+
if (column in out && out[column] !== null && out[column] !== undefined)
|
|
66
|
+
out[column] = shapeValue(out[column], shape, mode);
|
|
67
|
+
}
|
|
68
|
+
return out;
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
/** A wire mapper: `base` (timestamps), then the decimal rewrite, then the null-member rewrite. */
|
|
72
|
+
export function withWire(base, opts) {
|
|
73
|
+
const decimals = withDecimals(base, opts.decimalColumns, opts.decimalWire);
|
|
74
|
+
const members = nullMembersWire(opts.valueObjects, opts.nullMembers);
|
|
75
|
+
return members === undefined ? decimals : (row) => members(decimals(row));
|
|
76
|
+
}
|
|
77
|
+
//# sourceMappingURL=wire-format.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"wire-format.js","sourceRoot":"","sources":["../src/wire-format.ts"],"names":[],"mappings":"AAAA,kGAAkG;AAClG,gGAAgG;AAChG,iGAAiG;AACjG,kDAAkD;AAClD,EAAE;AACF,oFAAoF;AACpF,0FAA0F;AAC1F,+FAA+F;AAC/F,yFAAyF;AACzF,EAAE;AACF,+FAA+F;AAC/F,wEAAwE;AAExE,OAAO,EAAE,YAAY,EAAwB,MAAM,mBAAmB,CAAC;AAkCvE,SAAS,aAAa,CAAC,CAAU;IAC/B,OAAO,CAAC,KAAK,IAAI,IAAI,OAAO,CAAC,KAAK,QAAQ,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;AAClE,CAAC;AAED,SAAS,WAAW,CAAC,KAA8B,EAAE,KAAuB,EAAE,IAAqB;IACjG,MAAM,GAAG,GAA4B,EAAE,CAAC;IACxC,IAAI,IAAI,KAAK,MAAM,EAAE,CAAC;QACpB,gEAAgE;QAChE,KAAK,MAAM,CAAC,IAAI,KAAK,CAAC,OAAO;YAAE,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,IAAI,KAAK,IAAI,KAAK,CAAC,CAAC,CAAC,KAAK,SAAS,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;QAC/F,KAAK,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC;YAAE,IAAI,CAAC,CAAC,CAAC,IAAI,GAAG,CAAC,IAAI,CAAC,KAAK,SAAS;gBAAE,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;IAC7F,CAAC;SAAM,CAAC;QACN,KAAK,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC;YAAE,IAAI,CAAC,KAAK,IAAI,IAAI,CAAC,KAAK,SAAS;gBAAE,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;IAC5F,CAAC;IACD,KAAK,MAAM,CAAC,IAAI,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,MAAM,IAAI,EAAE,CAAC,EAAE,CAAC;QAC/D,IAAI,IAAI,IAAI,GAAG,IAAI,GAAG,CAAC,IAAI,CAAC,KAAK,IAAI;YAAE,GAAG,CAAC,IAAI,CAAC,GAAG,UAAU,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,IAAI,CAAC,CAAC;IACxF,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAED,mGAAmG;AACnG,SAAS,UAAU,CAAC,KAAc,EAAE,KAAuB,EAAE,IAAqB;IAChF,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC;QAAE,OAAO,KAAK,CAAC,GAAG,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,UAAU,CAAC,EAAE,EAAE,KAAK,EAAE,IAAI,CAAC,CAAC,CAAC;IAChF,IAAI,CAAC,aAAa,CAAC,KAAK,CAAC;QAAE,OAAO,KAAK,CAAC;IACxC,IAAI,KAAK,CAAC,GAAG,KAAK,IAAI,EAAE,CAAC;QACvB,gFAAgF;QAChF,MAAM,GAAG,GAA4B,EAAE,CAAC;QACxC,KAAK,MAAM,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC;YAAE,GAAG,CAAC,CAAC,CAAC,GAAG,aAAa,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,WAAW,CAAC,EAAE,EAAE,KAAK,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;QAC5G,OAAO,GAAG,CAAC;IACb,CAAC;IACD,OAAO,WAAW,CAAC,KAAK,EAAE,KAAK,EAAE,IAAI,CAAC,CAAC;AACzC,CAAC;AAED,qGAAqG;AACrG,MAAM,UAAU,eAAe,CAC7B,MAA8D,EAC9D,IAAiC;IAEjC,IAAI,MAAM,KAAK,SAAS,IAAI,IAAI,KAAK,SAAS,IAAI,IAAI,KAAK,QAAQ;QAAE,OAAO,SAAS,CAAC;IACtF,MAAM,OAAO,GAAG,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;IACvC,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,SAAS,CAAC;IAC3C,OAAO,CAAC,GAAG,EAAE,EAAE;QACb,IAAI,CAAC,aAAa,CAAC,GAAG,CAAC;YAAE,OAAO,GAAG,CAAC;QACpC,MAAM,GAAG,GAAG,EAAE,GAAG,GAAG,EAAE,CAAC;QACvB,KAAK,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,IAAI,OAAO,EAAE,CAAC;YACtC,IAAI,MAAM,IAAI,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,KAAK,IAAI,IAAI,GAAG,CAAC,MAAM,CAAC,KAAK,SAAS;gBAAE,GAAG,CAAC,MAAM,CAAC,GAAG,UAAU,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,KAAK,EAAE,IAAI,CAAC,CAAC;QAC7H,CAAC;QACD,OAAO,GAAG,CAAC;IACb,CAAC,CAAC;AACJ,CAAC;AAED,kGAAkG;AAClG,MAAM,UAAU,QAAQ,CAAC,IAA+B,EAAE,IAAiB;IACzE,MAAM,QAAQ,GAAG,YAAY,CAAC,IAAI,EAAE,IAAI,CAAC,cAAc,EAAE,IAAI,CAAC,WAAW,CAAC,CAAC;IAC3E,MAAM,OAAO,GAAG,eAAe,CAAC,IAAI,CAAC,YAAY,EAAE,IAAI,CAAC,WAAW,CAAC,CAAC;IACrE,OAAO,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC;AAC5E,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@metaobjectsdev/runtime-ts",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.2",
|
|
4
4
|
"description": "Node-side runtime helpers for MetaObjects: Fastify route builders, Drizzle filter/sort integration, Kysely drivers.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -64,8 +64,8 @@
|
|
|
64
64
|
"access": "public"
|
|
65
65
|
},
|
|
66
66
|
"dependencies": {
|
|
67
|
-
"@metaobjectsdev/metadata": "1.1.
|
|
68
|
-
"@metaobjectsdev/render": "1.1.
|
|
67
|
+
"@metaobjectsdev/metadata": "1.1.2",
|
|
68
|
+
"@metaobjectsdev/render": "1.1.2",
|
|
69
69
|
"qs": "^6.13.0"
|
|
70
70
|
},
|
|
71
71
|
"peerDependencies": {
|
package/src/decimal-wire.ts
CHANGED
|
@@ -125,20 +125,39 @@ export function decimalToWire(value: unknown): unknown {
|
|
|
125
125
|
}
|
|
126
126
|
|
|
127
127
|
/**
|
|
128
|
-
*
|
|
129
|
-
*
|
|
130
|
-
*
|
|
131
|
-
*
|
|
128
|
+
* The `native` output form (`wire.decimals: native`, ADR-0019 Amendment 2): the string the
|
|
129
|
+
* in-process type holds, NOT canonicalized. A string a driver handed back (Postgres `numeric`,
|
|
130
|
+
* MySQL `DECIMAL`, SQLite text) passes through as it is (`"12.50"` stays `"12.50"`), a number
|
|
131
|
+
* (a SQLite or D1 REAL or INTEGER) is rendered at 15 significant digits (W4, because the TS type
|
|
132
|
+
* is `string`), a bigint is its digits, and an array is rewritten element by element.
|
|
133
|
+
*/
|
|
134
|
+
export function decimalToNative(value: unknown): unknown {
|
|
135
|
+
if (Array.isArray(value)) return value.map(decimalToNative);
|
|
136
|
+
if (typeof value === "number") return decimalFromDouble15(value) ?? value;
|
|
137
|
+
if (typeof value === "bigint") return value.toString();
|
|
138
|
+
return value;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/** How a decimal is written: `native` (default) or the canonical `string` (`wire.decimals`). */
|
|
142
|
+
export type DecimalWireMode = "native" | "string";
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Rewrite the values under `columns` to their wire form (`mode`, default `native`). Returns
|
|
146
|
+
* `undefined` when there is nothing to do. Which keys are decimals comes from the caller (the
|
|
147
|
+
* generated route passes the names of the entity's `field.decimal` fields), never from the shape
|
|
148
|
+
* of a value: an integer column is not a decimal because it is a number.
|
|
132
149
|
*/
|
|
133
150
|
export function decimalWire(
|
|
134
151
|
columns: readonly string[] | undefined,
|
|
152
|
+
mode: DecimalWireMode = "native",
|
|
135
153
|
): ((row: unknown) => unknown) | undefined {
|
|
136
154
|
if (columns === undefined || columns.length === 0) return undefined;
|
|
155
|
+
const convert = mode === "string" ? decimalToWire : decimalToNative;
|
|
137
156
|
return (row) => {
|
|
138
157
|
if (row === null || typeof row !== "object") return row;
|
|
139
158
|
const out = { ...(row as Record<string, unknown>) };
|
|
140
159
|
for (const c of columns) {
|
|
141
|
-
if (c in out) out[c] =
|
|
160
|
+
if (c in out) out[c] = convert(out[c]);
|
|
142
161
|
}
|
|
143
162
|
return out;
|
|
144
163
|
};
|
|
@@ -148,7 +167,8 @@ export function decimalWire(
|
|
|
148
167
|
export function withDecimals(
|
|
149
168
|
base: (row: unknown) => unknown,
|
|
150
169
|
columns: readonly string[] | undefined,
|
|
170
|
+
mode: DecimalWireMode = "native",
|
|
151
171
|
): (row: unknown) => unknown {
|
|
152
|
-
const decimals = decimalWire(columns);
|
|
172
|
+
const decimals = decimalWire(columns, mode);
|
|
153
173
|
return decimals === undefined ? base : (row) => decimals(base(row));
|
|
154
174
|
}
|
|
@@ -29,13 +29,15 @@ export type { FilterAllowlist, SortAllowlist } from "./filter-allowlist.js";
|
|
|
29
29
|
import { parseFilterParams, parsePageBound, FilterParseError } from "./filter-parser.js";
|
|
30
30
|
import { isTruthyFlag, contractErrorCode, coerceIdForColumn, firstRow, extractRowCount } from "./util.js";
|
|
31
31
|
import { timestampWire } from "../timestamp-wire.js";
|
|
32
|
-
import {
|
|
32
|
+
import { withWire, type WireOptions } from "../wire-format.js";
|
|
33
33
|
import { supportsReturning, type SqlDialect } from "../sql-dialect.js";
|
|
34
34
|
import { withContractErrorHandler } from "./route-error-handler.js";
|
|
35
35
|
import { validationErrorBody } from "../route-errors.js";
|
|
36
36
|
export { isTruthyFlag, contractErrorCode, parseId, coerceIdForColumn } from "./util.js";
|
|
37
37
|
export { timestampWire, canonicalTimestamp } from "../timestamp-wire.js";
|
|
38
|
-
export { decimalWire, withDecimals, canonicalDecimal, parseDecimalInput, parseDecimalOperand } from "../decimal-wire.js";
|
|
38
|
+
export { decimalWire, withDecimals, decimalToNative, canonicalDecimal, parseDecimalInput, parseDecimalOperand } from "../decimal-wire.js";
|
|
39
|
+
export { withWire, nullMembersWire } from "../wire-format.js";
|
|
40
|
+
export type { WireOptions, ValueObjectShape, NullMembersMode } from "../wire-format.js";
|
|
39
41
|
|
|
40
42
|
// ---------------------------------------------------------------------------
|
|
41
43
|
// Loose types — we don't bind to a specific Drizzle backend so the helper
|
|
@@ -49,7 +51,7 @@ type AnyTable = any;
|
|
|
49
51
|
|
|
50
52
|
export type CrudVerb = "list" | "get" | "create" | "update" | "delete";
|
|
51
53
|
|
|
52
|
-
export interface CrudRoutesOptions {
|
|
54
|
+
export interface CrudRoutesOptions extends WireOptions {
|
|
53
55
|
fastify: FastifyInstance;
|
|
54
56
|
/** REST resource path, e.g. "/subscribers". */
|
|
55
57
|
path: string;
|
|
@@ -86,13 +88,6 @@ export interface CrudRoutesOptions {
|
|
|
86
88
|
updateMethod?: "patch" | "put";
|
|
87
89
|
filterAllowlist?: FilterAllowlist;
|
|
88
90
|
sortAllowlist?: SortAllowlist;
|
|
89
|
-
/**
|
|
90
|
-
* The names of the entity's `field.decimal` fields. A decimal is a canonical string on the
|
|
91
|
-
* wire on every dialect (ADR-0019 Amendment 1): Postgres and MySQL return the column's scale
|
|
92
|
-
* (`"100.5000"`) and SQLite hands back a number, so each row the routes send is rewritten
|
|
93
|
-
* under these keys. The generated route passes them; absent, rows are sent as read.
|
|
94
|
-
*/
|
|
95
|
-
decimalColumns?: readonly string[];
|
|
96
91
|
/** Dialect — required if filterAllowlist or sortAllowlist is set (for dialect-specific `like` lowering: SQLite lowers to GLOB to stay case-sensitive; ADR-0049). */
|
|
97
92
|
dialect?: SqlDialect;
|
|
98
93
|
/**
|
|
@@ -131,7 +126,7 @@ function readSource(opts: VerbOptions): AnyTable {
|
|
|
131
126
|
* non-timestamp of the same name.
|
|
132
127
|
*/
|
|
133
128
|
function readWire(opts: VerbOptions): (row: unknown) => unknown {
|
|
134
|
-
return
|
|
129
|
+
return withWire(timestampWire(readSource(opts)), opts);
|
|
135
130
|
}
|
|
136
131
|
|
|
137
132
|
/**
|
|
@@ -140,7 +135,7 @@ function readWire(opts: VerbOptions): (row: unknown) => unknown {
|
|
|
140
135
|
* against the union of both sources.
|
|
141
136
|
*/
|
|
142
137
|
function echoWire(opts: VerbOptions): (row: unknown) => unknown {
|
|
143
|
-
return
|
|
138
|
+
return withWire(timestampWire(opts.table, opts.readView), opts);
|
|
144
139
|
}
|
|
145
140
|
|
|
146
141
|
/** #214 — after a write to the base table, re-read the row THROUGH the replica
|
|
@@ -34,7 +34,7 @@ import type { FastifyInstance, RouteShorthandOptions } from "fastify";
|
|
|
34
34
|
import { and, eq, or, inArray } from "drizzle-orm";
|
|
35
35
|
import { coerceIdForColumn } from "./util.js";
|
|
36
36
|
import { timestampWire } from "../timestamp-wire.js";
|
|
37
|
-
import {
|
|
37
|
+
import { withWire, type WireOptions } from "../wire-format.js";
|
|
38
38
|
import { withContractErrorHandler } from "./route-error-handler.js";
|
|
39
39
|
|
|
40
40
|
// Loose Drizzle types — the helper works across libsql / better-sqlite3 / pg.
|
|
@@ -43,7 +43,7 @@ type AnyDrizzle = any;
|
|
|
43
43
|
// biome-ignore lint/suspicious/noExplicitAny: dynamic dispatch over user's Drizzle table
|
|
44
44
|
type AnyTable = any;
|
|
45
45
|
|
|
46
|
-
export interface M2mRouteOptions {
|
|
46
|
+
export interface M2mRouteOptions extends WireOptions {
|
|
47
47
|
fastify: FastifyInstance;
|
|
48
48
|
/** Source resource path, e.g. "/posts". The route mounts at `{path}/:id/{relationName}`. */
|
|
49
49
|
path: string;
|
|
@@ -61,8 +61,6 @@ export interface M2mRouteOptions {
|
|
|
61
61
|
targetColumn: string;
|
|
62
62
|
/** Target entity PK column (physical column name, e.g. "id"). Defaults to "id". */
|
|
63
63
|
targetPkColumn?: string;
|
|
64
|
-
/** The names of the TARGET entity's `field.decimal` fields, sent as canonical strings (ADR-0019 Amendment 1). */
|
|
65
|
-
decimalColumns?: readonly string[];
|
|
66
64
|
/** Undirected self-join: union both junction FK columns on read. */
|
|
67
65
|
symmetric: boolean;
|
|
68
66
|
/**
|
|
@@ -90,7 +88,7 @@ export function mountM2mRoute(opts: M2mRouteOptions): void {
|
|
|
90
88
|
const route = `${opts.path}/:id/${opts.relationName}`;
|
|
91
89
|
// Route-scoped contract error handler — see route-error-handler.ts.
|
|
92
90
|
const ro = withContractErrorHandler(opts.routeOptions);
|
|
93
|
-
const toWire =
|
|
91
|
+
const toWire = withWire(timestampWire(opts.targetTable), opts);
|
|
94
92
|
|
|
95
93
|
opts.fastify.get(route, ro, async (req, reply) => {
|
|
96
94
|
const { id } = req.params as { id: string };
|
|
@@ -6,13 +6,13 @@ import { parseFilterParams, parsePageBound, RAW_VIEW_MAX_LIMIT, FilterParseError
|
|
|
6
6
|
import type { FilterAllowlist, SortAllowlist } from "./filter-allowlist.js";
|
|
7
7
|
import { isTruthyFlag, contractErrorCode, coerceIdForColumn, rawIdLiteral, viewBaseConfig } from "./util.js";
|
|
8
8
|
import { timestampWire } from "../timestamp-wire.js";
|
|
9
|
-
import {
|
|
9
|
+
import { withWire, type WireOptions } from "../wire-format.js";
|
|
10
10
|
import { withContractErrorHandler } from "./route-error-handler.js";
|
|
11
11
|
|
|
12
12
|
// biome-ignore lint/suspicious/noExplicitAny: dynamic dispatch over user-supplied views
|
|
13
13
|
type AnyView = any;
|
|
14
14
|
|
|
15
|
-
export interface MountReadOnlyOptions {
|
|
15
|
+
export interface MountReadOnlyOptions extends WireOptions {
|
|
16
16
|
readonly fastify: FastifyInstance;
|
|
17
17
|
readonly path: string;
|
|
18
18
|
// biome-ignore lint/suspicious/noExplicitAny: dynamic Drizzle client
|
|
@@ -50,14 +50,6 @@ export interface MountReadOnlyOptions {
|
|
|
50
50
|
readonly itemRoutes?: boolean;
|
|
51
51
|
/** The noun in the 405 message, which is free prose. Default "projection". */
|
|
52
52
|
readonly resource?: "projection" | "report";
|
|
53
|
-
/**
|
|
54
|
-
* The names of the view's `field.decimal` fields. A decimal is a canonical string on the wire
|
|
55
|
-
* on every dialect (ADR-0019 Amendment 1): Postgres and MySQL return the column's scale
|
|
56
|
-
* (`"100.5000"`), and SQLite hands back a number, a REAL for a computed one (a report's ratio,
|
|
57
|
-
* average or sum). Each row is rewritten under these keys. The generated route passes them;
|
|
58
|
-
* absent, rows are sent as read. Default none.
|
|
59
|
-
*/
|
|
60
|
-
readonly decimalColumns?: readonly string[];
|
|
61
53
|
}
|
|
62
54
|
|
|
63
55
|
const rejectMutation = (resource: string) => async (
|
|
@@ -160,7 +152,7 @@ export function mountReadOnlyCrudRoutes(opts: MountReadOnlyOptions): void {
|
|
|
160
152
|
const useRawSql = isEmptyColumnView(view) && !!viewName;
|
|
161
153
|
// The raw-SQL branch has no declared columns, so nothing names a timestamp there.
|
|
162
154
|
const timestamps = timestampWire(view);
|
|
163
|
-
const toWire =
|
|
155
|
+
const toWire = withWire(timestamps, opts);
|
|
164
156
|
|
|
165
157
|
// ── List ──────────────────────────────────────────────────────────────────
|
|
166
158
|
fastify.get(path, ro, async (req, reply) => {
|
package/src/hono/index.ts
CHANGED
|
@@ -45,7 +45,7 @@ export type {
|
|
|
45
45
|
import { isTruthyFlag, coerceIdForColumn, firstRow, extractRowCount } from "../drizzle-fastify/util.js";
|
|
46
46
|
import { supportsReturning, type SqlDialect } from "../sql-dialect.js";
|
|
47
47
|
import { timestampWire } from "../timestamp-wire.js";
|
|
48
|
-
import {
|
|
48
|
+
import { withWire, type WireOptions } from "../wire-format.js";
|
|
49
49
|
// Every handler below is wrapped in guardRoute (an unexpected error answers
|
|
50
50
|
// `500 { error: "internal" }`, logged server-side) and reads its body through
|
|
51
51
|
// readJsonBody (a malformed body answers `400 { error: "invalid_json" }`).
|
|
@@ -54,7 +54,9 @@ import { guardRoute, readJsonBody } from "./route-guard.js";
|
|
|
54
54
|
// shared (deprecated) helper so the three adapters can't silently diverge.
|
|
55
55
|
export { parseId } from "../drizzle-fastify/util.js";
|
|
56
56
|
export { timestampWire, canonicalTimestamp } from "../timestamp-wire.js";
|
|
57
|
-
export { decimalWire, withDecimals, canonicalDecimal, parseDecimalInput, parseDecimalOperand } from "../decimal-wire.js";
|
|
57
|
+
export { decimalWire, withDecimals, decimalToNative, canonicalDecimal, parseDecimalInput, parseDecimalOperand } from "../decimal-wire.js";
|
|
58
|
+
export { withWire, nullMembersWire } from "../wire-format.js";
|
|
59
|
+
export type { WireOptions, ValueObjectShape, NullMembersMode } from "../wire-format.js";
|
|
58
60
|
|
|
59
61
|
// ---------------------------------------------------------------------------
|
|
60
62
|
// Loose types — we don't bind to a specific Drizzle backend so the helper
|
|
@@ -70,7 +72,7 @@ type AnyHono = Hono<any, any, any>;
|
|
|
70
72
|
|
|
71
73
|
export type CrudVerb = "list" | "get" | "create" | "update" | "delete";
|
|
72
74
|
|
|
73
|
-
export interface CrudRoutesOptions {
|
|
75
|
+
export interface CrudRoutesOptions extends WireOptions {
|
|
74
76
|
/** Hono app instance. Bindings type is the consumer's — kept generic here. */
|
|
75
77
|
app: AnyHono;
|
|
76
78
|
/** REST resource path, e.g. "/subscribers". */
|
|
@@ -106,13 +108,6 @@ export interface CrudRoutesOptions {
|
|
|
106
108
|
updateMethod?: "patch" | "put";
|
|
107
109
|
filterAllowlist?: FilterAllowlist;
|
|
108
110
|
sortAllowlist?: SortAllowlist;
|
|
109
|
-
/**
|
|
110
|
-
* The names of the entity's `field.decimal` fields. A decimal is a canonical string on the
|
|
111
|
-
* wire on every dialect (ADR-0019 Amendment 1): Postgres and MySQL return the column's scale
|
|
112
|
-
* (`"100.5000"`) and SQLite hands back a number, so each row the routes send is rewritten
|
|
113
|
-
* under these keys. The generated route passes them; absent, rows are sent as read.
|
|
114
|
-
*/
|
|
115
|
-
decimalColumns?: readonly string[];
|
|
116
111
|
/** Dialect — required if filterAllowlist or sortAllowlist is set (for dialect-specific `like` lowering: SQLite lowers to GLOB to stay case-sensitive; ADR-0049). */
|
|
117
112
|
dialect?: SqlDialect;
|
|
118
113
|
}
|
|
@@ -159,7 +154,7 @@ function readSource(opts: VerbOptions): AnyTable {
|
|
|
159
154
|
* non-timestamp of the same name.
|
|
160
155
|
*/
|
|
161
156
|
function readWire(opts: VerbOptions): (row: unknown) => unknown {
|
|
162
|
-
return
|
|
157
|
+
return withWire(timestampWire(readSource(opts)), opts);
|
|
163
158
|
}
|
|
164
159
|
|
|
165
160
|
/**
|
|
@@ -168,7 +163,7 @@ function readWire(opts: VerbOptions): (row: unknown) => unknown {
|
|
|
168
163
|
* against the union of both sources.
|
|
169
164
|
*/
|
|
170
165
|
function echoWire(opts: VerbOptions): (row: unknown) => unknown {
|
|
171
|
-
return
|
|
166
|
+
return withWire(timestampWire(opts.table, opts.readView), opts);
|
|
172
167
|
}
|
|
173
168
|
|
|
174
169
|
/** Re-read a just-written row through the replica view so the response carries the
|
|
@@ -13,7 +13,7 @@ import type {
|
|
|
13
13
|
} from "../drizzle-fastify/filter-allowlist.js";
|
|
14
14
|
import { isTruthyFlag, coerceIdForColumn, rawIdLiteral, contractErrorCode, viewBaseConfig } from "../drizzle-fastify/util.js";
|
|
15
15
|
import { timestampWire } from "../timestamp-wire.js";
|
|
16
|
-
import {
|
|
16
|
+
import { withWire, type WireOptions } from "../wire-format.js";
|
|
17
17
|
// An unexpected error on a mounted route answers `500 { error: "internal" }`.
|
|
18
18
|
import { guardRoute } from "./route-guard.js";
|
|
19
19
|
|
|
@@ -22,7 +22,7 @@ type AnyView = any;
|
|
|
22
22
|
// biome-ignore lint/suspicious/noExplicitAny: generic Hono app
|
|
23
23
|
type AnyHono = Hono<any, any, any>;
|
|
24
24
|
|
|
25
|
-
export interface MountReadOnlyOptions {
|
|
25
|
+
export interface MountReadOnlyOptions extends WireOptions {
|
|
26
26
|
readonly app: AnyHono;
|
|
27
27
|
readonly path: string;
|
|
28
28
|
// biome-ignore lint/suspicious/noExplicitAny: dynamic Drizzle client
|
|
@@ -49,14 +49,6 @@ export interface MountReadOnlyOptions {
|
|
|
49
49
|
readonly itemRoutes?: boolean;
|
|
50
50
|
/** The noun in the 405 message, which is free prose. Default "projection". */
|
|
51
51
|
readonly resource?: "projection" | "report";
|
|
52
|
-
/**
|
|
53
|
-
* The names of the view's `field.decimal` fields. A decimal is a canonical string on the wire
|
|
54
|
-
* on every dialect (ADR-0019 Amendment 1): Postgres and MySQL return the column's scale
|
|
55
|
-
* (`"100.5000"`), and SQLite hands back a number, a REAL for a computed one (a report's ratio,
|
|
56
|
-
* average or sum). Each row is rewritten under these keys. The generated route passes them;
|
|
57
|
-
* absent, rows are sent as read. Default none.
|
|
58
|
-
*/
|
|
59
|
-
readonly decimalColumns?: readonly string[];
|
|
60
52
|
}
|
|
61
53
|
|
|
62
54
|
function resolveViewName(view: AnyView): string | undefined {
|
|
@@ -139,7 +131,7 @@ export function mountReadOnlyCrudRoutes(opts: MountReadOnlyOptions): void {
|
|
|
139
131
|
const useRawSql = isEmptyColumnView(view) && !!viewName;
|
|
140
132
|
// The raw-SQL branch has no declared columns, so nothing names a timestamp there.
|
|
141
133
|
const timestamps = timestampWire(view);
|
|
142
|
-
const toWire =
|
|
134
|
+
const toWire = withWire(timestamps, opts);
|
|
143
135
|
|
|
144
136
|
// ── List ──────────────────────────────────────────────────────────────────
|
|
145
137
|
app.get(path, guardRoute(async (c) => {
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
// The OUTPUT form of a generated REST surface: the `wire` project setting (ADR-0019 Amendment 2).
|
|
2
|
+
// A generated route passes the options below to a mount helper; the helper rewrites each row it
|
|
3
|
+
// sends. Input rules do not follow the setting (decimal bodies are parsed by the generated write
|
|
4
|
+
// schemas, filter operands by the filter parser).
|
|
5
|
+
//
|
|
6
|
+
// decimals `native` (default): the string the TS type holds, not canonicalized
|
|
7
|
+
// (`decimalToNative`); `string`: the canonical W1 string (`decimalToWire`).
|
|
8
|
+
// nullMembers a value object's members. `native` (default): as stored. `omit`: a null member
|
|
9
|
+
// is dropped. `emit`: every DECLARED member is present, `null` when unset.
|
|
10
|
+
//
|
|
11
|
+
// Which columns are decimals and which hold value objects comes from the model, never from the
|
|
12
|
+
// shape of a value, so the generated route carries both as static data.
|
|
13
|
+
|
|
14
|
+
import { withDecimals, type DecimalWireMode } from "./decimal-wire.js";
|
|
15
|
+
|
|
16
|
+
/** `wire.nullMembers`. */
|
|
17
|
+
export type NullMembersMode = "native" | "emit" | "omit";
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* The declared shape of a value object held by a jsonb column: its member names, and the shape
|
|
21
|
+
* of any member that is itself a value object. `map` marks a `field.map` of value objects (the
|
|
22
|
+
* column's value is an object keyed by arbitrary text, each value a value object). An array
|
|
23
|
+
* (`isArray`) needs no flag: the walk reads the value's own shape.
|
|
24
|
+
*/
|
|
25
|
+
export interface ValueObjectShape {
|
|
26
|
+
readonly members: readonly string[];
|
|
27
|
+
readonly map?: boolean;
|
|
28
|
+
readonly nested?: Readonly<Record<string, ValueObjectShape>>;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** The wire options every read-side mount takes beside its other options. */
|
|
32
|
+
export interface WireOptions {
|
|
33
|
+
/**
|
|
34
|
+
* The names of the entity's `field.decimal` fields. Each row sent is rewritten under these
|
|
35
|
+
* keys: `native` (default) leaves the string a driver returned and renders a SQLite number at
|
|
36
|
+
* 15 significant digits; `decimalWire: "string"` sends the canonical string (ADR-0019
|
|
37
|
+
* Amendment 1). The generated route passes them; absent, rows are sent as read.
|
|
38
|
+
*/
|
|
39
|
+
readonly decimalColumns?: readonly string[];
|
|
40
|
+
/** `wire.decimals`. Default `native`. */
|
|
41
|
+
readonly decimalWire?: DecimalWireMode;
|
|
42
|
+
/** `wire.nullMembers` for the members of the value objects under `valueObjects`. Default `native`. */
|
|
43
|
+
readonly nullMembers?: NullMembersMode;
|
|
44
|
+
/** The jsonb columns that hold a value object (or an array / map of them), by column name. */
|
|
45
|
+
readonly valueObjects?: Readonly<Record<string, ValueObjectShape>>;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function isPlainObject(v: unknown): v is Record<string, unknown> {
|
|
49
|
+
return v !== null && typeof v === "object" && !Array.isArray(v);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function shapeMember(value: Record<string, unknown>, shape: ValueObjectShape, mode: NullMembersMode): Record<string, unknown> {
|
|
53
|
+
const out: Record<string, unknown> = {};
|
|
54
|
+
if (mode === "emit") {
|
|
55
|
+
// Every declared member, in declaration order, null when unset.
|
|
56
|
+
for (const m of shape.members) out[m] = m in value && value[m] !== undefined ? value[m] : null;
|
|
57
|
+
for (const [k, v] of Object.entries(value)) if (!(k in out) && v !== undefined) out[k] = v;
|
|
58
|
+
} else {
|
|
59
|
+
for (const [k, v] of Object.entries(value)) if (v !== null && v !== undefined) out[k] = v;
|
|
60
|
+
}
|
|
61
|
+
for (const [name, inner] of Object.entries(shape.nested ?? {})) {
|
|
62
|
+
if (name in out && out[name] !== null) out[name] = shapeValue(out[name], inner, mode);
|
|
63
|
+
}
|
|
64
|
+
return out;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** Apply `mode` to a stored value object, an array of them, or (for `shape.map`) a map of them. */
|
|
68
|
+
function shapeValue(value: unknown, shape: ValueObjectShape, mode: NullMembersMode): unknown {
|
|
69
|
+
if (Array.isArray(value)) return value.map((el) => shapeValue(el, shape, mode));
|
|
70
|
+
if (!isPlainObject(value)) return value;
|
|
71
|
+
if (shape.map === true) {
|
|
72
|
+
// Keys are arbitrary text, never compared with the value object's member names.
|
|
73
|
+
const out: Record<string, unknown> = {};
|
|
74
|
+
for (const [k, el] of Object.entries(value)) out[k] = isPlainObject(el) ? shapeMember(el, shape, mode) : el;
|
|
75
|
+
return out;
|
|
76
|
+
}
|
|
77
|
+
return shapeMember(value, shape, mode);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/** Rewrite the value-object columns of a row per `mode`. `undefined` when there is nothing to do. */
|
|
81
|
+
export function nullMembersWire(
|
|
82
|
+
shapes: Readonly<Record<string, ValueObjectShape>> | undefined,
|
|
83
|
+
mode: NullMembersMode | undefined,
|
|
84
|
+
): ((row: unknown) => unknown) | undefined {
|
|
85
|
+
if (shapes === undefined || mode === undefined || mode === "native") return undefined;
|
|
86
|
+
const entries = Object.entries(shapes);
|
|
87
|
+
if (entries.length === 0) return undefined;
|
|
88
|
+
return (row) => {
|
|
89
|
+
if (!isPlainObject(row)) return row;
|
|
90
|
+
const out = { ...row };
|
|
91
|
+
for (const [column, shape] of entries) {
|
|
92
|
+
if (column in out && out[column] !== null && out[column] !== undefined) out[column] = shapeValue(out[column], shape, mode);
|
|
93
|
+
}
|
|
94
|
+
return out;
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/** A wire mapper: `base` (timestamps), then the decimal rewrite, then the null-member rewrite. */
|
|
99
|
+
export function withWire(base: (row: unknown) => unknown, opts: WireOptions): (row: unknown) => unknown {
|
|
100
|
+
const decimals = withDecimals(base, opts.decimalColumns, opts.decimalWire);
|
|
101
|
+
const members = nullMembersWire(opts.valueObjects, opts.nullMembers);
|
|
102
|
+
return members === undefined ? decimals : (row) => members(decimals(row));
|
|
103
|
+
}
|