@vesk/adapter 0.2.63 → 0.2.65

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.
@@ -1504,10 +1504,34 @@ async function buildTreeShakenRuntimeNow(runtimeDir, usedNames) {
1504
1504
  const missing = unique.filter((n) => !available.has(n));
1505
1505
  if (missing.length > 0) {
1506
1506
  console.error(`vesk: runtime names not exported — ${missing.join(', ')}; falling back to full runtime`);
1507
+ // Fall back to a FULL esbuild bundle of every name that DOES exist, not
1508
+ // to the legacy concatenation. The concat strips `export ` from whole
1509
+ // runtime files and drops them into ONE module scope, where
1510
+ // hydrate.js and router.js both declare a top-level `hydrateInitial`
1511
+ // (`Identifier 'hydrateInitial' has already been declared`) — a
1512
+ // page-wide SyntaxError. So the "safety net" guaranteed total failure
1513
+ // for every page, which is the opposite of its job. The bundle keeps
1514
+ // the runtime in a closed IIFE, so the unknown names are simply absent
1515
+ // (a clear ReferenceError at the one call site) instead of every page
1516
+ // dying at evaluation.
1517
+ const full = await bundleRuntimeNames(runtimeDir, [...available]);
1518
+ if (full)
1519
+ return full;
1520
+ // esbuild itself failed — last resort is the old concat.
1507
1521
  return buildRuntimeCode(runtimeDir);
1508
1522
  }
1523
+ return (await bundleRuntimeNames(runtimeDir, unique, true)) ?? buildRuntimeCode(runtimeDir);
1524
+ }
1525
+ /**
1526
+ * Bundles the named runtime exports into one closed IIFE plus an explicit
1527
+ * re-export tail. Returns null when esbuild fails, so callers can fall back.
1528
+ */
1529
+ async function bundleRuntimeNames(runtimeDir, names, strict = false) {
1530
+ if (names.length === 0)
1531
+ return null;
1509
1532
  const entry = runtimeTreeShakeEntry();
1510
1533
  const indexClient = esbuildPath(join(runtimeDir, 'index-client.js'));
1534
+ const unique = [...new Set(names)];
1511
1535
  try {
1512
1536
  try {
1513
1537
  mkdirSync(dirname(entry), { recursive: true });
@@ -1541,8 +1565,9 @@ async function buildTreeShakenRuntimeNow(runtimeDir, usedNames) {
1541
1565
  return `${bundle}\nconst { ${unique.join(', ')} } = __veskRuntime;\nexport { ${unique.join(', ')} };\n`;
1542
1566
  }
1543
1567
  catch (e) {
1544
- console.error('vesk: runtime tree-shake failed, falling back to full runtime:', e.message);
1545
- return buildRuntimeCode(runtimeDir);
1568
+ if (strict)
1569
+ console.error('vesk: runtime tree-shake failed, falling back to full runtime:', e.message);
1570
+ return null;
1546
1571
  }
1547
1572
  finally {
1548
1573
  try {
@@ -903,11 +903,17 @@ function matchPath(pattern, pathname) {
903
903
  const pathParts = pathname.split('/').filter(Boolean);
904
904
  let pi = 0, pp = 0;
905
905
  while (pi < pathParts.length && pp < patternParts.length) {
906
- if (patternParts[pp].startsWith(':')) {
906
+ const seg = patternParts[pp];
907
+ // `*name` is a catch-all and absorbs the rest of the path.
908
+ if (seg.startsWith('*')) {
909
+ pi = pathParts.length;
910
+ pp++;
911
+ }
912
+ else if (seg.startsWith(':')) {
907
913
  pi++;
908
914
  pp++;
909
915
  }
910
- else if (patternParts[pp] === pathParts[pi]) {
916
+ else if (seg === pathParts[pi]) {
911
917
  pi++;
912
918
  pp++;
913
919
  }
@@ -915,5 +921,9 @@ function matchPath(pattern, pathname) {
915
921
  return false;
916
922
  }
917
923
  }
924
+ // A trailing catch-all still matches an empty tail; the loop above exits as
925
+ // soon as the URL is exhausted, so without this it was never reached.
926
+ if (pp === patternParts.length - 1 && patternParts[pp].startsWith('*'))
927
+ pp++;
918
928
  return pp === patternParts.length && pi === pathParts.length;
919
929
  }
@@ -1 +1 @@
1
- {"version":3,"file":"manifest.d.ts","sourceRoot":"","sources":["../src/manifest.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,YAAY,EAAE,QAAQ,EAAqE,cAAc,EAAE,MAAM,yBAAyB,CAAC;AAEpK,wBAAgB,gBAAgB,CAC9B,MAAM,EAAE,SAAS,EAAE,EACnB,SAAS,EAAE,SAAS,EAAE,EACtB,SAAS,EAAE,YAAY,EAAE,EACzB,YAAY,EAAE,cAAc,EAAE,EAC9B,iBAAiB,EAAE,OAAO,EAC1B,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAClC,SAAS,CAAC,EAAE,MAAM,EAClB,aAAa,UAAQ,GACpB,QAAQ,CAmDV"}
1
+ {"version":3,"file":"manifest.d.ts","sourceRoot":"","sources":["../src/manifest.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,YAAY,EAAE,QAAQ,EAAqE,cAAc,EAAE,MAAM,yBAAyB,CAAC;AAiCpK,wBAAgB,gBAAgB,CAC9B,MAAM,EAAE,SAAS,EAAE,EACnB,SAAS,EAAE,SAAS,EAAE,EACtB,SAAS,EAAE,YAAY,EAAE,EACzB,YAAY,EAAE,cAAc,EAAE,EAC9B,iBAAiB,EAAE,OAAO,EAC1B,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAClC,SAAS,CAAC,EAAE,MAAM,EAClB,aAAa,UAAQ,GACpB,QAAQ,CAuEV"}
package/dist/manifest.js CHANGED
@@ -1,3 +1,33 @@
1
+ /**
2
+ * Specificity key for a route pattern, compared segment by segment: a literal
3
+ * segment outranks a dynamic one, so `/users/me` is tried before
4
+ * `/users/:id`.
5
+ *
6
+ * Every consumer of the manifest — prod-server, adapter dev-server, platform
7
+ * handler — scans `routes` in order and takes the FIRST match, so the order
8
+ * written here IS the routing precedence. Entries came out in directory-scan
9
+ * order, which puts `[id]` ahead of `me`, leaving a static API route
10
+ * permanently unreachable in a production build while the dev tree matcher
11
+ * (which sorts siblings) answered correctly.
12
+ */
13
+ function specificityKey(path) {
14
+ return path
15
+ .split('/')
16
+ .filter(Boolean)
17
+ // `:x` is one segment, `*x` is the rest of the path — so `*` sorts below
18
+ // `:` and both below a literal.
19
+ .map(seg => (seg.startsWith('*') ? '2' + seg : seg.startsWith(':') ? '1' + seg : '0' + seg));
20
+ }
21
+ function compareSpecificity(a, b) {
22
+ const ka = specificityKey(a);
23
+ const kb = specificityKey(b);
24
+ const len = Math.min(ka.length, kb.length);
25
+ for (let i = 0; i < len; i++) {
26
+ if (ka[i] !== kb[i])
27
+ return ka[i] < kb[i] ? -1 : 1;
28
+ }
29
+ return kb.length - ka.length;
30
+ }
1
31
  export function generateManifest(routes, ssrRoutes, apiRoutes, staticRoutes, middlewareEnabled, actionMap, headExtra, eventsEnabled = false) {
2
32
  const routeEntries = [];
3
33
  for (const r of ssrRoutes) {
@@ -14,15 +44,36 @@ export function generateManifest(routes, ssrRoutes, apiRoutes, staticRoutes, mid
14
44
  entry.tags = r._isrTags;
15
45
  routeEntries.push(entry);
16
46
  }
47
+ const apiEntries = [];
17
48
  for (const r of apiRoutes) {
18
49
  const urlParts = r.fullPath.split('/').filter(Boolean);
19
- const name = urlParts.map(s => s.startsWith(':') ? s.slice(1) || 'param' : s).join('_') || 'index';
20
- routeEntries.push({
21
- path: `/api${r.fullPath}`,
50
+ // A catch-all segment is emitted as `*name`, not `:name`. They were both
51
+ // written as `:name`, which made `[...rest]` and `[id]` indistinguishable
52
+ // in the manifest — and since every consumer treats `:` as exactly ONE
53
+ // segment, a catch-all API route could never match more than one segment
54
+ // in a production build (`/api/files/a/b/c` never reached `[...rest]`,
55
+ // while the dev tree matcher handled it correctly).
56
+ const parts = urlParts.map((s, i) => {
57
+ const last = i === urlParts.length - 1;
58
+ if (last && r.isCatchAll && s.startsWith(':'))
59
+ return '*' + s.slice(1);
60
+ return s;
61
+ });
62
+ // Strip the `:name` / `*name` prefix from DYNAMIC AND CATCH-ALL segments
63
+ // only. A blanket `slice(1)` also decapitates static segments, which made
64
+ // `/api/posts` point at `server/api/osts.js` while the file on disk is
65
+ // `posts.js` — so every static API route 404'd in a production build.
66
+ const name = parts
67
+ .map(s => (s.startsWith(':') || s.startsWith('*')) ? s.slice(1) || 'param' : s)
68
+ .join('_') || 'index';
69
+ apiEntries.push({
70
+ path: parts.length ? `/api/${parts.join('/')}` : '/api',
22
71
  type: 'api',
23
72
  function: `server/api/${name}.js`,
24
73
  });
25
74
  }
75
+ apiEntries.sort((a, b) => compareSpecificity(a.path, b.path));
76
+ routeEntries.push(...apiEntries);
26
77
  const ssgEntries = [];
27
78
  for (const r of staticRoutes) {
28
79
  ssgEntries.push({
@@ -1 +1 @@
1
- {"version":3,"file":"platform-handler.d.ts","sourceRoot":"","sources":["../src/platform-handler.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAC;AASvE,MAAM,WAAW,oBAAoB;IACnC,SAAS,EAAE,SAAS,EAAE,CAAC;IACvB,SAAS,EAAE,YAAY,EAAE,CAAC;IAC1B,gBAAgB,EAAE,MAAM,EAAE,CAAC;IAC3B,aAAa,EAAE,OAAO,CAAC;IACvB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,iFAAiF;IACjF,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,wBAAgB,SAAS,CAAC,QAAQ,EAAE,MAAM,EAAE,GAAG,MAAM,CAMpD;AAED,wBAAgB,YAAY,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAGrD;AAED,wBAAgB,IAAI,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAEtC;AAgBD;;;;;GAKG;AACH,wBAAgB,6BAA6B,CAAC,KAAK,EAAE,oBAAoB,GAAG,MAAM,CAyMjF;AAED,MAAM,WAAW,qBAAqB;IACpC,kEAAkE;IAClE,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;CACf;AAED,wBAAsB,qBAAqB,CAAC,OAAO,EAAE,qBAAqB,GAAG,OAAO,CAAC,IAAI,CAAC,CAsFzF;AAED,MAAM,WAAW,oBAAoB;IACnC,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,SAAS,EAAE,CAAC;IACvB,SAAS,EAAE,YAAY,EAAE,CAAC;IAC1B,gBAAgB,EAAE,MAAM,EAAE,CAAC;IAC3B,aAAa,EAAE,OAAO,CAAC;IACvB,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB"}
1
+ {"version":3,"file":"platform-handler.d.ts","sourceRoot":"","sources":["../src/platform-handler.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAC;AASvE,MAAM,WAAW,oBAAoB;IACnC,SAAS,EAAE,SAAS,EAAE,CAAC;IACvB,SAAS,EAAE,YAAY,EAAE,CAAC;IAC1B,gBAAgB,EAAE,MAAM,EAAE,CAAC;IAC3B,aAAa,EAAE,OAAO,CAAC;IACvB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,iFAAiF;IACjF,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,wBAAgB,SAAS,CAAC,QAAQ,EAAE,MAAM,EAAE,GAAG,MAAM,CAMpD;AAED,wBAAgB,YAAY,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAGrD;AAED,wBAAgB,IAAI,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAEtC;AAgBD;;;;;GAKG;AACH,wBAAgB,6BAA6B,CAAC,KAAK,EAAE,oBAAoB,GAAG,MAAM,CA8MjF;AAED,MAAM,WAAW,qBAAqB;IACpC,kEAAkE;IAClE,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;CACf;AAED,wBAAsB,qBAAqB,CAAC,OAAO,EAAE,qBAAqB,GAAG,OAAO,CAAC,IAAI,CAAC,CAsFzF;AAED,MAAM,WAAW,oBAAoB;IACnC,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,SAAS,EAAE,CAAC;IACvB,SAAS,EAAE,YAAY,EAAE,CAAC;IAC1B,gBAAgB,EAAE,MAAM,EAAE,CAAC;IAC3B,aAAa,EAAE,OAAO,CAAC;IACvB,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB"}
@@ -119,16 +119,21 @@ function __matchPath(pattern, pathname) {
119
119
  let pi = 0, pp = 0;
120
120
  const params = {};
121
121
  while (pi < pathParts.length && pp < patternParts.length) {
122
- if (patternParts[pp].startsWith(':')) {
123
- const name = patternParts[pp].slice(1);
124
- params[name] = pathParts[pi];
122
+ const seg = patternParts[pp];
123
+ // A "*name" segment is a catch-all and absorbs the rest of the path.
124
+ if (seg.startsWith('*')) { params[seg.slice(1)] = pathParts.slice(pi).join('/'); pi = pathParts.length; pp++; }
125
+ else if (seg.startsWith(':')) {
126
+ params[seg.slice(1)] = pathParts[pi];
125
127
  pi++; pp++;
126
- } else if (patternParts[pp] === pathParts[pi]) {
128
+ } else if (seg === pathParts[pi]) {
127
129
  pi++; pp++;
128
130
  } else {
129
131
  return null;
130
132
  }
131
133
  }
134
+ // A trailing catch-all still matches an empty tail; the loop above exits as
135
+ // soon as the URL is exhausted, so without this it was never reached.
136
+ if (pp === patternParts.length - 1 && patternParts[pp].startsWith('*')) { params[patternParts[pp].slice(1)] = pathParts.slice(pi).join('/'); pp++; }
132
137
  if (pp === patternParts.length && pi === pathParts.length) return params;
133
138
  return null;
134
139
  }
@@ -1 +1 @@
1
- {"version":3,"file":"prod-server.d.ts","sourceRoot":"","sources":["../src/prod-server.ts"],"names":[],"mappings":"AAEA,OAAO,EAA2D,KAAK,MAAM,EAAE,MAAM,WAAW,CAAC;AAkDjG,wBAAgB,YAAY,CAAC,QAAQ,EAAE,MAAM,GAAG,KAAK,GAAG;IAAE,MAAM,EAAE,MAAM,CAAA;CAAE,CAIzE;AAkGD,wBAAsB,eAAe,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE;IAAE,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,YAAY,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CAqexI"}
1
+ {"version":3,"file":"prod-server.d.ts","sourceRoot":"","sources":["../src/prod-server.ts"],"names":[],"mappings":"AAEA,OAAO,EAA2D,KAAK,MAAM,EAAE,MAAM,WAAW,CAAC;AAkDjG,wBAAgB,YAAY,CAAC,QAAQ,EAAE,MAAM,GAAG,KAAK,GAAG;IAAE,MAAM,EAAE,MAAM,CAAA;CAAE,CAIzE;AAkGD,wBAAsB,eAAe,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE;IAAE,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,YAAY,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CAkfxI"}
@@ -215,13 +215,20 @@ export async function startProdServer(outDir, options) {
215
215
  let pi = 0, pp = 0;
216
216
  const params = {};
217
217
  while (pi < pathParts.length && pp < patternParts.length) {
218
- if (patternParts[pp].startsWith(':')) {
219
- const name = patternParts[pp].slice(1);
218
+ const seg = patternParts[pp];
219
+ if (seg.startsWith('*')) {
220
+ // Catch-all: absorbs every remaining segment (possibly none).
221
+ params[seg.slice(1)] = pathParts.slice(pi).join('/');
222
+ pi = pathParts.length;
223
+ pp++;
224
+ }
225
+ else if (seg.startsWith(':')) {
226
+ const name = seg.slice(1);
220
227
  params[name] = pathParts[pi];
221
228
  pi++;
222
229
  pp++;
223
230
  }
224
- else if (patternParts[pp] === pathParts[pi]) {
231
+ else if (seg === pathParts[pi]) {
225
232
  pi++;
226
233
  pp++;
227
234
  }
@@ -229,6 +236,13 @@ export async function startProdServer(outDir, options) {
229
236
  return null;
230
237
  }
231
238
  }
239
+ // A trailing catch-all still matches with an empty tail (`/api/files` ->
240
+ // `/api/files/*rest` with rest=''). The loop above exits as soon as the
241
+ // URL is exhausted, so without this the catch-all was never reached.
242
+ if (pp === patternParts.length - 1 && patternParts[pp].startsWith('*')) {
243
+ params[patternParts[pp].slice(1)] = pathParts.slice(pi).join('/');
244
+ pp++;
245
+ }
232
246
  if (pp === patternParts.length && pi === pathParts.length)
233
247
  return params;
234
248
  return null;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vesk/adapter",
3
- "version": "0.2.63",
3
+ "version": "0.2.65",
4
4
  "type": "module",
5
5
  "description": "Vesk adapter — builds deployable .vesk/ output for Deno and Node serverless",
6
6
  "main": "./dist/index.js",
@@ -23,9 +23,9 @@
23
23
  "sharp": "^0.35.3"
24
24
  },
25
25
  "dependencies": {
26
- "@vesk/compiler": "^0.2.63",
27
- "@vesk/runtime": "^0.2.63",
28
- "@vesk/types": "^0.2.63"
26
+ "@vesk/compiler": "^0.2.65",
27
+ "@vesk/runtime": "^0.2.65",
28
+ "@vesk/types": "^0.2.65"
29
29
  },
30
30
  "devDependencies": {
31
31
  "@types/ws": "^8.18.1",