@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.
- package/LICENSE +112 -0
- package/PUBLIC-API.md +22 -0
- package/README.md +180 -2
- package/dist/.source-hash +1 -0
- package/dist/_fetchers.d.ts +51 -0
- package/dist/_fetchers.js +144 -0
- package/dist/_stateMachine.d.ts +102 -0
- package/dist/_stateMachine.js +168 -0
- package/dist/_wireAuth.d.ts +48 -0
- package/dist/_wireAuth.js +52 -0
- package/dist/adapter.d.ts +14 -0
- package/dist/adapter.js +10 -0
- package/dist/content-values.d.ts +11 -0
- package/dist/content-values.js +401 -0
- package/dist/errors.d.ts +65 -0
- package/dist/errors.js +45 -0
- package/dist/hydrate-slice.d.ts +2 -0
- package/dist/hydrate-slice.js +1 -0
- package/dist/index.d.ts +23 -0
- package/dist/index.js +9 -0
- package/dist/infra-resolver.d.ts +94 -0
- package/dist/infra-resolver.js +130 -0
- package/dist/internal.d.ts +33 -0
- package/dist/internal.js +20 -0
- package/dist/locale-routing.d.ts +5 -0
- package/dist/locale-routing.js +33 -0
- package/dist/normalize-pathname.d.ts +79 -0
- package/dist/normalize-pathname.js +103 -0
- package/dist/preview-frame-dependencies.d.ts +39 -0
- package/dist/preview-frame-dependencies.js +84 -0
- package/dist/preview-query-receipt.d.ts +3 -0
- package/dist/preview-query-receipt.js +16 -0
- package/dist/preview-read-lifecycle.d.ts +10 -0
- package/dist/preview-read-lifecycle.js +40 -0
- package/dist/preview-read-observation.d.ts +18 -0
- package/dist/preview-read-observation.js +81 -0
- package/dist/preview-scope.d.ts +3 -0
- package/dist/preview-scope.js +22 -0
- package/dist/preview-target.d.ts +73 -0
- package/dist/preview-target.js +86 -0
- package/dist/project-locales.d.ts +4 -0
- package/dist/project-locales.js +12 -0
- package/dist/public-delivery.d.ts +36 -0
- package/dist/public-delivery.js +55 -0
- package/dist/published-routes.d.ts +3 -0
- package/dist/published-routes.js +100 -0
- package/dist/query-builder.d.ts +225 -0
- package/dist/query-builder.js +294 -0
- package/dist/query-client.d.ts +54 -0
- package/dist/query-client.js +306 -0
- package/dist/query-compiler.d.ts +5 -0
- package/dist/query-compiler.js +12 -0
- package/dist/query-schema.d.ts +32 -0
- package/dist/query-schema.js +1 -0
- package/dist/redirect-search.d.ts +4 -0
- package/dist/redirect-search.js +14 -0
- package/dist/reference-resolver.d.ts +37 -0
- package/dist/reference-resolver.js +126 -0
- package/dist/resolve-route.d.ts +27 -0
- package/dist/resolve-route.js +145 -0
- package/dist/resolve.d.ts +43 -0
- package/dist/resolve.js +160 -0
- package/dist/schemas.d.ts +71 -0
- package/dist/schemas.js +75 -0
- package/dist/seo.d.ts +52 -0
- package/dist/seo.js +93 -0
- package/dist/signed-webhook.d.ts +13 -0
- package/dist/signed-webhook.js +54 -0
- package/dist/transport.d.ts +29 -0
- package/dist/transport.js +584 -0
- package/dist/typed-route.d.ts +22 -0
- package/dist/typed-route.js +1 -0
- package/dist/types.d.ts +147 -0
- package/dist/types.js +1 -0
- 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
|
+
}
|
package/dist/errors.d.ts
ADDED
|
@@ -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';
|
package/dist/index.d.ts
ADDED
|
@@ -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>;
|