@metaobjectsdev/runtime-ts 0.9.0 → 0.11.0-rc.1

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.
Files changed (75) hide show
  1. package/dist/drivers/drizzle-driver.d.ts.map +1 -1
  2. package/dist/drivers/drizzle-driver.js +5 -1
  3. package/dist/drivers/drizzle-driver.js.map +1 -1
  4. package/dist/drivers/in-memory-driver.js +2 -0
  5. package/dist/drivers/in-memory-driver.js.map +1 -1
  6. package/dist/drivers/kysely-driver.js +1 -0
  7. package/dist/drivers/kysely-driver.js.map +1 -1
  8. package/dist/drizzle-fastify/index.d.ts +16 -2
  9. package/dist/drizzle-fastify/index.d.ts.map +1 -1
  10. package/dist/drizzle-fastify/index.js +45 -13
  11. package/dist/drizzle-fastify/index.js.map +1 -1
  12. package/dist/drizzle-fastify/mount-m2m.d.ts +29 -0
  13. package/dist/drizzle-fastify/mount-m2m.d.ts.map +1 -0
  14. package/dist/drizzle-fastify/mount-m2m.js +94 -0
  15. package/dist/drizzle-fastify/mount-m2m.js.map +1 -0
  16. package/dist/drizzle-fastify/util.d.ts +2 -0
  17. package/dist/drizzle-fastify/util.d.ts.map +1 -1
  18. package/dist/drizzle-fastify/util.js +5 -0
  19. package/dist/drizzle-fastify/util.js.map +1 -1
  20. package/dist/extract-object.d.ts.map +1 -1
  21. package/dist/extract-object.js +14 -5
  22. package/dist/extract-object.js.map +1 -1
  23. package/dist/identity-strategy.d.ts.map +1 -1
  24. package/dist/identity-strategy.js +2 -1
  25. package/dist/identity-strategy.js.map +1 -1
  26. package/dist/index.d.ts +3 -0
  27. package/dist/index.d.ts.map +1 -1
  28. package/dist/index.js +5 -0
  29. package/dist/index.js.map +1 -1
  30. package/dist/llm-recorder.d.ts +72 -0
  31. package/dist/llm-recorder.d.ts.map +1 -0
  32. package/dist/llm-recorder.js +82 -0
  33. package/dist/llm-recorder.js.map +1 -0
  34. package/dist/n2m-resolver.d.ts +7 -8
  35. package/dist/n2m-resolver.d.ts.map +1 -1
  36. package/dist/n2m-resolver.js +115 -38
  37. package/dist/n2m-resolver.js.map +1 -1
  38. package/dist/object-manager.d.ts +4 -0
  39. package/dist/object-manager.d.ts.map +1 -1
  40. package/dist/object-manager.js +71 -18
  41. package/dist/object-manager.js.map +1 -1
  42. package/dist/persistence-driver.d.ts +3 -0
  43. package/dist/persistence-driver.d.ts.map +1 -1
  44. package/dist/query-builder.d.ts +13 -3
  45. package/dist/query-builder.d.ts.map +1 -1
  46. package/dist/query-builder.js +19 -10
  47. package/dist/query-builder.js.map +1 -1
  48. package/dist/tph.d.ts +14 -0
  49. package/dist/tph.d.ts.map +1 -0
  50. package/dist/tph.js +37 -0
  51. package/dist/tph.js.map +1 -0
  52. package/dist/type-coercer.d.ts.map +1 -1
  53. package/dist/type-coercer.js +91 -8
  54. package/dist/type-coercer.js.map +1 -1
  55. package/dist/validator-runner.d.ts.map +1 -1
  56. package/dist/validator-runner.js +24 -3
  57. package/dist/validator-runner.js.map +1 -1
  58. package/package.json +62 -51
  59. package/src/drivers/drizzle-driver.ts +5 -0
  60. package/src/drivers/in-memory-driver.ts +2 -0
  61. package/src/drivers/kysely-driver.ts +1 -0
  62. package/src/drizzle-fastify/index.ts +55 -14
  63. package/src/drizzle-fastify/mount-m2m.ts +126 -0
  64. package/src/drizzle-fastify/util.ts +6 -0
  65. package/src/extract-object.ts +16 -6
  66. package/src/identity-strategy.ts +2 -1
  67. package/src/index.ts +7 -0
  68. package/src/llm-recorder.ts +166 -0
  69. package/src/n2m-resolver.ts +143 -57
  70. package/src/object-manager.ts +67 -18
  71. package/src/persistence-driver.ts +2 -1
  72. package/src/query-builder.ts +33 -8
  73. package/src/tph.ts +46 -0
  74. package/src/type-coercer.ts +94 -8
  75. package/src/validator-runner.ts +23 -3
@@ -1,18 +1,100 @@
1
- // v0.1 only handles SQLite's int↔boolean. Date/timestamp coercion (ISO string Date) is deferred.
2
- import { TYPE_FIELD, FIELD_SUBTYPE_BOOLEAN } from "@metaobjectsdev/metadata";
1
+ // Read/write value coercion at the persistence boundary.
2
+ //
3
+ // - SQLite has no native boolean: booleans are stored as 0/1 ints, so we map
4
+ // boolean↔int on the way in/out for that dialect only.
5
+ // - JSONB-backed object/map fields: the driver (node-postgres) does NOT
6
+ // serialize a plain JS object to a jsonb column — it must arrive as a JSON
7
+ // string, or the write fails / writes "[object Object]". A `field.object`
8
+ // (default + `@storage: jsonb`) and any field carrying `@dbColumnType: jsonb`
9
+ // whose value is an object are JSON.stringify'd on write. (Read-back parsing
10
+ // is handled by node-pg's jsonb parser, which returns a parsed object.)
11
+ //
12
+ // Date/timestamp coercion (ISO string ↔ Date) is deferred: pg accepts ISO
13
+ // strings for temporal columns directly, and the temporal read parsers pin the
14
+ // wire form.
15
+ import { TYPE_FIELD, FIELD_SUBTYPE_BOOLEAN, FIELD_SUBTYPE_OBJECT, FIELD_ATTR_STORAGE, FIELD_ATTR_DB_COLUMN_TYPE, STORAGE_JSONB, STORAGE_FLATTENED, DB_COLUMN_TYPE_JSONB, } from "@metaobjectsdev/metadata";
3
16
  export function coerceRowOnRead(entity, row, dialect) {
17
+ const hydrated = deserializeJsonbObjectFields(entity, row);
4
18
  if (dialect !== "sqlite")
5
- return row;
6
- return mapBooleansFromInt(entity, row);
19
+ return hydrated;
20
+ return mapBooleansFromInt(entity, hydrated);
7
21
  }
8
22
  export function coerceRowOnWrite(entity, row, dialect) {
23
+ const jsonbColumned = serializeJsonbColumns(entity, row);
9
24
  if (dialect !== "sqlite")
10
- return row;
11
- return mapBooleansToInt(entity, row);
25
+ return jsonbColumned;
26
+ return mapBooleansToInt(entity, jsonbColumned);
27
+ }
28
+ /**
29
+ * A `field.object` lands in a single JSONB column unless its `@storage` is
30
+ * `flattened` (then it expands to prefixed columns and has no column of its own).
31
+ * Default (no `@storage`) and `@storage: jsonb` both store one jsonb column.
32
+ */
33
+ function isJsonbObjectField(child) {
34
+ if (child.subType !== FIELD_SUBTYPE_OBJECT)
35
+ return false;
36
+ return child.ownAttr(FIELD_ATTR_STORAGE) !== STORAGE_FLATTENED;
37
+ }
38
+ /** A field explicitly pinned to a JSONB physical column via `@dbColumnType`. */
39
+ function isJsonbColumnTypeField(child) {
40
+ return child.ownAttr(FIELD_ATTR_DB_COLUMN_TYPE) === DB_COLUMN_TYPE_JSONB;
41
+ }
42
+ function serializeJsonbColumns(entity, row) {
43
+ let out = null;
44
+ for (const child of entity.ownChildren()) {
45
+ if (child.type !== TYPE_FIELD)
46
+ continue;
47
+ if (!isJsonbObjectField(child) && !isJsonbColumnTypeField(child))
48
+ continue;
49
+ if (!(child.name in row))
50
+ continue;
51
+ const v = row[child.name];
52
+ // Only objects/arrays need stringifying; an already-serialized string passes
53
+ // through unchanged (the existing Asset.payload contract), as does null.
54
+ if (v === null || v === undefined || typeof v !== "object")
55
+ continue;
56
+ out ??= { ...row };
57
+ out[child.name] = JSON.stringify(v);
58
+ }
59
+ return out ?? row;
60
+ }
61
+ /**
62
+ * Read-side complement of `serializeJsonbColumns`: a `field.object` jsonb column
63
+ * is the structured-VO storage and must round-trip as a native object (ADR-0019),
64
+ * not the serialized string. node-pg's jsonb parser already returns a parsed
65
+ * object for the postgres driver, so this only acts on a still-stringified value
66
+ * (the in-memory test driver, or any driver that does not parse jsonb) — an
67
+ * already-parsed object/array passes through untouched. Only `field.object`
68
+ * columns are hydrated; a `field.string @dbColumnType: jsonb` stays the raw
69
+ * string it is declared to be. A non-JSON string is left as-is.
70
+ */
71
+ function deserializeJsonbObjectFields(entity, row) {
72
+ let out = null;
73
+ for (const child of entity.ownChildren()) {
74
+ if (child.type !== TYPE_FIELD)
75
+ continue;
76
+ if (!isJsonbObjectField(child))
77
+ continue;
78
+ if (!(child.name in row))
79
+ continue;
80
+ const v = row[child.name];
81
+ if (typeof v !== "string")
82
+ continue;
83
+ try {
84
+ const parsed = JSON.parse(v);
85
+ out ??= { ...row };
86
+ out[child.name] = parsed;
87
+ }
88
+ catch {
89
+ // Not JSON — leave the raw string in place.
90
+ }
91
+ }
92
+ return out ?? row;
12
93
  }
13
94
  function mapBooleansFromInt(entity, row) {
14
95
  const out = { ...row };
15
- for (const child of entity.ownChildren()) {
96
+ // Effective children so a TPH subtype coerces inherited boolean fields too.
97
+ for (const child of entity.children()) {
16
98
  if (child.type !== TYPE_FIELD)
17
99
  continue;
18
100
  if (child.subType !== FIELD_SUBTYPE_BOOLEAN)
@@ -27,7 +109,8 @@ function mapBooleansFromInt(entity, row) {
27
109
  }
28
110
  function mapBooleansToInt(entity, row) {
29
111
  const out = { ...row };
30
- for (const child of entity.ownChildren()) {
112
+ // Effective children so a TPH subtype coerces inherited boolean fields too.
113
+ for (const child of entity.children()) {
31
114
  if (child.type !== TYPE_FIELD)
32
115
  continue;
33
116
  if (child.subType !== FIELD_SUBTYPE_BOOLEAN)
@@ -1 +1 @@
1
- {"version":3,"file":"type-coercer.js","sourceRoot":"","sources":["../src/type-coercer.ts"],"names":[],"mappings":"AAAA,mGAAmG;AAGnG,OAAO,EAAE,UAAU,EAAE,qBAAqB,EAAE,MAAM,0BAA0B,CAAC;AAG7E,MAAM,UAAU,eAAe,CAAC,MAAgB,EAAE,GAAQ,EAAE,OAAgB;IAC1E,IAAI,OAAO,KAAK,QAAQ;QAAE,OAAO,GAAG,CAAC;IACrC,OAAO,kBAAkB,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;AACzC,CAAC;AAED,MAAM,UAAU,gBAAgB,CAAC,MAAgB,EAAE,GAAQ,EAAE,OAAgB;IAC3E,IAAI,OAAO,KAAK,QAAQ;QAAE,OAAO,GAAG,CAAC;IACrC,OAAO,gBAAgB,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;AACvC,CAAC;AAED,SAAS,kBAAkB,CAAC,MAAgB,EAAE,GAAQ;IACpD,MAAM,GAAG,GAAQ,EAAE,GAAG,GAAG,EAAE,CAAC;IAC5B,KAAK,MAAM,KAAK,IAAI,MAAM,CAAC,WAAW,EAAE,EAAE,CAAC;QACzC,IAAI,KAAK,CAAC,IAAI,KAAK,UAAU;YAAE,SAAS;QACxC,IAAI,KAAK,CAAC,OAAO,KAAK,qBAAqB;YAAE,SAAS;QACtD,MAAM,CAAC,GAAG,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QAC1B,IAAI,CAAC,KAAK,CAAC;YAAE,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,KAAK,CAAC;aAChC,IAAI,CAAC,KAAK,CAAC;YAAE,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;IAC3C,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAED,SAAS,gBAAgB,CAAC,MAAgB,EAAE,GAAQ;IAClD,MAAM,GAAG,GAAQ,EAAE,GAAG,GAAG,EAAE,CAAC;IAC5B,KAAK,MAAM,KAAK,IAAI,MAAM,CAAC,WAAW,EAAE,EAAE,CAAC;QACzC,IAAI,KAAK,CAAC,IAAI,KAAK,UAAU;YAAE,SAAS;QACxC,IAAI,KAAK,CAAC,OAAO,KAAK,qBAAqB;YAAE,SAAS;QACtD,MAAM,CAAC,GAAG,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QAC1B,IAAI,CAAC,KAAK,IAAI;YAAE,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;aAC/B,IAAI,CAAC,KAAK,KAAK;YAAE,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IAC5C,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC"}
1
+ {"version":3,"file":"type-coercer.js","sourceRoot":"","sources":["../src/type-coercer.ts"],"names":[],"mappings":"AAAA,yDAAyD;AACzD,EAAE;AACF,8EAA8E;AAC9E,0DAA0D;AAC1D,yEAAyE;AACzE,8EAA8E;AAC9E,6EAA6E;AAC7E,iFAAiF;AACjF,gFAAgF;AAChF,2EAA2E;AAC3E,EAAE;AACF,0EAA0E;AAC1E,+EAA+E;AAC/E,aAAa;AAGb,OAAO,EACL,UAAU,EACV,qBAAqB,EACrB,oBAAoB,EACpB,kBAAkB,EAClB,yBAAyB,EACzB,aAAa,EACb,iBAAiB,EACjB,oBAAoB,GACrB,MAAM,0BAA0B,CAAC;AAGlC,MAAM,UAAU,eAAe,CAAC,MAAgB,EAAE,GAAQ,EAAE,OAAgB;IAC1E,MAAM,QAAQ,GAAG,4BAA4B,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IAC3D,IAAI,OAAO,KAAK,QAAQ;QAAE,OAAO,QAAQ,CAAC;IAC1C,OAAO,kBAAkB,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;AAC9C,CAAC;AAED,MAAM,UAAU,gBAAgB,CAAC,MAAgB,EAAE,GAAQ,EAAE,OAAgB;IAC3E,MAAM,aAAa,GAAG,qBAAqB,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IACzD,IAAI,OAAO,KAAK,QAAQ;QAAE,OAAO,aAAa,CAAC;IAC/C,OAAO,gBAAgB,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC;AACjD,CAAC;AAED;;;;GAIG;AACH,SAAS,kBAAkB,CAAC,KAAe;IACzC,IAAI,KAAK,CAAC,OAAO,KAAK,oBAAoB;QAAE,OAAO,KAAK,CAAC;IACzD,OAAO,KAAK,CAAC,OAAO,CAAC,kBAAkB,CAAC,KAAK,iBAAiB,CAAC;AACjE,CAAC;AAED,gFAAgF;AAChF,SAAS,sBAAsB,CAAC,KAAe;IAC7C,OAAO,KAAK,CAAC,OAAO,CAAC,yBAAyB,CAAC,KAAK,oBAAoB,CAAC;AAC3E,CAAC;AAED,SAAS,qBAAqB,CAAC,MAAgB,EAAE,GAAQ;IACvD,IAAI,GAAG,GAAe,IAAI,CAAC;IAC3B,KAAK,MAAM,KAAK,IAAI,MAAM,CAAC,WAAW,EAAE,EAAE,CAAC;QACzC,IAAI,KAAK,CAAC,IAAI,KAAK,UAAU;YAAE,SAAS;QACxC,IAAI,CAAC,kBAAkB,CAAC,KAAK,CAAC,IAAI,CAAC,sBAAsB,CAAC,KAAK,CAAC;YAAE,SAAS;QAC3E,IAAI,CAAC,CAAC,KAAK,CAAC,IAAI,IAAI,GAAG,CAAC;YAAE,SAAS;QACnC,MAAM,CAAC,GAAG,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QAC1B,6EAA6E;QAC7E,yEAAyE;QACzE,IAAI,CAAC,KAAK,IAAI,IAAI,CAAC,KAAK,SAAS,IAAI,OAAO,CAAC,KAAK,QAAQ;YAAE,SAAS;QACrE,GAAG,KAAK,EAAE,GAAG,GAAG,EAAE,CAAC;QACnB,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC;IACtC,CAAC;IACD,OAAO,GAAG,IAAI,GAAG,CAAC;AACpB,CAAC;AAED;;;;;;;;;GASG;AACH,SAAS,4BAA4B,CAAC,MAAgB,EAAE,GAAQ;IAC9D,IAAI,GAAG,GAAe,IAAI,CAAC;IAC3B,KAAK,MAAM,KAAK,IAAI,MAAM,CAAC,WAAW,EAAE,EAAE,CAAC;QACzC,IAAI,KAAK,CAAC,IAAI,KAAK,UAAU;YAAE,SAAS;QACxC,IAAI,CAAC,kBAAkB,CAAC,KAAK,CAAC;YAAE,SAAS;QACzC,IAAI,CAAC,CAAC,KAAK,CAAC,IAAI,IAAI,GAAG,CAAC;YAAE,SAAS;QACnC,MAAM,CAAC,GAAG,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QAC1B,IAAI,OAAO,CAAC,KAAK,QAAQ;YAAE,SAAS;QACpC,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;YAC7B,GAAG,KAAK,EAAE,GAAG,GAAG,EAAE,CAAC;YACnB,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,MAAM,CAAC;QAC3B,CAAC;QAAC,MAAM,CAAC;YACP,4CAA4C;QAC9C,CAAC;IACH,CAAC;IACD,OAAO,GAAG,IAAI,GAAG,CAAC;AACpB,CAAC;AAED,SAAS,kBAAkB,CAAC,MAAgB,EAAE,GAAQ;IACpD,MAAM,GAAG,GAAQ,EAAE,GAAG,GAAG,EAAE,CAAC;IAC5B,4EAA4E;IAC5E,KAAK,MAAM,KAAK,IAAI,MAAM,CAAC,QAAQ,EAAE,EAAE,CAAC;QACtC,IAAI,KAAK,CAAC,IAAI,KAAK,UAAU;YAAE,SAAS;QACxC,IAAI,KAAK,CAAC,OAAO,KAAK,qBAAqB;YAAE,SAAS;QACtD,MAAM,CAAC,GAAG,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QAC1B,IAAI,CAAC,KAAK,CAAC;YAAE,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,KAAK,CAAC;aAChC,IAAI,CAAC,KAAK,CAAC;YAAE,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;IAC3C,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAED,SAAS,gBAAgB,CAAC,MAAgB,EAAE,GAAQ;IAClD,MAAM,GAAG,GAAQ,EAAE,GAAG,GAAG,EAAE,CAAC;IAC5B,4EAA4E;IAC5E,KAAK,MAAM,KAAK,IAAI,MAAM,CAAC,QAAQ,EAAE,EAAE,CAAC;QACtC,IAAI,KAAK,CAAC,IAAI,KAAK,UAAU;YAAE,SAAS;QACxC,IAAI,KAAK,CAAC,OAAO,KAAK,qBAAqB;YAAE,SAAS;QACtD,MAAM,CAAC,GAAG,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QAC1B,IAAI,CAAC,KAAK,IAAI;YAAE,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;aAC/B,IAAI,CAAC,KAAK,KAAK;YAAE,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IAC5C,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"validator-runner.d.ts","sourceRoot":"","sources":["../src/validator-runner.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,0BAA0B,CAAC;AAUzD,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAErD,MAAM,MAAM,gBAAgB,GACxB;IAAE,EAAE,EAAE,IAAI,CAAA;CAAE,GACZ;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,iBAAiB,EAAE,CAAA;CAAE,CAAC;AAO/C,MAAM,WAAW,iBAAiB;IAChC,gGAAgG;IAChG,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED,wBAAgB,aAAa,CAC3B,MAAM,EAAE,QAAQ,EAChB,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC7B,IAAI,GAAE,iBAAsB,GAC3B,gBAAgB,CA6FlB"}
1
+ {"version":3,"file":"validator-runner.d.ts","sourceRoot":"","sources":["../src/validator-runner.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,0BAA0B,CAAC;AAUzD,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAErD,MAAM,MAAM,gBAAgB,GACxB;IAAE,EAAE,EAAE,IAAI,CAAA;CAAE,GACZ;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,iBAAiB,EAAE,CAAA;CAAE,CAAC;AAqB/C,MAAM,WAAW,iBAAiB;IAChC,gGAAgG;IAChG,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED,wBAAgB,aAAa,CAC3B,MAAM,EAAE,QAAQ,EAChB,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC7B,IAAI,GAAE,iBAAsB,GAC3B,gBAAgB,CA8FlB"}
@@ -1,13 +1,26 @@
1
1
  // Pure function: NEVER throws. ObjectManager wraps a non-ok result in a ValidationError on writes;
2
2
  // om.validate() returns the result directly.
3
- import { TYPE_FIELD, TYPE_VALIDATOR, VALIDATOR_SUBTYPE_REQUIRED, VALIDATOR_SUBTYPE_LENGTH, VALIDATOR_SUBTYPE_REGEX, FIELD_SUBTYPE_STRING, FIELD_SUBTYPE_INT, FIELD_SUBTYPE_LONG, FIELD_SUBTYPE_SHORT, FIELD_SUBTYPE_BYTE, FIELD_SUBTYPE_DOUBLE, FIELD_SUBTYPE_FLOAT, FIELD_SUBTYPE_BOOLEAN, FIELD_SUBTYPE_UUID, FIELD_ATTR_REQUIRED, FIELD_ATTR_MAX_LENGTH, FIELD_ATTR_DEFAULT, VALIDATOR_ATTR_MIN, VALIDATOR_ATTR_MAX, VALIDATOR_ATTR_PATTERN, } from "@metaobjectsdev/metadata";
3
+ import { TYPE_FIELD, TYPE_VALIDATOR, VALIDATOR_SUBTYPE_REQUIRED, VALIDATOR_SUBTYPE_LENGTH, VALIDATOR_SUBTYPE_REGEX, FIELD_SUBTYPE_STRING, FIELD_SUBTYPE_INT, FIELD_SUBTYPE_LONG, FIELD_SUBTYPE_DOUBLE, FIELD_SUBTYPE_FLOAT, FIELD_SUBTYPE_CURRENCY, FIELD_SUBTYPE_BOOLEAN, FIELD_SUBTYPE_UUID, FIELD_ATTR_REQUIRED, FIELD_ATTR_MAX_LENGTH, FIELD_ATTR_DEFAULT, VALIDATOR_ATTR_MIN, VALIDATOR_ATTR_MAX, VALIDATOR_ATTR_PATTERN, } from "@metaobjectsdev/metadata";
4
+ // JS-safe numeric fields: must arrive as a JS `number` (they fit in 2^53).
4
5
  const NUMERIC_FIELD_SUBTYPES = new Set([
5
- FIELD_SUBTYPE_INT, FIELD_SUBTYPE_LONG, FIELD_SUBTYPE_SHORT, FIELD_SUBTYPE_BYTE,
6
+ FIELD_SUBTYPE_INT,
6
7
  FIELD_SUBTYPE_DOUBLE, FIELD_SUBTYPE_FLOAT,
7
8
  ]);
9
+ // 64-bit integer fields (BIGINT on the wire). A full int64 (> 2^53) cannot
10
+ // survive a JS `number`, so the write contract additionally accepts a numeric
11
+ // `string` or a `bigint` for these — the runtime passes them through unchanged
12
+ // so the BIGINT round-trips exactly (read-back is BIGINT→string per
13
+ // normalization.md / ADR-0019). `field.currency` is integer minor units → BIGINT.
14
+ const INT64_FIELD_SUBTYPES = new Set([
15
+ FIELD_SUBTYPE_LONG, FIELD_SUBTYPE_CURRENCY,
16
+ ]);
17
+ // A base-10 signed integer literal with no fractional/exponent part — the only
18
+ // string shape accepted for an int64 field (a "1.5" or "1e3" is rejected).
19
+ const INT64_STRING_RE = /^-?\d+$/;
8
20
  export function runValidators(entity, data, opts = {}) {
9
21
  const errors = [];
10
- for (const field of entity.ownChildren()) {
22
+ // Effective children so a TPH subtype validates inherited base fields too.
23
+ for (const field of entity.children()) {
11
24
  if (field.type !== TYPE_FIELD)
12
25
  continue;
13
26
  const present = Object.prototype.hasOwnProperty.call(data, field.name);
@@ -146,6 +159,14 @@ function checkType(subType, value) {
146
159
  if (typeof value !== "number")
147
160
  return `expected number`;
148
161
  }
162
+ else if (INT64_FIELD_SUBTYPES.has(subType)) {
163
+ // number (in-band) | bigint | base-10 integer string (full int64 fidelity).
164
+ if (typeof value === "number" || typeof value === "bigint")
165
+ return null;
166
+ if (typeof value === "string" && INT64_STRING_RE.test(value))
167
+ return null;
168
+ return `expected a 64-bit integer (number, bigint, or numeric string)`;
169
+ }
149
170
  else if (subType === FIELD_SUBTYPE_BOOLEAN) {
150
171
  if (typeof value !== "boolean")
151
172
  return `expected boolean`;
@@ -1 +1 @@
1
- {"version":3,"file":"validator-runner.js","sourceRoot":"","sources":["../src/validator-runner.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"validator-runner.js","sourceRoot":"","sources":["../src/validator-runner.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@metaobjectsdev/runtime-ts",
3
- "version": "0.9.0",
3
+ "version": "0.11.0-rc.1",
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",
@@ -32,7 +32,12 @@
32
32
  "default": "./dist/hono/index.js"
33
33
  }
34
34
  },
35
- "files": ["dist", "src", "README.md", "LICENSE"],
35
+ "files": [
36
+ "dist",
37
+ "src",
38
+ "README.md",
39
+ "LICENSE"
40
+ ],
36
41
  "scripts": {
37
42
  "build": "tsc -p .",
38
43
  "typecheck": "tsc -p tsconfig.typecheck.json"
@@ -41,58 +46,64 @@
41
46
  "author": "Doug Mealing <doug@dougmealing.com>",
42
47
  "homepage": "https://metaobjects.dev",
43
48
  "bugs": {
44
- "url": "https://github.com/metaobjectsdev/metaobjects/issues"
45
- },
46
- "repository": {
47
- "type": "git",
48
- "url": "https://github.com/metaobjectsdev/metaobjects.git",
49
- "directory": "server/typescript/packages/runtime-ts"
50
- },
51
- "keywords": ["metaobjects", "runtime", "fastify", "drizzle", "kysely"],
52
- "publishConfig": {
53
- "access": "public"
54
- },
55
- "dependencies": {
56
- "@metaobjectsdev/metadata": "0.9.0",
57
- "@metaobjectsdev/render": "0.9.0",
58
- "qs": "^6.13.0"
59
- },
60
- "peerDependencies": {
61
- "drizzle-orm": ">=0.36.0",
62
- "fastify": ">=5.0.0",
63
- "hono": ">=4.0.0",
64
- "kysely": ">=0.27.0",
65
- "zod": ">=3.23.0"
66
- },
67
- "peerDependenciesMeta": {
68
- "drizzle-orm": {
69
- "optional": true
49
+ "url": "https://github.com/metaobjectsdev/metaobjects/issues"
70
50
  },
71
- "fastify": {
72
- "optional": true
51
+ "repository": {
52
+ "type": "git",
53
+ "url": "https://github.com/metaobjectsdev/metaobjects.git",
54
+ "directory": "server/typescript/packages/runtime-ts"
73
55
  },
74
- "hono": {
75
- "optional": true
56
+ "keywords": [
57
+ "metaobjects",
58
+ "runtime",
59
+ "fastify",
60
+ "drizzle",
61
+ "kysely"
62
+ ],
63
+ "publishConfig": {
64
+ "access": "public"
76
65
  },
77
- "kysely": {
78
- "optional": true
66
+ "dependencies": {
67
+ "@metaobjectsdev/metadata": "0.11.0-rc.1",
68
+ "@metaobjectsdev/render": "0.11.0-rc.1",
69
+ "qs": "^6.13.0"
79
70
  },
80
- "zod": {
81
- "optional": true
71
+ "peerDependencies": {
72
+ "drizzle-orm": ">=0.36.0",
73
+ "fastify": ">=5.0.0",
74
+ "hono": ">=4.0.0",
75
+ "kysely": ">=0.27.0",
76
+ "zod": ">=3.23.0"
77
+ },
78
+ "peerDependenciesMeta": {
79
+ "drizzle-orm": {
80
+ "optional": true
81
+ },
82
+ "fastify": {
83
+ "optional": true
84
+ },
85
+ "hono": {
86
+ "optional": true
87
+ },
88
+ "kysely": {
89
+ "optional": true
90
+ },
91
+ "zod": {
92
+ "optional": true
93
+ }
94
+ },
95
+ "devDependencies": {
96
+ "@libsql/client": "^0.15.0",
97
+ "@libsql/kysely-libsql": "^0.4.1",
98
+ "@types/qs": "^6.9.0",
99
+ "bun-types": "latest",
100
+ "drizzle-orm": "^0.45.1",
101
+ "fastify": "^5.6.2",
102
+ "hono": "^4.6.0",
103
+ "jsdom": "^29.1.1",
104
+ "kysely": "^0.27.0",
105
+ "pg-mem": "^3.0.4",
106
+ "typescript": "^5.6.0",
107
+ "zod": "^4.4.3"
82
108
  }
83
- },
84
- "devDependencies": {
85
- "@libsql/client": "^0.15.0",
86
- "@libsql/kysely-libsql": "^0.4.1",
87
- "@types/qs": "^6.9.0",
88
- "bun-types": "latest",
89
- "drizzle-orm": "^0.45.1",
90
- "fastify": "^5.6.2",
91
- "hono": "^4.6.0",
92
- "jsdom": "^29.1.1",
93
- "kysely": "^0.27.0",
94
- "pg-mem": "^3.0.4",
95
- "typescript": "^5.6.0",
96
- "zod": "^4.4.3"
97
- }
98
109
  }
@@ -13,6 +13,7 @@
13
13
 
14
14
  import {
15
15
  and as drzAnd,
16
+ or as drzOr,
16
17
  eq as drzEq,
17
18
  ne as drzNe,
18
19
  gt as drzGt,
@@ -410,6 +411,10 @@ function buildExpression(w: WhereClause, cols: Map<string, AnyColumn>): unknown
410
411
  const parts = w.clauses.map((c) => buildExpression(c, cols)) as SQL[];
411
412
  return drzAnd(...parts);
412
413
  }
414
+ case "or": {
415
+ const parts = w.clauses.map((c) => buildExpression(c, cols)) as SQL[];
416
+ return drzOr(...parts);
417
+ }
413
418
  default: {
414
419
  const exhaustive: never = w;
415
420
  throw new Error(`Unhandled WhereClause kind: ${JSON.stringify(exhaustive)}`);
@@ -216,6 +216,8 @@ function matchesWhere(row: Row, where?: WhereClause): boolean {
216
216
  }
217
217
  case "and":
218
218
  return where.clauses.every((c) => matchesWhere(row, c));
219
+ case "or":
220
+ return where.clauses.some((c) => matchesWhere(row, c));
219
221
  default: {
220
222
  const exhaustive: never = where;
221
223
  throw new Error(`Unhandled WhereClause kind: ${JSON.stringify(exhaustive)}`);
@@ -160,6 +160,7 @@ function buildExpression(eb: AnyExprBuilder, w: WhereClause): Expression<SqlBool
160
160
  case "in": return eb(w.column, "in", w.values);
161
161
  case "isNull": return w.not ? eb(w.column, "is not", null) : eb(w.column, "is", null);
162
162
  case "and": return eb.and(w.clauses.map((c: WhereClause) => buildExpression(eb, c)));
163
+ case "or": return eb.or(w.clauses.map((c: WhereClause) => buildExpression(eb, c)));
163
164
  default: {
164
165
  const exhaustive: never = w;
165
166
  throw new Error(`Unhandled WhereClause kind: ${JSON.stringify(exhaustive)}`);
@@ -21,8 +21,8 @@ import qs from "qs";
21
21
  import type { FilterAllowlist, SortAllowlist } from "./filter-allowlist.js";
22
22
  export type { FilterAllowlist, SortAllowlist } from "./filter-allowlist.js";
23
23
  import { parseFilterParams, FilterParseError } from "./filter-parser.js";
24
- import { isTruthyFlag, contractErrorCode } from "./util.js";
25
- export { isTruthyFlag, contractErrorCode } from "./util.js";
24
+ import { isTruthyFlag, contractErrorCode, parseId } from "./util.js";
25
+ export { isTruthyFlag, contractErrorCode, parseId } from "./util.js";
26
26
 
27
27
  // ---------------------------------------------------------------------------
28
28
  // Loose types — we don't bind to a specific Drizzle backend so the helper
@@ -65,10 +65,28 @@ export interface CrudRoutesOptions {
65
65
  sortAllowlist?: SortAllowlist;
66
66
  /** Dialect — required if filterAllowlist or sortAllowlist is set (for like/ilike dispatch). */
67
67
  dialect?: "sqlite" | "postgres";
68
+ /**
69
+ * FR-017 TPH — scope this route set to a single subtype of a single-table-
70
+ * inheritance base. When set:
71
+ * - list/get filter to `eq(table[column], value)`;
72
+ * - a get/update/delete targeting a row of another subtype 404s;
73
+ * - create injects `{ [column]: value }` AFTER body validation (the body
74
+ * omits the discriminator — the URL already names the subtype);
75
+ * - update strips `column` from the patch (a row's subtype is immutable).
76
+ * Absent → ordinary single-table CRUD, behaviour unchanged.
77
+ */
78
+ discriminator?: { column: string; value: string };
68
79
  }
69
80
 
70
81
  const ALL_VERBS: readonly CrudVerb[] = ["list", "get", "create", "update", "delete"];
71
82
 
83
+ /** The TPH discriminator predicate for this route set, or undefined when the
84
+ * routes are not subtype-scoped. */
85
+ function discriminatorCond(opts: VerbOptions) {
86
+ const d = opts.discriminator;
87
+ return d ? eq(opts.table[d.column], d.value) : undefined;
88
+ }
89
+
72
90
  export function mountCrudRoutes(opts: CrudRoutesOptions): void {
73
91
  const verbs = new Set<CrudVerb>(opts.expose ?? ALL_VERBS);
74
92
  if (verbs.has("list")) mountListRoute(opts);
@@ -96,6 +114,10 @@ export function mountListRoute(opts: VerbOptions): void {
96
114
  const qsParsed = qs.parse(rawSearch) as Record<string, unknown>;
97
115
  const withCount = isTruthyFlag(qsParsed.withCount);
98
116
 
117
+ // FR-017 TPH: when subtype-scoped, AND the discriminator predicate into
118
+ // every list query (combined with any allowlist filters).
119
+ const discCond = discriminatorCond(opts);
120
+
99
121
  let where: ReturnType<typeof parseFilterParams>["where"];
100
122
  if (opts.filterAllowlist && opts.sortAllowlist) {
101
123
  const parsed = parseFilterParams({
@@ -105,9 +127,12 @@ export function mountListRoute(opts: VerbOptions): void {
105
127
  sortAllowlist: opts.sortAllowlist,
106
128
  dialect: opts.dialect ?? "sqlite",
107
129
  });
108
- const combinedWhere = parsed.where && parsed.searchWhere
130
+ const filterWhere = parsed.where && parsed.searchWhere
109
131
  ? and(parsed.where, parsed.searchWhere)
110
132
  : (parsed.where ?? parsed.searchWhere);
133
+ const combinedWhere = filterWhere && discCond
134
+ ? and(filterWhere, discCond)
135
+ : (filterWhere ?? discCond);
111
136
  if (combinedWhere) { q = q.where(combinedWhere); where = combinedWhere; }
112
137
  // Default to stable id-ascending order when the caller specifies no
113
138
  // sort — the cross-port contract asserts deterministic ordering for
@@ -118,8 +143,10 @@ export function mountListRoute(opts: VerbOptions): void {
118
143
  if (parsed.limit !== undefined) q = q.limit(parsed.limit);
119
144
  if (parsed.offset !== undefined) q = q.offset(parsed.offset);
120
145
  } else {
121
- // Legacy path — no allowlists configured. Only limit/offset.
146
+ // Legacy path — no allowlists configured. Only limit/offset (+ the TPH
147
+ // discriminator predicate when subtype-scoped).
122
148
  const { limit, offset } = req.query as { limit?: string; offset?: string };
149
+ if (discCond) { q = q.where(discCond); where = discCond; }
123
150
  if (opts.table.id !== undefined) q = q.orderBy(asc(opts.table.id));
124
151
  if (limit !== undefined) q = q.limit(Number(limit));
125
152
  if (offset !== undefined) q = q.offset(Number(offset));
@@ -151,13 +178,15 @@ export function mountListRoute(opts: VerbOptions): void {
151
178
  export function mountGetRoute(opts: VerbOptions): void {
152
179
  opts.fastify.get(`${opts.path}/:id`, routeOpts(opts), async (req, reply) => {
153
180
  const { id } = req.params as { id: string };
181
+ const discCond = discriminatorCond(opts);
182
+ const idCond = eq(opts.table.id, parseId(id));
154
183
  // Await + take the first row rather than `.get()` — `.get()` is a
155
184
  // libsql/better-sqlite3-only method; the node-postgres builder is thenable
156
185
  // but has no `.get()`. Awaiting works on both dialects.
157
186
  const rows = await opts.db
158
187
  .select()
159
188
  .from(opts.table)
160
- .where(eq(opts.table.id, parseId(id)))
189
+ .where(discCond ? and(idCond, discCond) : idCond)
161
190
  .limit(1);
162
191
  const row = (rows as unknown[])[0];
163
192
  return row ?? reply.code(404).send({ error: "not_found" });
@@ -170,7 +199,12 @@ export function mountCreateRoute(opts: VerbOptions): void {
170
199
  if (!parsed.success) {
171
200
  return reply.code(400).send({ error: "validation", issues: parsed.error.issues });
172
201
  }
173
- const result = await opts.db.insert(opts.table).values(parsed.data).returning();
202
+ // FR-017 TPH: the body omits the discriminator (the URL names the subtype);
203
+ // inject it server-side so the row lands tagged with the right subtype.
204
+ const values = opts.discriminator
205
+ ? { ...(parsed.data as Record<string, unknown>), [opts.discriminator.column]: opts.discriminator.value }
206
+ : parsed.data;
207
+ const result = await opts.db.insert(opts.table).values(values).returning();
174
208
  const row = (result as unknown[])[0];
175
209
  return reply.code(201).send(row);
176
210
  });
@@ -186,10 +220,19 @@ export function mountUpdateRoute(opts: VerbOptions): void {
186
220
  if (!parsed.success) {
187
221
  return reply.code(400).send({ error: "validation", issues: parsed.error.issues });
188
222
  }
223
+ const discCond = discriminatorCond(opts);
224
+ // FR-017 TPH: a row's subtype is immutable — strip the discriminator from
225
+ // the patch so a client can't flip a Bridge into a Copay.
226
+ let data = parsed.data as Record<string, unknown>;
227
+ if (opts.discriminator) {
228
+ const { [opts.discriminator.column]: _omit, ...rest } = data;
229
+ data = rest;
230
+ }
231
+ const idCond = eq(opts.table.id, parseId(id));
189
232
  const result = await opts.db
190
233
  .update(opts.table)
191
- .set(parsed.data)
192
- .where(eq(opts.table.id, parseId(id)))
234
+ .set(data)
235
+ .where(discCond ? and(idCond, discCond) : idCond)
193
236
  .returning();
194
237
  const row = (result as unknown[])[0];
195
238
  return row ?? reply.code(404).send({ error: "not_found" });
@@ -216,9 +259,11 @@ export function mountUpdateRoute(opts: VerbOptions): void {
216
259
  export function mountDeleteRoute(opts: VerbOptions): void {
217
260
  opts.fastify.delete(`${opts.path}/:id`, routeOpts(opts), async (req, reply) => {
218
261
  const { id } = req.params as { id: string };
262
+ const discCond = discriminatorCond(opts);
263
+ const idCond = eq(opts.table.id, parseId(id));
219
264
  const result = await opts.db
220
265
  .delete(opts.table)
221
- .where(eq(opts.table.id, parseId(id)));
266
+ .where(discCond ? and(idCond, discCond) : idCond);
222
267
  // Both libsql and pg drivers expose a rows-affected counter, in different
223
268
  // shapes. Treat anything > 0 as "found and deleted."
224
269
  const affected = extractRowCount(result);
@@ -240,9 +285,5 @@ function extractRowCount(result: unknown): number {
240
285
  return 0;
241
286
  }
242
287
 
243
- export function parseId(raw: string): number | string {
244
- const n = Number(raw);
245
- return Number.isFinite(n) && raw.trim() !== "" ? n : raw;
246
- }
247
-
248
288
  export { mountReadOnlyCrudRoutes, type MountReadOnlyOptions } from "./mount-read-only.js";
289
+ export { mountM2mRoute, type M2mRouteOptions } from "./mount-m2m.js";