@askrjs/askr 0.0.31 → 0.0.33
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/dist/benchmark.js +2 -2
- package/dist/data/index.d.ts +43 -0
- package/dist/data/index.d.ts.map +1 -0
- package/dist/data/index.js +293 -0
- package/dist/data/index.js.map +1 -0
- package/dist/index.d.ts +2 -1
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/renderer/children.js +1 -0
- package/dist/renderer/children.js.map +1 -1
- package/dist/renderer/dom.js +1 -1
- package/dist/renderer/dom.js.map +1 -1
- package/dist/renderer/fastpath.js +2 -1
- package/dist/renderer/fastpath.js.map +1 -1
- package/dist/resources/index.d.ts +2 -1
- package/dist/resources/index.js +2 -1
- package/package.json +6 -2
package/dist/benchmark.js
CHANGED
|
@@ -10,8 +10,8 @@ import { BenchmarkTable } from "./bench/components/benchmark-table.js";
|
|
|
10
10
|
installRendererBridge();
|
|
11
11
|
const benchmarkMetadata = {
|
|
12
12
|
packageName: "@askrjs/askr",
|
|
13
|
-
packageVersion: "0.0.
|
|
14
|
-
buildLabel: "0.0.
|
|
13
|
+
packageVersion: "0.0.33",
|
|
14
|
+
buildLabel: "0.0.33-local"
|
|
15
15
|
};
|
|
16
16
|
function getBenchmarkMetadata() {
|
|
17
17
|
return { ...benchmarkMetadata };
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
//#region src/data/index.d.ts
|
|
2
|
+
type QueryConsistency = 'fresh' | 'stale' | 'refreshing' | 'pending-write';
|
|
3
|
+
type Query<T> = {
|
|
4
|
+
data: T | null;
|
|
5
|
+
error: unknown | null;
|
|
6
|
+
loading: boolean;
|
|
7
|
+
refreshing: boolean;
|
|
8
|
+
stale: boolean;
|
|
9
|
+
consistency: QueryConsistency;
|
|
10
|
+
refresh(): Promise<void>;
|
|
11
|
+
};
|
|
12
|
+
type Mutation<TInput, TResult> = {
|
|
13
|
+
status: 'idle' | 'pending' | 'success' | 'error';
|
|
14
|
+
pending: boolean;
|
|
15
|
+
error: unknown | null;
|
|
16
|
+
result: TResult | null;
|
|
17
|
+
execute(input: TInput): Promise<TResult>;
|
|
18
|
+
abort(): void;
|
|
19
|
+
reset(): void;
|
|
20
|
+
};
|
|
21
|
+
type QueryOptions<T> = {
|
|
22
|
+
key: string;
|
|
23
|
+
fetch: (ctx: {
|
|
24
|
+
signal: AbortSignal;
|
|
25
|
+
}) => Promise<T>;
|
|
26
|
+
isConsistent?: (data: T) => boolean;
|
|
27
|
+
reconcile?: (data: T, ctx: {
|
|
28
|
+
key: string;
|
|
29
|
+
}) => Promise<boolean> | boolean;
|
|
30
|
+
};
|
|
31
|
+
type MutationOptions<TInput, TResult> = {
|
|
32
|
+
action: (input: TInput, ctx: {
|
|
33
|
+
signal: AbortSignal;
|
|
34
|
+
}) => Promise<TResult>;
|
|
35
|
+
affects?: (input: TInput, result: TResult) => string[];
|
|
36
|
+
afterSuccess?: 'invalidate';
|
|
37
|
+
};
|
|
38
|
+
declare function createQuery<T>(options: QueryOptions<T>): Query<T>;
|
|
39
|
+
declare function invalidate(prefix: string): void;
|
|
40
|
+
declare function createMutation<TInput, TResult>(options: MutationOptions<TInput, TResult>): Mutation<TInput, TResult>;
|
|
41
|
+
//#endregion
|
|
42
|
+
export { Mutation, Query, QueryConsistency, createMutation, createQuery, invalidate };
|
|
43
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","names":[],"sources":["../../src/data/index.ts"],"mappings":";KASY,gBAAA;AAAA,KAMA,KAAA;EACV,IAAA,EAAM,CAAA;EACN,KAAA;EACA,OAAA;EACA,UAAA;EACA,KAAA;EACA,WAAA,EAAa,gBAAA;EAEb,OAAA,IAAW,OAAA;AAAA;AAAA,KAGD,QAAA;EACV,MAAA;EACA,OAAA;EACA,KAAA;EACA,MAAA,EAAQ,OAAA;EAER,OAAA,CAAQ,KAAA,EAAO,MAAA,GAAS,OAAA,CAAQ,OAAA;EAChC,KAAA;EACA,KAAA;AAAA;AAAA,KAGG,YAAA;EACH,GAAA;EACA,KAAA,GAAQ,GAAA;IAAO,MAAA,EAAQ,WAAA;EAAA,MAAkB,OAAA,CAAQ,CAAA;EACjD,YAAA,IAAgB,IAAA,EAAM,CAAA;EACtB,SAAA,IAAa,IAAA,EAAM,CAAA,EAAG,GAAA;IAAO,GAAA;EAAA,MAAkB,OAAA;AAAA;AAAA,KAG5C,eAAA;EACH,MAAA,GAAS,KAAA,EAAO,MAAA,EAAQ,GAAA;IAAO,MAAA,EAAQ,WAAA;EAAA,MAAkB,OAAA,CAAQ,OAAA;EACjE,OAAA,IAAW,KAAA,EAAO,MAAA,EAAQ,MAAA,EAAQ,OAAA;EAClC,YAAA;AAAA;AAAA,iBA6Wc,WAAA,GAAA,CAAe,OAAA,EAAS,YAAA,CAAa,CAAA,IAAK,KAAA,CAAM,CAAA;AAAA,iBAahD,UAAA,CAAW,MAAA;AAAA,iBAIX,cAAA,iBAAA,CACd,OAAA,EAAS,eAAA,CAAgB,MAAA,EAAQ,OAAA,IAChC,QAAA,CAAS,MAAA,EAAQ,OAAA"}
|
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
import { globalScheduler } from "../runtime/scheduler.js";
|
|
2
|
+
import { markReactivePropsDirtySource, markReadableDerivedSubscribersDirty, notifyReadableReaders, recordReadableRead } from "../runtime/readable.js";
|
|
3
|
+
//#region src/data/index.ts
|
|
4
|
+
function createReadableSource() {
|
|
5
|
+
return (() => void 0);
|
|
6
|
+
}
|
|
7
|
+
function notifySource(source) {
|
|
8
|
+
markReadableDerivedSubscribersDirty(source);
|
|
9
|
+
markReactivePropsDirtySource(source);
|
|
10
|
+
notifyReadableReaders(source);
|
|
11
|
+
}
|
|
12
|
+
function isAbortError(error, signal) {
|
|
13
|
+
return signal.aborted || error instanceof Error && error.name === "AbortError" || typeof DOMException !== "undefined" && error instanceof DOMException && error.name === "AbortError";
|
|
14
|
+
}
|
|
15
|
+
const queryCache = /* @__PURE__ */ new Map();
|
|
16
|
+
function invalidateQueries(prefix, markPendingWrite) {
|
|
17
|
+
for (const [key, query] of queryCache) {
|
|
18
|
+
if (!key.startsWith(prefix)) continue;
|
|
19
|
+
if (markPendingWrite) query.markPendingWrite();
|
|
20
|
+
query.refresh();
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
var QueryCell = class {
|
|
24
|
+
constructor(options) {
|
|
25
|
+
this.source = createReadableSource();
|
|
26
|
+
this.controller = null;
|
|
27
|
+
this.generation = 0;
|
|
28
|
+
this.startQueued = false;
|
|
29
|
+
this.pendingRefresh = null;
|
|
30
|
+
this.reconcileAttemptCount = 0;
|
|
31
|
+
this.state = {
|
|
32
|
+
data: null,
|
|
33
|
+
error: null,
|
|
34
|
+
loading: true,
|
|
35
|
+
refreshing: false,
|
|
36
|
+
stale: false,
|
|
37
|
+
consistency: "fresh"
|
|
38
|
+
};
|
|
39
|
+
this.options = options;
|
|
40
|
+
}
|
|
41
|
+
setOptions(options) {
|
|
42
|
+
this.options = options;
|
|
43
|
+
}
|
|
44
|
+
get data() {
|
|
45
|
+
recordReadableRead(this.source);
|
|
46
|
+
return this.state.data;
|
|
47
|
+
}
|
|
48
|
+
get error() {
|
|
49
|
+
recordReadableRead(this.source);
|
|
50
|
+
return this.state.error;
|
|
51
|
+
}
|
|
52
|
+
get loading() {
|
|
53
|
+
recordReadableRead(this.source);
|
|
54
|
+
return this.state.loading;
|
|
55
|
+
}
|
|
56
|
+
get refreshing() {
|
|
57
|
+
recordReadableRead(this.source);
|
|
58
|
+
return this.state.refreshing;
|
|
59
|
+
}
|
|
60
|
+
get stale() {
|
|
61
|
+
recordReadableRead(this.source);
|
|
62
|
+
return this.state.stale;
|
|
63
|
+
}
|
|
64
|
+
get consistency() {
|
|
65
|
+
recordReadableRead(this.source);
|
|
66
|
+
return this.state.consistency;
|
|
67
|
+
}
|
|
68
|
+
ensureStarted() {
|
|
69
|
+
if (this.state.data !== null || this.pendingRefresh || this.startQueued) return;
|
|
70
|
+
this.queueStart("initial");
|
|
71
|
+
}
|
|
72
|
+
refresh() {
|
|
73
|
+
if (this.pendingRefresh) return this.pendingRefresh;
|
|
74
|
+
this.queueStart("manual");
|
|
75
|
+
return this.pendingRefresh ?? Promise.resolve();
|
|
76
|
+
}
|
|
77
|
+
markPendingWrite() {
|
|
78
|
+
this.setState({
|
|
79
|
+
refreshing: true,
|
|
80
|
+
stale: true,
|
|
81
|
+
consistency: "pending-write"
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
queueStart(reason) {
|
|
85
|
+
this.startQueued = true;
|
|
86
|
+
this.pendingRefresh = new Promise((resolve) => {
|
|
87
|
+
globalScheduler.enqueue(() => {
|
|
88
|
+
this.startQueued = false;
|
|
89
|
+
this.start(reason).finally(() => {
|
|
90
|
+
this.pendingRefresh = null;
|
|
91
|
+
resolve();
|
|
92
|
+
});
|
|
93
|
+
});
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
setState(next) {
|
|
97
|
+
this.state = {
|
|
98
|
+
...this.state,
|
|
99
|
+
...next
|
|
100
|
+
};
|
|
101
|
+
notifySource(this.source);
|
|
102
|
+
}
|
|
103
|
+
async start(reason) {
|
|
104
|
+
this.generation += 1;
|
|
105
|
+
const generation = this.generation;
|
|
106
|
+
this.controller?.abort();
|
|
107
|
+
const controller = new AbortController();
|
|
108
|
+
this.controller = controller;
|
|
109
|
+
const hasData = this.state.data !== null;
|
|
110
|
+
this.setState({
|
|
111
|
+
loading: !hasData,
|
|
112
|
+
refreshing: hasData,
|
|
113
|
+
stale: hasData,
|
|
114
|
+
consistency: reason === "pending-write" ? "pending-write" : hasData ? "refreshing" : "fresh",
|
|
115
|
+
error: null
|
|
116
|
+
});
|
|
117
|
+
let nextData;
|
|
118
|
+
try {
|
|
119
|
+
nextData = await this.options.fetch({ signal: controller.signal });
|
|
120
|
+
} catch (error) {
|
|
121
|
+
if (this.generation !== generation || this.controller !== controller) return;
|
|
122
|
+
if (isAbortError(error, controller.signal)) {
|
|
123
|
+
this.setState({
|
|
124
|
+
loading: false,
|
|
125
|
+
refreshing: false
|
|
126
|
+
});
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
this.setState({
|
|
130
|
+
loading: false,
|
|
131
|
+
refreshing: false,
|
|
132
|
+
stale: true,
|
|
133
|
+
consistency: "stale",
|
|
134
|
+
error
|
|
135
|
+
});
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
if (this.generation !== generation || this.controller !== controller) return;
|
|
139
|
+
if (!(this.options.isConsistent?.(nextData) ?? true)) {
|
|
140
|
+
this.setState({
|
|
141
|
+
data: nextData,
|
|
142
|
+
loading: false,
|
|
143
|
+
refreshing: false,
|
|
144
|
+
stale: true,
|
|
145
|
+
consistency: "stale"
|
|
146
|
+
});
|
|
147
|
+
await this.reconcile(nextData);
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
this.reconcileAttemptCount = 0;
|
|
151
|
+
this.setState({
|
|
152
|
+
data: nextData,
|
|
153
|
+
loading: false,
|
|
154
|
+
refreshing: false,
|
|
155
|
+
stale: false,
|
|
156
|
+
consistency: "fresh",
|
|
157
|
+
error: null
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
async reconcile(data) {
|
|
161
|
+
if (!(this.options.reconcile?.(data, { key: this.options.key }) ?? false)) return;
|
|
162
|
+
this.reconcileAttemptCount += 1;
|
|
163
|
+
if (this.reconcileAttemptCount > 3) {
|
|
164
|
+
this.setState({
|
|
165
|
+
consistency: "stale",
|
|
166
|
+
refreshing: false
|
|
167
|
+
});
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
await new Promise((resolve) => setTimeout(resolve, 25));
|
|
171
|
+
if (this.state.consistency === "fresh") return;
|
|
172
|
+
await this.refresh();
|
|
173
|
+
}
|
|
174
|
+
};
|
|
175
|
+
var MutationCell = class {
|
|
176
|
+
constructor(options) {
|
|
177
|
+
this.source = createReadableSource();
|
|
178
|
+
this.controller = null;
|
|
179
|
+
this.generation = 0;
|
|
180
|
+
this.state = {
|
|
181
|
+
status: "idle",
|
|
182
|
+
error: null,
|
|
183
|
+
result: null
|
|
184
|
+
};
|
|
185
|
+
this.action = options.action;
|
|
186
|
+
this.affects = options.affects;
|
|
187
|
+
this.afterSuccess = options.afterSuccess;
|
|
188
|
+
}
|
|
189
|
+
get status() {
|
|
190
|
+
recordReadableRead(this.source);
|
|
191
|
+
return this.state.status;
|
|
192
|
+
}
|
|
193
|
+
get pending() {
|
|
194
|
+
recordReadableRead(this.source);
|
|
195
|
+
return this.state.status === "pending";
|
|
196
|
+
}
|
|
197
|
+
get error() {
|
|
198
|
+
recordReadableRead(this.source);
|
|
199
|
+
return this.state.error;
|
|
200
|
+
}
|
|
201
|
+
get result() {
|
|
202
|
+
recordReadableRead(this.source);
|
|
203
|
+
return this.state.result;
|
|
204
|
+
}
|
|
205
|
+
setState(next) {
|
|
206
|
+
this.state = {
|
|
207
|
+
...this.state,
|
|
208
|
+
...next
|
|
209
|
+
};
|
|
210
|
+
notifySource(this.source);
|
|
211
|
+
}
|
|
212
|
+
async execute(input) {
|
|
213
|
+
this.generation += 1;
|
|
214
|
+
const generation = this.generation;
|
|
215
|
+
this.controller?.abort();
|
|
216
|
+
const controller = new AbortController();
|
|
217
|
+
this.controller = controller;
|
|
218
|
+
this.setState({
|
|
219
|
+
status: "pending",
|
|
220
|
+
error: null,
|
|
221
|
+
result: null
|
|
222
|
+
});
|
|
223
|
+
let result;
|
|
224
|
+
try {
|
|
225
|
+
result = await this.action(input, { signal: controller.signal });
|
|
226
|
+
} catch (error) {
|
|
227
|
+
if (this.generation !== generation || this.controller !== controller) throw error;
|
|
228
|
+
if (isAbortError(error, controller.signal)) {
|
|
229
|
+
this.setState({
|
|
230
|
+
status: "idle",
|
|
231
|
+
error: null
|
|
232
|
+
});
|
|
233
|
+
throw error;
|
|
234
|
+
}
|
|
235
|
+
this.setState({
|
|
236
|
+
status: "error",
|
|
237
|
+
error
|
|
238
|
+
});
|
|
239
|
+
throw error;
|
|
240
|
+
}
|
|
241
|
+
if (this.generation !== generation || this.controller !== controller) return result;
|
|
242
|
+
this.setState({
|
|
243
|
+
status: "success",
|
|
244
|
+
error: null,
|
|
245
|
+
result
|
|
246
|
+
});
|
|
247
|
+
if (this.afterSuccess === "invalidate") {
|
|
248
|
+
const prefixes = this.affects?.(input, result) ?? [];
|
|
249
|
+
for (const prefix of new Set(prefixes)) invalidateQueries(prefix, true);
|
|
250
|
+
}
|
|
251
|
+
return result;
|
|
252
|
+
}
|
|
253
|
+
abort() {
|
|
254
|
+
this.generation += 1;
|
|
255
|
+
this.controller?.abort();
|
|
256
|
+
this.controller = null;
|
|
257
|
+
this.setState({
|
|
258
|
+
status: "idle",
|
|
259
|
+
error: null
|
|
260
|
+
});
|
|
261
|
+
}
|
|
262
|
+
reset() {
|
|
263
|
+
this.generation += 1;
|
|
264
|
+
this.controller?.abort();
|
|
265
|
+
this.controller = null;
|
|
266
|
+
this.setState({
|
|
267
|
+
status: "idle",
|
|
268
|
+
error: null,
|
|
269
|
+
result: null
|
|
270
|
+
});
|
|
271
|
+
}
|
|
272
|
+
};
|
|
273
|
+
function createQuery(options) {
|
|
274
|
+
const existing = queryCache.get(options.key);
|
|
275
|
+
if (existing) {
|
|
276
|
+
existing.setOptions(options);
|
|
277
|
+
return existing;
|
|
278
|
+
}
|
|
279
|
+
const created = new QueryCell(options);
|
|
280
|
+
queryCache.set(options.key, created);
|
|
281
|
+
created.ensureStarted();
|
|
282
|
+
return created;
|
|
283
|
+
}
|
|
284
|
+
function invalidate(prefix) {
|
|
285
|
+
invalidateQueries(prefix, false);
|
|
286
|
+
}
|
|
287
|
+
function createMutation(options) {
|
|
288
|
+
return new MutationCell(options);
|
|
289
|
+
}
|
|
290
|
+
//#endregion
|
|
291
|
+
export { createMutation, createQuery, invalidate };
|
|
292
|
+
|
|
293
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../../src/data/index.ts"],"sourcesContent":["import { globalScheduler } from '../runtime/scheduler';\nimport {\n markReadableDerivedSubscribersDirty,\n markReactivePropsDirtySource,\n notifyReadableReaders,\n recordReadableRead,\n type ReadableSource,\n} from '../runtime/readable';\n\nexport type QueryConsistency =\n | 'fresh'\n | 'stale'\n | 'refreshing'\n | 'pending-write';\n\nexport type Query<T> = {\n data: T | null;\n error: unknown | null;\n loading: boolean;\n refreshing: boolean;\n stale: boolean;\n consistency: QueryConsistency;\n\n refresh(): Promise<void>;\n};\n\nexport type Mutation<TInput, TResult> = {\n status: 'idle' | 'pending' | 'success' | 'error';\n pending: boolean;\n error: unknown | null;\n result: TResult | null;\n\n execute(input: TInput): Promise<TResult>;\n abort(): void;\n reset(): void;\n};\n\ntype QueryOptions<T> = {\n key: string;\n fetch: (ctx: { signal: AbortSignal }) => Promise<T>;\n isConsistent?: (data: T) => boolean;\n reconcile?: (data: T, ctx: { key: string }) => Promise<boolean> | boolean;\n};\n\ntype MutationOptions<TInput, TResult> = {\n action: (input: TInput, ctx: { signal: AbortSignal }) => Promise<TResult>;\n affects?: (input: TInput, result: TResult) => string[];\n afterSuccess?: 'invalidate';\n};\n\ntype QueryState<T> = {\n data: T | null;\n error: unknown | null;\n loading: boolean;\n refreshing: boolean;\n stale: boolean;\n consistency: QueryConsistency;\n};\n\ntype MutationState<TResult> = {\n status: 'idle' | 'pending' | 'success' | 'error';\n error: unknown | null;\n result: TResult | null;\n};\n\nfunction createReadableSource(): ReadableSource<unknown> {\n return (() => undefined) as ReadableSource<unknown>;\n}\n\nfunction notifySource(source: ReadableSource<unknown>): void {\n markReadableDerivedSubscribersDirty(source);\n markReactivePropsDirtySource(source);\n notifyReadableReaders(source);\n}\n\nfunction isAbortError(error: unknown, signal: AbortSignal): boolean {\n return (\n signal.aborted ||\n (error instanceof Error && error.name === 'AbortError') ||\n (typeof DOMException !== 'undefined' &&\n error instanceof DOMException &&\n error.name === 'AbortError')\n );\n}\n\nconst queryCache = new Map<string, QueryCell<unknown>>();\n\nfunction invalidateQueries(prefix: string, markPendingWrite: boolean): void {\n for (const [key, query] of queryCache) {\n if (!key.startsWith(prefix)) {\n continue;\n }\n\n if (markPendingWrite) {\n query.markPendingWrite();\n }\n\n void query.refresh();\n }\n}\n\nclass QueryCell<T> implements Query<T> {\n private readonly source = createReadableSource();\n private options: QueryOptions<T>;\n private controller: AbortController | null = null;\n private generation = 0;\n private startQueued = false;\n private pendingRefresh: Promise<void> | null = null;\n private reconcileAttemptCount = 0;\n\n private state: QueryState<T> = {\n data: null,\n error: null,\n loading: true,\n refreshing: false,\n stale: false,\n consistency: 'fresh',\n };\n\n constructor(options: QueryOptions<T>) {\n this.options = options;\n }\n\n setOptions(options: QueryOptions<T>): void {\n this.options = options;\n }\n\n get data(): T | null {\n recordReadableRead(this.source);\n return this.state.data;\n }\n\n get error(): unknown | null {\n recordReadableRead(this.source);\n return this.state.error;\n }\n\n get loading(): boolean {\n recordReadableRead(this.source);\n return this.state.loading;\n }\n\n get refreshing(): boolean {\n recordReadableRead(this.source);\n return this.state.refreshing;\n }\n\n get stale(): boolean {\n recordReadableRead(this.source);\n return this.state.stale;\n }\n\n get consistency(): QueryConsistency {\n recordReadableRead(this.source);\n return this.state.consistency;\n }\n\n ensureStarted(): void {\n if (this.state.data !== null || this.pendingRefresh || this.startQueued) {\n return;\n }\n\n this.queueStart('initial');\n }\n\n refresh(): Promise<void> {\n if (this.pendingRefresh) {\n return this.pendingRefresh;\n }\n\n this.queueStart('manual');\n return this.pendingRefresh ?? Promise.resolve();\n }\n\n markPendingWrite(): void {\n this.setState({\n refreshing: true,\n stale: true,\n consistency: 'pending-write',\n });\n }\n\n private queueStart(\n reason: 'initial' | 'manual' | 'invalidate' | 'pending-write'\n ): void {\n this.startQueued = true;\n this.pendingRefresh = new Promise<void>((resolve) => {\n globalScheduler.enqueue(() => {\n this.startQueued = false;\n void this.start(reason).finally(() => {\n this.pendingRefresh = null;\n resolve();\n });\n });\n });\n }\n\n private setState(next: Partial<QueryState<T>>): void {\n this.state = {\n ...this.state,\n ...next,\n };\n notifySource(this.source);\n }\n\n private async start(\n reason: 'initial' | 'manual' | 'invalidate' | 'pending-write'\n ): Promise<void> {\n this.generation += 1;\n const generation = this.generation;\n\n this.controller?.abort();\n const controller = new AbortController();\n this.controller = controller;\n\n const hasData = this.state.data !== null;\n this.setState({\n loading: !hasData,\n refreshing: hasData,\n stale: hasData,\n consistency:\n reason === 'pending-write'\n ? 'pending-write'\n : hasData\n ? 'refreshing'\n : 'fresh',\n error: null,\n });\n\n let nextData: T;\n try {\n nextData = await this.options.fetch({ signal: controller.signal });\n } catch (error) {\n if (this.generation !== generation || this.controller !== controller) {\n return;\n }\n\n if (isAbortError(error, controller.signal)) {\n this.setState({ loading: false, refreshing: false });\n return;\n }\n\n this.setState({\n loading: false,\n refreshing: false,\n stale: true,\n consistency: 'stale',\n error,\n });\n return;\n }\n\n if (this.generation !== generation || this.controller !== controller) {\n return;\n }\n\n const isConsistent = this.options.isConsistent?.(nextData) ?? true;\n if (!isConsistent) {\n this.setState({\n data: nextData,\n loading: false,\n refreshing: false,\n stale: true,\n consistency: 'stale',\n });\n await this.reconcile(nextData);\n return;\n }\n\n this.reconcileAttemptCount = 0;\n this.setState({\n data: nextData,\n loading: false,\n refreshing: false,\n stale: false,\n consistency: 'fresh',\n error: null,\n });\n }\n\n private async reconcile(data: T): Promise<void> {\n const shouldRetry =\n this.options.reconcile?.(data, { key: this.options.key }) ?? false;\n\n if (!shouldRetry) {\n return;\n }\n\n this.reconcileAttemptCount += 1;\n if (this.reconcileAttemptCount > 3) {\n this.setState({ consistency: 'stale', refreshing: false });\n return;\n }\n\n await new Promise<void>((resolve) => setTimeout(resolve, 25));\n if (this.state.consistency === 'fresh') {\n return;\n }\n\n await this.refresh();\n }\n}\n\nclass MutationCell<TInput, TResult> implements Mutation<TInput, TResult> {\n private readonly source = createReadableSource();\n private readonly action: MutationOptions<TInput, TResult>['action'];\n private readonly affects?: MutationOptions<TInput, TResult>['affects'];\n private readonly afterSuccess?: MutationOptions<\n TInput,\n TResult\n >['afterSuccess'];\n private controller: AbortController | null = null;\n private generation = 0;\n\n private state: MutationState<TResult> = {\n status: 'idle',\n error: null,\n result: null,\n };\n\n constructor(options: MutationOptions<TInput, TResult>) {\n this.action = options.action;\n this.affects = options.affects;\n this.afterSuccess = options.afterSuccess;\n }\n\n get status(): 'idle' | 'pending' | 'success' | 'error' {\n recordReadableRead(this.source);\n return this.state.status;\n }\n\n get pending(): boolean {\n recordReadableRead(this.source);\n return this.state.status === 'pending';\n }\n\n get error(): unknown | null {\n recordReadableRead(this.source);\n return this.state.error;\n }\n\n get result(): TResult | null {\n recordReadableRead(this.source);\n return this.state.result;\n }\n\n private setState(next: Partial<MutationState<TResult>>): void {\n this.state = {\n ...this.state,\n ...next,\n };\n notifySource(this.source);\n }\n\n async execute(input: TInput): Promise<TResult> {\n this.generation += 1;\n const generation = this.generation;\n\n this.controller?.abort();\n const controller = new AbortController();\n this.controller = controller;\n\n this.setState({ status: 'pending', error: null, result: null });\n\n let result: TResult;\n try {\n result = await this.action(input, { signal: controller.signal });\n } catch (error) {\n if (this.generation !== generation || this.controller !== controller) {\n throw error;\n }\n\n if (isAbortError(error, controller.signal)) {\n this.setState({ status: 'idle', error: null });\n throw error;\n }\n\n this.setState({ status: 'error', error });\n throw error;\n }\n\n if (this.generation !== generation || this.controller !== controller) {\n return result;\n }\n\n this.setState({ status: 'success', error: null, result });\n\n if (this.afterSuccess === 'invalidate') {\n const prefixes = this.affects?.(input, result) ?? [];\n for (const prefix of new Set(prefixes)) {\n invalidateQueries(prefix, true);\n }\n }\n\n return result;\n }\n\n abort(): void {\n this.generation += 1;\n this.controller?.abort();\n this.controller = null;\n this.setState({ status: 'idle', error: null });\n }\n\n reset(): void {\n this.generation += 1;\n this.controller?.abort();\n this.controller = null;\n this.setState({ status: 'idle', error: null, result: null });\n }\n}\n\nexport function createQuery<T>(options: QueryOptions<T>): Query<T> {\n const existing = queryCache.get(options.key) as QueryCell<T> | undefined;\n if (existing) {\n existing.setOptions(options);\n return existing;\n }\n\n const created = new QueryCell(options);\n queryCache.set(options.key, created as QueryCell<unknown>);\n created.ensureStarted();\n return created;\n}\n\nexport function invalidate(prefix: string): void {\n invalidateQueries(prefix, false);\n}\n\nexport function createMutation<TInput, TResult>(\n options: MutationOptions<TInput, TResult>\n): Mutation<TInput, TResult> {\n return new MutationCell(options);\n}\n"],"mappings":"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"}
|
package/dist/index.d.ts
CHANGED
|
@@ -15,8 +15,9 @@ import { DefaultPortal, Portal, PortalProps, definePortal } from "./foundations/
|
|
|
15
15
|
import { Derived, derive } from "./runtime/derive.js";
|
|
16
16
|
import { Selector, selector } from "./runtime/selector.js";
|
|
17
17
|
import { ResourceResult, resource } from "./runtime/operations.js";
|
|
18
|
+
import { Mutation, Query, QueryConsistency, createMutation, createQuery, invalidate } from "./data/index.js";
|
|
18
19
|
import { currentRoute, fallback, group, lazy, registerRoutes, resolveRouteRequest, route } from "./router/route.js";
|
|
19
20
|
import { allow, deny, forbidden, notFound, redirect, requireAuth, requirePermission, requireRole, unauthorized } from "./router/policy.js";
|
|
20
21
|
import { Link, LinkProps } from "./components/link.js";
|
|
21
22
|
import { ErrorBoundary, ErrorBoundaryFallbackRender, ErrorBoundaryProps } from "./components/error-boundary.js";
|
|
22
|
-
export { type AccessDecision, type AccessDenyDecision, type AccessRedirectDecision, Case, type CaseProps, type Context, DefaultPortal, type Derived, ErrorBoundary, type ErrorBoundaryFallbackRender, type ErrorBoundaryProps, For, type ForProps, Fragment, type GroupHelperOptions, type HistoryScrollBehavior, type HydrateSPAConfig, type IslandConfig, type IslandsConfig, Link, type LinkProps, Match, type MatchProps, type NavigateOptions, type NavigationScrollBehavior, Portal, type PortalProps, type Props, type RegisterRoutesOptions, type ResourceResult, type RouteAuthMode, type RouteAuthOptions, type RouteAuthResolver, type RouteAuthState, type RouteComponent, type RouteContext, type RouteDefinition, type RouteManifest, type RouteMatch, type RouteMode, type RouteOptions, type RoutePolicy, type RouteRecord, type RouteRenderResult, type RouteRequestOptions, type RouteRequestResult, type RouteSnapshot, type SPAConfig, type ScrollRestorationOptions, type Selector, Show, type ShowProps, Slot, type SlotProps, type State, allow, cleanupApp, createIsland, createIslands, createSPA, currentRoute, defineContext, definePortal, deny, derive, fallback, forbidden, getSignal, group, hasApp, hydrateSPA, jsx, jsxs, lazy, navigate, notFound, readContext, redirect, registerRoutes, requireAuth, requirePermission, requireRole, resolveRouteRequest, resource, route, selector, state, unauthorized };
|
|
23
|
+
export { type AccessDecision, type AccessDenyDecision, type AccessRedirectDecision, Case, type CaseProps, type Context, DefaultPortal, type Derived, ErrorBoundary, type ErrorBoundaryFallbackRender, type ErrorBoundaryProps, For, type ForProps, Fragment, type GroupHelperOptions, type HistoryScrollBehavior, type HydrateSPAConfig, type IslandConfig, type IslandsConfig, Link, type LinkProps, Match, type MatchProps, type Mutation, type NavigateOptions, type NavigationScrollBehavior, Portal, type PortalProps, type Props, type Query, type QueryConsistency, type RegisterRoutesOptions, type ResourceResult, type RouteAuthMode, type RouteAuthOptions, type RouteAuthResolver, type RouteAuthState, type RouteComponent, type RouteContext, type RouteDefinition, type RouteManifest, type RouteMatch, type RouteMode, type RouteOptions, type RoutePolicy, type RouteRecord, type RouteRenderResult, type RouteRequestOptions, type RouteRequestResult, type RouteSnapshot, type SPAConfig, type ScrollRestorationOptions, type Selector, Show, type ShowProps, Slot, type SlotProps, type State, allow, cleanupApp, createIsland, createIslands, createMutation, createQuery, createSPA, currentRoute, defineContext, definePortal, deny, derive, fallback, forbidden, getSignal, group, hasApp, hydrateSPA, invalidate, jsx, jsxs, lazy, navigate, notFound, readContext, redirect, registerRoutes, requireAuth, requirePermission, requireRole, resolveRouteRequest, resource, route, selector, state, unauthorized };
|
package/dist/index.js
CHANGED
|
@@ -8,6 +8,7 @@ import { state } from "./runtime/state.js";
|
|
|
8
8
|
import { derive } from "./runtime/derive.js";
|
|
9
9
|
import { selector } from "./runtime/selector.js";
|
|
10
10
|
import { resource } from "./runtime/operations.js";
|
|
11
|
+
import { createMutation, createQuery, invalidate } from "./data/index.js";
|
|
11
12
|
import { allow, deny, forbidden, notFound, redirect, requireAuth, requirePermission, requireRole, unauthorized } from "./router/policy.js";
|
|
12
13
|
import { currentRoute, fallback, group, lazy, registerRoutes, resolveRouteRequest, route } from "./router/route.js";
|
|
13
14
|
import { DefaultPortal, Portal, definePortal } from "./foundations/structures/portal.js";
|
|
@@ -30,6 +31,6 @@ import "./foundations/structures.js";
|
|
|
30
31
|
*/
|
|
31
32
|
installRendererBridge();
|
|
32
33
|
//#endregion
|
|
33
|
-
export { Case, DefaultPortal, ErrorBoundary, For, Fragment, Link, Match, Portal, Show, Slot, allow, cleanupApp, createIsland, createIslands, createSPA, currentRoute, defineContext, definePortal, deny, derive, fallback, forbidden, getSignal, group, hasApp, hydrateSPA, jsx, jsxs, lazy, navigate, notFound, readContext, redirect, registerRoutes, requireAuth, requirePermission, requireRole, resolveRouteRequest, resource, route, selector, state, unauthorized };
|
|
34
|
+
export { Case, DefaultPortal, ErrorBoundary, For, Fragment, Link, Match, Portal, Show, Slot, allow, cleanupApp, createIsland, createIslands, createMutation, createQuery, createSPA, currentRoute, defineContext, definePortal, deny, derive, fallback, forbidden, getSignal, group, hasApp, hydrateSPA, invalidate, jsx, jsxs, lazy, navigate, notFound, readContext, redirect, registerRoutes, requireAuth, requirePermission, requireRole, resolveRouteRequest, resource, route, selector, state, unauthorized };
|
|
34
35
|
|
|
35
36
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":[],"sources":["../src/index.ts"],"sourcesContent":["/**\n * Askr: Actor-backed deterministic UI framework\n *\n * Public API surface — only users should import from here\n */\n\nimport { installRendererBridge } from './renderer';\n\ninstallRendererBridge();\n\n// Runtime primitives\nexport { state } from './runtime/state';\nexport type { State } from './runtime/state';\nexport { derive } from './runtime/derive';\nexport type { Derived } from './runtime/derive';\nexport { getSignal } from './runtime/component';\nexport { selector } from './runtime/selector';\nexport type { Selector } from './runtime/selector';\n\n// Context\nexport { defineContext, readContext } from './runtime/context';\nexport type { Context } from './runtime/context';\n\n// Resources\nexport { resource } from './runtime/operations';\nexport type { ResourceResult } from './runtime/operations';\n\n// App bootstrap\nexport {\n createIsland,\n createIslands,\n createSPA,\n hydrateSPA,\n cleanupApp,\n hasApp,\n} from './boot';\nexport type {\n IslandConfig,\n IslandsConfig,\n SPAConfig,\n HydrateSPAConfig,\n} from './boot';\n\n// Routing\nexport {\n registerRoutes,\n route,\n currentRoute,\n group,\n fallback,\n lazy,\n allow,\n redirect,\n deny,\n unauthorized,\n forbidden,\n notFound,\n requireAuth,\n requireRole,\n requirePermission,\n resolveRouteRequest,\n type RouteSnapshot,\n type RouteMatch,\n type RouteComponent,\n type RouteMode,\n type RouteAuthMode,\n type RouteContext,\n type RoutePolicy,\n type RouteOptions,\n type RouteRecord,\n type RouteManifest,\n type AccessDecision,\n type AccessDenyDecision,\n type AccessRedirectDecision,\n type GroupHelperOptions,\n type RegisterRoutesOptions,\n type RouteDefinition,\n type RouteAuthOptions,\n type RouteAuthResolver,\n type RouteAuthState,\n type RouteRenderResult,\n type RouteRequestOptions,\n type RouteRequestResult,\n} from './router';\nexport { navigate } from './router/navigate';\nexport type {\n NavigateOptions,\n NavigationScrollBehavior,\n HistoryScrollBehavior,\n ScrollRestorationOptions,\n} from './router/navigate';\n\n// Components\nexport { Link } from './components/link';\nexport type { LinkProps } from './components/link';\nexport { ErrorBoundary } from './components/error-boundary';\nexport type {\n ErrorBoundaryProps,\n ErrorBoundaryFallbackRender,\n} from './components/error-boundary';\nexport { Case, For, Match, Show } from './control';\nexport type { CaseProps, ForProps, MatchProps, ShowProps } from './control';\nexport {\n Slot,\n definePortal,\n DefaultPortal,\n Portal,\n} from './foundations/structures';\nexport type { SlotProps, PortalProps } from './foundations/structures';\n\n// Re-export JSX runtime for tsconfig jsxImportSource\nexport { jsx, jsxs, Fragment } from './jsx-runtime';\n\n// Public types\nexport type { Props } from './common/props';\n"],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../src/index.ts"],"sourcesContent":["/**\n * Askr: Actor-backed deterministic UI framework\n *\n * Public API surface — only users should import from here\n */\n\nimport { installRendererBridge } from './renderer';\n\ninstallRendererBridge();\n\n// Runtime primitives\nexport { state } from './runtime/state';\nexport type { State } from './runtime/state';\nexport { derive } from './runtime/derive';\nexport type { Derived } from './runtime/derive';\nexport { getSignal } from './runtime/component';\nexport { selector } from './runtime/selector';\nexport type { Selector } from './runtime/selector';\n\n// Context\nexport { defineContext, readContext } from './runtime/context';\nexport type { Context } from './runtime/context';\n\n// Resources\nexport { resource } from './runtime/operations';\nexport type { ResourceResult } from './runtime/operations';\n\n// Data\nexport { createQuery, createMutation, invalidate } from './data';\nexport type { Query, Mutation, QueryConsistency } from './data';\n\n// App bootstrap\nexport {\n createIsland,\n createIslands,\n createSPA,\n hydrateSPA,\n cleanupApp,\n hasApp,\n} from './boot';\nexport type {\n IslandConfig,\n IslandsConfig,\n SPAConfig,\n HydrateSPAConfig,\n} from './boot';\n\n// Routing\nexport {\n registerRoutes,\n route,\n currentRoute,\n group,\n fallback,\n lazy,\n allow,\n redirect,\n deny,\n unauthorized,\n forbidden,\n notFound,\n requireAuth,\n requireRole,\n requirePermission,\n resolveRouteRequest,\n type RouteSnapshot,\n type RouteMatch,\n type RouteComponent,\n type RouteMode,\n type RouteAuthMode,\n type RouteContext,\n type RoutePolicy,\n type RouteOptions,\n type RouteRecord,\n type RouteManifest,\n type AccessDecision,\n type AccessDenyDecision,\n type AccessRedirectDecision,\n type GroupHelperOptions,\n type RegisterRoutesOptions,\n type RouteDefinition,\n type RouteAuthOptions,\n type RouteAuthResolver,\n type RouteAuthState,\n type RouteRenderResult,\n type RouteRequestOptions,\n type RouteRequestResult,\n} from './router';\nexport { navigate } from './router/navigate';\nexport type {\n NavigateOptions,\n NavigationScrollBehavior,\n HistoryScrollBehavior,\n ScrollRestorationOptions,\n} from './router/navigate';\n\n// Components\nexport { Link } from './components/link';\nexport type { LinkProps } from './components/link';\nexport { ErrorBoundary } from './components/error-boundary';\nexport type {\n ErrorBoundaryProps,\n ErrorBoundaryFallbackRender,\n} from './components/error-boundary';\nexport { Case, For, Match, Show } from './control';\nexport type { CaseProps, ForProps, MatchProps, ShowProps } from './control';\nexport {\n Slot,\n definePortal,\n DefaultPortal,\n Portal,\n} from './foundations/structures';\nexport type { SlotProps, PortalProps } from './foundations/structures';\n\n// Re-export JSX runtime for tsconfig jsxImportSource\nexport { jsx, jsxs, Fragment } from './jsx-runtime';\n\n// Public types\nexport type { Props } from './common/props';\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAQA,uBAAuB"}
|
|
@@ -94,6 +94,7 @@ function tryUpdateTwoChildTextPattern(parentEl, vnode) {
|
|
|
94
94
|
const firstElement = parentEl.children[0];
|
|
95
95
|
const secondElement = parentEl.children[1];
|
|
96
96
|
if (!firstElement || !secondElement) return false;
|
|
97
|
+
if (parentEl.children.length !== 2 || parentEl.childNodes.length !== 2) return false;
|
|
97
98
|
if (!tagsEqualIgnoreCase(firstElement.tagName, firstChild.type)) return false;
|
|
98
99
|
if (!tagsEqualIgnoreCase(secondElement.tagName, secondChild.type)) return false;
|
|
99
100
|
const firstText = firstChild.children || firstChild.props?.children;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"children.js","names":[],"sources":["../../src/renderer/children.ts"],"sourcesContent":["import { setDevValue, incDevCounter } from '../runtime/dev-namespace';\nimport { getRuntimeEnv } from './env';\nimport { keyedElements } from './keyed';\nimport { teardownNodeSubtree } from './cleanup';\nimport { createDOMNode, updateElementFromVnode } from './dom';\nimport { _isDOMElement, type DOMElement, type VNode } from './types';\nimport {\n hasNonTrivialProps,\n logFastPathDebug,\n now,\n recordDOMReplace,\n recordFastPathStats,\n tagNamesEqualIgnoreCase,\n} from './utils';\n\nfunction upperCommonTagName(tag: string): string | null {\n switch (tag) {\n case 'div':\n return 'DIV';\n case 'span':\n return 'SPAN';\n case 'p':\n return 'P';\n case 'a':\n return 'A';\n case 'button':\n return 'BUTTON';\n case 'input':\n return 'INPUT';\n case 'ul':\n return 'UL';\n case 'ol':\n return 'OL';\n case 'li':\n return 'LI';\n default:\n return null;\n }\n}\n\nfunction tagsEqualIgnoreCase(\n elementTagName: string,\n vnodeType: string\n): boolean {\n if (elementTagName === vnodeType) return true;\n\n const upperCommon = upperCommonTagName(vnodeType);\n if (upperCommon !== null && elementTagName === upperCommon) return true;\n\n return tagNamesEqualIgnoreCase(elementTagName, vnodeType);\n}\n\nexport function performBulkPositionalKeyedTextUpdate(\n parent: Element,\n keyedVnodes: Array<{ key: string | number; vnode: VNode }>\n) {\n const total = keyedVnodes.length;\n let reused = 0;\n let updatedKeys = 0;\n const start = now();\n const env = getRuntimeEnv();\n const debugFastPath =\n env.ASKR_FASTPATH_DEBUG === '1' || env.ASKR_FASTPATH_DEBUG === 'true';\n\n for (let index = 0; index < total; index += 1) {\n const { key, vnode } = keyedVnodes[index];\n const child = parent.children[index] as Element | undefined;\n\n if (\n child &&\n _isDOMElement(vnode) &&\n typeof (vnode as DOMElement).type === 'string'\n ) {\n const vnodeType = (vnode as DOMElement).type as string;\n\n if (tagsEqualIgnoreCase(child.tagName, vnodeType)) {\n const children =\n (vnode as DOMElement).children ||\n (vnode as DOMElement).props?.children;\n\n if (debugFastPath) {\n logFastPathDebug('positional idx', index, {\n chTag: child.tagName,\n vnodeType,\n chChildNodes: child.childNodes.length,\n childrenType: Array.isArray(children) ? 'array' : typeof children,\n });\n }\n\n updateTextContent(child, children, vnode as DOMElement);\n setDataKey(child, key, () => (updatedKeys += 1));\n reused += 1;\n continue;\n }\n\n if (debugFastPath) {\n logFastPathDebug('positional tag mismatch', index, {\n chTag: child.tagName,\n vnodeType,\n });\n }\n } else if (debugFastPath) {\n logFastPathDebug('positional missing or invalid', index, { ch: !!child });\n }\n\n replaceNodeAtPosition(parent, index, vnode);\n }\n\n const elapsed = now() - start;\n updateKeyedElementsMap(parent, keyedVnodes);\n\n const stats = { n: total, reused, updatedKeys, t: elapsed } as const;\n recordFastPathStats(stats, 'bulkKeyedPositionalHits');\n\n return stats;\n}\n\nfunction updateTextContent(\n el: Element,\n children: unknown,\n vnode: DOMElement\n): void {\n if (typeof children === 'string' || typeof children === 'number') {\n setTextNodeData(el, String(children));\n if (vnode.props && hasNonTrivialProps(vnode.props)) {\n updateElementFromVnode(el, vnode, false);\n }\n return;\n }\n\n if (\n Array.isArray(children) &&\n children.length === 1 &&\n (typeof children[0] === 'string' || typeof children[0] === 'number')\n ) {\n setTextNodeData(el, String(children[0]));\n if (vnode.props && hasNonTrivialProps(vnode.props)) {\n updateElementFromVnode(el, vnode, false);\n }\n return;\n }\n\n if (!tryUpdateTwoChildTextPattern(el, vnode)) {\n updateElementFromVnode(el, vnode);\n }\n}\n\nfunction tryUpdateTwoChildTextPattern(\n parentEl: Element,\n vnode: DOMElement\n): boolean {\n const vnodeChildren = vnode.children || vnode.props?.children;\n if (!Array.isArray(vnodeChildren) || vnodeChildren.length !== 2) return false;\n\n const firstChild = vnodeChildren[0];\n const secondChild = vnodeChildren[1];\n if (!_isDOMElement(firstChild) || !_isDOMElement(secondChild)) return false;\n if (\n typeof firstChild.type !== 'string' ||\n typeof secondChild.type !== 'string'\n ) {\n return false;\n }\n\n const firstElement = parentEl.children[0] as Element | undefined;\n const secondElement = parentEl.children[1] as Element | undefined;\n if (!firstElement || !secondElement) return false;\n\n if (!tagsEqualIgnoreCase(firstElement.tagName, firstChild.type)) return false;\n if (!tagsEqualIgnoreCase(secondElement.tagName, secondChild.type))\n return false;\n\n const firstText = (firstChild.children ||\n firstChild.props?.children) as unknown;\n const secondText = (secondChild.children ||\n secondChild.props?.children) as unknown;\n\n if (typeof firstText === 'string' || typeof firstText === 'number') {\n setTextNodeData(firstElement, String(firstText));\n } else if (\n Array.isArray(firstText) &&\n firstText.length === 1 &&\n (typeof firstText[0] === 'string' || typeof firstText[0] === 'number')\n ) {\n setTextNodeData(firstElement, String(firstText[0]));\n } else {\n return false;\n }\n\n if (typeof secondText === 'string' || typeof secondText === 'number') {\n setTextNodeData(secondElement, String(secondText));\n } else if (\n Array.isArray(secondText) &&\n secondText.length === 1 &&\n (typeof secondText[0] === 'string' || typeof secondText[0] === 'number')\n ) {\n setTextNodeData(secondElement, String(secondText[0]));\n } else {\n return false;\n }\n\n return true;\n}\n\nfunction setTextNodeData(el: Element, text: string): void {\n if (el.childNodes.length === 1 && el.firstChild?.nodeType === 3) {\n const textNode = el.firstChild as Text;\n if (textNode.data !== text) textNode.data = text;\n } else {\n el.textContent = text;\n }\n}\n\nfunction setDataKey(\n el: Element,\n key: string | number,\n onSet: () => void\n): void {\n try {\n const next = String(key);\n if (el.getAttribute('data-key') === next) return;\n el.setAttribute('data-key', next);\n onSet();\n } catch {\n // Ignore errors setting data-key\n }\n}\n\nfunction replaceNodeAtPosition(\n parent: Element,\n index: number,\n vnode: VNode\n): void {\n const dom = createDOMNode(vnode);\n if (dom) {\n const existing = parent.children[index];\n if (existing) {\n teardownNodeSubtree(existing);\n parent.replaceChild(dom, existing);\n } else {\n parent.appendChild(dom);\n }\n }\n}\n\nfunction updateKeyedElementsMap(\n parent: Element,\n keyedVnodes: Array<{ key: string | number; vnode: VNode }>\n): void {\n try {\n const existing = keyedElements.get(parent);\n const newKeyMap = existing\n ? (existing.clear(), existing)\n : new Map<string | number, Element>();\n\n for (let index = 0; index < keyedVnodes.length; index += 1) {\n const key = keyedVnodes[index].key;\n const child = parent.children[index] as Element | undefined;\n if (child) newKeyMap.set(key, child);\n }\n\n keyedElements.set(parent, newKeyMap);\n } catch {\n // Ignore errors updating key map\n }\n}\n\nexport function performBulkTextReplace(parent: Element, newChildren: VNode[]) {\n const start = now();\n const existing = Array.from(parent.childNodes);\n const finalNodes: Node[] = [];\n let reused = 0;\n let created = 0;\n\n for (let index = 0; index < newChildren.length; index += 1) {\n const result = processChildNode(\n newChildren[index],\n existing[index],\n finalNodes\n );\n if (result === 'reused') reused += 1;\n else if (result === 'created') created += 1;\n }\n\n const tBuild = now() - start;\n const tCommit = commitBulkReplace(parent, finalNodes);\n\n keyedElements.delete(parent);\n\n const stats = {\n n: newChildren.length,\n reused,\n created,\n tBuild,\n tCommit,\n } as const;\n recordBulkTextStats(stats);\n\n return stats;\n}\n\nfunction processChildNode(\n vnode: VNode,\n existingNode: ChildNode | undefined,\n finalNodes: Node[]\n): 'reused' | 'created' | 'skipped' {\n if (typeof vnode === 'string' || typeof vnode === 'number') {\n return processTextVnode(String(vnode), existingNode, finalNodes);\n }\n\n if (typeof vnode === 'object' && vnode !== null && 'type' in vnode) {\n return processElementVnode(vnode, existingNode, finalNodes);\n }\n\n return 'skipped';\n}\n\nfunction processTextVnode(\n text: string,\n existingNode: ChildNode | undefined,\n finalNodes: Node[]\n): 'reused' | 'created' {\n if (existingNode && existingNode.nodeType === 3) {\n (existingNode as Text).data = text;\n finalNodes.push(existingNode);\n return 'reused';\n }\n\n finalNodes.push(document.createTextNode(text));\n return 'created';\n}\n\nfunction processElementVnode(\n vnode: VNode,\n existingNode: ChildNode | undefined,\n finalNodes: Node[]\n): 'reused' | 'created' | 'skipped' {\n const vnodeObj = vnode as unknown as { type?: unknown };\n\n if (typeof vnodeObj.type === 'string') {\n const tag = vnodeObj.type;\n if (\n existingNode &&\n existingNode.nodeType === 1 &&\n tagsEqualIgnoreCase((existingNode as Element).tagName, tag)\n ) {\n updateElementFromVnode(existingNode as Element, vnode);\n finalNodes.push(existingNode);\n return 'reused';\n }\n }\n\n const dom = createDOMNode(vnode);\n if (dom) {\n finalNodes.push(dom);\n return 'created';\n }\n\n return 'skipped';\n}\n\nfunction commitBulkReplace(parent: Element, nodes: Node[]): number {\n const startedAt = Date.now();\n const fragment = document.createDocumentFragment();\n\n for (let index = 0; index < nodes.length; index += 1) {\n fragment.appendChild(nodes[index]);\n }\n\n try {\n for (let node = parent.firstChild; node; ) {\n const next = node.nextSibling;\n teardownNodeSubtree(node);\n node = next;\n }\n } catch {\n // SLOW PATH: cleanup failure\n }\n\n recordDOMReplace('bulk-text-replace');\n parent.replaceChildren(fragment);\n return Date.now() - startedAt;\n}\n\nfunction recordBulkTextStats(stats: {\n n: number;\n reused: number;\n created: number;\n tBuild: number;\n tCommit: number;\n}): void {\n try {\n setDevValue('__LAST_BULK_TEXT_FASTPATH_STATS', stats);\n setDevValue('__LAST_FASTPATH_STATS', stats);\n setDevValue('__LAST_FASTPATH_COMMIT_COUNT', 1);\n incDevCounter('bulkTextFastpathHits');\n } catch {\n // Ignore stats errors\n }\n}\n\nexport function isBulkTextFastPathEligible(\n parent: Element,\n newChildren: VNode[]\n) {\n const env = getRuntimeEnv();\n const threshold = Number(env.ASKR_BULK_TEXT_THRESHOLD) || 1024;\n const requiredFraction = 0.8;\n\n const total = Array.isArray(newChildren) ? newChildren.length : 0;\n\n if (total < threshold) {\n recordBulkDiag({\n phase: 'bulk-unkeyed-eligible',\n reason: 'too-small',\n total,\n threshold,\n });\n return false;\n }\n\n const result = countSimpleChildren(newChildren);\n if (result.componentFound !== undefined) {\n recordBulkDiag({\n phase: 'bulk-unkeyed-eligible',\n reason: 'component-child',\n index: result.componentFound,\n });\n return false;\n }\n\n const fraction = result.simple / total;\n const eligible =\n fraction >= requiredFraction && parent.childNodes.length >= total;\n\n recordBulkDiag({\n phase: 'bulk-unkeyed-eligible',\n total,\n simple: result.simple,\n fraction,\n requiredFraction,\n eligible,\n });\n\n return eligible;\n}\n\nfunction countSimpleChildren(children: VNode[]): {\n simple: number;\n componentFound?: number;\n} {\n let simple = 0;\n\n for (let index = 0; index < children.length; index += 1) {\n const child = children[index];\n\n if (typeof child === 'string' || typeof child === 'number') {\n simple += 1;\n continue;\n }\n\n if (typeof child === 'object' && child !== null && 'type' in child) {\n const dom = child as DOMElement;\n\n if (typeof dom.type === 'function') {\n return { simple, componentFound: index };\n }\n\n if (typeof dom.type === 'string' && isSimpleElement(dom)) {\n simple += 1;\n }\n }\n }\n\n return { simple };\n}\n\nfunction isSimpleElement(dom: DOMElement): boolean {\n const children = dom.children || dom.props?.children;\n\n if (children === null || children === undefined) return true;\n\n if (typeof children === 'string' || typeof children === 'number') {\n return true;\n }\n\n if (\n Array.isArray(children) &&\n children.length === 1 &&\n (typeof children[0] === 'string' || typeof children[0] === 'number')\n ) {\n return true;\n }\n\n return false;\n}\n\nfunction recordBulkDiag(data: Record<string, unknown>): void {\n const env = getRuntimeEnv();\n if (env.NODE_ENV !== 'production' || env.ASKR_FASTPATH_DEBUG === '1') {\n try {\n setDevValue('__BULK_DIAG', data);\n } catch {\n // Ignore\n }\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"children.js","names":[],"sources":["../../src/renderer/children.ts"],"sourcesContent":["import { setDevValue, incDevCounter } from '../runtime/dev-namespace';\nimport { getRuntimeEnv } from './env';\nimport { keyedElements } from './keyed';\nimport { teardownNodeSubtree } from './cleanup';\nimport { createDOMNode, updateElementFromVnode } from './dom';\nimport { _isDOMElement, type DOMElement, type VNode } from './types';\nimport {\n hasNonTrivialProps,\n logFastPathDebug,\n now,\n recordDOMReplace,\n recordFastPathStats,\n tagNamesEqualIgnoreCase,\n} from './utils';\n\nfunction upperCommonTagName(tag: string): string | null {\n switch (tag) {\n case 'div':\n return 'DIV';\n case 'span':\n return 'SPAN';\n case 'p':\n return 'P';\n case 'a':\n return 'A';\n case 'button':\n return 'BUTTON';\n case 'input':\n return 'INPUT';\n case 'ul':\n return 'UL';\n case 'ol':\n return 'OL';\n case 'li':\n return 'LI';\n default:\n return null;\n }\n}\n\nfunction tagsEqualIgnoreCase(\n elementTagName: string,\n vnodeType: string\n): boolean {\n if (elementTagName === vnodeType) return true;\n\n const upperCommon = upperCommonTagName(vnodeType);\n if (upperCommon !== null && elementTagName === upperCommon) return true;\n\n return tagNamesEqualIgnoreCase(elementTagName, vnodeType);\n}\n\nexport function performBulkPositionalKeyedTextUpdate(\n parent: Element,\n keyedVnodes: Array<{ key: string | number; vnode: VNode }>\n) {\n const total = keyedVnodes.length;\n let reused = 0;\n let updatedKeys = 0;\n const start = now();\n const env = getRuntimeEnv();\n const debugFastPath =\n env.ASKR_FASTPATH_DEBUG === '1' || env.ASKR_FASTPATH_DEBUG === 'true';\n\n for (let index = 0; index < total; index += 1) {\n const { key, vnode } = keyedVnodes[index];\n const child = parent.children[index] as Element | undefined;\n\n if (\n child &&\n _isDOMElement(vnode) &&\n typeof (vnode as DOMElement).type === 'string'\n ) {\n const vnodeType = (vnode as DOMElement).type as string;\n\n if (tagsEqualIgnoreCase(child.tagName, vnodeType)) {\n const children =\n (vnode as DOMElement).children ||\n (vnode as DOMElement).props?.children;\n\n if (debugFastPath) {\n logFastPathDebug('positional idx', index, {\n chTag: child.tagName,\n vnodeType,\n chChildNodes: child.childNodes.length,\n childrenType: Array.isArray(children) ? 'array' : typeof children,\n });\n }\n\n updateTextContent(child, children, vnode as DOMElement);\n setDataKey(child, key, () => (updatedKeys += 1));\n reused += 1;\n continue;\n }\n\n if (debugFastPath) {\n logFastPathDebug('positional tag mismatch', index, {\n chTag: child.tagName,\n vnodeType,\n });\n }\n } else if (debugFastPath) {\n logFastPathDebug('positional missing or invalid', index, { ch: !!child });\n }\n\n replaceNodeAtPosition(parent, index, vnode);\n }\n\n const elapsed = now() - start;\n updateKeyedElementsMap(parent, keyedVnodes);\n\n const stats = { n: total, reused, updatedKeys, t: elapsed } as const;\n recordFastPathStats(stats, 'bulkKeyedPositionalHits');\n\n return stats;\n}\n\nfunction updateTextContent(\n el: Element,\n children: unknown,\n vnode: DOMElement\n): void {\n if (typeof children === 'string' || typeof children === 'number') {\n setTextNodeData(el, String(children));\n if (vnode.props && hasNonTrivialProps(vnode.props)) {\n updateElementFromVnode(el, vnode, false);\n }\n return;\n }\n\n if (\n Array.isArray(children) &&\n children.length === 1 &&\n (typeof children[0] === 'string' || typeof children[0] === 'number')\n ) {\n setTextNodeData(el, String(children[0]));\n if (vnode.props && hasNonTrivialProps(vnode.props)) {\n updateElementFromVnode(el, vnode, false);\n }\n return;\n }\n\n if (!tryUpdateTwoChildTextPattern(el, vnode)) {\n updateElementFromVnode(el, vnode);\n }\n}\n\nfunction tryUpdateTwoChildTextPattern(\n parentEl: Element,\n vnode: DOMElement\n): boolean {\n const vnodeChildren = vnode.children || vnode.props?.children;\n if (!Array.isArray(vnodeChildren) || vnodeChildren.length !== 2) return false;\n\n const firstChild = vnodeChildren[0];\n const secondChild = vnodeChildren[1];\n if (!_isDOMElement(firstChild) || !_isDOMElement(secondChild)) return false;\n if (\n typeof firstChild.type !== 'string' ||\n typeof secondChild.type !== 'string'\n ) {\n return false;\n }\n\n const firstElement = parentEl.children[0] as Element | undefined;\n const secondElement = parentEl.children[1] as Element | undefined;\n if (!firstElement || !secondElement) return false;\n if (parentEl.children.length !== 2 || parentEl.childNodes.length !== 2) {\n return false;\n }\n\n if (!tagsEqualIgnoreCase(firstElement.tagName, firstChild.type)) return false;\n if (!tagsEqualIgnoreCase(secondElement.tagName, secondChild.type))\n return false;\n\n const firstText = (firstChild.children ||\n firstChild.props?.children) as unknown;\n const secondText = (secondChild.children ||\n secondChild.props?.children) as unknown;\n\n if (typeof firstText === 'string' || typeof firstText === 'number') {\n setTextNodeData(firstElement, String(firstText));\n } else if (\n Array.isArray(firstText) &&\n firstText.length === 1 &&\n (typeof firstText[0] === 'string' || typeof firstText[0] === 'number')\n ) {\n setTextNodeData(firstElement, String(firstText[0]));\n } else {\n return false;\n }\n\n if (typeof secondText === 'string' || typeof secondText === 'number') {\n setTextNodeData(secondElement, String(secondText));\n } else if (\n Array.isArray(secondText) &&\n secondText.length === 1 &&\n (typeof secondText[0] === 'string' || typeof secondText[0] === 'number')\n ) {\n setTextNodeData(secondElement, String(secondText[0]));\n } else {\n return false;\n }\n\n return true;\n}\n\nfunction setTextNodeData(el: Element, text: string): void {\n if (el.childNodes.length === 1 && el.firstChild?.nodeType === 3) {\n const textNode = el.firstChild as Text;\n if (textNode.data !== text) textNode.data = text;\n } else {\n el.textContent = text;\n }\n}\n\nfunction setDataKey(\n el: Element,\n key: string | number,\n onSet: () => void\n): void {\n try {\n const next = String(key);\n if (el.getAttribute('data-key') === next) return;\n el.setAttribute('data-key', next);\n onSet();\n } catch {\n // Ignore errors setting data-key\n }\n}\n\nfunction replaceNodeAtPosition(\n parent: Element,\n index: number,\n vnode: VNode\n): void {\n const dom = createDOMNode(vnode);\n if (dom) {\n const existing = parent.children[index];\n if (existing) {\n teardownNodeSubtree(existing);\n parent.replaceChild(dom, existing);\n } else {\n parent.appendChild(dom);\n }\n }\n}\n\nfunction updateKeyedElementsMap(\n parent: Element,\n keyedVnodes: Array<{ key: string | number; vnode: VNode }>\n): void {\n try {\n const existing = keyedElements.get(parent);\n const newKeyMap = existing\n ? (existing.clear(), existing)\n : new Map<string | number, Element>();\n\n for (let index = 0; index < keyedVnodes.length; index += 1) {\n const key = keyedVnodes[index].key;\n const child = parent.children[index] as Element | undefined;\n if (child) newKeyMap.set(key, child);\n }\n\n keyedElements.set(parent, newKeyMap);\n } catch {\n // Ignore errors updating key map\n }\n}\n\nexport function performBulkTextReplace(parent: Element, newChildren: VNode[]) {\n const start = now();\n const existing = Array.from(parent.childNodes);\n const finalNodes: Node[] = [];\n let reused = 0;\n let created = 0;\n\n for (let index = 0; index < newChildren.length; index += 1) {\n const result = processChildNode(\n newChildren[index],\n existing[index],\n finalNodes\n );\n if (result === 'reused') reused += 1;\n else if (result === 'created') created += 1;\n }\n\n const tBuild = now() - start;\n const tCommit = commitBulkReplace(parent, finalNodes);\n\n keyedElements.delete(parent);\n\n const stats = {\n n: newChildren.length,\n reused,\n created,\n tBuild,\n tCommit,\n } as const;\n recordBulkTextStats(stats);\n\n return stats;\n}\n\nfunction processChildNode(\n vnode: VNode,\n existingNode: ChildNode | undefined,\n finalNodes: Node[]\n): 'reused' | 'created' | 'skipped' {\n if (typeof vnode === 'string' || typeof vnode === 'number') {\n return processTextVnode(String(vnode), existingNode, finalNodes);\n }\n\n if (typeof vnode === 'object' && vnode !== null && 'type' in vnode) {\n return processElementVnode(vnode, existingNode, finalNodes);\n }\n\n return 'skipped';\n}\n\nfunction processTextVnode(\n text: string,\n existingNode: ChildNode | undefined,\n finalNodes: Node[]\n): 'reused' | 'created' {\n if (existingNode && existingNode.nodeType === 3) {\n (existingNode as Text).data = text;\n finalNodes.push(existingNode);\n return 'reused';\n }\n\n finalNodes.push(document.createTextNode(text));\n return 'created';\n}\n\nfunction processElementVnode(\n vnode: VNode,\n existingNode: ChildNode | undefined,\n finalNodes: Node[]\n): 'reused' | 'created' | 'skipped' {\n const vnodeObj = vnode as unknown as { type?: unknown };\n\n if (typeof vnodeObj.type === 'string') {\n const tag = vnodeObj.type;\n if (\n existingNode &&\n existingNode.nodeType === 1 &&\n tagsEqualIgnoreCase((existingNode as Element).tagName, tag)\n ) {\n updateElementFromVnode(existingNode as Element, vnode);\n finalNodes.push(existingNode);\n return 'reused';\n }\n }\n\n const dom = createDOMNode(vnode);\n if (dom) {\n finalNodes.push(dom);\n return 'created';\n }\n\n return 'skipped';\n}\n\nfunction commitBulkReplace(parent: Element, nodes: Node[]): number {\n const startedAt = Date.now();\n const fragment = document.createDocumentFragment();\n\n for (let index = 0; index < nodes.length; index += 1) {\n fragment.appendChild(nodes[index]);\n }\n\n try {\n for (let node = parent.firstChild; node; ) {\n const next = node.nextSibling;\n teardownNodeSubtree(node);\n node = next;\n }\n } catch {\n // SLOW PATH: cleanup failure\n }\n\n recordDOMReplace('bulk-text-replace');\n parent.replaceChildren(fragment);\n return Date.now() - startedAt;\n}\n\nfunction recordBulkTextStats(stats: {\n n: number;\n reused: number;\n created: number;\n tBuild: number;\n tCommit: number;\n}): void {\n try {\n setDevValue('__LAST_BULK_TEXT_FASTPATH_STATS', stats);\n setDevValue('__LAST_FASTPATH_STATS', stats);\n setDevValue('__LAST_FASTPATH_COMMIT_COUNT', 1);\n incDevCounter('bulkTextFastpathHits');\n } catch {\n // Ignore stats errors\n }\n}\n\nexport function isBulkTextFastPathEligible(\n parent: Element,\n newChildren: VNode[]\n) {\n const env = getRuntimeEnv();\n const threshold = Number(env.ASKR_BULK_TEXT_THRESHOLD) || 1024;\n const requiredFraction = 0.8;\n\n const total = Array.isArray(newChildren) ? newChildren.length : 0;\n\n if (total < threshold) {\n recordBulkDiag({\n phase: 'bulk-unkeyed-eligible',\n reason: 'too-small',\n total,\n threshold,\n });\n return false;\n }\n\n const result = countSimpleChildren(newChildren);\n if (result.componentFound !== undefined) {\n recordBulkDiag({\n phase: 'bulk-unkeyed-eligible',\n reason: 'component-child',\n index: result.componentFound,\n });\n return false;\n }\n\n const fraction = result.simple / total;\n const eligible =\n fraction >= requiredFraction && parent.childNodes.length >= total;\n\n recordBulkDiag({\n phase: 'bulk-unkeyed-eligible',\n total,\n simple: result.simple,\n fraction,\n requiredFraction,\n eligible,\n });\n\n return eligible;\n}\n\nfunction countSimpleChildren(children: VNode[]): {\n simple: number;\n componentFound?: number;\n} {\n let simple = 0;\n\n for (let index = 0; index < children.length; index += 1) {\n const child = children[index];\n\n if (typeof child === 'string' || typeof child === 'number') {\n simple += 1;\n continue;\n }\n\n if (typeof child === 'object' && child !== null && 'type' in child) {\n const dom = child as DOMElement;\n\n if (typeof dom.type === 'function') {\n return { simple, componentFound: index };\n }\n\n if (typeof dom.type === 'string' && isSimpleElement(dom)) {\n simple += 1;\n }\n }\n }\n\n return { simple };\n}\n\nfunction isSimpleElement(dom: DOMElement): boolean {\n const children = dom.children || dom.props?.children;\n\n if (children === null || children === undefined) return true;\n\n if (typeof children === 'string' || typeof children === 'number') {\n return true;\n }\n\n if (\n Array.isArray(children) &&\n children.length === 1 &&\n (typeof children[0] === 'string' || typeof children[0] === 'number')\n ) {\n return true;\n }\n\n return false;\n}\n\nfunction recordBulkDiag(data: Record<string, unknown>): void {\n const env = getRuntimeEnv();\n if (env.NODE_ENV !== 'production' || env.ASKR_FASTPATH_DEBUG === '1') {\n try {\n setDevValue('__BULK_DIAG', data);\n } catch {\n // Ignore\n }\n }\n}\n"],"mappings":"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"}
|
package/dist/renderer/dom.js
CHANGED
|
@@ -2305,7 +2305,7 @@ function updateUnkeyedChildren(parent, newChildren) {
|
|
|
2305
2305
|
const hasEmptyChildren = newChildren.some(isEmptyChild);
|
|
2306
2306
|
const hasComponentChildren = newChildren.some((c) => _isDOMElement(c) && typeof c.type === "function");
|
|
2307
2307
|
const hasNonElementDomChildren = parent.childNodes.length !== parent.children.length;
|
|
2308
|
-
if (!hasEmptyChildren && !hasText && !hasComponentChildren && hasElements && parent.children.length === newChildren.length) {
|
|
2308
|
+
if (!hasEmptyChildren && !hasText && !hasComponentChildren && !hasNonElementDomChildren && hasElements && parent.children.length === newChildren.length) {
|
|
2309
2309
|
const c = parent.children;
|
|
2310
2310
|
for (let i = 0; i < newChildren.length; i++) {
|
|
2311
2311
|
const next = newChildren[i];
|