@hops-ops/distributed 4.9.0 → 4.11.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.
- package/README.md +89 -8
- package/dist/replica/distributed-replica/impl-protocol.d.ts +1 -0
- package/dist/replica/distributed-replica/impl-protocol.js +1 -0
- package/dist/replica/distributed-replica/impl.js +11 -0
- package/dist/replica/distributed-replica/watch.js +13 -1
- package/dist/replica/identity/codec.d.ts +1 -0
- package/dist/replica/identity/codec.js +16 -3
- package/dist/replica/index.d.ts +1 -1
- package/dist/replica/types.d.ts +42 -1
- package/dist/sveltekit/boundary-lifecycle.d.ts +37 -0
- package/dist/sveltekit/boundary-lifecycle.js +355 -0
- package/dist/sveltekit/boundary-variables.d.ts +57 -0
- package/dist/sveltekit/boundary-variables.js +294 -0
- package/dist/sveltekit/context.d.ts +3 -0
- package/dist/sveltekit/context.js +8 -0
- package/dist/sveltekit/index.d.ts +6 -3
- package/dist/sveltekit/index.js +5 -2
- package/dist/sveltekit/island-bindings.d.ts +17 -0
- package/dist/sveltekit/island-bindings.js +58 -0
- package/dist/sveltekit/islands/boundaries.d.ts +107 -0
- package/dist/sveltekit/islands/boundaries.js +788 -0
- package/dist/sveltekit/operation-identity.d.ts +4 -0
- package/dist/sveltekit/operation-identity.js +10 -0
- package/dist/sveltekit/replica.d.ts +24 -0
- package/dist/sveltekit/replica.js +72 -5
- package/dist/sveltekit/server-replica.d.ts +10 -26
- package/dist/sveltekit/server-replica.js +159 -132
- package/dist/sveltekit/vite.d.ts +10 -3
- package/dist/sveltekit/vite.js +310 -39
- package/package.json +2 -2
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
import { canonicalizeOperationVariables } from '../replica/index.js';
|
|
2
|
+
const BINDING_VERSION = 1;
|
|
3
|
+
const GRAPHQL_NAME = /^[_A-Za-z][_0-9A-Za-z]*$/;
|
|
4
|
+
const MAX_BINDING_VARIABLES = 128;
|
|
5
|
+
const MAX_PATH_SEGMENTS = 16;
|
|
6
|
+
const MAX_LITERAL_DEPTH = 32;
|
|
7
|
+
const MAX_LITERAL_VALUES = 4_096;
|
|
8
|
+
const HOSTILE_KEYS = new Set(['__proto__', 'prototype', 'constructor']);
|
|
9
|
+
/**
|
|
10
|
+
* Define one closed, inspectable variable binding for every boundary lifecycle.
|
|
11
|
+
* The operation artifact remains the sole owner of coercion and cache identity.
|
|
12
|
+
*/
|
|
13
|
+
export function defineDistributedBoundaryBinding(artifact, sources) {
|
|
14
|
+
const validated = validateSources(artifact, sources);
|
|
15
|
+
const id = `boundary-v${BINDING_VERSION}:${fnv1a64(`${artifact.id}\n${stableJson(validated)}`)}`;
|
|
16
|
+
const resolve = (context) => resolveDistributedBoundaryVariables(artifact, validated, context);
|
|
17
|
+
return Object.freeze({
|
|
18
|
+
version: BINDING_VERSION,
|
|
19
|
+
id,
|
|
20
|
+
artifactId: artifact.id,
|
|
21
|
+
sources: validated,
|
|
22
|
+
resolve,
|
|
23
|
+
canonicalBytes(context) {
|
|
24
|
+
return JSON.stringify(resolve(context));
|
|
25
|
+
}
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
export function defineDistributedBoundaryOperation(plan, artifact, binding) {
|
|
29
|
+
if (binding.artifactId !== artifact.id) {
|
|
30
|
+
throw new TypeError('Distributed boundary binding belongs to a different operation artifact');
|
|
31
|
+
}
|
|
32
|
+
if (plan === null ||
|
|
33
|
+
typeof plan !== 'object' ||
|
|
34
|
+
!GRAPHQL_NAME.test(plan.operation) ||
|
|
35
|
+
!plan.route.startsWith('/') ||
|
|
36
|
+
(plan.kind !== 'layout' && plan.kind !== 'page') ||
|
|
37
|
+
!['component', 'route_document', 'explicit'].includes(plan.discovery)) {
|
|
38
|
+
throw new TypeError('Distributed boundary operation plan is invalid');
|
|
39
|
+
}
|
|
40
|
+
return Object.freeze({
|
|
41
|
+
plan: Object.freeze({ ...plan }),
|
|
42
|
+
artifact,
|
|
43
|
+
binding
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
export function resolveDistributedBoundaryVariables(artifact, sources, context) {
|
|
47
|
+
if (context === null || typeof context !== 'object') {
|
|
48
|
+
throw new TypeError('Distributed boundary variable context is required');
|
|
49
|
+
}
|
|
50
|
+
const entries = [];
|
|
51
|
+
for (const [variable, source] of Object.entries(sources).sort(([left], [right]) => left.localeCompare(right))) {
|
|
52
|
+
const value = resolveSource(source, context);
|
|
53
|
+
if (value !== OMITTED)
|
|
54
|
+
entries.push([variable, value]);
|
|
55
|
+
}
|
|
56
|
+
return canonicalizeOperationVariables(artifact, Object.fromEntries(entries));
|
|
57
|
+
}
|
|
58
|
+
const OMITTED = Symbol('distributed.boundary.omitted');
|
|
59
|
+
function resolveSource(source, context) {
|
|
60
|
+
switch (source.kind) {
|
|
61
|
+
case 'omit':
|
|
62
|
+
return OMITTED;
|
|
63
|
+
case 'constant':
|
|
64
|
+
return source.value;
|
|
65
|
+
case 'route_param': {
|
|
66
|
+
const value = ownValue(context.params, source.name);
|
|
67
|
+
return value === undefined ? OMITTED : value;
|
|
68
|
+
}
|
|
69
|
+
case 'search_param': {
|
|
70
|
+
const search = context.search;
|
|
71
|
+
if (isSearchParams(search)) {
|
|
72
|
+
if (source.mode === 'all')
|
|
73
|
+
return search.getAll(source.name);
|
|
74
|
+
const value = search.get(source.name);
|
|
75
|
+
return value === null ? OMITTED : value;
|
|
76
|
+
}
|
|
77
|
+
const value = ownValue(search, source.name);
|
|
78
|
+
if (value === undefined)
|
|
79
|
+
return source.mode === 'all' ? [] : OMITTED;
|
|
80
|
+
if (source.mode === 'all')
|
|
81
|
+
return Array.isArray(value) ? [...value] : [value];
|
|
82
|
+
return Array.isArray(value) ? (value[0] ?? OMITTED) : value;
|
|
83
|
+
}
|
|
84
|
+
case 'trusted_session': {
|
|
85
|
+
const value = readPath(context.session, source.path, 'trusted session');
|
|
86
|
+
return value === undefined ? OMITTED : value;
|
|
87
|
+
}
|
|
88
|
+
case 'forwarded_prop': {
|
|
89
|
+
const value = readPath(context.props, source.path, 'forwarded prop');
|
|
90
|
+
return value === undefined ? OMITTED : value;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
function validateSources(artifact, sources) {
|
|
95
|
+
if (sources === null || typeof sources !== 'object' || Array.isArray(sources)) {
|
|
96
|
+
throw new TypeError('Distributed boundary variable sources must be an object');
|
|
97
|
+
}
|
|
98
|
+
const definitions = artifact.variableCodec?.variables;
|
|
99
|
+
const defaults = artifact.variableCodec?.defaults;
|
|
100
|
+
if (definitions === null || typeof definitions !== 'object' || Array.isArray(definitions)) {
|
|
101
|
+
throw new TypeError('Distributed boundary artifact has no variable codec');
|
|
102
|
+
}
|
|
103
|
+
if (defaults === null || typeof defaults !== 'object' || Array.isArray(defaults)) {
|
|
104
|
+
throw new TypeError('Distributed boundary artifact has no variable defaults');
|
|
105
|
+
}
|
|
106
|
+
const names = Object.keys(sources);
|
|
107
|
+
if (names.length > MAX_BINDING_VARIABLES) {
|
|
108
|
+
throw new TypeError(`Distributed boundary binding exceeds ${MAX_BINDING_VARIABLES} variables`);
|
|
109
|
+
}
|
|
110
|
+
const entries = [];
|
|
111
|
+
for (const name of names.sort()) {
|
|
112
|
+
if (!GRAPHQL_NAME.test(name) || !Object.hasOwn(definitions, name)) {
|
|
113
|
+
throw new TypeError(`Distributed boundary binding names unknown variable ${name}`);
|
|
114
|
+
}
|
|
115
|
+
const source = ownValue(sources, name);
|
|
116
|
+
entries.push([name, validateSource(source, name)]);
|
|
117
|
+
}
|
|
118
|
+
for (const [name, definition] of Object.entries(definitions)) {
|
|
119
|
+
const required = definition !== null &&
|
|
120
|
+
typeof definition === 'object' &&
|
|
121
|
+
'nullable' in definition &&
|
|
122
|
+
definition.nullable === false;
|
|
123
|
+
if (required && !Object.hasOwn(defaults, name) && !Object.hasOwn(sources, name)) {
|
|
124
|
+
throw new TypeError(`Distributed boundary binding is missing required variable ${name}; use an explicit binding, parent/boundary query, client-only execution, or a better read root`);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
return Object.freeze(Object.fromEntries(entries));
|
|
128
|
+
}
|
|
129
|
+
function validateSource(value, variable) {
|
|
130
|
+
const source = exactRecord(value, `binding source ${variable}`);
|
|
131
|
+
if (typeof source.kind !== 'string') {
|
|
132
|
+
throw new TypeError(`Distributed boundary source ${variable} has no kind`);
|
|
133
|
+
}
|
|
134
|
+
switch (source.kind) {
|
|
135
|
+
case 'omit':
|
|
136
|
+
exactKeys(source, ['kind'], variable);
|
|
137
|
+
return Object.freeze({ kind: 'omit' });
|
|
138
|
+
case 'route_param':
|
|
139
|
+
exactKeys(source, ['kind', 'name'], variable);
|
|
140
|
+
return Object.freeze({ kind: 'route_param', name: safeName(source.name, variable) });
|
|
141
|
+
case 'search_param': {
|
|
142
|
+
exactKeys(source, ['kind', 'name', 'mode'], variable, ['mode']);
|
|
143
|
+
if (source.mode !== undefined && source.mode !== 'first' && source.mode !== 'all') {
|
|
144
|
+
throw new TypeError(`Distributed boundary source ${variable} has invalid search mode`);
|
|
145
|
+
}
|
|
146
|
+
return Object.freeze({
|
|
147
|
+
kind: 'search_param',
|
|
148
|
+
name: safeName(source.name, variable),
|
|
149
|
+
...(source.mode === undefined ? {} : { mode: source.mode })
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
case 'trusted_session':
|
|
153
|
+
exactKeys(source, ['kind', 'path'], variable);
|
|
154
|
+
return Object.freeze({ kind: 'trusted_session', path: safePath(source.path, variable) });
|
|
155
|
+
case 'forwarded_prop':
|
|
156
|
+
exactKeys(source, ['kind', 'path'], variable);
|
|
157
|
+
return Object.freeze({ kind: 'forwarded_prop', path: safePath(source.path, variable) });
|
|
158
|
+
case 'constant':
|
|
159
|
+
exactKeys(source, ['kind', 'value'], variable);
|
|
160
|
+
return Object.freeze({
|
|
161
|
+
kind: 'constant',
|
|
162
|
+
value: freezeJson(stableValue(source.value))
|
|
163
|
+
});
|
|
164
|
+
default:
|
|
165
|
+
throw new TypeError(`Distributed boundary source ${variable} is unsupported; use an explicit binding, parent/boundary query, client-only execution, or a better read root`);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
function safePath(value, variable) {
|
|
169
|
+
if (!Array.isArray(value) ||
|
|
170
|
+
value.length === 0 ||
|
|
171
|
+
value.length > MAX_PATH_SEGMENTS ||
|
|
172
|
+
value.some((part) => typeof part !== 'string' || !GRAPHQL_NAME.test(part) || HOSTILE_KEYS.has(part))) {
|
|
173
|
+
throw new TypeError(`Distributed boundary source ${variable} has an invalid path`);
|
|
174
|
+
}
|
|
175
|
+
return Object.freeze([...value]);
|
|
176
|
+
}
|
|
177
|
+
function safeName(value, variable) {
|
|
178
|
+
if (typeof value !== 'string' || value.length === 0 || value.length > 512) {
|
|
179
|
+
throw new TypeError(`Distributed boundary source ${variable} has an invalid name`);
|
|
180
|
+
}
|
|
181
|
+
return value;
|
|
182
|
+
}
|
|
183
|
+
function readPath(value, path, label) {
|
|
184
|
+
let current = value;
|
|
185
|
+
for (const segment of path) {
|
|
186
|
+
if (current === null || typeof current !== 'object')
|
|
187
|
+
return undefined;
|
|
188
|
+
const descriptor = Object.getOwnPropertyDescriptor(current, segment);
|
|
189
|
+
if (descriptor === undefined)
|
|
190
|
+
return undefined;
|
|
191
|
+
if (!('value' in descriptor)) {
|
|
192
|
+
throw new TypeError(`Distributed boundary ${label} path contains an accessor`);
|
|
193
|
+
}
|
|
194
|
+
current = descriptor.value;
|
|
195
|
+
}
|
|
196
|
+
return current;
|
|
197
|
+
}
|
|
198
|
+
function ownValue(value, key) {
|
|
199
|
+
const descriptor = Object.getOwnPropertyDescriptor(value, key);
|
|
200
|
+
if (descriptor === undefined)
|
|
201
|
+
return undefined;
|
|
202
|
+
if (!('value' in descriptor)) {
|
|
203
|
+
throw new TypeError('Distributed boundary input contains an accessor');
|
|
204
|
+
}
|
|
205
|
+
return descriptor.value;
|
|
206
|
+
}
|
|
207
|
+
function isSearchParams(value) {
|
|
208
|
+
return (value !== null &&
|
|
209
|
+
typeof value === 'object' &&
|
|
210
|
+
typeof value.get === 'function' &&
|
|
211
|
+
typeof value.getAll === 'function');
|
|
212
|
+
}
|
|
213
|
+
function exactRecord(value, label) {
|
|
214
|
+
if (value === null ||
|
|
215
|
+
typeof value !== 'object' ||
|
|
216
|
+
Array.isArray(value) ||
|
|
217
|
+
(Object.getPrototypeOf(value) !== Object.prototype && Object.getPrototypeOf(value) !== null)) {
|
|
218
|
+
throw new TypeError(`Distributed ${label} must be a plain object`);
|
|
219
|
+
}
|
|
220
|
+
return value;
|
|
221
|
+
}
|
|
222
|
+
function exactKeys(value, allowed, variable, optional = []) {
|
|
223
|
+
const permitted = new Set(allowed);
|
|
224
|
+
for (const key of Object.keys(value)) {
|
|
225
|
+
if (!permitted.has(key)) {
|
|
226
|
+
throw new TypeError(`Distributed boundary source ${variable} contains unknown field ${key}`);
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
for (const key of allowed) {
|
|
230
|
+
if (!optional.includes(key) && !Object.hasOwn(value, key)) {
|
|
231
|
+
throw new TypeError(`Distributed boundary source ${variable} is missing field ${key}`);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
function stableJson(value) {
|
|
236
|
+
return JSON.stringify(stableValue(value));
|
|
237
|
+
}
|
|
238
|
+
function stableValue(value) {
|
|
239
|
+
let visited = 0;
|
|
240
|
+
const active = new Set();
|
|
241
|
+
const visit = (current, depth) => {
|
|
242
|
+
visited += 1;
|
|
243
|
+
if (visited > MAX_LITERAL_VALUES || depth > MAX_LITERAL_DEPTH) {
|
|
244
|
+
throw new TypeError('Distributed boundary constant exceeds structural limits');
|
|
245
|
+
}
|
|
246
|
+
if (current === null ||
|
|
247
|
+
typeof current === 'string' ||
|
|
248
|
+
typeof current === 'boolean')
|
|
249
|
+
return current;
|
|
250
|
+
if (typeof current === 'number' && Number.isFinite(current))
|
|
251
|
+
return current;
|
|
252
|
+
if (typeof current !== 'object') {
|
|
253
|
+
throw new TypeError('Distributed boundary constant is not JSON-compatible');
|
|
254
|
+
}
|
|
255
|
+
if (active.has(current))
|
|
256
|
+
throw new TypeError('Distributed boundary constant is cyclic');
|
|
257
|
+
active.add(current);
|
|
258
|
+
try {
|
|
259
|
+
if (Array.isArray(current))
|
|
260
|
+
return current.map((entry) => visit(entry, depth + 1));
|
|
261
|
+
const record = exactRecord(current, 'boundary constant');
|
|
262
|
+
return Object.fromEntries(Object.keys(record).sort().map((key) => {
|
|
263
|
+
if (HOSTILE_KEYS.has(key)) {
|
|
264
|
+
throw new TypeError('Distributed boundary constant contains a hostile object key');
|
|
265
|
+
}
|
|
266
|
+
return [key, visit(ownValue(record, key), depth + 1)];
|
|
267
|
+
}));
|
|
268
|
+
}
|
|
269
|
+
finally {
|
|
270
|
+
active.delete(current);
|
|
271
|
+
}
|
|
272
|
+
};
|
|
273
|
+
return visit(value, 0);
|
|
274
|
+
}
|
|
275
|
+
function fnv1a64(value) {
|
|
276
|
+
let hash = 0xcbf29ce484222325n;
|
|
277
|
+
for (const byte of new TextEncoder().encode(value)) {
|
|
278
|
+
hash ^= BigInt(byte);
|
|
279
|
+
hash = BigInt.asUintN(64, hash * 0x100000001b3n);
|
|
280
|
+
}
|
|
281
|
+
return hash.toString(16).padStart(16, '0');
|
|
282
|
+
}
|
|
283
|
+
function freezeJson(value) {
|
|
284
|
+
if (value === null || typeof value !== 'object')
|
|
285
|
+
return value;
|
|
286
|
+
if (Array.isArray(value)) {
|
|
287
|
+
for (const entry of value)
|
|
288
|
+
freezeJson(entry);
|
|
289
|
+
return Object.freeze(value);
|
|
290
|
+
}
|
|
291
|
+
for (const entry of Object.values(value))
|
|
292
|
+
freezeJson(entry);
|
|
293
|
+
return Object.freeze(value);
|
|
294
|
+
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { ReplicaOperationArtifact } from '../replica/index.js';
|
|
2
2
|
import type { GraphqlVariables } from '../types.js';
|
|
3
|
+
import type { DistributedSvelteKitBoundaryInstance, SveltekitBoundaryRetention } from './boundary-lifecycle.js';
|
|
3
4
|
import type { DistributedSvelteKitClient, SveltekitBoundOperation } from './replica.js';
|
|
4
5
|
/**
|
|
5
6
|
* Install one client in the current Svelte component tree.
|
|
@@ -19,6 +20,8 @@ export declare function provideDistributedSvelteKitClient<TCommands>(client: Dis
|
|
|
19
20
|
export declare function useDistributedSvelteKitClient<TCommands = Readonly<Record<never, never>>>(): DistributedSvelteKitClient<TCommands>;
|
|
20
21
|
/** Resolve the nearest generated command surface without a global proxy. */
|
|
21
22
|
export declare function useDistributedSvelteKitCommands<TCommands>(): TCommands;
|
|
23
|
+
/** Retain the generated selections owned by the nearest page/layout instance. */
|
|
24
|
+
export declare function retainDistributedSvelteKitBoundary<TSession, TProps>(instance: DistributedSvelteKitBoundaryInstance, context: import('./boundary-variables.js').DistributedBoundaryVariableContext<TSession, TProps>): SveltekitBoundaryRetention;
|
|
22
25
|
/**
|
|
23
26
|
* Define one SSR-safe generated operation wrapper.
|
|
24
27
|
*
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { getContext, setContext } from 'svelte';
|
|
2
|
+
import { defineDistributedBoundaryBinding, defineDistributedBoundaryOperation } from './boundary-variables.js';
|
|
2
3
|
const DISTRIBUTED_CLIENT_CONTEXT = Symbol('@hops-ops/distributed/sveltekit/client');
|
|
3
4
|
/**
|
|
4
5
|
* Install one client in the current Svelte component tree.
|
|
@@ -34,6 +35,10 @@ export function useDistributedSvelteKitClient() {
|
|
|
34
35
|
export function useDistributedSvelteKitCommands() {
|
|
35
36
|
return useDistributedSvelteKitClient().commands;
|
|
36
37
|
}
|
|
38
|
+
/** Retain the generated selections owned by the nearest page/layout instance. */
|
|
39
|
+
export function retainDistributedSvelteKitBoundary(instance, context) {
|
|
40
|
+
return useDistributedSvelteKitClient().retainBoundary(instance, context);
|
|
41
|
+
}
|
|
37
42
|
/**
|
|
38
43
|
* Define one SSR-safe generated operation wrapper.
|
|
39
44
|
*
|
|
@@ -58,6 +63,9 @@ export function defineDistributedSvelteKitOperation(artifact) {
|
|
|
58
63
|
return useDistributedSvelteKitClient()
|
|
59
64
|
.operation(artifact)
|
|
60
65
|
.prefetch(variables);
|
|
66
|
+
},
|
|
67
|
+
boundary(plan, sources) {
|
|
68
|
+
return defineDistributedBoundaryOperation(plan, artifact, defineDistributedBoundaryBinding(artifact, sources));
|
|
61
69
|
}
|
|
62
70
|
});
|
|
63
71
|
}
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
export { authFromPageData, type PageGraphqlData } from './auth.js';
|
|
2
2
|
export { distributedReloadLifecycle, registerDistributedReloadClient, validateDistributedReloadLocation, validateDistributedReloadState, type DistributedReloadLifecycle, type DistributedReloadOptions, type DistributedReloadStateDeclaration } from './lifecycle.js';
|
|
3
3
|
export { parseDistributedGenerationEnvelope, type DistributedGenerationEnvelope } from '../generation.js';
|
|
4
|
-
export {
|
|
5
|
-
export {
|
|
6
|
-
export {
|
|
4
|
+
export { defineDistributedBoundaryBinding, defineDistributedBoundaryOperation, resolveDistributedBoundaryVariables, type DistributedBoundaryBinding, type DistributedBoundaryOperation, type DistributedBoundaryPlan, type DistributedBoundaryVariableContext, type DistributedBoundaryVariableSource, type DistributedBoundaryVariableSources } from './boundary-variables.js';
|
|
5
|
+
export { constant, defineGraphqlIslandBindings, forwardedProp, omitVariable, routeParam, searchParam, sessionClaim } from './island-bindings.js';
|
|
6
|
+
export { DistributedSvelteKitBoundaryController, type DistributedSvelteKitBoundaryInstance, type DistributedSvelteKitBoundaryLocation, type DistributedSvelteKitLocationContext, type SveltekitBoundaryLifecycleDiagnostic, type SveltekitBoundaryRetention } from './boundary-lifecycle.js';
|
|
7
|
+
export { defineDistributedSvelteKitOperation, provideDistributedSvelteKitClient, retainDistributedSvelteKitBoundary, useDistributedSvelteKitClient, useDistributedSvelteKitCommands } from './context.js';
|
|
8
|
+
export { bindSveltekitOperation, createPageDataSessionSource, createDistributedSvelteKit, sessionSourceFromPageData, type CreateDistributedSvelteKitOptions, type DistributedSvelteKitClient, type SveltekitBoundOperation, type SveltekitBoundBoundaryOperation, type SveltekitCommandRuntimeFactory, type SveltekitCommandRuntimeFactoryOptions, type SveltekitCommandRuntimeLike, type SveltekitDistributedPageData, type SveltekitPageDataSessionSource, type SveltekitPageDataSource, type SveltekitQuerySnapshot, type SveltekitQueryStore, type SveltekitReplicaAuthority, type SveltekitReplicaHydration, type SveltekitSessionSource, type UseSveltekitOperationOptions } from './replica.js';
|
|
9
|
+
export { createDistributedSvelteKitServer, type CreateDistributedSvelteKitServerOptions, type DistributedSvelteKitServer, type SveltekitServerLoadEventLike } from './server-replica.js';
|
package/dist/sveltekit/index.js
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
export { authFromPageData } from './auth.js';
|
|
2
2
|
export { distributedReloadLifecycle, registerDistributedReloadClient, validateDistributedReloadLocation, validateDistributedReloadState } from './lifecycle.js';
|
|
3
3
|
export { parseDistributedGenerationEnvelope } from '../generation.js';
|
|
4
|
-
export {
|
|
4
|
+
export { defineDistributedBoundaryBinding, defineDistributedBoundaryOperation, resolveDistributedBoundaryVariables } from './boundary-variables.js';
|
|
5
|
+
export { constant, defineGraphqlIslandBindings, forwardedProp, omitVariable, routeParam, searchParam, sessionClaim } from './island-bindings.js';
|
|
6
|
+
export { DistributedSvelteKitBoundaryController } from './boundary-lifecycle.js';
|
|
7
|
+
export { defineDistributedSvelteKitOperation, provideDistributedSvelteKitClient, retainDistributedSvelteKitBoundary, useDistributedSvelteKitClient, useDistributedSvelteKitCommands } from './context.js';
|
|
5
8
|
export { bindSveltekitOperation, createPageDataSessionSource, createDistributedSvelteKit, sessionSourceFromPageData } from './replica.js';
|
|
6
|
-
export { createDistributedSvelteKitServer
|
|
9
|
+
export { createDistributedSvelteKitServer } from './server-replica.js';
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { GraphqlVariables } from '../types.js';
|
|
2
|
+
import type { DistributedBoundaryVariableSource, DistributedBoundaryVariableSources } from './boundary-variables.js';
|
|
3
|
+
/**
|
|
4
|
+
* Define the exceptional variables for one colocated GraphQL island.
|
|
5
|
+
*
|
|
6
|
+
* Save the default export as `<document>.bindings.js`. Route parameters with
|
|
7
|
+
* the same name and GraphQL variable defaults need no sidecar entry.
|
|
8
|
+
*/
|
|
9
|
+
export declare function defineGraphqlIslandBindings<TVariables extends GraphqlVariables = GraphqlVariables>(sources: DistributedBoundaryVariableSources<TVariables>): DistributedBoundaryVariableSources<TVariables>;
|
|
10
|
+
/** @internal Validate a discovered sidecar without trusting its prototype. */
|
|
11
|
+
export declare function isGraphqlIslandBindings(value: unknown): value is DistributedBoundaryVariableSources<GraphqlVariables>;
|
|
12
|
+
export declare function routeParam(name: string): DistributedBoundaryVariableSource;
|
|
13
|
+
export declare function searchParam(name: string, mode?: 'first' | 'all'): DistributedBoundaryVariableSource;
|
|
14
|
+
export declare function sessionClaim(...path: string[]): DistributedBoundaryVariableSource;
|
|
15
|
+
export declare function forwardedProp(...path: string[]): DistributedBoundaryVariableSource;
|
|
16
|
+
export declare function constant<TValue>(value: TValue): DistributedBoundaryVariableSource<TValue>;
|
|
17
|
+
export declare function omitVariable(): DistributedBoundaryVariableSource;
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
const GRAPHQL_ISLAND_BINDINGS = Symbol.for('@hops-ops/distributed/graphql-island-bindings');
|
|
2
|
+
const HOSTILE_KEYS = new Set(['__proto__', 'prototype', 'constructor']);
|
|
3
|
+
/**
|
|
4
|
+
* Define the exceptional variables for one colocated GraphQL island.
|
|
5
|
+
*
|
|
6
|
+
* Save the default export as `<document>.bindings.js`. Route parameters with
|
|
7
|
+
* the same name and GraphQL variable defaults need no sidecar entry.
|
|
8
|
+
*/
|
|
9
|
+
export function defineGraphqlIslandBindings(sources) {
|
|
10
|
+
if (sources === null ||
|
|
11
|
+
typeof sources !== 'object' ||
|
|
12
|
+
Array.isArray(sources) ||
|
|
13
|
+
(Object.getPrototypeOf(sources) !== Object.prototype &&
|
|
14
|
+
Object.getPrototypeOf(sources) !== null)) {
|
|
15
|
+
throw new TypeError('GraphQL island bindings must be an object');
|
|
16
|
+
}
|
|
17
|
+
const bindings = {};
|
|
18
|
+
for (const key of Object.keys(sources)) {
|
|
19
|
+
if (HOSTILE_KEYS.has(key)) {
|
|
20
|
+
throw new TypeError(`GraphQL island binding name ${key} is unsafe`);
|
|
21
|
+
}
|
|
22
|
+
const descriptor = Object.getOwnPropertyDescriptor(sources, key);
|
|
23
|
+
if (descriptor === undefined || !('value' in descriptor)) {
|
|
24
|
+
throw new TypeError(`GraphQL island binding ${key} must be a data property`);
|
|
25
|
+
}
|
|
26
|
+
bindings[key] = descriptor.value;
|
|
27
|
+
}
|
|
28
|
+
Object.defineProperty(bindings, GRAPHQL_ISLAND_BINDINGS, {
|
|
29
|
+
value: true,
|
|
30
|
+
enumerable: false
|
|
31
|
+
});
|
|
32
|
+
return Object.freeze(bindings);
|
|
33
|
+
}
|
|
34
|
+
/** @internal Validate a discovered sidecar without trusting its prototype. */
|
|
35
|
+
export function isGraphqlIslandBindings(value) {
|
|
36
|
+
if (value === null || typeof value !== 'object' || Array.isArray(value))
|
|
37
|
+
return false;
|
|
38
|
+
const descriptor = Object.getOwnPropertyDescriptor(value, GRAPHQL_ISLAND_BINDINGS);
|
|
39
|
+
return descriptor !== undefined && 'value' in descriptor && descriptor.value === true;
|
|
40
|
+
}
|
|
41
|
+
export function routeParam(name) {
|
|
42
|
+
return Object.freeze({ kind: 'route_param', name });
|
|
43
|
+
}
|
|
44
|
+
export function searchParam(name, mode = 'first') {
|
|
45
|
+
return Object.freeze({ kind: 'search_param', name, mode });
|
|
46
|
+
}
|
|
47
|
+
export function sessionClaim(...path) {
|
|
48
|
+
return Object.freeze({ kind: 'trusted_session', path: Object.freeze(path) });
|
|
49
|
+
}
|
|
50
|
+
export function forwardedProp(...path) {
|
|
51
|
+
return Object.freeze({ kind: 'forwarded_prop', path: Object.freeze(path) });
|
|
52
|
+
}
|
|
53
|
+
export function constant(value) {
|
|
54
|
+
return Object.freeze({ kind: 'constant', value });
|
|
55
|
+
}
|
|
56
|
+
export function omitVariable() {
|
|
57
|
+
return Object.freeze({ kind: 'omit' });
|
|
58
|
+
}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import type { DistributedBoundaryVariableSource } from '../boundary-variables.js';
|
|
2
|
+
export type DistributedIslandInventory = Readonly<{
|
|
3
|
+
version: number;
|
|
4
|
+
schemaFingerprint: string;
|
|
5
|
+
protocolFingerprint: string;
|
|
6
|
+
surface: unknown;
|
|
7
|
+
islands: readonly DistributedIslandPlanInput[];
|
|
8
|
+
}>;
|
|
9
|
+
export type DistributedIslandPlanInput = Readonly<{
|
|
10
|
+
version: number;
|
|
11
|
+
id: string;
|
|
12
|
+
operation: string;
|
|
13
|
+
operationHash: string;
|
|
14
|
+
modulePath: string;
|
|
15
|
+
exportName: string;
|
|
16
|
+
source: Readonly<{
|
|
17
|
+
path: string;
|
|
18
|
+
line: number;
|
|
19
|
+
column: number;
|
|
20
|
+
}>;
|
|
21
|
+
directives: Readonly<{
|
|
22
|
+
load: boolean;
|
|
23
|
+
live: boolean;
|
|
24
|
+
}>;
|
|
25
|
+
liveCoverage: Readonly<{
|
|
26
|
+
requested: boolean;
|
|
27
|
+
finite: boolean;
|
|
28
|
+
kind: string;
|
|
29
|
+
maxItems?: number;
|
|
30
|
+
}>;
|
|
31
|
+
variableSchema: Readonly<{
|
|
32
|
+
reference: string;
|
|
33
|
+
codecVersion: number;
|
|
34
|
+
variables: readonly Readonly<{
|
|
35
|
+
name: string;
|
|
36
|
+
graphqlType: string;
|
|
37
|
+
defaultValue?: unknown;
|
|
38
|
+
}>[];
|
|
39
|
+
}>;
|
|
40
|
+
}>;
|
|
41
|
+
export type DistributedSvelteKitBoundaryOccurrence = Readonly<{
|
|
42
|
+
islandId: string;
|
|
43
|
+
operation: string;
|
|
44
|
+
modulePath: string;
|
|
45
|
+
exportName: string;
|
|
46
|
+
component: string;
|
|
47
|
+
graphqlSource: string;
|
|
48
|
+
reason: 'route_document' | 'static_component_import' | 'explicit';
|
|
49
|
+
conservative: boolean;
|
|
50
|
+
directives: Readonly<{
|
|
51
|
+
load: boolean;
|
|
52
|
+
live: boolean;
|
|
53
|
+
}>;
|
|
54
|
+
liveCoverage: DistributedIslandPlanInput['liveCoverage'];
|
|
55
|
+
binding: Readonly<{
|
|
56
|
+
version: 1;
|
|
57
|
+
id: string;
|
|
58
|
+
discovery: 'route_param' | 'empty' | 'explicit';
|
|
59
|
+
sources: Readonly<Record<string, DistributedBoundaryVariableSource>>;
|
|
60
|
+
}>;
|
|
61
|
+
}>;
|
|
62
|
+
export type DistributedSvelteKitBoundary = Readonly<{
|
|
63
|
+
id: string;
|
|
64
|
+
route: string;
|
|
65
|
+
kind: 'layout' | 'page';
|
|
66
|
+
source: string;
|
|
67
|
+
islands: readonly DistributedSvelteKitBoundaryOccurrence[];
|
|
68
|
+
}>;
|
|
69
|
+
export type DistributedSvelteKitBoundaryPlan = Readonly<{
|
|
70
|
+
version: number;
|
|
71
|
+
module: string;
|
|
72
|
+
schemaFingerprint: string;
|
|
73
|
+
protocolFingerprint: string;
|
|
74
|
+
boundaries: readonly DistributedSvelteKitBoundary[];
|
|
75
|
+
unplaced: readonly Readonly<{
|
|
76
|
+
islandId: string;
|
|
77
|
+
operation: string;
|
|
78
|
+
graphqlSource: string;
|
|
79
|
+
}>[];
|
|
80
|
+
}>;
|
|
81
|
+
export type DistributedSvelteKitBoundaryAnalysisClient = Readonly<{
|
|
82
|
+
module: string;
|
|
83
|
+
inventory: DistributedIslandInventory;
|
|
84
|
+
explicitBoundaries?: readonly DistributedSvelteKitBoundaryRegistration[];
|
|
85
|
+
}>;
|
|
86
|
+
export type DistributedSvelteKitBoundaryRegistration = Readonly<{
|
|
87
|
+
operation: string;
|
|
88
|
+
route: string;
|
|
89
|
+
kind: 'layout' | 'page';
|
|
90
|
+
variables?: Readonly<Record<string, DistributedBoundaryVariableSource>>;
|
|
91
|
+
}>;
|
|
92
|
+
export type DistributedSvelteKitBoundaryAnalysisOptions = Readonly<{
|
|
93
|
+
cwd: string;
|
|
94
|
+
routesDir?: string;
|
|
95
|
+
libDir?: string;
|
|
96
|
+
aliases?: Readonly<Record<string, string>>;
|
|
97
|
+
clients: readonly DistributedSvelteKitBoundaryAnalysisClient[];
|
|
98
|
+
}>;
|
|
99
|
+
/** Validate a persisted adapter plan before check/dev treats it as coherent. */
|
|
100
|
+
export declare function validateDistributedSvelteKitBoundaryPlan(value: unknown, module?: string): DistributedSvelteKitBoundaryPlan;
|
|
101
|
+
/**
|
|
102
|
+
* Analyze Svelte component reachability without evaluating application
|
|
103
|
+
* components. Colocated, bounded GraphQL binding sidecars are the only app
|
|
104
|
+
* modules evaluated. The returned plan is deterministic and contains
|
|
105
|
+
* project-relative paths only.
|
|
106
|
+
*/
|
|
107
|
+
export declare function analyzeDistributedSvelteKitBoundaries(options: DistributedSvelteKitBoundaryAnalysisOptions): Promise<readonly DistributedSvelteKitBoundaryPlan[]>;
|