@hops-ops/distributed 4.9.0 → 4.10.0

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.
@@ -0,0 +1,734 @@
1
+ import { lstat, readFile, readdir } from 'node:fs/promises';
2
+ import { dirname, extname, isAbsolute, join, posix, relative, resolve, sep } from 'node:path';
3
+ import { parse } from 'svelte/compiler';
4
+ const BOUNDARY_PLAN_VERSION = 1;
5
+ const MAX_COMPONENTS = 4_096;
6
+ const MAX_ISLANDS = 4_096;
7
+ const MAX_COMPONENT_BYTES = 2 * 1024 * 1024;
8
+ const MAX_GRAPH_EDGES = 32_768;
9
+ /** Validate a persisted adapter plan before check/dev treats it as coherent. */
10
+ export function validateDistributedSvelteKitBoundaryPlan(value, module) {
11
+ if (value === null ||
12
+ typeof value !== 'object' ||
13
+ Array.isArray(value) ||
14
+ value.version !== BOUNDARY_PLAN_VERSION ||
15
+ typeof value.module !== 'string' ||
16
+ (module !== undefined && value.module !== module) ||
17
+ !Array.isArray(value.boundaries) ||
18
+ !Array.isArray(value.unplaced)) {
19
+ throw new Error(`[distributed.island.boundary_plan_invalid] ${module ?? '<unknown>'} boundaries.json is not version ${BOUNDARY_PLAN_VERSION}`);
20
+ }
21
+ return value;
22
+ }
23
+ /**
24
+ * Analyze Svelte component reachability without evaluating application code.
25
+ * The returned plan is deterministic and contains project-relative paths only.
26
+ */
27
+ export async function analyzeDistributedSvelteKitBoundaries(options) {
28
+ const cwd = resolve(options.cwd);
29
+ const routesDir = contained(cwd, options.routesDir ?? 'src/routes', 'routesDir');
30
+ const libDir = contained(cwd, options.libDir ?? 'src/lib', 'libDir');
31
+ const aliases = new Map([
32
+ ['$lib', libDir],
33
+ ...Object.entries(options.aliases ?? {}).map(([key, value]) => [
34
+ aliasKey(key),
35
+ contained(cwd, value, `alias ${key}`)
36
+ ])
37
+ ]);
38
+ const components = await loadComponents(cwd, [
39
+ ...new Set([routesDir, libDir, ...aliases.values()])
40
+ ]);
41
+ const roots = boundaryRoots(cwd, routesDir, components);
42
+ const sourceOwners = new Map();
43
+ for (const client of options.clients) {
44
+ validateInventory(client.module, client.inventory);
45
+ for (const island of client.inventory.islands.filter((candidate) => candidate.directives.load)) {
46
+ const source = portableSource(island.source.path);
47
+ const previous = sourceOwners.get(source);
48
+ if (previous !== undefined && previous !== client.module) {
49
+ throw diagnostic('distributed.island.cross_surface', source, island.source.line, island.source.column, `operation ${island.operation} is owned by both ${previous} and ${client.module}; split the GraphQL source by authorization surface`);
50
+ }
51
+ sourceOwners.set(source, client.module);
52
+ }
53
+ }
54
+ const plans = [];
55
+ for (const client of [...options.clients].sort((left, right) => left.module.localeCompare(right.module))) {
56
+ const loadIslands = client.inventory.islands
57
+ .filter((island) => island.directives.load)
58
+ .map((island) => ({ island, source: portableSource(island.source.path) }))
59
+ .sort((left, right) => left.source.localeCompare(right.source) ||
60
+ left.island.operation.localeCompare(right.island.operation));
61
+ const componentIslands = new Map();
62
+ const routeIslands = new Map();
63
+ const islandByOperation = new Map(loadIslands.map((entry) => [entry.island.operation, entry]));
64
+ const explicitByBoundary = new Map();
65
+ const explicitByIdentity = new Map();
66
+ const explicitlyPlaced = new Set();
67
+ for (const registration of client.explicitBoundaries ?? []) {
68
+ const entry = islandByOperation.get(registration.operation);
69
+ if (entry === undefined) {
70
+ throw new Error(`[distributed.island.explicit_operation_missing] ${client.module} explicit boundary references unknown @load operation ${registration.operation}`);
71
+ }
72
+ const id = `${registration.kind}:${normalizeRoute(registration.route)}`;
73
+ const bucket = explicitByBoundary.get(id) ?? [];
74
+ const identity = `${id}\u0000${registration.operation}`;
75
+ if (explicitByIdentity.has(identity)) {
76
+ throw new Error(`[distributed.island.explicit_duplicate] ${client.module} repeats ${registration.operation} at ${registration.route}`);
77
+ }
78
+ bucket.push(Object.freeze({ entry, registration }));
79
+ explicitByBoundary.set(id, bucket);
80
+ explicitByIdentity.set(identity, registration);
81
+ explicitlyPlaced.add(entry.island.id);
82
+ }
83
+ for (const entry of loadIslands) {
84
+ const target = islandOwnerComponent(cwd, entry.source);
85
+ if (target.kind === 'component') {
86
+ const bucket = componentIslands.get(target.path) ?? [];
87
+ const conflicting = bucket.find((candidate) => candidate.source !== entry.source);
88
+ if (conflicting !== undefined) {
89
+ throw diagnostic('distributed.island.sibling_conflict', entry.source, entry.island.source.line, entry.island.source.column, `operation ${entry.island.operation} conflicts with sibling source ${conflicting.source}; keep one GraphQL sibling or register an explicit boundary`);
90
+ }
91
+ bucket.push(entry);
92
+ componentIslands.set(target.path, bucket);
93
+ }
94
+ else {
95
+ const bucket = routeIslands.get(target.key) ?? [];
96
+ bucket.push(entry);
97
+ routeIslands.set(target.key, bucket);
98
+ }
99
+ }
100
+ for (const component of componentIslands.keys()) {
101
+ const needsComponent = componentIslands
102
+ .get(component)
103
+ .some(({ island }) => !explicitlyPlaced.has(island.id));
104
+ if (!needsComponent)
105
+ continue;
106
+ if (!components.has(component)) {
107
+ const entry = componentIslands.get(component)[0];
108
+ throw diagnostic('distributed.island.component_missing', entry.source, entry.island.source.line, entry.island.source.column, `operation ${entry.island.operation} requires sibling ${projectPath(cwd, component)}; add the component, register an explicit boundary, or mark it client-only`);
109
+ }
110
+ }
111
+ const boundaries = [];
112
+ const placed = new Set();
113
+ for (const root of roots) {
114
+ const occurrences = [];
115
+ const explicitEntries = explicitByBoundary.get(root.id) ?? [];
116
+ const explicitAtBoundary = new Set(explicitEntries.map(({ entry }) => entry.island.id));
117
+ for (const { entry, registration } of explicitEntries) {
118
+ occurrences.push(occurrence(cwd, entry, root, root.path, 'explicit', false, registration.variables));
119
+ placed.add(entry.island.id);
120
+ }
121
+ for (const entry of routeIslands.get(routeBoundaryKey(root.kind, dirname(root.path))) ?? []) {
122
+ occurrences.push(occurrence(cwd, entry, root, root.path, 'route_document', false, explicitByIdentity.get(`${root.id}\u0000${entry.island.operation}`)?.variables));
123
+ placed.add(entry.island.id);
124
+ }
125
+ const dynamicComponentIslands = new Map([...componentIslands.entries()]
126
+ .map(([path, entries]) => [
127
+ path,
128
+ entries.filter(({ island }) => !explicitAtBoundary.has(island.id))
129
+ ])
130
+ .filter(([, entries]) => entries.length > 0));
131
+ const reachable = traverse(root, components, aliases, dynamicComponentIslands, cwd);
132
+ for (const component of reachable) {
133
+ for (const entry of componentIslands.get(component) ?? []) {
134
+ occurrences.push(occurrence(cwd, entry, root, component, 'static_component_import', true, explicitByIdentity.get(`${root.id}\u0000${entry.island.operation}`)?.variables));
135
+ placed.add(entry.island.id);
136
+ }
137
+ }
138
+ const deduplicated = [...new Map(occurrences.map((entry) => [entry.islandId, entry])).values()].sort(compareOccurrences);
139
+ if (deduplicated.length > 0) {
140
+ boundaries.push(Object.freeze({
141
+ id: root.id,
142
+ route: root.route,
143
+ kind: root.kind,
144
+ source: projectPath(cwd, root.path),
145
+ islands: Object.freeze(deduplicated)
146
+ }));
147
+ }
148
+ }
149
+ const unplaced = loadIslands
150
+ .filter(({ island }) => !placed.has(island.id))
151
+ .map(({ island, source }) => Object.freeze({
152
+ islandId: island.id,
153
+ operation: island.operation,
154
+ graphqlSource: source
155
+ }));
156
+ plans.push(Object.freeze({
157
+ version: BOUNDARY_PLAN_VERSION,
158
+ module: client.module,
159
+ schemaFingerprint: client.inventory.schemaFingerprint,
160
+ protocolFingerprint: client.inventory.protocolFingerprint,
161
+ boundaries: Object.freeze(boundaries),
162
+ unplaced: Object.freeze(unplaced)
163
+ }));
164
+ }
165
+ return Object.freeze(plans);
166
+ }
167
+ function occurrence(cwd, entry, root, component, reason, conservative, explicitSources) {
168
+ if (root.kind === 'layout' &&
169
+ entry.island.directives.live &&
170
+ !entry.island.liveCoverage.finite) {
171
+ throw diagnostic('distributed.island.layout_live_unbounded', entry.source, entry.island.source.line, entry.island.source.column, `operation ${entry.island.operation} is live for layout ${root.route} without finite coverage; add a compiler-proved limit, move it to a page, register a bounded boundary-owned query, or mark it client-only`);
172
+ }
173
+ const binding = boundaryBinding(entry, root, explicitSources);
174
+ return Object.freeze({
175
+ islandId: entry.island.id,
176
+ operation: entry.island.operation,
177
+ modulePath: entry.island.modulePath,
178
+ exportName: entry.island.exportName,
179
+ component: projectPath(cwd, component),
180
+ graphqlSource: entry.source,
181
+ reason,
182
+ conservative,
183
+ directives: entry.island.directives,
184
+ liveCoverage: entry.island.liveCoverage,
185
+ binding
186
+ });
187
+ }
188
+ function boundaryBinding(entry, root, explicitSources) {
189
+ if (typeof entry.island.operationHash !== 'string' ||
190
+ entry.island.operationHash.length === 0 ||
191
+ !Array.isArray(entry.island.variableSchema?.variables)) {
192
+ throw diagnostic('distributed.island.binding_inventory_invalid', entry.source, entry.island.source.line, entry.island.source.column, `operation ${entry.island.operation} has invalid variable-binding inventory; regenerate the framework-neutral client`);
193
+ }
194
+ const variables = [...entry.island.variableSchema.variables].sort((left, right) => left.name.localeCompare(right.name));
195
+ const allowed = new Set(variables.map(({ name }) => name));
196
+ for (const name of Object.keys(explicitSources ?? {})) {
197
+ if (!allowed.has(name)) {
198
+ throw diagnostic('distributed.island.variable_unknown', entry.source, entry.island.source.line, entry.island.source.column, `operation ${entry.island.operation} binding names unknown variable ${name}; use an explicit binding, parent/boundary query, client-only execution, or a better read root`);
199
+ }
200
+ }
201
+ const routeParams = new Set(routeParameterNames(root.route));
202
+ const sources = [];
203
+ let hasExplicit = false;
204
+ let hasRoute = false;
205
+ for (const variable of variables) {
206
+ const explicit = ownDataValue(explicitSources, variable.name);
207
+ if (explicit !== undefined) {
208
+ let normalized;
209
+ try {
210
+ normalized = normalizeBindingSource(explicit, variable.name);
211
+ }
212
+ catch {
213
+ throw diagnostic('distributed.island.variable_source_invalid', entry.source, entry.island.source.line, entry.island.source.column, `operation ${entry.island.operation} variable ${variable.name} has an unsupported or unsafe explicit variable source at boundary ${root.route}; use a route/search parameter, trusted-session path, constant, forwarded prop, omission, parent/boundary query, client-only execution, or a better read root`);
214
+ }
215
+ sources.push([variable.name, normalized]);
216
+ hasExplicit = true;
217
+ }
218
+ else if (routeParams.has(variable.name)) {
219
+ sources.push([
220
+ variable.name,
221
+ Object.freeze({ kind: 'route_param', name: variable.name })
222
+ ]);
223
+ hasRoute = true;
224
+ }
225
+ else if (!variable.graphqlType.endsWith('!')) {
226
+ sources.push([variable.name, Object.freeze({ kind: 'omit' })]);
227
+ }
228
+ else {
229
+ throw diagnostic('distributed.island.variable_unprovable', entry.source, entry.island.source.line, entry.island.source.column, `operation ${entry.island.operation} variable ${variable.name} is not boundary-visible at ${root.route}; use an explicit binding, parent/boundary query, client-only execution, or a better read root`);
230
+ }
231
+ }
232
+ const sourceRecord = Object.freeze(Object.fromEntries(sources));
233
+ return Object.freeze({
234
+ version: 1,
235
+ id: `boundary-v1:${fnv1a64(`${entry.island.operationHash}\n${stableJson(sourceRecord)}`)}`,
236
+ discovery: hasExplicit ? 'explicit' : hasRoute ? 'route_param' : 'empty',
237
+ sources: sourceRecord
238
+ });
239
+ }
240
+ function routeParameterNames(route) {
241
+ const names = [];
242
+ for (const segment of route.split('/')) {
243
+ const match = /^\[\[?(?:\.\.\.)?([_A-Za-z][_0-9A-Za-z]*)(?:=[^\]]+)?\]?\]$/.exec(segment);
244
+ if (match?.[1] !== undefined)
245
+ names.push(match[1]);
246
+ }
247
+ return names;
248
+ }
249
+ function normalizeBindingSource(value, variable) {
250
+ if (value === null || typeof value !== 'object' || Array.isArray(value)) {
251
+ throw new TypeError(`Distributed boundary variable ${variable} source must be an object`);
252
+ }
253
+ const record = value;
254
+ const kind = ownDataValue(record, 'kind');
255
+ switch (kind) {
256
+ case 'omit':
257
+ return Object.freeze({ kind: 'omit' });
258
+ case 'route_param': {
259
+ const name = ownDataValue(record, 'name');
260
+ if (typeof name !== 'string' || name.length === 0) {
261
+ throw new TypeError(`Distributed boundary variable ${variable} source name is invalid`);
262
+ }
263
+ return Object.freeze({ kind: 'route_param', name });
264
+ }
265
+ case 'search_param': {
266
+ const name = ownDataValue(record, 'name');
267
+ if (typeof name !== 'string' || name.length === 0) {
268
+ throw new TypeError(`Distributed boundary variable ${variable} source name is invalid`);
269
+ }
270
+ const mode = ownDataValue(record, 'mode');
271
+ if (mode !== undefined && mode !== 'first' && mode !== 'all') {
272
+ throw new TypeError(`Distributed boundary variable ${variable} search mode is invalid`);
273
+ }
274
+ return Object.freeze({
275
+ kind: 'search_param',
276
+ name,
277
+ ...(mode === undefined ? {} : { mode })
278
+ });
279
+ }
280
+ case 'trusted_session':
281
+ case 'forwarded_prop': {
282
+ const path = ownDataValue(record, 'path');
283
+ if (!Array.isArray(path) ||
284
+ path.length === 0 ||
285
+ path.length > 16 ||
286
+ path.some((part) => typeof part !== 'string' ||
287
+ !/^[_A-Za-z][_0-9A-Za-z]*$/.test(part) ||
288
+ ['__proto__', 'prototype', 'constructor'].includes(part))) {
289
+ throw new TypeError(`Distributed boundary variable ${variable} source path is invalid`);
290
+ }
291
+ return Object.freeze({ kind, path: Object.freeze([...path]) });
292
+ }
293
+ case 'constant':
294
+ return Object.freeze({
295
+ kind: 'constant',
296
+ value: freezeStable(stableValue(ownDataValue(record, 'value')))
297
+ });
298
+ default:
299
+ throw new TypeError(`Distributed boundary variable ${variable} source is unsupported; use an explicit binding, parent/boundary query, client-only execution, or a better read root`);
300
+ }
301
+ }
302
+ function traverse(root, components, aliases, componentIslands, cwd) {
303
+ const reached = new Set();
304
+ const active = new Set();
305
+ const parent = new Map();
306
+ const visiting = [
307
+ { path: root.path, exit: false }
308
+ ];
309
+ let edges = 0;
310
+ while (visiting.length > 0) {
311
+ const frame = visiting.pop();
312
+ const path = frame.path;
313
+ if (frame.exit) {
314
+ active.delete(path);
315
+ continue;
316
+ }
317
+ if (reached.has(path))
318
+ continue;
319
+ reached.add(path);
320
+ active.add(path);
321
+ visiting.push({ path, exit: true });
322
+ const component = components.get(path);
323
+ if (component === undefined)
324
+ continue;
325
+ const targets = [];
326
+ for (const imported of component.imports) {
327
+ edges += 1;
328
+ if (edges > MAX_GRAPH_EDGES) {
329
+ throw diagnostic('distributed.island.graph_unbounded', projectPath(cwd, path), imported.line, imported.column, `boundary ${root.route} exceeds ${MAX_GRAPH_EDGES} static component edges; split the boundary or register a bounded explicit boundary`);
330
+ }
331
+ const target = resolveComponentImport(path, imported, aliases, components, cwd);
332
+ if (target === undefined)
333
+ continue;
334
+ if (active.has(target)) {
335
+ const cycle = [target];
336
+ let cursor = path;
337
+ while (cursor !== target) {
338
+ cycle.push(cursor);
339
+ const previous = parent.get(cursor);
340
+ if (previous === undefined)
341
+ break;
342
+ cursor = previous;
343
+ }
344
+ if (cycle.some((candidate) => componentIslands.has(candidate))) {
345
+ throw diagnostic('distributed.island.component_cycle', projectPath(cwd, path), imported.line, imported.column, `boundary ${root.route} reaches an @load island through a cyclic component graph; break the cycle, register an explicit boundary-owned query, or mark it client-only`);
346
+ }
347
+ continue;
348
+ }
349
+ if (!reached.has(target)) {
350
+ parent.set(target, path);
351
+ targets.push(target);
352
+ }
353
+ }
354
+ for (const target of targets.reverse()) {
355
+ visiting.push({ path: target, exit: false });
356
+ }
357
+ for (const imported of component.dynamicImports) {
358
+ if (imported.opaque) {
359
+ if (componentIslands.size > 0) {
360
+ throw diagnostic('distributed.island.dynamic_opaque', projectPath(cwd, path), imported.line, imported.column, `boundary ${root.route} has opaque dynamic component reachability while @load islands remain unplaced; use a static import, explicit boundary registration, or client-only execution`);
361
+ }
362
+ continue;
363
+ }
364
+ const target = resolveComponentImport(path, imported, aliases, components, cwd);
365
+ if (target !== undefined && componentIslands.has(target)) {
366
+ throw diagnostic('distributed.island.dynamic_load', projectPath(cwd, path), imported.line, imported.column, `boundary ${root.route} dynamically reaches an @load island; use a static import, explicit boundary registration, or client-only execution`);
367
+ }
368
+ }
369
+ }
370
+ return [...reached].sort();
371
+ }
372
+ function resolveComponentImport(from, imported, aliases, components, cwd) {
373
+ const specifier = imported.specifier;
374
+ let candidate;
375
+ if (specifier.startsWith('.')) {
376
+ candidate = resolve(dirname(from), specifier);
377
+ }
378
+ else {
379
+ const alias = [...aliases.entries()]
380
+ .sort((left, right) => right[0].length - left[0].length)
381
+ .find(([key]) => specifier === key || specifier.startsWith(`${key}/`));
382
+ if (alias !== undefined) {
383
+ candidate = resolve(alias[1], specifier.slice(alias[0].length).replace(/^\//, ''));
384
+ }
385
+ else if (specifier.startsWith('$') && looksLikeComponent(specifier)) {
386
+ throw diagnostic('distributed.island.alias_unresolved', projectPath(cwd, from), imported.line, imported.column, `component alias ${specifier.split('/')[0]} is unresolved; add it to distributedSvelteKit aliases or use a resolvable static import`);
387
+ }
388
+ }
389
+ if (candidate === undefined)
390
+ return undefined;
391
+ if (!isWithin(cwd, candidate)) {
392
+ throw diagnostic('distributed.island.import_escape', projectPath(cwd, from), imported.line, imported.column, 'component import escapes the project root; use a project-local component or client-only execution');
393
+ }
394
+ const attempts = extname(candidate) === '.svelte'
395
+ ? [candidate]
396
+ : [`${candidate}.svelte`, join(candidate, 'index.svelte')];
397
+ for (const attempt of attempts) {
398
+ if (components.has(attempt))
399
+ return attempt;
400
+ }
401
+ if (looksLikeComponent(specifier)) {
402
+ throw diagnostic('distributed.island.import_unresolved', projectPath(cwd, from), imported.line, imported.column, `component import ${specifier} is unresolved; fix the static import, register an explicit boundary, or mark it client-only`);
403
+ }
404
+ return undefined;
405
+ }
406
+ function looksLikeComponent(specifier) {
407
+ return specifier.endsWith('.svelte') || /\/[A-Z][^/]*$/.test(specifier);
408
+ }
409
+ async function loadComponents(cwd, roots) {
410
+ const paths = new Set();
411
+ for (const root of roots)
412
+ await collectSvelteFiles(root, cwd, paths);
413
+ if (paths.size > MAX_COMPONENTS) {
414
+ throw new Error(`[distributed.island.graph_unbounded] Svelte project exceeds ${MAX_COMPONENTS} components; narrow routesDir/libDir`);
415
+ }
416
+ const components = new Map();
417
+ for (const path of [...paths].sort()) {
418
+ const metadata = await lstat(path);
419
+ if (!metadata.isFile() || metadata.isSymbolicLink())
420
+ continue;
421
+ if (metadata.size > MAX_COMPONENT_BYTES) {
422
+ throw diagnostic('distributed.island.component_too_large', projectPath(cwd, path), 1, 1, `component exceeds ${MAX_COMPONENT_BYTES} bytes; split it or use client-only execution`);
423
+ }
424
+ const source = await readFile(path, 'utf8');
425
+ components.set(path, parseComponent(path, source, cwd));
426
+ }
427
+ return components;
428
+ }
429
+ async function collectSvelteFiles(root, cwd, paths) {
430
+ let entries;
431
+ try {
432
+ entries = await readdir(root, { withFileTypes: true });
433
+ }
434
+ catch (error) {
435
+ if (isMissing(error))
436
+ return;
437
+ throw error;
438
+ }
439
+ for (const entry of entries.sort((left, right) => left.name.localeCompare(right.name))) {
440
+ const path = join(root, entry.name);
441
+ if (!isWithin(cwd, path) || entry.isSymbolicLink())
442
+ continue;
443
+ if (entry.isDirectory())
444
+ await collectSvelteFiles(path, cwd, paths);
445
+ else if (entry.isFile() && entry.name.endsWith('.svelte'))
446
+ paths.add(path);
447
+ }
448
+ }
449
+ function parseComponent(path, source, cwd) {
450
+ let ast;
451
+ try {
452
+ ast = parse(source, { filename: projectPath(cwd, path), modern: true });
453
+ }
454
+ catch (error) {
455
+ throw new Error(`[distributed.island.svelte_parse] ${projectPath(cwd, path)}: ${error instanceof Error ? error.message : String(error)}`);
456
+ }
457
+ const imports = [];
458
+ const dynamicImports = [];
459
+ walkAst(ast, (node) => {
460
+ if (node.type === 'ImportDeclaration') {
461
+ const value = literalValue(node.source);
462
+ if (value !== undefined)
463
+ imports.push(located(value, node));
464
+ }
465
+ else if (node.type === 'ImportExpression') {
466
+ const value = literalValue(node.source);
467
+ dynamicImports.push({
468
+ ...located(value ?? '<dynamic>', node),
469
+ opaque: value === undefined
470
+ });
471
+ }
472
+ });
473
+ return Object.freeze({
474
+ path,
475
+ imports: Object.freeze(imports),
476
+ dynamicImports: Object.freeze(dynamicImports)
477
+ });
478
+ }
479
+ function walkAst(value, visit) {
480
+ const stack = [value];
481
+ const seen = new WeakSet();
482
+ while (stack.length > 0) {
483
+ const current = stack.pop();
484
+ if (current === null || typeof current !== 'object')
485
+ continue;
486
+ if (seen.has(current))
487
+ continue;
488
+ seen.add(current);
489
+ if (!Array.isArray(current))
490
+ visit(current);
491
+ for (const child of Array.isArray(current)
492
+ ? current
493
+ : Object.values(current)) {
494
+ if (child !== null && typeof child === 'object')
495
+ stack.push(child);
496
+ }
497
+ }
498
+ }
499
+ function literalValue(value) {
500
+ if (value === null || typeof value !== 'object')
501
+ return undefined;
502
+ const candidate = value;
503
+ return candidate.type === 'Literal' && typeof candidate.value === 'string'
504
+ ? candidate.value
505
+ : undefined;
506
+ }
507
+ function located(specifier, node) {
508
+ return Object.freeze({
509
+ specifier,
510
+ line: node.loc?.start?.line ?? 1,
511
+ column: (node.loc?.start?.column ?? 0) + 1
512
+ });
513
+ }
514
+ function boundaryRoots(cwd, routesDir, components) {
515
+ return [...components.keys()]
516
+ .filter((path) => path.startsWith(`${routesDir}${sep}`) || path === routesDir)
517
+ .flatMap((path) => {
518
+ const name = posix.basename(portable(path));
519
+ const kind = boundaryComponentKind(name);
520
+ if (kind === undefined)
521
+ return [];
522
+ const routeDirectory = portable(relative(routesDir, dirname(path)));
523
+ const route = routeDirectory === '' ? '/' : `/${routeDirectory}`;
524
+ return [{
525
+ id: `${kind}:${route}`,
526
+ route,
527
+ kind,
528
+ path
529
+ }];
530
+ })
531
+ .sort((left, right) => left.route.localeCompare(right.route) ||
532
+ left.kind.localeCompare(right.kind) ||
533
+ projectPath(cwd, left.path).localeCompare(projectPath(cwd, right.path)));
534
+ }
535
+ function boundaryComponentKind(name) {
536
+ const match = /^\+(page|layout)(?:@[^/]*)?\.svelte$/.exec(name);
537
+ return match?.[1] === 'page' || match?.[1] === 'layout'
538
+ ? match[1]
539
+ : undefined;
540
+ }
541
+ function routeBoundaryKey(kind, directory) {
542
+ return `${kind}\u0000${directory}`;
543
+ }
544
+ function islandOwnerComponent(cwd, source) {
545
+ const absolute = contained(cwd, source, 'island source');
546
+ const suffix = extname(absolute);
547
+ const base = absolute.slice(0, -suffix.length);
548
+ const name = posix.basename(portable(base));
549
+ if (name === '+page' || name === '+layout') {
550
+ return {
551
+ kind: 'route',
552
+ key: routeBoundaryKey(name === '+page' ? 'page' : 'layout', dirname(base))
553
+ };
554
+ }
555
+ return { kind: 'component', path: `${base}.svelte` };
556
+ }
557
+ function validateInventory(module, inventory) {
558
+ if (inventory === null ||
559
+ typeof inventory !== 'object' ||
560
+ inventory.version !== 1 ||
561
+ !Array.isArray(inventory.islands) ||
562
+ inventory.islands.length > MAX_ISLANDS ||
563
+ typeof inventory.schemaFingerprint !== 'string' ||
564
+ typeof inventory.protocolFingerprint !== 'string') {
565
+ throw new Error(`[distributed.island.inventory_invalid] ${module} islands.json is not version 1`);
566
+ }
567
+ for (const island of inventory.islands) {
568
+ const source = island?.source;
569
+ const variableSchema = island?.variableSchema;
570
+ if (island?.version !== 1 ||
571
+ typeof island.id !== 'string' ||
572
+ typeof island.operation !== 'string' ||
573
+ typeof island.modulePath !== 'string' ||
574
+ !island.modulePath.startsWith('operations/') ||
575
+ !island.modulePath.endsWith('.ts') ||
576
+ typeof island.exportName !== 'string' ||
577
+ typeof source?.path !== 'string' ||
578
+ !Number.isSafeInteger(source.line) ||
579
+ source.line < 1 ||
580
+ !Number.isSafeInteger(source.column) ||
581
+ source.column < 1 ||
582
+ island.directives === null ||
583
+ typeof island.directives !== 'object' ||
584
+ typeof island.directives.load !== 'boolean' ||
585
+ typeof island.directives.live !== 'boolean' ||
586
+ island.liveCoverage === null ||
587
+ typeof island.liveCoverage !== 'object' ||
588
+ variableSchema === null ||
589
+ typeof variableSchema !== 'object' ||
590
+ Array.isArray(variableSchema) ||
591
+ typeof variableSchema.reference !== 'string' ||
592
+ !Number.isSafeInteger(variableSchema.codecVersion) ||
593
+ !Array.isArray(variableSchema.variables) ||
594
+ variableSchema.variables.some((variable) => variable === null ||
595
+ typeof variable !== 'object' ||
596
+ Array.isArray(variable) ||
597
+ typeof variable.name !== 'string' ||
598
+ typeof variable.graphqlType !== 'string') ||
599
+ typeof island.liveCoverage.requested !== 'boolean' ||
600
+ typeof island.liveCoverage.finite !== 'boolean' ||
601
+ typeof island.liveCoverage.kind !== 'string' ||
602
+ (island.liveCoverage.maxItems !== undefined &&
603
+ (!Number.isSafeInteger(island.liveCoverage.maxItems) ||
604
+ island.liveCoverage.maxItems < 0))) {
605
+ throw new Error(`[distributed.island.version_unsupported] ${typeof source?.path === 'string' ? source.path : module}:${Number.isSafeInteger(source?.line) ? source.line : 1}:${Number.isSafeInteger(source?.column) ? source.column : 1}: island metadata is not version 1; regenerate the framework-neutral client and boundary plan`);
606
+ }
607
+ }
608
+ }
609
+ function portableSource(path) {
610
+ if (typeof path !== 'string' ||
611
+ path.length === 0 ||
612
+ isAbsolute(path) ||
613
+ path.includes('\\') ||
614
+ path.split('/').some((part) => part === '' || part === '.' || part === '..')) {
615
+ throw new Error('[distributed.island.source_invalid] island source path is not portable');
616
+ }
617
+ return path;
618
+ }
619
+ function compareOccurrences(left, right) {
620
+ return (left.operation.localeCompare(right.operation) ||
621
+ left.component.localeCompare(right.component) ||
622
+ left.graphqlSource.localeCompare(right.graphqlSource));
623
+ }
624
+ function aliasKey(value) {
625
+ if (!/^\$[A-Za-z0-9_-]+$/.test(value)) {
626
+ throw new TypeError(`Distributed SvelteKit alias ${value} must be a single $name segment`);
627
+ }
628
+ return value;
629
+ }
630
+ function normalizeRoute(value) {
631
+ if (typeof value !== 'string' ||
632
+ !value.startsWith('/') ||
633
+ value.includes('\\') ||
634
+ value.includes('?') ||
635
+ value.includes('#') ||
636
+ value.split('/').some((part) => part === '.' || part === '..')) {
637
+ throw new TypeError('Distributed explicit boundary route must be a normalized SvelteKit route id');
638
+ }
639
+ return value.length > 1 && value.endsWith('/') ? value.slice(0, -1) : value;
640
+ }
641
+ function contained(root, value, label) {
642
+ const path = resolve(root, value);
643
+ if (!isWithin(root, path))
644
+ throw new TypeError(`${label} must stay within project root`);
645
+ return path;
646
+ }
647
+ function isWithin(root, target) {
648
+ const path = relative(root, target);
649
+ return path === '' || (!path.startsWith(`..${sep}`) && path !== '..' && !isAbsolute(path));
650
+ }
651
+ function projectPath(cwd, path) {
652
+ return portable(relative(cwd, path));
653
+ }
654
+ function portable(path) {
655
+ return path.split(sep).join('/');
656
+ }
657
+ function diagnostic(code, path, line, column, message) {
658
+ return new Error(`[${code}] ${path}:${line}:${column}: ${message}`);
659
+ }
660
+ function ownDataValue(value, key) {
661
+ if (value === null || typeof value !== 'object')
662
+ return undefined;
663
+ const descriptor = Object.getOwnPropertyDescriptor(value, key);
664
+ if (descriptor === undefined)
665
+ return undefined;
666
+ if (!('value' in descriptor)) {
667
+ throw new TypeError('Distributed boundary binding contains an accessor');
668
+ }
669
+ return descriptor.value;
670
+ }
671
+ function stableJson(value) {
672
+ return JSON.stringify(stableValue(value));
673
+ }
674
+ function stableValue(value) {
675
+ const active = new Set();
676
+ let visited = 0;
677
+ const visit = (current, depth) => {
678
+ visited += 1;
679
+ if (visited > 4_096 || depth > 32) {
680
+ throw new TypeError('Distributed boundary binding exceeds structural limits');
681
+ }
682
+ if (current === null ||
683
+ typeof current === 'string' ||
684
+ typeof current === 'boolean')
685
+ return current;
686
+ if (typeof current === 'number' && Number.isFinite(current))
687
+ return current;
688
+ if (typeof current !== 'object') {
689
+ throw new TypeError('Distributed boundary binding is not JSON-compatible');
690
+ }
691
+ if (active.has(current))
692
+ throw new TypeError('Distributed boundary binding is cyclic');
693
+ active.add(current);
694
+ try {
695
+ if (Array.isArray(current))
696
+ return current.map((entry) => visit(entry, depth + 1));
697
+ if (Object.getPrototypeOf(current) !== Object.prototype &&
698
+ Object.getPrototypeOf(current) !== null) {
699
+ throw new TypeError('Distributed boundary binding must contain plain objects');
700
+ }
701
+ return Object.fromEntries(Object.keys(current).sort().map((key) => {
702
+ if (['__proto__', 'prototype', 'constructor'].includes(key)) {
703
+ throw new TypeError('Distributed boundary binding contains a hostile object key');
704
+ }
705
+ return [key, visit(ownDataValue(current, key), depth + 1)];
706
+ }));
707
+ }
708
+ finally {
709
+ active.delete(current);
710
+ }
711
+ };
712
+ return visit(value, 0);
713
+ }
714
+ function freezeStable(value) {
715
+ if (value === null || typeof value !== 'object')
716
+ return value;
717
+ for (const entry of Array.isArray(value)
718
+ ? value
719
+ : Object.values(value)) {
720
+ freezeStable(entry);
721
+ }
722
+ return Object.freeze(value);
723
+ }
724
+ function fnv1a64(value) {
725
+ let hash = 0xcbf29ce484222325n;
726
+ for (const byte of new TextEncoder().encode(value)) {
727
+ hash ^= BigInt(byte);
728
+ hash = BigInt.asUintN(64, hash * 0x100000001b3n);
729
+ }
730
+ return hash.toString(16).padStart(16, '0');
731
+ }
732
+ function isMissing(error) {
733
+ return error !== null && typeof error === 'object' && 'code' in error && error.code === 'ENOENT';
734
+ }