@storystick/client 0.0.0-stage → 0.2.0-rc.1

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.
Files changed (75) hide show
  1. package/LICENSE +112 -0
  2. package/PUBLIC-API.md +22 -0
  3. package/README.md +180 -2
  4. package/dist/.source-hash +1 -0
  5. package/dist/_fetchers.d.ts +51 -0
  6. package/dist/_fetchers.js +144 -0
  7. package/dist/_stateMachine.d.ts +102 -0
  8. package/dist/_stateMachine.js +168 -0
  9. package/dist/_wireAuth.d.ts +48 -0
  10. package/dist/_wireAuth.js +52 -0
  11. package/dist/adapter.d.ts +14 -0
  12. package/dist/adapter.js +10 -0
  13. package/dist/content-values.d.ts +11 -0
  14. package/dist/content-values.js +401 -0
  15. package/dist/errors.d.ts +65 -0
  16. package/dist/errors.js +45 -0
  17. package/dist/hydrate-slice.d.ts +2 -0
  18. package/dist/hydrate-slice.js +1 -0
  19. package/dist/index.d.ts +23 -0
  20. package/dist/index.js +9 -0
  21. package/dist/infra-resolver.d.ts +94 -0
  22. package/dist/infra-resolver.js +130 -0
  23. package/dist/internal.d.ts +33 -0
  24. package/dist/internal.js +20 -0
  25. package/dist/locale-routing.d.ts +5 -0
  26. package/dist/locale-routing.js +33 -0
  27. package/dist/normalize-pathname.d.ts +79 -0
  28. package/dist/normalize-pathname.js +103 -0
  29. package/dist/preview-frame-dependencies.d.ts +39 -0
  30. package/dist/preview-frame-dependencies.js +84 -0
  31. package/dist/preview-query-receipt.d.ts +3 -0
  32. package/dist/preview-query-receipt.js +16 -0
  33. package/dist/preview-read-lifecycle.d.ts +10 -0
  34. package/dist/preview-read-lifecycle.js +40 -0
  35. package/dist/preview-read-observation.d.ts +18 -0
  36. package/dist/preview-read-observation.js +81 -0
  37. package/dist/preview-scope.d.ts +3 -0
  38. package/dist/preview-scope.js +22 -0
  39. package/dist/preview-target.d.ts +73 -0
  40. package/dist/preview-target.js +86 -0
  41. package/dist/project-locales.d.ts +4 -0
  42. package/dist/project-locales.js +12 -0
  43. package/dist/public-delivery.d.ts +36 -0
  44. package/dist/public-delivery.js +55 -0
  45. package/dist/published-routes.d.ts +3 -0
  46. package/dist/published-routes.js +100 -0
  47. package/dist/query-builder.d.ts +225 -0
  48. package/dist/query-builder.js +294 -0
  49. package/dist/query-client.d.ts +54 -0
  50. package/dist/query-client.js +306 -0
  51. package/dist/query-compiler.d.ts +5 -0
  52. package/dist/query-compiler.js +12 -0
  53. package/dist/query-schema.d.ts +32 -0
  54. package/dist/query-schema.js +1 -0
  55. package/dist/redirect-search.d.ts +4 -0
  56. package/dist/redirect-search.js +14 -0
  57. package/dist/reference-resolver.d.ts +37 -0
  58. package/dist/reference-resolver.js +126 -0
  59. package/dist/resolve-route.d.ts +27 -0
  60. package/dist/resolve-route.js +145 -0
  61. package/dist/resolve.d.ts +43 -0
  62. package/dist/resolve.js +160 -0
  63. package/dist/schemas.d.ts +71 -0
  64. package/dist/schemas.js +75 -0
  65. package/dist/seo.d.ts +52 -0
  66. package/dist/seo.js +93 -0
  67. package/dist/signed-webhook.d.ts +13 -0
  68. package/dist/signed-webhook.js +54 -0
  69. package/dist/transport.d.ts +29 -0
  70. package/dist/transport.js +584 -0
  71. package/dist/typed-route.d.ts +22 -0
  72. package/dist/typed-route.js +1 -0
  73. package/dist/types.d.ts +147 -0
  74. package/dist/types.js +1 -0
  75. package/package.json +70 -3
@@ -0,0 +1,401 @@
1
+ import { CONTENT_RENDER_MAX_DEPTH, ContentRenderDepthError } from '@storystick/types';
2
+ import { carryPublicReadIdentity } from './public-delivery.js';
3
+ import { attachPreviewQueryReceipt, getPreviewQueryReceiptId, } from './preview-query-receipt.js';
4
+ import { isContentRenderPlan, mergeContentFields, getPreviewReadDependencies, setPreviewReadDependencies, getPreviewReadSourceMap, setPreviewReadSourceMap, } from '@storystick/types';
5
+ import { hydrateSliceWithDiagnostics, } from './hydrate-slice.js';
6
+ import { attachPreviewReadObservation, getPreviewReadObservation, } from './preview-read-observation.js';
7
+ import { readPreviewProvenanceDocumentPathKey } from '@storystick/types/internal/preview-provenance';
8
+ export { readContentOrigin } from '@storystick/types/internal/content-origin';
9
+ import { readContentOrigin } from '@storystick/types/internal/content-origin';
10
+ const rawReads = new WeakMap();
11
+ /** Internal: live projections apply to authored reads before effective hydration. */
12
+ export function rawContentValues(value) {
13
+ return value !== null && typeof value === 'object'
14
+ ? (rawReads.get(value) ?? value)
15
+ : value;
16
+ }
17
+ /** Prepare once per consumer, after the raw read cache and Preview coding. */
18
+ export function prepareContentValues(value, requirePlan = false, preview = false) {
19
+ const visit = (v) => {
20
+ if (Array.isArray(v)) {
21
+ const children = v.map(visit);
22
+ const result = children.every((child, index) => child === v[index])
23
+ ? v
24
+ : children;
25
+ rawReads.set(result, rawContentValues(v));
26
+ carryPublicReadIdentity(v, result);
27
+ setPreviewReadDependencies(result, getPreviewReadDependencies(v));
28
+ setPreviewReadSourceMap(result, getPreviewReadSourceMap(v));
29
+ const observation = getPreviewReadObservation(v);
30
+ if (observation)
31
+ attachPreviewReadObservation(result, observation);
32
+ attachPreviewQueryReceipt(result, getPreviewQueryReceiptId(v) ?? undefined);
33
+ return result;
34
+ }
35
+ if (!record(v))
36
+ return v;
37
+ if (readContentOrigin(v))
38
+ return v;
39
+ // Diagnostic results are not usable reads; preserve their original error and partial material.
40
+ if (v.exactness === 'blocked' || v.exactness === 'degraded')
41
+ return v;
42
+ let result;
43
+ if (typeof v.documentId === 'string' &&
44
+ typeof v.storyType === 'string' &&
45
+ record(v.fields)) {
46
+ if (v._render === undefined) {
47
+ if (requirePlan)
48
+ throw new TypeError('storystick/CONTENT_PROTOCOL: Delivery render plan is missing. Update the backend and reconcile the Content Plane before using this SDK.');
49
+ return v;
50
+ }
51
+ if (!isContentRenderPlan(v._render))
52
+ throw new TypeError('storystick/CONTENT_PROTOCOL: Unsupported delivery render plan.');
53
+ const prepare = documentPreparer(v, v._render, preview);
54
+ const authoredFields = v.fields;
55
+ const fields = prepare.fields(authoredFields, v._render.fields, '', []);
56
+ result = Object.keys(fields).every((key) => fields[key] === authoredFields[key])
57
+ ? v
58
+ : { ...v, fields };
59
+ }
60
+ else {
61
+ const children = Object.fromEntries(Object.entries(v).map(([key, child]) => [key, visit(child)]));
62
+ result = Object.keys(children).every((key) => children[key] === v[key])
63
+ ? v
64
+ : children;
65
+ }
66
+ rawReads.set(result, rawContentValues(v));
67
+ setPreviewReadDependencies(result, getPreviewReadDependencies(v));
68
+ setPreviewReadSourceMap(result, getPreviewReadSourceMap(v));
69
+ const observation = getPreviewReadObservation(v);
70
+ if (observation)
71
+ attachPreviewReadObservation(result, observation);
72
+ attachPreviewQueryReceipt(result, getPreviewQueryReceiptId(v) ?? undefined);
73
+ carryPublicReadIdentity(v, result);
74
+ return result;
75
+ };
76
+ return visit(value);
77
+ }
78
+ /** Explicit raw-content escape hatch. Never performs I/O or reads app context. */
79
+ export function prepareContentSource(value, source) {
80
+ if (!source)
81
+ return value;
82
+ const doc = source.document;
83
+ if (!record(doc))
84
+ throw new TypeError('storystick/CONTENT_PROTOCOL: source.document needs a delivery render plan.');
85
+ const check = (v) => {
86
+ const origin = readContentOrigin(v);
87
+ if (origin &&
88
+ (origin.documentId !== doc.documentId ||
89
+ !(origin.path === source.path ||
90
+ origin.path.startsWith(source.path + '[')))) {
91
+ throw new TypeError('storystick/CONTENT_SOURCE_MISMATCH: Content and source have different origins.');
92
+ }
93
+ };
94
+ if (Array.isArray(value))
95
+ value.forEach(check);
96
+ else
97
+ check(value);
98
+ if (doc._render === undefined)
99
+ return value;
100
+ if (!isContentRenderPlan(doc._render))
101
+ throw new TypeError('storystick/CONTENT_PROTOCOL: Unsupported delivery render plan.');
102
+ const tokens = source.path.match(/[^.[\]]+|\[\d+\]/g) ?? [];
103
+ let defs = doc._render.fields;
104
+ let current = mergeContentFields(recordOrEmpty(doc._render.dependencies), recordOrEmpty(doc.fields));
105
+ let def;
106
+ let prefix = '';
107
+ const anchors = [];
108
+ for (const token of tokens) {
109
+ if (token.startsWith('[')) {
110
+ current = Array.isArray(current)
111
+ ? current[Number(token.slice(1, -1))]
112
+ : undefined;
113
+ if (record(current) && typeof current._key === 'string')
114
+ anchors.push({ pathPrefix: prefix, key: current._key });
115
+ prefix += token;
116
+ if (def?.array)
117
+ def = { ...def, array: false };
118
+ }
119
+ else {
120
+ def = defs[token];
121
+ current = record(current) ? current[token] : undefined;
122
+ prefix += `${prefix ? '.' : ''}${token}`;
123
+ }
124
+ const type = record(current) && typeof current._type === 'string'
125
+ ? current._type
126
+ : def?.types?.[0];
127
+ const story = type ? doc._render.stories[type] : undefined;
128
+ defs =
129
+ record(current) && typeof current._variation === 'string'
130
+ ? (story?.variations?.[current._variation] ?? {})
131
+ : (story?.fields ?? {});
132
+ }
133
+ if (!def || prefix !== source.path)
134
+ throw new TypeError('storystick/CONTENT_PROTOCOL: Unknown source field path.');
135
+ return documentPreparer(doc, doc._render).field(def, value, source.path, anchors);
136
+ }
137
+ function documentPreparer(doc, plan, preview = false) {
138
+ const host = {
139
+ ...doc,
140
+ fields: mergeContentFields(recordOrEmpty(plan.dependencies), recordOrEmpty(doc.fields)),
141
+ };
142
+ const activeZones = new Set();
143
+ let compositionDepth = 0;
144
+ const origin = (kind, path, anchors, value) => {
145
+ const ids = new Set();
146
+ const collect = (v) => {
147
+ if (typeof v === 'string')
148
+ ids.add(v);
149
+ else if (Array.isArray(v))
150
+ v.forEach(collect);
151
+ else if (record(v))
152
+ for (const [key, child] of Object.entries(v))
153
+ if (!key.startsWith('__ss'))
154
+ collect(child);
155
+ };
156
+ collect(value);
157
+ const subset = (sidecar, byPath) => {
158
+ if (!record(sidecar))
159
+ return undefined;
160
+ const selected = {};
161
+ for (const [mapName, entries] of Object.entries(sidecar)) {
162
+ if (!record(entries))
163
+ continue;
164
+ selected[mapName] = Object.fromEntries(Object.entries(entries).filter(([key]) => mapName === 'byRef' || (byPath && mapName === 'unresolved')
165
+ ? key === path ||
166
+ key.startsWith(path + '.') ||
167
+ key.startsWith(path + '[')
168
+ : ids.has(key)));
169
+ }
170
+ return Object.values(selected).some((map) => Object.keys(map).length)
171
+ ? selected
172
+ : undefined;
173
+ };
174
+ const assets = subset(doc._assets, false);
175
+ const refs = subset(doc._referencedDocs, true);
176
+ const previewProvenance = kind === 'richtext'
177
+ ? documentFieldPreviewProvenance(doc, path)
178
+ : undefined;
179
+ // Resolve this occurrence before the compatibility by-ID index. The same
180
+ // document may be linked both live and at a pinned snapshot on one page.
181
+ if (refs && record(refs.byRef) && record(refs.byId)) {
182
+ for (const [refPath, entry] of Object.entries(refs.byRef)) {
183
+ if ((refPath === path || refPath === `${path}.document`) &&
184
+ record(entry) &&
185
+ typeof entry.documentId === 'string')
186
+ refs.byId[entry.documentId] = entry;
187
+ }
188
+ for (const [refPath, failure] of Object.entries(recordOrEmpty(refs.unresolved))) {
189
+ if ((refPath === path || refPath === `${path}.document`) &&
190
+ record(failure) &&
191
+ typeof failure.documentId === 'string')
192
+ delete refs.byId[failure.documentId];
193
+ }
194
+ }
195
+ return {
196
+ version: 1,
197
+ documentId: String(doc.documentId),
198
+ path,
199
+ kind,
200
+ parentAnchors: anchors,
201
+ ...(preview || doc.__ssPreviewProvenance || doc.__ssPreviewRouteRender
202
+ ? { preview: true }
203
+ : {}),
204
+ ...(doc._localeRouting ? { _localeRouting: doc._localeRouting } : {}),
205
+ ...(assets
206
+ ? { _assets: assets }
207
+ : {}),
208
+ ...(refs
209
+ ? {
210
+ _referencedDocs: refs,
211
+ }
212
+ : {}),
213
+ ...(previewProvenance
214
+ ? { __ssPreviewProvenance: previewProvenance }
215
+ : {}),
216
+ };
217
+ };
218
+ const fields = (values, defs, prefix, anchors) => Object.fromEntries(Object.entries(values).map(([name, value]) => [
219
+ name,
220
+ defs[name]
221
+ ? field(defs[name], value, prefix ? `${prefix}.${name}` : name, anchors)
222
+ : value,
223
+ ]));
224
+ const slice = (raw, path, anchors, zoneField, index, anchorPrefix = path.replace(/\[\d+\]$/, '')) => {
225
+ if (!record(raw) ||
226
+ typeof raw._type !== 'string' ||
227
+ typeof raw._key !== 'string')
228
+ return raw;
229
+ if (readContentOrigin(raw))
230
+ return raw;
231
+ if (++compositionDepth > CONTENT_RENDER_MAX_DEPTH)
232
+ throw new ContentRenderDepthError();
233
+ try {
234
+ const result = hydrateSliceWithDiagnostics(raw, host, {
235
+ sourceRefPath: `${path}._model`,
236
+ });
237
+ const story = plan.stories[raw._type];
238
+ const defs = typeof raw._variation === 'string'
239
+ ? story?.variations?.[raw._variation]
240
+ : story?.fields;
241
+ const currentAnchors = [
242
+ ...anchors,
243
+ { pathPrefix: anchorPrefix, key: raw._key },
244
+ ];
245
+ const hydrated = result.slice;
246
+ const effective = Object.fromEntries(Object.entries(hydrated).filter(([key]) => key.startsWith('_') || !defs || key in defs));
247
+ const prepared = fields(effective, defs ?? {}, path, currentAnchors);
248
+ const meta = origin('slice', path, anchors, effective);
249
+ const attrs = recordOrEmpty(recordOrEmpty(doc.__ssPreviewProvenance).attrsByKey);
250
+ const localAttrs = Object.fromEntries(Object.entries(attrs).filter(([key]) => sliceAttributeOwner(key) === raw._key));
251
+ if (Object.keys(localAttrs).length)
252
+ meta.__ssPreviewProvenance = { attrsByKey: localAttrs };
253
+ meta.index = index;
254
+ meta.anchorPrefix = anchorPrefix;
255
+ meta.zoneField = zoneField;
256
+ if (result.failures.length)
257
+ meta.failures = result.failures;
258
+ const mappings = recordOrEmpty(recordOrEmpty(doc._zoneMappings)[raw._type]);
259
+ const zones = {};
260
+ for (const [name, mapped] of Object.entries(mappings)) {
261
+ if (typeof mapped !== 'string')
262
+ continue;
263
+ if (activeZones.has(mapped))
264
+ throw new TypeError('storystick/CONTENT_PROTOCOL: Circular container zone mapping.');
265
+ activeZones.add(mapped);
266
+ const rootDef = plan.fields[mapped];
267
+ zones[name] = {
268
+ path: mapped,
269
+ value: rootDef
270
+ ? field(rootDef, host.fields[mapped], mapped, [])
271
+ : null,
272
+ };
273
+ activeZones.delete(mapped);
274
+ }
275
+ if (Object.keys(zones).length)
276
+ meta.zones = zones;
277
+ return { ...prepared, __ssContent: meta };
278
+ }
279
+ finally {
280
+ compositionDepth--;
281
+ }
282
+ };
283
+ const field = (def, value, path, anchors) => {
284
+ if (value == null)
285
+ return value;
286
+ if (record(value) &&
287
+ typeof value.documentId === 'string' &&
288
+ typeof value.storyType === 'string' &&
289
+ record(value.fields))
290
+ return prepareContentValues(value, false, preview);
291
+ if (readContentOrigin(value))
292
+ return value;
293
+ if (def.array && Array.isArray(value))
294
+ return value.map((v, i) => field({ ...def, array: false }, v, `${path}[${i}]`, record(v) && typeof v._key === 'string'
295
+ ? [...anchors, { pathPrefix: path, key: v._key }]
296
+ : anchors));
297
+ const kinds = Array.isArray(def.type) ? def.type : [def.type];
298
+ if (kinds.includes('sliceZone'))
299
+ return Array.isArray(value)
300
+ ? value.map((v, i) => slice(v, `${path}[${i}]`, anchors, path.split('.').at(-1), i))
301
+ : value;
302
+ if (kinds.includes('richtext')) {
303
+ let node = value;
304
+ if (typeof node === 'string') {
305
+ try {
306
+ node = JSON.parse(node);
307
+ }
308
+ catch {
309
+ return null;
310
+ }
311
+ }
312
+ if (!record(node) || node.type !== 'doc')
313
+ return null;
314
+ let embedIndex = 0;
315
+ const walk = (n, nodePath) => {
316
+ if (!record(n))
317
+ return n;
318
+ if (n.type === 'sliceEmbed' && record(n.attrs)) {
319
+ const attrs = n.attrs;
320
+ const prepared = slice({
321
+ ...recordOrEmpty(attrs.data),
322
+ _key: attrs._key,
323
+ _type: attrs._type,
324
+ ...(attrs._variation === undefined
325
+ ? {}
326
+ : { _variation: attrs._variation }),
327
+ }, `${nodePath}.data`, anchors, path.split('.').at(-1), embedIndex++, path);
328
+ return { ...n, attrs: { ...attrs, data: prepared } };
329
+ }
330
+ return {
331
+ ...n,
332
+ ...(Array.isArray(n.marks)
333
+ ? {
334
+ marks: n.marks.map((mark, i) => record(mark) && mark.type === 'link' && record(mark.attrs)
335
+ ? {
336
+ ...mark,
337
+ attrs: {
338
+ ...mark.attrs,
339
+ __ssContent: origin('link', `${nodePath}.marks[${i}]`, anchors, mark.attrs),
340
+ },
341
+ }
342
+ : mark),
343
+ }
344
+ : {}),
345
+ ...(Array.isArray(n.content)
346
+ ? {
347
+ content: n.content.map((child, i) => walk(child, `${nodePath}.content[${i}]`)),
348
+ }
349
+ : {}),
350
+ };
351
+ };
352
+ return {
353
+ ...walk(node, path),
354
+ __ssContent: origin('richtext', path, anchors, node),
355
+ };
356
+ }
357
+ if (!record(value))
358
+ return value;
359
+ const type = typeof value._type === 'string' ? value._type : def.types?.[0];
360
+ const prepared = type && plan.stories[type]
361
+ ? fields(value, plan.stories[type].fields, path, anchors)
362
+ : { ...value };
363
+ return { ...prepared, __ssContent: origin(kinds[0], path, anchors, value) };
364
+ };
365
+ return { fields, field };
366
+ }
367
+ function record(v) {
368
+ return v !== null && typeof v === 'object' && !Array.isArray(v);
369
+ }
370
+ function recordOrEmpty(v) {
371
+ return record(v) ? v : {};
372
+ }
373
+ function sliceAttributeOwner(key) {
374
+ if (!key.startsWith('route:slice:v2:'))
375
+ return undefined;
376
+ try {
377
+ const payload = JSON.parse(decodeURIComponent(key.slice('route:slice:v2:'.length)));
378
+ return payload.v === 2 && typeof payload.sliceKey === 'string'
379
+ ? payload.sliceKey
380
+ : undefined;
381
+ }
382
+ catch {
383
+ return undefined;
384
+ }
385
+ }
386
+ function documentFieldPreviewProvenance(document, path) {
387
+ // Container zones resolve document-owned root fields. Keep autonomous values
388
+ // compact by carrying only that field's markers, never the owner-wide map.
389
+ if (!/^[^.[\]]+$/.test(path))
390
+ return undefined;
391
+ const attrs = recordOrEmpty(recordOrEmpty(document.__ssPreviewProvenance).attrsByKey);
392
+ const documentId = String(document.documentId);
393
+ const selected = Object.fromEntries(Object.entries(attrs).filter(([key]) => {
394
+ const parsed = readPreviewProvenanceDocumentPathKey(key);
395
+ const root = parsed?.path[0];
396
+ return (parsed?.documentId === documentId &&
397
+ root?.type === 'field' &&
398
+ root.name === path);
399
+ }));
400
+ return Object.keys(selected).length > 0 ? { attrsByKey: selected } : undefined;
401
+ }
@@ -0,0 +1,65 @@
1
+ import type { QueryResult, QueryPageInfo, QueryMeta, QueryReason } from '@storystick/types/query-contract';
2
+ /**
3
+ * StorystickError — single source of truth for SDK errors (ADR-091
4
+ * §Consequences > @storystick/client).
5
+ *
6
+ * Lifted from `packages/next/src/server.ts` per the ADR-091 mandate that
7
+ * `@storystick/client` owns the class. Lane C re-exports it from
8
+ * `@storystick/next` for source-compat with existing user imports.
9
+ *
10
+ * Field parity is load-bearing — the consolidated transport throws
11
+ * `StorystickError` for every non-OK gateway response and consumers
12
+ * (`error.tsx` boundaries, devtools, support tickets) read every field.
13
+ * The CRITICAL `StorystickError field parity` test in
14
+ * `tests/transport.test.ts` pins the contract.
15
+ */
16
+ export type StorystickErrorCategory = 'validation' | 'conflict' | 'auth' | 'system' | 'cancel';
17
+ export interface StorystickErrorOptions {
18
+ /**
19
+ * Gateway-issued ref ID (`err_XXXXXXXX`) — populated when the gateway
20
+ * responded with a `{ error: { ref } }` body. Present on every non-2xx
21
+ * response from up-to-date deployments; absent for local validation
22
+ * errors that never hit the gateway.
23
+ */
24
+ ref?: string;
25
+ /** Boundary error code (post ADR-079 batch 5 namespaced `storystick/<NAME>`). */
26
+ code?: string;
27
+ /** Canonical category from the gateway envelope (post ADR-079 batch 4). */
28
+ category?: StorystickErrorCategory;
29
+ /** True if the user can fix the cause and retry. */
30
+ recoverable?: boolean;
31
+ /** True if the SDK / CLI can auto-retry without user input. */
32
+ retryable?: boolean;
33
+ /** Per-code structured payload from the gateway envelope. */
34
+ payload?: unknown;
35
+ /** HTTP status when a gateway response was received. */
36
+ status?: number;
37
+ /** Server retry delay; callers own retry/deadline/abort policy. */
38
+ retryAfterMs?: number;
39
+ }
40
+ export declare class StorystickError extends Error {
41
+ readonly ref: string | undefined;
42
+ readonly code: string | undefined;
43
+ readonly category: StorystickErrorCategory | undefined;
44
+ readonly recoverable: boolean | undefined;
45
+ readonly retryable: boolean | undefined;
46
+ readonly status: number | undefined;
47
+ readonly retryAfterMs: number | undefined;
48
+ readonly payload: unknown | undefined;
49
+ constructor(message: string, refOrOpts?: string | StorystickErrorOptions);
50
+ }
51
+ /** A strict content read could not return an exact Query V2 result. */
52
+ export declare class QueryExecutionError extends StorystickError {
53
+ readonly result: {
54
+ exactness: 'blocked' | 'degraded';
55
+ data: unknown;
56
+ pageInfo: QueryPageInfo | null;
57
+ reason: QueryReason;
58
+ meta: QueryMeta;
59
+ };
60
+ constructor(result: QueryExecutionError['result']);
61
+ }
62
+ /** Shared strict-read policy for SDK adapters that also need pagination metadata. */
63
+ export declare function assertExactQueryResult<TData, TPageInfo extends QueryPageInfo | null>(result: QueryResult<TData, TPageInfo>): asserts result is Extract<QueryResult<TData, TPageInfo>, {
64
+ exactness: 'exact';
65
+ }>;
package/dist/errors.js ADDED
@@ -0,0 +1,45 @@
1
+ export class StorystickError extends Error {
2
+ ref;
3
+ code;
4
+ category;
5
+ recoverable;
6
+ retryable;
7
+ status;
8
+ retryAfterMs;
9
+ payload;
10
+ constructor(message, refOrOpts) {
11
+ const opts = typeof refOrOpts === 'string' ? { ref: refOrOpts } : (refOrOpts ?? {});
12
+ const ref = opts.ref;
13
+ const refLine = ref
14
+ ? `\n\nReference ID: ${ref}\nShare this with support when reporting this issue.`
15
+ : '';
16
+ super(`${message}${refLine}`);
17
+ this.name = 'StorystickError';
18
+ this.ref = ref;
19
+ this.code = opts.code;
20
+ this.category = opts.category;
21
+ this.recoverable = opts.recoverable;
22
+ this.retryable = opts.retryable;
23
+ this.payload = opts.payload;
24
+ this.status = opts.status;
25
+ this.retryAfterMs = opts.retryAfterMs;
26
+ }
27
+ }
28
+ /** A strict content read could not return an exact Query V2 result. */
29
+ export class QueryExecutionError extends StorystickError {
30
+ constructor(result) {
31
+ super('The content query did not produce an exact result.', {
32
+ code: 'storystick/QUERY_NOT_EXACT',
33
+ category: 'system',
34
+ retryable: result.reason.retryable,
35
+ });
36
+ this.name = 'QueryExecutionError';
37
+ // Keep the complete diagnostic server-side, out of ordinary serialization.
38
+ Object.defineProperty(this, 'result', { value: result, enumerable: false });
39
+ }
40
+ }
41
+ /** Shared strict-read policy for SDK adapters that also need pagination metadata. */
42
+ export function assertExactQueryResult(result) {
43
+ if (result.exactness !== 'exact')
44
+ throw new QueryExecutionError(result);
45
+ }
@@ -0,0 +1,2 @@
1
+ export { hydrateSlice, hydrateSliceWithDiagnostics, getFieldBindings, resolveBinding, getByPath } from '@storystick/types/internal/hydrate-slice';
2
+ export type { FieldBindingsSidecar, HydrateSliceOptions, HydratedSliceResult, SliceHydrationFailure } from '@storystick/types/internal/hydrate-slice';
@@ -0,0 +1 @@
1
+ export { hydrateSlice, hydrateSliceWithDiagnostics, getFieldBindings, resolveBinding, getByPath } from '@storystick/types/internal/hydrate-slice';
@@ -0,0 +1,23 @@
1
+ export { stripPreviewProvenance } from '@storystick/types/preview-provenance';
2
+ /** @storystick/client — generated, typed Query V2 client. */
3
+ export type { StorystickAsset, StorystickAssetAltDefault, StorystickAssetDeliveryStatus, StorystickAssetMetadata, StorystickAssetSidecar, StorystickAssetType, StorystickImageField, } from '@storystick/media';
4
+ export type { QueryResult, ResolvedRoute, RequestLocaleContext, PublicationSeo, RequestOptions, DefaultRequestOptions, ClientAuth, ReferencedDocumentFailureReason, ReferencedDocumentPolicy, ReferencedDocumentSidecarEntry, ReferencedDocumentSidecarFailure, ReferencedDocumentsSidecar, ResolveRouteInput, PublicTransport as Transport, PublicTransportRequest as TransportRequest, TransportResponse, } from './types.js';
5
+ export { resolveLink, resolveSafeHref, safeHref, } from '@storystick/types';
6
+ export type { LinkOptions, LinkProps, LinkStatus, LinkValue, PathnameLiteralSegment, PathnameReferenceSegment, PathnameSegment, PathnameValue, ResolvedLink, SafeHrefResolution, SafeHrefStatus, } from '@storystick/types';
7
+ export { createClient } from './query-client.js';
8
+ export { createPublicRenderReceipt, serializePublicRenderReceipt, createPublicSiteManifest } from './public-delivery.js';
9
+ export { PUBLIC_RENDER_RECEIPT_ID, PUBLIC_SITE_MANIFEST_PATH } from '@storystick/types/public-delivery';
10
+ export type { PublicRenderReceiptV1, PublicSiteManifestV1 } from '@storystick/types/public-delivery';
11
+ export type { Client, ClientOptions } from './query-client.js';
12
+ export type { AddressedQuery, CollectionQuery, FieldAccessor, PagedQuery, QueryExecuteOptions, QueryFieldAccessors, QueryForSchema, QuerySelectionInput, QuerySortAccessors, QuerySortClause, QuerySortTuple, SelectedDocument, SelectedFields, SingletonQuery, UnselectedQuery, } from './query-builder.js';
13
+ export type { QueryDslOperator, QueryFieldMap, QueryFieldType, QuerySchemaDefinition, QueryStoryMap, QueryStoryType, QueryStoriesOf, } from './query-schema.js';
14
+ export { StorystickError, QueryExecutionError } from './errors.js';
15
+ export type { StorystickErrorCategory, StorystickErrorOptions } from './errors.js';
16
+ export { normalizePathname, pathnameToHref, pathnameToSegments, segmentsToPathname, } from './normalize-pathname.js';
17
+ export { publicHrefForLocale, publishedLocaleHrefs, normalizePublicLocaleOrigins, prefixHref, publicPathnameForLocale, resolveLocalePathname } from './locale-routing.js';
18
+ export type { LocalePathnameResolution, LocaleRouting, LocaleRoutingSettings, RoutingLocale, } from './locale-routing.js';
19
+ export { resolveReference, resolveReferences } from './reference-resolver.js';
20
+ export type { ReferenceDocument, ResolveReferenceOptions, ResolvedReference, } from './reference-resolver.js';
21
+ export type { QueryRouteAccessors, QueryRoutePathAccessor } from './query-builder.js';
22
+ export type { PublicLocaleOrigins } from './locale-routing.js';
23
+ export type { SchemaStoryName, RoutableSchemaStoryName, RequestedStoryName, TypedRouteDocument, TypedDocumentRoute, ResolvedRouteForSchema } from './typed-route.js';
package/dist/index.js ADDED
@@ -0,0 +1,9 @@
1
+ export { stripPreviewProvenance } from '@storystick/types/preview-provenance';
2
+ export { resolveLink, resolveSafeHref, safeHref, } from '@storystick/types';
3
+ export { createClient } from './query-client.js';
4
+ export { createPublicRenderReceipt, serializePublicRenderReceipt, createPublicSiteManifest } from './public-delivery.js';
5
+ export { PUBLIC_RENDER_RECEIPT_ID, PUBLIC_SITE_MANIFEST_PATH } from '@storystick/types/public-delivery';
6
+ export { StorystickError, QueryExecutionError } from './errors.js';
7
+ export { normalizePathname, pathnameToHref, pathnameToSegments, segmentsToPathname, } from './normalize-pathname.js';
8
+ export { publicHrefForLocale, publishedLocaleHrefs, normalizePublicLocaleOrigins, prefixHref, publicPathnameForLocale, resolveLocalePathname } from './locale-routing.js';
9
+ export { resolveReference, resolveReferences } from './reference-resolver.js';
@@ -0,0 +1,94 @@
1
+ /**
2
+ * Infrastructure URL resolution — ADR-131 C9.
3
+ *
4
+ * Centralizes derivation of the three Convex-flavoured URLs the customer-
5
+ * bundled SDK packages need at runtime :
6
+ *
7
+ * - `endpoint` — gateway HTTP root, `.convex.site` form. Hosts the
8
+ * custom HTTP routes (JWKS, Pattern-D queries).
9
+ * - `convexUrl` — standard Convex RPC + WebSocket sync URL, `.convex.cloud`
10
+ * form. Used by `ConvexReactClient` (WS) and the server-
11
+ * side `ConvexHttpClient` (preview session lookup,
12
+ * observation tap mutation).
13
+ * - `jwksUrl` — `${endpoint}/api/jwks.json` by default.
14
+ *
15
+ * Cascade (per URL, independent) :
16
+ *
17
+ * 1. Explicit `options.*Url` — call-site override (props, env vars read
18
+ * by the adapter and forwarded as options, factory args).
19
+ * 2. Auto-derive from `resolveProject(projectId).endpoint`.
20
+ * 3. Fail-loud — `resolveProject` throws a typed `StorystickError` if
21
+ * the gateway returns 404/503/network error.
22
+ *
23
+ * The single `resolveProject` round-trip is shared across the three URLs ;
24
+ * subsequent calls hit the per-process cache (server) or sessionStorage
25
+ * cache (browser).
26
+ *
27
+ * **Env vars are NOT read here.** The bundler-replace behaviour of
28
+ * `process.env.NEXT_PUBLIC_*` requires literal property access in the
29
+ * adapter package (Next/React) so the static replacement at build time
30
+ * resolves the value into the browser bundle. Adapters call this helper
31
+ * after reading their respective env vars and forwarding them as options.
32
+ *
33
+ * Direction produit (owner 2026-05-26 PM) :
34
+ *
35
+ * > "Convex doit rester transparent au dev. Hors de question qu'une env
36
+ * > var mentionnant convex.cloud / .site / etc apparaisse côté customer."
37
+ *
38
+ * The 5-var customer allowlist is `STORYSTICK_PROJECT_ID`, `STORYSTICK_API_KEY`,
39
+ * `NEXT_PUBLIC_STORYSTICK_PROJECT_ID`, `NEXT_PUBLIC_STORYSTICK_PUBLISHABLE_KEY`,
40
+ * `NEXT_PUBLIC_STORYSTICK_STUDIO_ORIGIN`. All Convex-flavoured URLs are
41
+ * derived from `projectId` via this module.
42
+ * Resource-storage bucket credentials belong exclusively to the server-side
43
+ * storage resolver; they are never customer env vars or resolver options.
44
+ */
45
+ export interface ResolveInfraOptions {
46
+ /** Explicit endpoint override (gateway HTTP root, `.convex.site`). */
47
+ endpoint?: string;
48
+ /** Explicit Convex RPC URL override (`.convex.cloud`). */
49
+ convexUrl?: string;
50
+ /** Explicit JWKS URL override. */
51
+ jwksUrl?: string;
52
+ /**
53
+ * Optional gateway URL passed through to `resolveProject()` for self-host
54
+ * overrides. Unused by default ; resolution falls back to
55
+ * `DEFAULT_GATEWAY_URL` inside `resolve.ts`.
56
+ */
57
+ gatewayUrl?: string;
58
+ }
59
+ export interface ResolvedInfra {
60
+ /** Gateway HTTP root (`.convex.site` form). HTTP custom routes + JWKS. */
61
+ endpoint: string;
62
+ /** Convex RPC / WS URL (`.convex.cloud` form). */
63
+ convexUrl: string;
64
+ /** JWKS endpoint (default `${endpoint}/api/jwks.json`). */
65
+ jwksUrl: string;
66
+ }
67
+ /**
68
+ * Synchronously derive `ResolvedInfra` when `endpoint` is already known
69
+ * (explicit prop or env override). Used by adapters that need an
70
+ * initial value during render — e.g. `<StorystickQueryProvider>`
71
+ * synchronously seeds its `infra` state when all required values are
72
+ * explicit, so generated clients can execute on the first render
73
+ * inside an immediately-available `ConvexProvider` (codex R2 P2 fix).
74
+ *
75
+ * Returns `null` when `endpoint` is missing (gateway round-trip needed).
76
+ */
77
+ export declare function resolveInfraSync(options?: ResolveInfraOptions): ResolvedInfra | null;
78
+ /**
79
+ * Resolve the three infrastructure URLs for `projectId`.
80
+ *
81
+ * Idempotent + cache-aware — repeated calls with the same `projectId` reuse
82
+ * the `resolveProject` cache (per-process Map server-side, sessionStorage
83
+ * browser-side). Safe to call eagerly at module init.
84
+ *
85
+ * The `resolveProject` round-trip happens only when no explicit endpoint
86
+ * override is supplied — `convexUrl` + `jwksUrl` are pure transforms on
87
+ * `endpoint`. If `endpoint` is provided (prop or env escape hatch), the
88
+ * helper completes without any network call ; gateway failures cannot
89
+ * stall the boot path in that case.
90
+ *
91
+ * Throws (propagates) the `StorystickError` from `resolveProject` when the
92
+ * gateway returns 404/503/network error AND no explicit `endpoint` is set.
93
+ */
94
+ export declare function resolveInfra(projectId: string, options?: ResolveInfraOptions): Promise<ResolvedInfra>;