@teamscala/service-boot 0.23.355 → 0.23.356

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/package.json CHANGED
@@ -6,7 +6,7 @@
6
6
  },
7
7
  "name": "@teamscala/service-boot",
8
8
  "type": "module",
9
- "version": "0.23.355",
9
+ "version": "0.23.356",
10
10
  "sideEffects": false,
11
11
  "imports": {
12
12
  "#service-boot/*": "./src/*",
@@ -10,7 +10,14 @@
10
10
  */
11
11
 
12
12
  import { describe, expect, test } from "bun:test";
13
- import { interpolateMeta } from "./resolve-from-loader-config";
13
+ import {
14
+ interpolateMeta,
15
+ resolveFromLoaderConfig,
16
+ } from "./resolve-from-loader-config";
17
+ import {
18
+ getServerClient,
19
+ setServerClient,
20
+ } from "./page-server-orpc/server-client";
14
21
 
15
22
  describe("interpolateMeta (resolver_input ref contract)", () => {
16
23
  test("a bare :sourceKey ref passes the whole resolved source object", () => {
@@ -42,3 +49,57 @@ describe("interpolateMeta (resolver_input ref contract)", () => {
42
49
  expect(out.score).toBe(48);
43
50
  });
44
51
  });
52
+
53
+ describe("resource-loader model capability", () => {
54
+ test("an optional source still refuses a model absent from the generated client", async () => {
55
+ const original = getServerClient();
56
+ setServerClient({} as never);
57
+ try {
58
+ await expect(
59
+ resolveFromLoaderConfig(
60
+ {
61
+ sources: [
62
+ {
63
+ key: "body",
64
+ source: "orpc",
65
+ model: "builda_pages",
66
+ optional: true,
67
+ },
68
+ ],
69
+ },
70
+ {},
71
+ ),
72
+ ).rejects.toThrow(
73
+ 'requires model "builda_pages", but the generated server client does not expose findFirst',
74
+ );
75
+ } finally {
76
+ setServerClient(original);
77
+ }
78
+ });
79
+
80
+ test("an optional source may resolve an absent row when the model capability exists", async () => {
81
+ const original = getServerClient();
82
+ setServerClient({
83
+ builda_pages: { findFirst: async () => null },
84
+ } as never);
85
+ try {
86
+ await expect(
87
+ resolveFromLoaderConfig(
88
+ {
89
+ sources: [
90
+ {
91
+ key: "body",
92
+ source: "orpc",
93
+ model: "builda_pages",
94
+ optional: true,
95
+ },
96
+ ],
97
+ },
98
+ {},
99
+ ),
100
+ ).resolves.toEqual({ body: null });
101
+ } finally {
102
+ setServerClient(original);
103
+ }
104
+ });
105
+ });
@@ -34,6 +34,15 @@ interface LoaderConfig {
34
34
  merge_strategy?: Record<string, unknown>;
35
35
  }
36
36
 
37
+ class MissingLoaderModelCapabilityError extends Error {
38
+ constructor(model: string, sourceKey: string) {
39
+ super(
40
+ `[resource_loader] source "${sourceKey}" requires model "${model}", but the generated server client does not expose findFirst for it — regenerate the service schema from its page loaders`,
41
+ );
42
+ this.name = "MissingLoaderModelCapabilityError";
43
+ }
44
+ }
45
+
37
46
  function resolveParamRef(
38
47
  ref: string,
39
48
  params: Record<string, unknown>,
@@ -161,7 +170,9 @@ async function resolveSource(
161
170
  }) => Promise<unknown>;
162
171
  }
163
172
  | undefined;
164
- if (!model?.findFirst) return null;
173
+ if (!model?.findFirst) {
174
+ throw new MissingLoaderModelCapabilityError(source.model, source.key);
175
+ }
165
176
  return model.findFirst({
166
177
  where,
167
178
  select: toSelectMap(source.select),
@@ -261,7 +272,10 @@ export async function resolveFromLoaderConfig(
261
272
  if (!source || !result) continue;
262
273
  if (result.status === "fulfilled") {
263
274
  resolved[source.key] = result.value;
264
- } else if (source.optional) {
275
+ } else if (
276
+ source.optional &&
277
+ !(result.reason instanceof MissingLoaderModelCapabilityError)
278
+ ) {
265
279
  resolved[source.key] = null;
266
280
  } else {
267
281
  throw result.reason;