@source-crafter/xzor-runtime 0.1.17 → 0.1.19

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/index.d.ts CHANGED
@@ -68,10 +68,19 @@ type FineHooks = {
68
68
  /** A handler of a fine component ran: it re-runs the bindings that read nothing tracked. */
69
69
  handled(owner: ComponentBase, result: unknown): void;
70
70
  };
71
- /** @internal On `FineComponent.prototype`: its handlers and callbacks write tracked fields, so nothing re-renders it. */
71
+ /** @internal On a component whose fine root was built (its fine context): its handlers and callbacks write tracked fields, so nothing re-renders it. */
72
72
  export declare const FINE: unique symbol;
73
73
  /** @internal Set by `fine.ts` (the model entry), so the renderer pays nothing without it. */
74
74
  export declare function setFineHooks(h: FineHooks): void;
75
+ type Tracking = {
76
+ model: {
77
+ $reader: ((source: any, index: number) => void) | undefined;
78
+ same(a: unknown, b: unknown): boolean;
79
+ };
80
+ notify(dep: object, index: number): void;
81
+ };
82
+ /** @internal Set by the model entry: what a component's tracked fields report to. */
83
+ export declare function setTracking(t: Tracking): void;
75
84
  /**
76
85
  * @internal A virtual-DOM island inside a fine template: renders `build` before `end`, patching `old`.
77
86
  * `scope` is the cascade scope the fine component was mounted in.
@@ -106,17 +115,17 @@ export declare class Route<P = Record<string, unknown>, Tags extends object = Re
106
115
  readonly params: P;
107
116
  readonly query: URLSearchParams;
108
117
  readonly match: Record<string, string>;
109
- constructor(path: string, tag: (keyof Tags & string) | undefined, params: P, query: URLSearchParams, match: Record<string, string>, nav?: Navigation);
118
+ constructor(path: string, tag: (keyof Tags & string) | undefined, params: P, query: URLSearchParams, match: Record<string, string>, nav: Navigation);
110
119
  /** Narrows `params` to the routes carrying `tag`. */
111
120
  is<K extends keyof Tags & string>(tag: K): this is Route<Tags[K], Tags> & {
112
121
  readonly tag: K;
113
122
  };
114
- /** History navigation under `@ClientRoutes`; `@ServerRoutes` loads the url. */
123
+ /** Navigates through the browser's history. */
115
124
  navigate(url: string, opts?: {
116
125
  replace?: boolean;
117
126
  }): void;
118
127
  }
119
- /** One section of a `@ClientRoutes`/`@ServerRoutes` block; xzor emits these in source order. */
128
+ /** One section of a `@Routes` block; xzor emits these in source order. */
120
129
  export type RouteSection = {
121
130
  kind: "tag";
122
131
  tag: string;
@@ -141,7 +150,7 @@ export type RouteSection = {
141
150
  kind: "default";
142
151
  render: (route: Route, page: RenderFragment) => RenderFragment;
143
152
  };
144
- /** How `@ClientRoutes` reaches the browser's history; `setNavigationForTests` swaps it. */
153
+ /** How navigation reaches the browser's history; `setNavigationForTests` swaps it. */
145
154
  export type Navigation = {
146
155
  current(): URL;
147
156
  navigate(url: string, replace: boolean): void;
@@ -151,6 +160,8 @@ export type Navigation = {
151
160
  };
152
161
  /** Commits each navigation inside `document.startViewTransition` where the browser has it. */
153
162
  export declare function viewTransitions(on: boolean): void;
163
+ /** @internal The longest a view transition waits for a page's data, in ms; `undefined` restores 1500. */
164
+ export declare function setTransitionCapForTests(ms: number | undefined): void;
154
165
  /** @internal Swaps in a memory navigation; set it before mounting the routes block. */
155
166
  export declare function setNavigationForTests(nav: Navigation | undefined): void;
156
167
  /** The path of `<base href>` without its last segment: "" without one, "/docs" under `<base href="/docs/">`. */
@@ -165,12 +176,25 @@ type Mounted = {
165
176
  pass: number;
166
177
  nav?: number;
167
178
  };
168
- /** Builds components that need injected services; generated by xzor from `@Mount` and `@ServiceProvider`. */
179
+ /** Builds components that need injected services; generated by xzor from `@Run` and `@ServiceProvider`. */
169
180
  export interface IApplication {
170
181
  onStarted?(): void | Promise<void>;
171
182
  /** Consulted only for component classes with a non-empty `static inject`; returns the component, a promise for it, or `undefined` (the runtime then throws). */
172
183
  createComponent?(type: Function): ComponentBase | Promise<ComponentBase> | undefined;
173
184
  }
185
+ /** A contract as browser code sees it: every method's result `R` becomes `Promise<Awaited<R>>`, other members `never`. */
186
+ export type Remote<T> = {
187
+ [K in keyof T]: T[K] extends (...args: infer A) => infer R ? (...args: A) => Promise<Awaited<R>> : never;
188
+ };
189
+ /** Thrown from an endpoint to answer with `status`; a generated client throws it for a non-2xx answer. */
190
+ export declare class HttpError extends Error {
191
+ readonly status: number;
192
+ constructor(status: number, message?: string);
193
+ }
194
+ /** One request of a generated contract client: 404 and an empty body resolve `undefined`, other non-2xx statuses reject with `HttpError`. */
195
+ export declare function call(method: string, path: string, query: Record<string, unknown> | undefined, body: unknown): Promise<unknown>;
196
+ /** A path parameter of a generated client, encoded; `""`, `.` and `..` throw `HttpError(400)` before any request (URL normalization would turn them into another route). */
197
+ export declare function segment(value: string | number | boolean): string;
174
198
  type DisposeSymbols = {
175
199
  dispose: symbol;
176
200
  asyncDispose: symbol;
@@ -182,7 +206,15 @@ type ServiceDecorator = {
182
206
  <T>(key: string, factory: (...a: any[]) => T | Promise<T>): ClassDecorator;
183
207
  };
184
208
  export declare const ServiceProvider: () => ClassDecorator;
185
- export declare const Mount: (_component: Function, _target: string) => ClassDecorator;
209
+ type RunDecorator = {
210
+ /** Browser app: mounts `component` on `target`. */
211
+ (component: Function, target: string): ClassDecorator;
212
+ /** Server app with a page shell. */
213
+ (component: Function): ClassDecorator;
214
+ /** Server app, API only. */
215
+ (): ClassDecorator;
216
+ };
217
+ export declare const Run: RunDecorator;
186
218
  export declare const Import: (..._providers: Function[]) => ClassDecorator;
187
219
  export declare const Singleton: ServiceDecorator;
188
220
  export declare const Scoped: ServiceDecorator;
@@ -199,9 +231,25 @@ export type Renderer = RenderFragment;
199
231
  export type EventCallback<T = void> = (arg: T) => unknown;
200
232
  /** The DOM element type for a tag name, as `document.createElement` would return it. */
201
233
  export type ElementOf<T extends string> = T extends keyof HTMLElementTagNameMap ? HTMLElementTagNameMap[T] : T extends keyof SVGElementTagNameMap ? SVGElementTagNameMap[T] : HTMLElement;
234
+ /**
235
+ * Base of every component. A `.xzor` component's fields are tracked like a model's (the bundle rewrites them), so a
236
+ * handler that writes one updates what reads it; `stateHasChanged()` re-runs every binding.
237
+ */
202
238
  export declare abstract class ComponentBase {
239
+ #private;
203
240
  /** @internal Set by components that use `@leave`: until then, removal skips the `data-leave` check. */
204
241
  static $leaves: boolean;
242
+ /** @internal What tracked getters report to; the bundle emits `ComponentBase.$reader?.(this, i)`. */
243
+ static get $reader(): ((source: object, index: number) => void) | undefined;
244
+ static same(a: unknown, b: unknown): boolean;
245
+ /** @internal Records a class's tracked fields after its base's; where they start is the class's `$at`. */
246
+ static $define(cls: Function, names: string[]): number;
247
+ /** @internal */
248
+ $watch(dep: object): void;
249
+ /** @internal */
250
+ $unwatch(dep: object): void;
251
+ /** @internal What a tracked setter reports once it stored a different value. */
252
+ protected $changed(index: number, _previous: unknown): void;
205
253
  /** Wraps builder calls as a RenderFragment; xzor emits this for markup used as a value. */
206
254
  static fragment(build: (builder: RenderTreeBuilder) => void): RenderFragment;
207
255
  /** Reads a bound input's new value, converted to the bound field's current type. */
@@ -237,8 +285,8 @@ export declare class RenderTreeBuilder {
237
285
  /** `@cascade:name(value)`: a region whose children see `value` under `name`; `fixed` keeps the first value. */
238
286
  openCascade(name: string, value: unknown, fixed?: boolean): void;
239
287
  closeRegion(): void;
240
- /** `@ClientRoutes`/`@ServerRoutes`: renders the section that matches the current navigation. */
241
- addRoutes(engine: "client" | "server", table: RouteTable, sections: readonly RouteSection[]): void;
288
+ /** `@Routes`: renders the section that matches the current navigation. */
289
+ addRoutes(table: RouteTable, sections: readonly RouteSection[]): void;
242
290
  addAttribute(name: string, value: unknown): void;
243
291
  /** `class:name="@on"`: adds `name` to the open element's class while `on` is truthy. */
244
292
  toggleClass(name: string, on: unknown): void;
package/index.js CHANGED
@@ -4,11 +4,12 @@ import {
4
4
  $leave,
5
5
  ComponentBase,
6
6
  FINE,
7
+ HttpError,
7
8
  Import,
8
- Mount,
9
9
  NavLink,
10
10
  RenderTreeBuilder,
11
11
  Route,
12
+ Run,
12
13
  SVG,
13
14
  Scoped,
14
15
  ServiceProvider,
@@ -16,27 +17,32 @@ import {
16
17
  Singleton,
17
18
  Transient,
18
19
  basePath,
20
+ call,
19
21
  disposeIsland,
20
22
  lazy,
21
23
  mount,
22
24
  patchIsland,
23
25
  polyfillDisposeSymbols,
26
+ segment,
24
27
  setFineHooks,
25
28
  setNavigationForTests,
26
29
  setRenderObserver,
30
+ setTracking,
31
+ setTransitionCapForTests,
27
32
  viewTransitions
28
- } from "./index-45b6cjyv.js";
33
+ } from "./index-960nssa4.js";
29
34
  export {
30
35
  $inSvg,
31
36
  $isFragment,
32
37
  $leave,
33
38
  ComponentBase,
34
39
  FINE,
40
+ HttpError,
35
41
  Import,
36
- Mount,
37
42
  NavLink,
38
43
  RenderTreeBuilder,
39
44
  Route,
45
+ Run,
40
46
  SVG,
41
47
  Scoped,
42
48
  ServiceProvider,
@@ -44,16 +50,20 @@ export {
44
50
  Singleton,
45
51
  Transient,
46
52
  basePath,
53
+ call,
47
54
  disposeIsland,
48
55
  lazy,
49
56
  mount,
50
57
  patchIsland,
51
58
  polyfillDisposeSymbols,
59
+ segment,
52
60
  setFineHooks,
53
61
  setNavigationForTests,
54
62
  setRenderObserver,
63
+ setTracking,
64
+ setTransitionCapForTests,
55
65
  viewTransitions
56
66
  };
57
67
 
58
- //# debugId=5539239CA86FB80264756E2164756E21
68
+ //# debugId=3529EB678D53AF3D64756E2164756E21
59
69
  //# sourceMappingURL=index.js.map
package/index.js.map CHANGED
@@ -4,6 +4,6 @@
4
4
  "sourcesContent": [
5
5
  ],
6
6
  "mappings": "",
7
- "debugId": "5539239CA86FB80264756E2164756E21",
7
+ "debugId": "3529EB678D53AF3D64756E2164756E21",
8
8
  "names": []
9
9
  }
package/model.js CHANGED
@@ -8,8 +8,9 @@ import {
8
8
  disposeIsland,
9
9
  patchIsland,
10
10
  setFineHooks,
11
- setRenderObserver
12
- } from "./index-45b6cjyv.js";
11
+ setRenderObserver,
12
+ setTracking
13
+ } from "./index-960nssa4.js";
13
14
 
14
15
  // runtime/entity-set.ts
15
16
  class EntitySet extends Array {
@@ -572,9 +573,29 @@ function track(b) {
572
573
  b.stateHasChanged();
573
574
  }
574
575
  function disposeAll(h) {
575
- for (let b = h.bindings;b !== undefined; b = b.next)
576
+ let loose = false;
577
+ for (let b = h.bindings;b !== undefined; b = b.next) {
578
+ if ((b.flags & LOOSE) !== 0)
579
+ loose = true;
576
580
  b.dispose();
581
+ }
577
582
  h.bindings = undefined;
583
+ if (loose)
584
+ sweepLoose(h.ctx);
585
+ }
586
+ function sweepLoose(ctx) {
587
+ if (ctx.sweep)
588
+ return;
589
+ ctx.sweep = true;
590
+ queueMicrotask(() => {
591
+ ctx.sweep = false;
592
+ const loose = ctx.loose;
593
+ let kept = 0;
594
+ for (const b of loose)
595
+ if ((b.flags & DEAD) === 0)
596
+ loose[kept++] = b;
597
+ loose.length = kept;
598
+ });
578
599
  }
579
600
  function refreshAll(h) {
580
601
  for (let b = h.bindings;b !== undefined; b = b.next) {
@@ -707,7 +728,7 @@ function removeRange(head, tail) {
707
728
  }
708
729
  function newRow(ctx, item, reads, build) {
709
730
  const frag = document.createDocumentFragment();
710
- const row = { bindings: undefined, ctx, head: frag, tail: frag, item, reads };
731
+ const row = { bindings: undefined, ctx, head: frag, tail: frag, item, reads, set: undefined };
711
732
  within(row, () => build(frag, item));
712
733
  seal(row, frag);
713
734
  return row;
@@ -768,11 +789,21 @@ function reconcile(list, items) {
768
789
  throw duplicate(keys[i]);
769
790
  at.set(keys[i], next[i]);
770
791
  }
771
- for (const [key, row] of rows)
772
- if (!at.has(key) || at.get(key) !== row.item) {
792
+ for (const [key, row] of rows) {
793
+ if (!at.has(key)) {
773
794
  dropRow(row);
774
795
  rows.delete(key);
796
+ continue;
775
797
  }
798
+ const item = at.get(key);
799
+ if (item === row.item)
800
+ continue;
801
+ if (row.set === undefined && state.build.length > 1) {
802
+ dropRow(row);
803
+ rows.delete(key);
804
+ } else
805
+ retake(row, item);
806
+ }
776
807
  }
777
808
  const order = [];
778
809
  for (let i = 0;i < next.length; i++) {
@@ -787,6 +818,21 @@ function reconcile(list, items) {
787
818
  place(list.node, state.order, order);
788
819
  state.order = order;
789
820
  }
821
+ function retake(row, item) {
822
+ row.item = item;
823
+ if (row.set !== undefined)
824
+ row.set(item);
825
+ for (let b = row.bindings;b !== undefined; b = b.next) {
826
+ if ((b.flags & ITEM) === 0)
827
+ continue;
828
+ if ((b.flags & KIND) === REGION)
829
+ b.data.item = item;
830
+ if (row.set === undefined)
831
+ b.stateHasChanged();
832
+ }
833
+ if (row.set !== undefined)
834
+ refreshAll(row);
835
+ }
790
836
  function sameValues(a, b) {
791
837
  if (a.length !== b.length)
792
838
  return false;
@@ -823,7 +869,7 @@ function runKeyed(s, end, run) {
823
869
  return;
824
870
  }
825
871
  const frag = document.createDocumentFragment();
826
- row = { bindings: undefined, ctx: s.ctx, head: frag, tail: frag, item: values, reads: undefined };
872
+ row = { bindings: undefined, ctx: s.ctx, head: frag, tail: frag, item: values, reads: undefined, set: undefined };
827
873
  s.rows.set(key, row);
828
874
  order.push(row);
829
875
  building = row;
@@ -1094,10 +1140,20 @@ var fine = {
1094
1140
  anchor(parent);
1095
1141
  return anchor(parent);
1096
1142
  },
1143
+ rowItem(set) {
1144
+ holder.set = set;
1145
+ },
1097
1146
  regionItem(end, i) {
1098
1147
  const row = holder;
1099
1148
  region(end.previousSibling, end, row.reads[i], row.item, ITEM);
1100
1149
  },
1150
+ branchItem(end, i) {
1151
+ const row = holder;
1152
+ const read = row.reads[i];
1153
+ const state = { bindings: undefined, ctx: row.ctx, start: end.previousSibling, item: row.item, branch: undefined, index: -1 };
1154
+ state.branch = (p) => read(state.item, p);
1155
+ track(new Binding(REGION | ITEM, end, read, state));
1156
+ },
1101
1157
  list(parent, items, build) {
1102
1158
  list(anchor(parent), items, undefined, build);
1103
1159
  },
@@ -1118,52 +1174,13 @@ var fine = {
1118
1174
  }
1119
1175
  };
1120
1176
  var noop = () => {};
1121
- var FIELDS = Symbol("xzor.fine.fields");
1122
-
1123
- class FineComponent extends ComponentBase {
1124
- #subs;
1125
- static get $reader() {
1126
- return Model.$reader;
1127
- }
1128
- static same(a, b) {
1129
- return Model.same(a, b);
1130
- }
1131
- static $define(cls, names) {
1132
- const counts = cls;
1133
- const at = counts[FIELDS] ?? 0;
1134
- counts[FIELDS] = at + names.length;
1135
- return at;
1136
- }
1137
- $watch(dep) {
1138
- const subs = this.#subs ??= [];
1139
- dep.slot = subs.length;
1140
- subs.push(dep);
1141
- }
1142
- $unwatch(dep) {
1143
- const subs = this.#subs;
1144
- if (subs === undefined || subs.length === 0)
1145
- return;
1146
- const last = subs.pop();
1147
- if (last !== dep) {
1148
- subs[dep.slot] = last;
1149
- last.slot = dep.slot;
1150
- }
1151
- }
1152
- $changed(index, _previous) {
1153
- const subs = this.#subs;
1154
- if (subs !== undefined)
1155
- for (let i = 0;i < subs.length; i++)
1156
- $notify(subs[i], index);
1157
- }
1158
- }
1159
- FineComponent.prototype[FINE] = true;
1160
- var CTX = Symbol("xzor.fine.ctx");
1177
+ var FineComponent = ComponentBase;
1161
1178
  setFineHooks({
1162
1179
  create(build, owner, app, scope) {
1163
1180
  const frag = document.createDocumentFragment();
1164
1181
  const start = frag.appendChild(document.createComment(""));
1165
- const ctx = { owner, app, scope, loose: [] };
1166
- owner[CTX] = ctx;
1182
+ const ctx = { owner, app, scope, loose: [], sweep: false };
1183
+ owner[FINE] = ctx;
1167
1184
  const root = { bindings: undefined, ctx, start, end: start };
1168
1185
  const reader = Model.$reader;
1169
1186
  Model.$reader = undefined;
@@ -1192,7 +1209,7 @@ setFineHooks({
1192
1209
  disposeAll(root);
1193
1210
  },
1194
1211
  handled(owner, result) {
1195
- const ctx = owner[CTX];
1212
+ const ctx = owner[FINE];
1196
1213
  if (ctx !== undefined)
1197
1214
  afterHandler(ctx, result);
1198
1215
  }
@@ -1285,7 +1302,9 @@ class Model extends EventTarget {
1285
1302
  pendingIgnored = undefined;
1286
1303
  }
1287
1304
  Object.defineProperty(cls, meta, { value: own });
1288
- return base ? base.names.length : 0;
1305
+ const at = base ? base.names.length : 0;
1306
+ Object.defineProperty(cls, "$at", { value: at });
1307
+ return at;
1289
1308
  }
1290
1309
  static same(a, b) {
1291
1310
  if (Object.is(a, b))
@@ -1806,6 +1825,7 @@ function flush() {
1806
1825
  }
1807
1826
  var $notify = changed;
1808
1827
  var $flush = flush;
1828
+ setTracking({ model: Model, notify: $notify });
1809
1829
  function sweep(own) {
1810
1830
  const more = own.more;
1811
1831
  if (more !== undefined) {
@@ -1900,5 +1920,5 @@ export {
1900
1920
  requiredIdValue
1901
1921
  };
1902
1922
 
1903
- //# debugId=E7409C03C0112EDB64756E2164756E21
1923
+ //# debugId=A289C126D860800164756E2164756E21
1904
1924
  //# sourceMappingURL=model.js.map