@fictjs/runtime 0.7.0 → 0.9.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 +46 -0
- package/dist/advanced.cjs +9 -9
- package/dist/advanced.d.cts +4 -4
- package/dist/advanced.d.ts +4 -4
- package/dist/advanced.js +4 -4
- package/dist/{effect-DAzpH7Mm.d.cts → binding-BWchH3Kp.d.cts} +33 -24
- package/dist/{effect-DAzpH7Mm.d.ts → binding-BWchH3Kp.d.ts} +33 -24
- package/dist/{chunk-7YQK3XKY.js → chunk-DXG3TARY.js} +520 -518
- package/dist/chunk-DXG3TARY.js.map +1 -0
- package/dist/{chunk-TLDT76RV.js → chunk-FVX77557.js} +3 -3
- package/dist/{chunk-WRU3IZOA.js → chunk-JVYH76ZX.js} +3 -3
- package/dist/chunk-LBE6DC3V.cjs +768 -0
- package/dist/chunk-LBE6DC3V.cjs.map +1 -0
- package/dist/chunk-N6ODUM2Y.js +768 -0
- package/dist/chunk-N6ODUM2Y.js.map +1 -0
- package/dist/{chunk-PRF4QG73.cjs → chunk-OAM7HABA.cjs} +423 -246
- package/dist/chunk-OAM7HABA.cjs.map +1 -0
- package/dist/{chunk-CEV6TO5U.cjs → chunk-PD6IQY2Y.cjs} +8 -8
- package/dist/{chunk-CEV6TO5U.cjs.map → chunk-PD6IQY2Y.cjs.map} +1 -1
- package/dist/{chunk-HHDHQGJY.cjs → chunk-PG4QX2I2.cjs} +17 -17
- package/dist/{chunk-HHDHQGJY.cjs.map → chunk-PG4QX2I2.cjs.map} +1 -1
- package/dist/{chunk-4LCHQ7U4.js → chunk-T2LNV5Q5.js} +271 -94
- package/dist/chunk-T2LNV5Q5.js.map +1 -0
- package/dist/{chunk-FSCBL7RI.cjs → chunk-UBFDB6OL.cjs} +521 -519
- package/dist/chunk-UBFDB6OL.cjs.map +1 -0
- package/dist/{context-C4vBQbb4.d.ts → devtools-5AipK9CX.d.cts} +35 -35
- package/dist/{context-BFbHf9nC.d.cts → devtools-BDp76luf.d.ts} +35 -35
- package/dist/index.cjs +42 -42
- package/dist/index.d.cts +4 -4
- package/dist/index.d.ts +4 -4
- package/dist/index.dev.js +3 -3
- package/dist/index.dev.js.map +1 -1
- package/dist/index.js +3 -3
- package/dist/internal-list.cjs +12 -0
- package/dist/internal-list.cjs.map +1 -0
- package/dist/internal-list.d.cts +2 -0
- package/dist/internal-list.d.ts +2 -0
- package/dist/internal-list.js +12 -0
- package/dist/internal-list.js.map +1 -0
- package/dist/internal.cjs +6 -746
- package/dist/internal.cjs.map +1 -1
- package/dist/internal.d.cts +6 -74
- package/dist/internal.d.ts +6 -74
- package/dist/internal.js +12 -752
- package/dist/internal.js.map +1 -1
- package/dist/list-DL5DOFcO.d.ts +71 -0
- package/dist/list-hP7hQ9Vk.d.cts +71 -0
- package/dist/loader.cjs +94 -15
- package/dist/loader.cjs.map +1 -1
- package/dist/loader.d.cts +16 -2
- package/dist/loader.d.ts +16 -2
- package/dist/loader.js +87 -8
- package/dist/loader.js.map +1 -1
- package/dist/{props-84UJeWO8.d.cts → props-BpZz0AOq.d.cts} +2 -2
- package/dist/{props-BRhFK50f.d.ts → props-CjLH0JE-.d.ts} +2 -2
- package/dist/{resume-i-A3EFox.d.cts → resume-BJ4oHLi_.d.cts} +3 -1
- package/dist/{resume-CqeQ3v_q.d.ts → resume-CuyJWXP_.d.ts} +3 -1
- package/dist/{scope-DlCBL1Ft.d.cts → scope-BJCtq8hJ.d.cts} +1 -1
- package/dist/{scope-D3DpsfoG.d.ts → scope-jPt5DHRT.d.ts} +1 -1
- package/package.json +8 -1
- package/src/binding.ts +113 -36
- package/src/cycle-guard.ts +3 -3
- package/src/internal/list.ts +7 -0
- package/src/internal.ts +1 -0
- package/src/list-helpers.ts +1 -1
- package/src/loader.ts +119 -9
- package/src/resume.ts +6 -3
- package/src/signal.ts +8 -1
- package/dist/chunk-4LCHQ7U4.js.map +0 -1
- package/dist/chunk-7YQK3XKY.js.map +0 -1
- package/dist/chunk-FSCBL7RI.cjs.map +0 -1
- package/dist/chunk-PRF4QG73.cjs.map +0 -1
- /package/dist/{chunk-TLDT76RV.js.map → chunk-FVX77557.js.map} +0 -0
- /package/dist/{chunk-WRU3IZOA.js.map → chunk-JVYH76ZX.js.map} +0 -0
package/src/internal.ts
CHANGED
package/src/list-helpers.ts
CHANGED
|
@@ -30,7 +30,7 @@ export { insertNodesBefore, removeNodes, toNodeArray }
|
|
|
30
30
|
const isDev =
|
|
31
31
|
typeof __DEV__ !== 'undefined'
|
|
32
32
|
? __DEV__
|
|
33
|
-
: typeof process
|
|
33
|
+
: typeof process !== 'undefined' && process.env?.NODE_ENV !== 'production'
|
|
34
34
|
|
|
35
35
|
const isShadowRoot = (node: Node): node is ShadowRoot =>
|
|
36
36
|
typeof ShadowRoot !== 'undefined' && node instanceof ShadowRoot
|
package/src/loader.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { DelegatedEvents } from './constants'
|
|
2
2
|
import {
|
|
3
|
+
FICT_SSR_SNAPSHOT_SCHEMA_VERSION,
|
|
4
|
+
type SSRState,
|
|
3
5
|
__fictEnableResumable,
|
|
4
6
|
__fictEnsureScope,
|
|
5
7
|
__fictGetResume,
|
|
@@ -67,6 +69,11 @@ export interface ResumableLoaderOptions {
|
|
|
67
69
|
document?: Document
|
|
68
70
|
snapshotScriptId?: string
|
|
69
71
|
events?: string[]
|
|
72
|
+
/**
|
|
73
|
+
* Receives structured snapshot/resume issues detected by the loader.
|
|
74
|
+
* Useful for telemetry and fail-safe fallback orchestration.
|
|
75
|
+
*/
|
|
76
|
+
onSnapshotIssue?: (issue: SnapshotIssue) => void
|
|
70
77
|
/**
|
|
71
78
|
* Prefetch strategy configuration.
|
|
72
79
|
* Set to false to disable all prefetching.
|
|
@@ -75,6 +82,21 @@ export interface ResumableLoaderOptions {
|
|
|
75
82
|
prefetch?: PrefetchStrategy | false
|
|
76
83
|
}
|
|
77
84
|
|
|
85
|
+
export type SnapshotIssueCode =
|
|
86
|
+
| 'snapshot_parse_error'
|
|
87
|
+
| 'snapshot_invalid_shape'
|
|
88
|
+
| 'snapshot_unsupported_version'
|
|
89
|
+
| 'scope_snapshot_missing'
|
|
90
|
+
|
|
91
|
+
export interface SnapshotIssue {
|
|
92
|
+
code: SnapshotIssueCode
|
|
93
|
+
message: string
|
|
94
|
+
source: string
|
|
95
|
+
expectedVersion: number
|
|
96
|
+
actualVersion?: number
|
|
97
|
+
scopeId?: string
|
|
98
|
+
}
|
|
99
|
+
|
|
78
100
|
// ============================================================================
|
|
79
101
|
// State
|
|
80
102
|
// ============================================================================
|
|
@@ -85,6 +107,8 @@ let prefetchCleanup: (() => void) | null = null
|
|
|
85
107
|
let eventListenerCleanup: (() => void) | null = null
|
|
86
108
|
let snapshotObserver: MutationObserver | null = null
|
|
87
109
|
const processedSnapshots = new Set<HTMLScriptElement>()
|
|
110
|
+
let snapshotIssueHandler: ((issue: SnapshotIssue) => void) | null = null
|
|
111
|
+
const emittedIssueKeys = new Set<string>()
|
|
88
112
|
|
|
89
113
|
/**
|
|
90
114
|
* Reset the hydrated scopes set. Useful for testing.
|
|
@@ -130,11 +154,14 @@ export function cleanupEventListeners(): void {
|
|
|
130
154
|
export function installResumableLoader(options: ResumableLoaderOptions = {}): void {
|
|
131
155
|
const doc = options.document ?? window.document
|
|
132
156
|
const scriptId = options.snapshotScriptId ?? '__FICT_SNAPSHOT__'
|
|
157
|
+
snapshotIssueHandler = options.onSnapshotIssue ?? null
|
|
133
158
|
|
|
134
159
|
// Reset hydrated scopes for fresh loader installation
|
|
135
160
|
hydratedScopes.clear()
|
|
136
161
|
prefetchedUrls.clear()
|
|
137
162
|
processedSnapshots.clear()
|
|
163
|
+
emittedIssueKeys.clear()
|
|
164
|
+
__fictSetSSRState(null)
|
|
138
165
|
|
|
139
166
|
// Clean up previous event listeners
|
|
140
167
|
if (eventListenerCleanup) {
|
|
@@ -155,11 +182,9 @@ export function installResumableLoader(options: ResumableLoaderOptions = {}): vo
|
|
|
155
182
|
|
|
156
183
|
const snapshotEl = doc.getElementById(scriptId)
|
|
157
184
|
if (snapshotEl?.textContent) {
|
|
158
|
-
|
|
159
|
-
|
|
185
|
+
const state = parseSnapshotText(snapshotEl.textContent, `#${scriptId}`)
|
|
186
|
+
if (state) {
|
|
160
187
|
__fictSetSSRState(state)
|
|
161
|
-
} catch {
|
|
162
|
-
// Ignore parse errors
|
|
163
188
|
}
|
|
164
189
|
}
|
|
165
190
|
|
|
@@ -224,11 +249,88 @@ function parseSnapshotScript(script: HTMLScriptElement): void {
|
|
|
224
249
|
processedSnapshots.add(script)
|
|
225
250
|
const text = script.textContent
|
|
226
251
|
if (!text) return
|
|
227
|
-
|
|
228
|
-
|
|
252
|
+
const source = script.id ? `#${script.id}` : '<script[data-fict-snapshot]>'
|
|
253
|
+
const state = parseSnapshotText(text, source)
|
|
254
|
+
if (state) {
|
|
229
255
|
__fictMergeSSRState(state)
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
function parseSnapshotText(text: string, source: string): SSRState | null {
|
|
260
|
+
let parsed: unknown
|
|
261
|
+
try {
|
|
262
|
+
parsed = JSON.parse(text)
|
|
230
263
|
} catch {
|
|
231
|
-
|
|
264
|
+
emitSnapshotIssue({
|
|
265
|
+
code: 'snapshot_parse_error',
|
|
266
|
+
message: '[fict/loader] Failed to parse SSR snapshot JSON.',
|
|
267
|
+
source,
|
|
268
|
+
expectedVersion: FICT_SSR_SNAPSHOT_SCHEMA_VERSION,
|
|
269
|
+
})
|
|
270
|
+
return null
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
return normalizeSnapshotState(parsed, source)
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
function normalizeSnapshotState(value: unknown, source: string): SSRState | null {
|
|
277
|
+
if (!isRecord(value)) {
|
|
278
|
+
emitSnapshotIssue({
|
|
279
|
+
code: 'snapshot_invalid_shape',
|
|
280
|
+
message: '[fict/loader] Snapshot payload must be an object.',
|
|
281
|
+
source,
|
|
282
|
+
expectedVersion: FICT_SSR_SNAPSHOT_SCHEMA_VERSION,
|
|
283
|
+
})
|
|
284
|
+
return null
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
const rawVersion = value.v
|
|
288
|
+
const version = rawVersion === undefined ? FICT_SSR_SNAPSHOT_SCHEMA_VERSION : rawVersion
|
|
289
|
+
if (!Number.isInteger(version) || version !== FICT_SSR_SNAPSHOT_SCHEMA_VERSION) {
|
|
290
|
+
const versionIssue: SnapshotIssue = {
|
|
291
|
+
code: 'snapshot_unsupported_version',
|
|
292
|
+
message: `[fict/loader] Snapshot schema version ${String(version)} is not supported by this runtime.`,
|
|
293
|
+
source,
|
|
294
|
+
expectedVersion: FICT_SSR_SNAPSHOT_SCHEMA_VERSION,
|
|
295
|
+
}
|
|
296
|
+
if (typeof version === 'number') {
|
|
297
|
+
versionIssue.actualVersion = version
|
|
298
|
+
}
|
|
299
|
+
emitSnapshotIssue({
|
|
300
|
+
...versionIssue,
|
|
301
|
+
})
|
|
302
|
+
return null
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
const scopes = value.scopes
|
|
306
|
+
if (!isRecord(scopes)) {
|
|
307
|
+
emitSnapshotIssue({
|
|
308
|
+
code: 'snapshot_invalid_shape',
|
|
309
|
+
message: '[fict/loader] Snapshot payload is missing a valid `scopes` object.',
|
|
310
|
+
source,
|
|
311
|
+
expectedVersion: FICT_SSR_SNAPSHOT_SCHEMA_VERSION,
|
|
312
|
+
})
|
|
313
|
+
return null
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
return { v: FICT_SSR_SNAPSHOT_SCHEMA_VERSION, scopes: scopes as SSRState['scopes'] }
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
320
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value)
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
function emitSnapshotIssue(issue: SnapshotIssue): void {
|
|
324
|
+
const key =
|
|
325
|
+
`${issue.code}|${issue.source}|${issue.scopeId ?? ''}|` +
|
|
326
|
+
`${issue.actualVersion ?? ''}|${issue.expectedVersion}`
|
|
327
|
+
if (emittedIssueKeys.has(key)) return
|
|
328
|
+
emittedIssueKeys.add(key)
|
|
329
|
+
|
|
330
|
+
snapshotIssueHandler?.(issue)
|
|
331
|
+
|
|
332
|
+
if (typeof console !== 'undefined' && typeof console.warn === 'function') {
|
|
333
|
+
console.warn(issue.message)
|
|
232
334
|
}
|
|
233
335
|
}
|
|
234
336
|
|
|
@@ -425,9 +527,17 @@ async function handleResumableEventAsync(event: Event): Promise<void> {
|
|
|
425
527
|
if (!scopeId) continue
|
|
426
528
|
|
|
427
529
|
const snapshot = __fictGetSSRScope(scopeId)
|
|
428
|
-
if (snapshot) {
|
|
429
|
-
|
|
530
|
+
if (!snapshot) {
|
|
531
|
+
emitSnapshotIssue({
|
|
532
|
+
code: 'scope_snapshot_missing',
|
|
533
|
+
message: `[fict/loader] Missing scope snapshot for ${scopeId}; skipping resumable handler execution.`,
|
|
534
|
+
source: 'event',
|
|
535
|
+
expectedVersion: FICT_SSR_SNAPSHOT_SCHEMA_VERSION,
|
|
536
|
+
scopeId,
|
|
537
|
+
})
|
|
538
|
+
return
|
|
430
539
|
}
|
|
540
|
+
__fictEnsureScope(scopeId, host, snapshot)
|
|
431
541
|
|
|
432
542
|
const { url, exportName } = parseQrl(qrl)
|
|
433
543
|
|
package/src/resume.ts
CHANGED
|
@@ -36,7 +36,10 @@ export interface ScopeSnapshot {
|
|
|
36
36
|
vars?: Record<string, number>
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
+
export const FICT_SSR_SNAPSHOT_SCHEMA_VERSION = 1
|
|
40
|
+
|
|
39
41
|
export interface SSRState {
|
|
42
|
+
v: number
|
|
40
43
|
scopes: Record<string, ScopeSnapshot>
|
|
41
44
|
}
|
|
42
45
|
|
|
@@ -173,7 +176,7 @@ export function __fictSerializeSSRState(): SSRState {
|
|
|
173
176
|
scopes[id] = snapshot
|
|
174
177
|
}
|
|
175
178
|
|
|
176
|
-
return { scopes }
|
|
179
|
+
return { v: FICT_SSR_SNAPSHOT_SCHEMA_VERSION, scopes }
|
|
177
180
|
}
|
|
178
181
|
|
|
179
182
|
export function __fictSerializeSSRStateForScopes(scopeIds: Iterable<string>): SSRState {
|
|
@@ -198,7 +201,7 @@ export function __fictSerializeSSRStateForScopes(scopeIds: Iterable<string>): SS
|
|
|
198
201
|
scopes[id] = snapshot
|
|
199
202
|
}
|
|
200
203
|
|
|
201
|
-
return { scopes }
|
|
204
|
+
return { v: FICT_SSR_SNAPSHOT_SCHEMA_VERSION, scopes }
|
|
202
205
|
}
|
|
203
206
|
|
|
204
207
|
export function __fictSetSSRState(state: SSRState | null): void {
|
|
@@ -211,7 +214,7 @@ export function __fictSetSSRState(state: SSRState | null): void {
|
|
|
211
214
|
export function __fictMergeSSRState(state: SSRState | null): void {
|
|
212
215
|
if (!state) return
|
|
213
216
|
if (!snapshotState) {
|
|
214
|
-
snapshotState = { scopes: { ...state.scopes } }
|
|
217
|
+
snapshotState = { v: state.v, scopes: { ...state.scopes } }
|
|
215
218
|
return
|
|
216
219
|
}
|
|
217
220
|
Object.assign(snapshotState.scopes, state.scopes)
|
package/src/signal.ts
CHANGED
|
@@ -1595,7 +1595,14 @@ let effectRunDevtools: (node: EffectNode) => void = () => {}
|
|
|
1595
1595
|
let trackDependencyDevtools: (dep: ReactiveNode, sub: ReactiveNode) => void = () => {}
|
|
1596
1596
|
let untrackDependencyDevtools: (dep: ReactiveNode, sub: ReactiveNode) => void = () => {}
|
|
1597
1597
|
|
|
1598
|
-
if (isDev)
|
|
1598
|
+
// Keep this as a direct conditional expression (instead of `if (isDev)`) so
|
|
1599
|
+
// bundlers can eliminate the entire devtools setup block when `__DEV__` is
|
|
1600
|
+
// defined as `false` in production builds.
|
|
1601
|
+
if (
|
|
1602
|
+
typeof __DEV__ !== 'undefined'
|
|
1603
|
+
? __DEV__
|
|
1604
|
+
: typeof process !== 'undefined' && process.env?.NODE_ENV !== 'production'
|
|
1605
|
+
) {
|
|
1599
1606
|
// Unified ID counter for all reactive nodes (signal/computed/effect)
|
|
1600
1607
|
// to prevent ID collisions when storing in single devtools maps
|
|
1601
1608
|
let nextDevtoolsId = 0
|