@teamscala/service-boot 0.23.556 → 0.23.559

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
@@ -27,7 +27,7 @@
27
27
  "repository": "https://github.com/teamgbg/service-boot",
28
28
  "sideEffects": false,
29
29
  "type": "module",
30
- "version": "0.23.556",
30
+ "version": "0.23.559",
31
31
  "dependencies": {
32
32
  "@orpc/openapi": "*",
33
33
  "@orpc/server": "*",
@@ -18,6 +18,8 @@ const HYDRATE_BASE = "hydrate";
18
18
  export interface HydrateBundleResult {
19
19
  /** Served entry path (`/assets/hydrate-<hash>.js`), or null when no entry exists. */
20
20
  hydratePath: string | null;
21
+ /** Chunk URLs the entry itself can load, for modulepreload emission. */
22
+ preloads: string[];
21
23
  /** The VERIFIED source-map upload outcome for this build's release. */
22
24
  sourceMaps: SourceMapUploadResult;
23
25
  }
@@ -195,8 +197,30 @@ export async function buildHydrateBundle(
195
197
  .filter((o) => o.kind === "chunk")
196
198
  .map((o) => o.path.split("/").pop() as string),
197
199
  );
200
+ const entryText = await Bun.file(hashedPath).text();
201
+ const preloads = [
202
+ ...new Set(
203
+ [...entryText.matchAll(/["'](\/assets\/[A-Za-z0-9-]+\.js)["']/g)].map(
204
+ (m) => m[1],
205
+ ),
206
+ ),
207
+ ].sort();
208
+ const unknownPreloads = preloads.filter(
209
+ (url) =>
210
+ url !== `${ASSET_PUBLIC_PREFIX}${hashedName}` &&
211
+ !currentChunks.has(url.replace(/^\/assets\//, "")),
212
+ );
213
+ if (unknownPreloads.length > 0) {
214
+ throw new Error(
215
+ `[build-client-assets] entry references chunk URL(s) Bun did not emit: ${unknownPreloads.join(", ")} — refusing a manifest whose preloads name unbuilt assets`,
216
+ );
217
+ }
198
218
  const sourceMaps = await uploadSourceMaps(cwd, outDir, hash, log, currentOutputs);
199
- return { hydratePath: `${ASSET_PUBLIC_PREFIX}${hashedName}`, sourceMaps };
219
+ return {
220
+ hydratePath: `${ASSET_PUBLIC_PREFIX}${hashedName}`,
221
+ preloads,
222
+ sourceMaps,
223
+ };
200
224
  }
201
225
 
202
226
  async function removeStaleChunks(
@@ -12,6 +12,7 @@ export const MANIFEST_FILE = "asset-manifest.json";
12
12
  export interface AssetManifest {
13
13
  stylesPath?: string;
14
14
  hydratePath?: string;
15
+ preloads?: string[];
15
16
  sourceMaps?: SourceMapUploadResult;
16
17
  }
17
18
 
@@ -47,6 +48,7 @@ export async function readManifestState(outDir: string): Promise<ManifestState>
47
48
  manifest: {
48
49
  stylesPath: parsed.stylesPath,
49
50
  hydratePath: parsed.hydratePath,
51
+ preloads: parsed.preloads,
50
52
  sourceMaps: parsed.sourceMaps,
51
53
  },
52
54
  };
@@ -96,10 +98,12 @@ export async function verifyClientBuildAssets(
96
98
  const verdict = verifyManifestState(await readManifestState(outDir), outDir);
97
99
  if (!verdict.ok) return verdict;
98
100
  const manifest = verdict.manifest;
99
- for (const [label, url] of [
101
+ const named: Array<readonly [string, string]> = [
100
102
  ["stylesheet", manifest.stylesPath],
101
103
  ["hydrate bundle", manifest.hydratePath],
102
- ] as const) {
104
+ ];
105
+ for (const url of manifest.preloads ?? []) named.push(["preload chunk", url]);
106
+ for (const [label, url] of named) {
103
107
  const file = join(outDir, url.replace(/^\/assets\//, ""));
104
108
  if (!(await Bun.file(file).exists())) {
105
109
  return {
@@ -109,6 +109,7 @@ async function performBuild(
109
109
  await writeManifest(outDir, {
110
110
  stylesPath,
111
111
  hydratePath: hydrate.hydratePath,
112
+ preloads: hydrate.preloads,
112
113
  sourceMaps: hydrate.sourceMaps,
113
114
  });
114
115
 
@@ -20,12 +20,12 @@ export async function resolveProposalAuthorizedEmails(
20
20
  UNION
21
21
  SELECT lower(cd.primary_email) AS email
22
22
  FROM client_database cd
23
- WHERE cd.id = (SELECT contact_id FROM proposals WHERE id = ${proposalId})
23
+ WHERE cd.id = (SELECT contact_id::text FROM proposals WHERE id = ${proposalId})
24
24
  AND cd.primary_email IS NOT NULL
25
25
  UNION
26
26
  SELECT lower(cd.primary_email) AS email
27
27
  FROM client_database cd
28
- WHERE cd.company_id = (SELECT partner_id FROM proposals WHERE id = ${proposalId})::uuid
28
+ WHERE cd.company_id::text = (SELECT partner_id FROM proposals WHERE id = ${proposalId})
29
29
  AND cd.primary_email IS NOT NULL
30
30
  ` as { email: string }[];
31
31
  return new Set(
@@ -11,6 +11,7 @@ import { join } from "node:path";
11
11
  interface TemplateManifest {
12
12
  stylesPath: string;
13
13
  hydratePath: string;
14
+ preloads: string[];
14
15
  }
15
16
 
16
17
  const PAGE_DOCUMENT = `<!doctype html>
@@ -22,6 +23,7 @@ const PAGE_DOCUMENT = `<!doctype html>
22
23
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
23
24
  <!--theme-fonts-->
24
25
  <link rel="stylesheet" href="{{stylesPath}}" />
26
+ <!--module-preloads-->
25
27
  <!--head-outlet-->
26
28
  </head>
27
29
  <body>
@@ -56,7 +58,11 @@ async function manifestMtimeMs(): Promise<number> {
56
58
  async function loadManifest(): Promise<TemplateManifest | null> {
57
59
  const manifest = await readManifest(join(process.cwd(), "dist", "client"));
58
60
  if (!manifest?.stylesPath || !manifest.hydratePath) return null;
59
- return { stylesPath: manifest.stylesPath, hydratePath: manifest.hydratePath };
61
+ return {
62
+ stylesPath: manifest.stylesPath,
63
+ hydratePath: manifest.hydratePath,
64
+ preloads: manifest.preloads ?? [],
65
+ };
60
66
  }
61
67
 
62
68
  function manifestCacheKey(): string {
@@ -78,6 +84,16 @@ export async function readHtmlTemplate(): Promise<string> {
78
84
  let html = PAGE_DOCUMENT
79
85
  .replace(/\{\{stylesPath\}\}/g, manifest.stylesPath)
80
86
  .replace(/\{\{hydratePath\}\}/g, manifest.hydratePath)
87
+ .replace(
88
+ "<!--module-preloads-->",
89
+ () =>
90
+ manifest.preloads
91
+ .map(
92
+ (url) =>
93
+ `<link rel="modulepreload" href="${url}" />`,
94
+ )
95
+ .join("\n\t\t"),
96
+ )
81
97
  .replace("<!--theme-fonts-->", () => themeFontsLink());
82
98
  htmlCache.set(manifestCacheKey(), { html, manifestMtime });
83
99
  return html;