@metaobjectsdev/runtime-ts 1.0.5-rc.1 → 1.0.5-rc.3

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.
@@ -21,6 +21,30 @@ export interface M2mRouteOptions {
21
21
  targetPkColumn?: string;
22
22
  /** Undirected self-join: union both junction FK columns on read. */
23
23
  symmetric: boolean;
24
+ /**
25
+ * The target is a TPH subtype: `targetTable` is its discriminator base's table, and
26
+ * stage 2 keeps only rows whose discriminator `column` (physical name) equals `value`.
27
+ * Absent → every related row is returned, behaviour unchanged.
28
+ */
29
+ targetDiscriminator?: {
30
+ column: string;
31
+ value: string;
32
+ };
33
+ /**
34
+ * The SOURCE is a TPH subtype: this route is mounted under the subtype's path segment,
35
+ * but the junction FK addresses the shared base table, so an id belonging to a SIBLING
36
+ * subtype would traverse it successfully and return that sibling's relations. Stage 0
37
+ * reads the source row's discriminator from `table` (the base table the row lives in)
38
+ * and yields an empty list when it is not `value` — the id names no row of THIS
39
+ * subtype, so it has no relations, which is a 200 with `[]` rather than an error.
40
+ * Absent → no source check, behaviour unchanged.
41
+ */
42
+ sourceDiscriminator?: {
43
+ table: AnyTable;
44
+ pkColumn: string;
45
+ column: string;
46
+ value: string;
47
+ };
24
48
  /** Fastify route-level hooks (auth, etc.). */
25
49
  routeOptions?: RouteShorthandOptions;
26
50
  }
@@ -1 +1 @@
1
- {"version":3,"file":"mount-m2m.d.ts","sourceRoot":"","sources":["../../src/drizzle-fastify/mount-m2m.ts"],"names":[],"mappings":"AAqBA,OAAO,KAAK,EAAE,eAAe,EAAE,qBAAqB,EAAE,MAAM,SAAS,CAAC;AAMtE,KAAK,UAAU,GAAG,GAAG,CAAC;AAEtB,KAAK,QAAQ,GAAG,GAAG,CAAC;AAEpB,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE,eAAe,CAAC;IACzB,4FAA4F;IAC5F,IAAI,EAAE,MAAM,CAAC;IACb,iEAAiE;IACjE,YAAY,EAAE,MAAM,CAAC;IACrB,+BAA+B;IAC/B,EAAE,EAAE,UAAU,CAAC;IACf,wDAAwD;IACxD,aAAa,EAAE,QAAQ,CAAC;IACxB,kDAAkD;IAClD,WAAW,EAAE,QAAQ,CAAC;IACtB,wFAAwF;IACxF,YAAY,EAAE,MAAM,CAAC;IACrB,uFAAuF;IACvF,YAAY,EAAE,MAAM,CAAC;IACrB,mFAAmF;IACnF,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,oEAAoE;IACpE,SAAS,EAAE,OAAO,CAAC;IACnB,8CAA8C;IAC9C,YAAY,CAAC,EAAE,qBAAqB,CAAC;CACtC;AAED,wBAAgB,aAAa,CAAC,IAAI,EAAE,eAAe,GAAG,IAAI,CAqDzD"}
1
+ {"version":3,"file":"mount-m2m.d.ts","sourceRoot":"","sources":["../../src/drizzle-fastify/mount-m2m.ts"],"names":[],"mappings":"AAgCA,OAAO,KAAK,EAAE,eAAe,EAAE,qBAAqB,EAAE,MAAM,SAAS,CAAC;AAMtE,KAAK,UAAU,GAAG,GAAG,CAAC;AAEtB,KAAK,QAAQ,GAAG,GAAG,CAAC;AAEpB,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE,eAAe,CAAC;IACzB,4FAA4F;IAC5F,IAAI,EAAE,MAAM,CAAC;IACb,iEAAiE;IACjE,YAAY,EAAE,MAAM,CAAC;IACrB,+BAA+B;IAC/B,EAAE,EAAE,UAAU,CAAC;IACf,wDAAwD;IACxD,aAAa,EAAE,QAAQ,CAAC;IACxB,kDAAkD;IAClD,WAAW,EAAE,QAAQ,CAAC;IACtB,wFAAwF;IACxF,YAAY,EAAE,MAAM,CAAC;IACrB,uFAAuF;IACvF,YAAY,EAAE,MAAM,CAAC;IACrB,mFAAmF;IACnF,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,oEAAoE;IACpE,SAAS,EAAE,OAAO,CAAC;IACnB;;;;OAIG;IACH,mBAAmB,CAAC,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC;IACxD;;;;;;;;OAQG;IACH,mBAAmB,CAAC,EAAE;QAAE,KAAK,EAAE,QAAQ,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC;IAC3F,8CAA8C;IAC9C,YAAY,CAAC,EAAE,qBAAqB,CAAC;CACtC;AAED,wBAAgB,aAAa,CAAC,IAAI,EAAE,eAAe,GAAG,IAAI,CAyEzD"}
@@ -18,7 +18,18 @@
18
18
  // the source side (via @sourceRefField) → sourceColumn/targetColumn.
19
19
  // 3. Symmetric self-join: junction WHERE sourceCol = :id OR targetCol = :id;
20
20
  // per row the related id is whichever column is NOT the source id.
21
- import { eq, or, inArray } from "drizzle-orm";
21
+ //
22
+ // A target that is a TPH SUBTYPE lives in its discriminator base's table, and the
23
+ // junction FK can only point at that base table — so it can hold the id of a row of
24
+ // another subtype. `targetDiscriminator` ANDs the subtype predicate into stage 2 so
25
+ // only rows of the declared target come back.
26
+ //
27
+ // The SOURCE has the mirror problem. An M:N declared ON a subtype mounts under that
28
+ // subtype's segment (`/auths/bridge/:id/linkedAuths`), but the junction FK still points
29
+ // at the shared base table, so a SIBLING subtype's id traverses it just as well and the
30
+ // subtype segment in the path would be decorative. `sourceDiscriminator` adds a stage 0
31
+ // that checks the source row's own discriminator first.
32
+ import { and, eq, or, inArray } from "drizzle-orm";
22
33
  import { coerceIdForColumn } from "./util.js";
23
34
  export function mountM2mRoute(opts) {
24
35
  const targetPk = opts.targetPkColumn ?? "id";
@@ -35,6 +46,24 @@ export function mountM2mRoute(opts) {
35
46
  if (sourceId === undefined) {
36
47
  return reply.code(400).send({ error: "invalid_id" });
37
48
  }
49
+ // Stage 0 — the source id must name a row of THIS subtype. Skipping it would let
50
+ // `/auths/bridge/{a Copay's id}/linkedAuths` return that Copay's relations, because
51
+ // the junction FK cannot tell the subtypes apart: it points at the shared base table.
52
+ const srcDisc = opts.sourceDiscriminator;
53
+ if (srcDisc !== undefined) {
54
+ const srcPk = columnRef(srcDisc.table, srcDisc.pkColumn);
55
+ const ownId = coerceIdForColumn(srcPk, id);
56
+ if (ownId === undefined) {
57
+ return reply.code(400).send({ error: "invalid_id" });
58
+ }
59
+ const owner = await opts.db
60
+ .select({ d: columnRef(srcDisc.table, srcDisc.column) })
61
+ .from(srcDisc.table)
62
+ .where(eq(srcPk, ownId))
63
+ .limit(1);
64
+ if (owner.length === 0 || String(owner[0]?.d) !== srcDisc.value)
65
+ return [];
66
+ }
38
67
  // Stage 1 — junction rows for this source id.
39
68
  const joinWhere = opts.symmetric
40
69
  ? or(eq(srcCol, sourceId), eq(tgtCol, sourceId))
@@ -64,10 +93,12 @@ export function mountM2mRoute(opts) {
64
93
  return [];
65
94
  // Stage 2 — load the target rows.
66
95
  const pkCol = columnRef(opts.targetTable, targetPk);
96
+ const byPk = inArray(pkCol, [...relatedIds]);
97
+ const disc = opts.targetDiscriminator;
67
98
  return await opts.db
68
99
  .select()
69
100
  .from(opts.targetTable)
70
- .where(inArray(pkCol, [...relatedIds]));
101
+ .where(disc ? and(byPk, eq(columnRef(opts.targetTable, disc.column), disc.value)) : byPk);
71
102
  });
72
103
  }
73
104
  function addId(set, v) {
@@ -1 +1 @@
1
- {"version":3,"file":"mount-m2m.js","sourceRoot":"","sources":["../../src/drizzle-fastify/mount-m2m.ts"],"names":[],"mappings":"AAAA,6EAA6E;AAC7E,wDAAwD;AACxD,EAAE;AACF,+EAA+E;AAC/E,6EAA6E;AAC7E,gFAAgF;AAChF,+EAA+E;AAC/E,iFAAiF;AACjF,4EAA4E;AAC5E,6EAA6E;AAC7E,8EAA8E;AAC9E,0CAA0C;AAC1C,EAAE;AACF,kDAAkD;AAClD,+EAA+E;AAC/E,oDAAoD;AACpD,+EAA+E;AAC/E,0EAA0E;AAC1E,+EAA+E;AAC/E,wEAAwE;AAGxE,OAAO,EAAE,EAAE,EAAE,EAAE,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAC9C,OAAO,EAAE,iBAAiB,EAAE,MAAM,WAAW,CAAC;AAgC9C,MAAM,UAAU,aAAa,CAAC,IAAqB;IACjD,MAAM,QAAQ,GAAG,IAAI,CAAC,cAAc,IAAI,IAAI,CAAC;IAC7C,MAAM,KAAK,GAAG,GAAG,IAAI,CAAC,IAAI,QAAQ,IAAI,CAAC,YAAY,EAAE,CAAC;IACtD,MAAM,EAAE,GAAG,IAAI,CAAC,YAAY,IAAI,EAAE,CAAC;IAEnC,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,KAAK,EAAE,EAAE,EAAE,KAAK,EAAE,GAAG,EAAE,KAAK,EAAE,EAAE;QAC/C,MAAM,EAAE,EAAE,EAAE,GAAG,GAAG,CAAC,MAAwB,CAAC;QAE5C,MAAM,MAAM,GAAG,SAAS,CAAC,IAAI,CAAC,aAAa,EAAE,IAAI,CAAC,YAAY,CAAC,CAAC;QAChE,MAAM,MAAM,GAAG,SAAS,CAAC,IAAI,CAAC,aAAa,EAAE,IAAI,CAAC,YAAY,CAAC,CAAC;QAEhE,0EAA0E;QAC1E,yEAAyE;QACzE,gCAAgC;QAChC,MAAM,QAAQ,GAAG,iBAAiB,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;QAC/C,IAAI,QAAQ,KAAK,SAAS,EAAE,CAAC;YAC3B,OAAO,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,YAAY,EAAE,CAAC,CAAC;QACvD,CAAC;QAED,8CAA8C;QAC9C,MAAM,SAAS,GAAG,IAAI,CAAC,SAAS;YAC9B,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,MAAM,EAAE,QAAQ,CAAC,EAAE,EAAE,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;YAChD,CAAC,CAAC,EAAE,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;QACzB,MAAM,QAAQ,GAAG,CAAC,MAAM,IAAI,CAAC,EAAE;aAC5B,MAAM,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,CAAC;aACpC,IAAI,CAAC,IAAI,CAAC,aAAa,CAAC;aACxB,KAAK,CAAC,SAAS,CAAC,CAA0C,CAAC;QAE9D,yEAAyE;QACzE,qEAAqE;QACrE,6EAA6E;QAC7E,MAAM,SAAS,GAAG,MAAM,CAAC,QAAQ,CAAC,CAAC;QACnC,MAAM,UAAU,GAAG,IAAI,GAAG,EAA4B,CAAC;QACvD,KAAK,MAAM,CAAC,IAAI,QAAQ,EAAE,CAAC;YACzB,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,CAAC;gBACpB,KAAK,CAAC,UAAU,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC;gBACzB,SAAS;YACX,CAAC;YACD,MAAM,WAAW,GAAG,CAAC,CAAC,GAAG,IAAI,IAAI,IAAI,MAAM,CAAC,CAAC,CAAC,GAAG,CAAC,KAAK,SAAS,CAAC;YACjE,yEAAyE;YACzE,IAAI,WAAW;gBAAE,KAAK,CAAC,UAAU,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC;;gBACrC,KAAK,CAAC,UAAU,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC;QAChC,CAAC;QAED,IAAI,UAAU,CAAC,IAAI,KAAK,CAAC;YAAE,OAAO,EAAE,CAAC;QAErC,kCAAkC;QAClC,MAAM,KAAK,GAAG,SAAS,CAAC,IAAI,CAAC,WAAW,EAAE,QAAQ,CAAC,CAAC;QACpD,OAAO,MAAM,IAAI,CAAC,EAAE;aACjB,MAAM,EAAE;aACR,IAAI,CAAC,IAAI,CAAC,WAAW,CAAC;aACtB,KAAK,CAAC,OAAO,CAAC,KAAK,EAAE,CAAC,GAAG,UAAU,CAAC,CAAC,CAAC,CAAC;IAC5C,CAAC,CAAC,CAAC;AACL,CAAC;AAED,SAAS,KAAK,CAAC,GAAkC,EAAE,CAAU;IAC3D,IAAI,CAAC,KAAK,IAAI,IAAI,CAAC,KAAK,SAAS;QAAE,OAAO;IAC1C,wEAAwE;IACxE,yEAAyE;IACzE,uEAAuE;IACvE,IAAI,OAAO,CAAC,KAAK,QAAQ,IAAI,OAAO,CAAC,KAAK,QAAQ,IAAI,OAAO,CAAC,KAAK,QAAQ;QAAE,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;AAC1F,CAAC;AAED;;;;;GAKG;AACH,uGAAuG;AACvG,SAAS,SAAS,CAAC,KAAe,EAAE,YAAoB;IACtD,KAAK,MAAM,GAAG,IAAI,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC;QACrC,MAAM,GAAG,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC;QACvB,IAAI,GAAG,IAAI,OAAO,GAAG,KAAK,QAAQ,IAAI,GAAG,CAAC,IAAI,KAAK,YAAY;YAAE,OAAO,GAAG,CAAC;IAC9E,CAAC;IACD,gFAAgF;IAChF,MAAM,MAAM,GAAG,KAAK,CAAC,YAAY,CAAC,CAAC;IACnC,IAAI,MAAM,KAAK,SAAS;QAAE,OAAO,MAAM,CAAC;IACxC,MAAM,IAAI,KAAK,CAAC,0BAA0B,YAAY,sBAAsB,CAAC,CAAC;AAChF,CAAC"}
1
+ {"version":3,"file":"mount-m2m.js","sourceRoot":"","sources":["../../src/drizzle-fastify/mount-m2m.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": "1.0.5-rc.1",
3
+ "version": "1.0.5-rc.3",
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.0.5-rc.1",
68
- "@metaobjectsdev/render": "1.0.5-rc.1",
67
+ "@metaobjectsdev/metadata": "1.0.5-rc.3",
68
+ "@metaobjectsdev/render": "1.0.5-rc.3",
69
69
  "qs": "^6.13.0"
70
70
  },
71
71
  "peerDependencies": {
@@ -18,9 +18,20 @@
18
18
  // the source side (via @sourceRefField) → sourceColumn/targetColumn.
19
19
  // 3. Symmetric self-join: junction WHERE sourceCol = :id OR targetCol = :id;
20
20
  // per row the related id is whichever column is NOT the source id.
21
+ //
22
+ // A target that is a TPH SUBTYPE lives in its discriminator base's table, and the
23
+ // junction FK can only point at that base table — so it can hold the id of a row of
24
+ // another subtype. `targetDiscriminator` ANDs the subtype predicate into stage 2 so
25
+ // only rows of the declared target come back.
26
+ //
27
+ // The SOURCE has the mirror problem. An M:N declared ON a subtype mounts under that
28
+ // subtype's segment (`/auths/bridge/:id/linkedAuths`), but the junction FK still points
29
+ // at the shared base table, so a SIBLING subtype's id traverses it just as well and the
30
+ // subtype segment in the path would be decorative. `sourceDiscriminator` adds a stage 0
31
+ // that checks the source row's own discriminator first.
21
32
 
22
33
  import type { FastifyInstance, RouteShorthandOptions } from "fastify";
23
- import { eq, or, inArray } from "drizzle-orm";
34
+ import { and, eq, or, inArray } from "drizzle-orm";
24
35
  import { coerceIdForColumn } from "./util.js";
25
36
 
26
37
  // Loose Drizzle types — the helper works across libsql / better-sqlite3 / pg.
@@ -49,6 +60,22 @@ export interface M2mRouteOptions {
49
60
  targetPkColumn?: string;
50
61
  /** Undirected self-join: union both junction FK columns on read. */
51
62
  symmetric: boolean;
63
+ /**
64
+ * The target is a TPH subtype: `targetTable` is its discriminator base's table, and
65
+ * stage 2 keeps only rows whose discriminator `column` (physical name) equals `value`.
66
+ * Absent → every related row is returned, behaviour unchanged.
67
+ */
68
+ targetDiscriminator?: { column: string; value: string };
69
+ /**
70
+ * The SOURCE is a TPH subtype: this route is mounted under the subtype's path segment,
71
+ * but the junction FK addresses the shared base table, so an id belonging to a SIBLING
72
+ * subtype would traverse it successfully and return that sibling's relations. Stage 0
73
+ * reads the source row's discriminator from `table` (the base table the row lives in)
74
+ * and yields an empty list when it is not `value` — the id names no row of THIS
75
+ * subtype, so it has no relations, which is a 200 with `[]` rather than an error.
76
+ * Absent → no source check, behaviour unchanged.
77
+ */
78
+ sourceDiscriminator?: { table: AnyTable; pkColumn: string; column: string; value: string };
52
79
  /** Fastify route-level hooks (auth, etc.). */
53
80
  routeOptions?: RouteShorthandOptions;
54
81
  }
@@ -72,6 +99,24 @@ export function mountM2mRoute(opts: M2mRouteOptions): void {
72
99
  return reply.code(400).send({ error: "invalid_id" });
73
100
  }
74
101
 
102
+ // Stage 0 — the source id must name a row of THIS subtype. Skipping it would let
103
+ // `/auths/bridge/{a Copay's id}/linkedAuths` return that Copay's relations, because
104
+ // the junction FK cannot tell the subtypes apart: it points at the shared base table.
105
+ const srcDisc = opts.sourceDiscriminator;
106
+ if (srcDisc !== undefined) {
107
+ const srcPk = columnRef(srcDisc.table, srcDisc.pkColumn);
108
+ const ownId = coerceIdForColumn(srcPk, id);
109
+ if (ownId === undefined) {
110
+ return reply.code(400).send({ error: "invalid_id" });
111
+ }
112
+ const owner = await opts.db
113
+ .select({ d: columnRef(srcDisc.table, srcDisc.column) })
114
+ .from(srcDisc.table)
115
+ .where(eq(srcPk, ownId))
116
+ .limit(1);
117
+ if (owner.length === 0 || String(owner[0]?.d) !== srcDisc.value) return [];
118
+ }
119
+
75
120
  // Stage 1 — junction rows for this source id.
76
121
  const joinWhere = opts.symmetric
77
122
  ? or(eq(srcCol, sourceId), eq(tgtCol, sourceId))
@@ -101,10 +146,12 @@ export function mountM2mRoute(opts: M2mRouteOptions): void {
101
146
 
102
147
  // Stage 2 — load the target rows.
103
148
  const pkCol = columnRef(opts.targetTable, targetPk);
149
+ const byPk = inArray(pkCol, [...relatedIds]);
150
+ const disc = opts.targetDiscriminator;
104
151
  return await opts.db
105
152
  .select()
106
153
  .from(opts.targetTable)
107
- .where(inArray(pkCol, [...relatedIds]));
154
+ .where(disc ? and(byPk, eq(columnRef(opts.targetTable, disc.column), disc.value)) : byPk);
108
155
  });
109
156
  }
110
157