lecodes-sdk 2.2.3 → 2.2.4

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/global.d.ts CHANGED
@@ -586,6 +586,7 @@ declare global {
586
586
  type With<N, A extends Aspect<any, any, any>> = SDK.With<N, A>
587
587
  // ---- server globals (available in *.server.ts; using them in client code is a build error) ----
588
588
  const ApiError: typeof SERVER.ApiError
589
+ const SvgSource: typeof SERVER.SvgSource
589
590
  const channel: typeof SERVER.channel
590
591
  const defineDb: typeof SERVER.defineDb
591
592
  const model: typeof SERVER.model
@@ -124,11 +124,10 @@ export declare const uiValue: (v: unknown) => ({
124
124
  * function is unknown (the caller falls back to matrix decomposition on the host). `scale(2)` and
125
125
  * `translate(5px)` complete their second argument like CSS.
126
126
  *
127
- * `percent`: a `%` in a translate is a percent of the target's box (`translateX(100%)` →
128
- * `translateX%` [100]; a translate mixing the units splits into its two axes). Without it a `%`
129
- * reads as px, as the style's own parser reads it — with a warning: only a screen transition
130
- * measures a translate in percents. */
131
- export declare const parseTransformList: (s: string, percent?: boolean) => {
127
+ * A `%` in a translate is a percent of the target's own box (`translateX(100%)` → `translateX%`
128
+ * [100]; a translate mixing the units splits into its two axes), the way the runtime reads a
129
+ * style's transform: the box is measured when the matrix is composed, so a track fits any size. */
130
+ export declare const parseTransformList: (s: string) => {
132
131
  sig: string;
133
132
  lanes: number[];
134
133
  } | null;
@@ -41,7 +41,8 @@ export type FieldDef = {
41
41
  kind: FieldKind;
42
42
  scalar?: ScalarKind;
43
43
  optional: boolean;
44
- unique: boolean;
44
+ /** `true` = `@unique`; a string = `@unique(<group>)`: the fields of a model sharing the group are unique together. */
45
+ unique: boolean | string;
45
46
  index: boolean;
46
47
  hasDefault: boolean;
47
48
  default?: unknown;
@@ -73,8 +74,10 @@ export interface Field<T, M extends FieldMeta = FieldMeta> {
73
74
  readonly def: FieldDef;
74
75
  /** Nullable column (`String?`). */
75
76
  optional(): Field<T, SetMeta<M, "optional", true>>;
76
- /** `@unique` (on a relation: one-to-one). */
77
- unique(): Field<T, M>;
77
+ /** `@unique` (on a relation: one-to-one). With a group name, the fields of the model that share it are
78
+ * unique TOGETHER — `user: t.one("User").unique("pair"), post: t.one("Post").unique("pair")` is one
79
+ * like per user and post; a null part is not constrained. */
80
+ unique(group?: string): Field<T, M>;
78
81
  /** `@index` — used by `$where`, `$order`, aggregations. */
79
82
  index(): Field<T, M>;
80
83
  /** `@default(...)`; `"now"` on `t.date()` → `now()`. Makes the field optional on insert. */
@@ -197,6 +197,8 @@ export type MarciOp = {
197
197
  query?: any;
198
198
  data?: any;
199
199
  id?: any;
200
+ key?: any;
201
+ onInsert?: any;
200
202
  journal?: JournalArgs;
201
203
  };
202
204
  /** The arguments of a journal action (`$journalOpen` / `$journalRead` / `$journalDrop`). `wait` is in seconds. */
@@ -255,9 +257,11 @@ export interface Query<T extends ModelTypes, Sel = T["scalars"]> extends Promise
255
257
  readonly [__sel]: Effective<T, Sel>;
256
258
  /** Filter (marcidb `$where`). Repeated calls are ANDed. */
257
259
  where(where: T["where"]): Query<T, Sel>;
258
- /** Sort by one field: `order("age", "desc")` or `order({ age: "desc" })`. */
260
+ /** Sort: `order("age", "desc")` or `order({ age: "desc" })`. A second `order()` adds the next key — ties on
261
+ * the first are broken by it (`order("createdAt", "desc").order("id")`); the keys may also come as an array
262
+ * in one call. Several keys sort in memory. */
259
263
  order(field: keyof T["order"] & string, direction?: "asc" | "desc"): Query<T, Sel>;
260
- order(order: T["order"]): Query<T, Sel>;
264
+ order(order: T["order"] | T["order"][]): Query<T, Sel>;
261
265
  limit(n: number): Query<T, Sel>;
262
266
  skip(n: number): Query<T, Sel>;
263
267
  /** Keyset cursor: rows strictly after this id in the current order (`$cursor`). */
@@ -287,6 +291,18 @@ export interface Query<T extends ModelTypes, Sel = T["scalars"]> extends Promise
287
291
  select: S;
288
292
  }): Promise<Rows<T, S>>;
289
293
  update(id: IdArg<T["id"]>, data: T["update"]): Op<void>;
294
+ /** Writes the row `key` names — the id, or the fields of ONE unique index (a `@unique` field, all the fields of
295
+ * a `@unique(<name>)` group): an update of `data` when it exists, an insert of key + data when not. `data` is
296
+ * in the insert form (plain values, `{ id }` for a relation; no `$` operators). `onInsert` adds fields used
297
+ * only when the row is created. Resolves to the id, like `insert`. */
298
+ upsert<K extends Partial<T["insert"]> | T["id"]>(key: K, data: Omit<T["insert"], keyof K>, options?: {
299
+ onInsert?: Partial<T["insert"]>;
300
+ }): Op<T["id"]>;
301
+ /** The row written, by `select`'s shape — as stored, whichever path the upsert took. */
302
+ upsert<K extends Partial<T["insert"]> | T["id"], S extends T["query"]>(key: K, data: Omit<T["insert"], keyof K>, options: {
303
+ onInsert?: Partial<T["insert"]>;
304
+ select: S;
305
+ }): Promise<Rows<T, S>>;
290
306
  /** Applies `data` to every row the chain's `where` matches (all rows without one); resolves to the number of rows. */
291
307
  updateMany(data: T["update"]): Op<number>;
292
308
  /** @deprecated pass the filter through the chain: `db.user.where(w).updateMany(data)` (removed in the next minor). */
@@ -354,6 +370,9 @@ export type QueryLayerOptions = {
354
370
  run(op: MarciOp): Promise<any>;
355
371
  /** Hook for `insert` payloads (the lecodes SDK generates uuid ids here). */
356
372
  prepareInsert?(model: string, data: any): any;
373
+ /** Hook for the `onInsert` part of an `upsert` — the fields used only when the row is created (the lecodes SDK
374
+ * puts a uuid id here). Receives what the caller gave (or undefined), returns what is sent. */
375
+ prepareUpsert?(model: string, onInsert: any): any;
357
376
  };
358
377
  /**
359
378
  * The query builder over a set of models and a `run` function. Returns `op(descriptor)` — a lazy operation
@@ -66,20 +66,21 @@ export type Scalars<S, N extends keyof S> = Pick<Row<S, N>, (ScalarKeys<FieldsOf
66
66
  export type ScalarSelect<S, N extends keyof S> = {
67
67
  [K in (ScalarKeys<FieldsOf<S[N]>> | keyof Id<S, N>) & keyof Row<S, N>]: true;
68
68
  };
69
- type FieldInsert<S, Fld, M extends FieldMeta = MetaOf<Fld>> = M["kind"] extends "one" ? Nullable<M, Id<S, ModelName<S, Fld>>> : M["kind"] extends "many" | "list" ? Id<S, ModelName<S, Fld>>[] : M["kind"] extends "struct" ? Nullable<M, StructInsert<S, StructFieldsOf<Fld>>> : M["kind"] extends "file" ? Nullable<M, Arr<M, FileWrite>> : Nullable<M, Arr<M, TsOf<Fld>>>;
69
+ type FieldInsert<S, Fld, FW, M extends FieldMeta = MetaOf<Fld>> = M["kind"] extends "one" ? Nullable<M, Id<S, ModelName<S, Fld>>> : M["kind"] extends "many" | "list" ? Id<S, ModelName<S, Fld>>[] : M["kind"] extends "struct" ? Nullable<M, StructInsert<S, StructFieldsOf<Fld>, FW>> : M["kind"] extends "file" ? Nullable<M, Arr<M, FW>> : Nullable<M, Arr<M, TsOf<Fld>>>;
70
70
  type RequiredKeys<F extends Fields> = {
71
71
  [K in keyof F]: K extends IdKeys<F> ? never : MetaOf<F[K]>["optional"] extends true ? never : MetaOf<F[K]>["hasDefault"] extends true ? never : MetaOf<F[K]>["kind"] extends "many" | "list" ? never : MetaOf<F[K]>["kind"] extends "file" ? (MetaOf<F[K]>["array"] extends true ? never : K) : K;
72
72
  }[keyof F];
73
73
  type Simplify<T> = {
74
74
  [K in keyof T]: T[K];
75
75
  } & {};
76
- type InsertOf<S, F extends Fields> = Simplify<{
77
- [K in RequiredKeys<F>]: FieldInsert<S, F[K]>;
76
+ type InsertOf<S, F extends Fields, FW = FileWrite> = Simplify<{
77
+ [K in RequiredKeys<F>]: FieldInsert<S, F[K], FW>;
78
78
  } & {
79
- [K in Exclude<keyof F, RequiredKeys<F>>]?: FieldInsert<S, F[K]>;
79
+ [K in Exclude<keyof F, RequiredKeys<F>>]?: FieldInsert<S, F[K], FW>;
80
80
  }>;
81
- export type Insert<S, N extends keyof S> = InsertOf<S, FieldsOf<S[N]>>;
82
- type StructInsert<S, F extends Fields> = InsertOf<S, F>;
81
+ /** `FW` is what a file field is written with: an upload of the call; in a seed, a resource of the project as well. */
82
+ export type Insert<S, N extends keyof S, FW = FileWrite> = InsertOf<S, FieldsOf<S[N]>, FW>;
83
+ type StructInsert<S, F extends Fields, FW = FileWrite> = InsertOf<S, F, FW>;
83
84
  type FieldUpdate<S, Fld, M extends FieldMeta = MetaOf<Fld>> = M["kind"] extends "one" ? Nullable<M, RefUpdate<Id<S, ModelName<S, Fld>>>> : M["kind"] extends "many" ? RefListUpdate<Id<S, ModelName<S, Fld>>> : M["kind"] extends "list" ? RefListUpdateOrdered<Id<S, ModelName<S, Fld>>> : M["kind"] extends "struct" ? RefUpdateStruct<StructInsert<S, StructFieldsOf<Fld>>, UpdateOf<S, StructFieldsOf<Fld>>> | null : M["kind"] extends "file" ? Nullable<M, Arr<M, FileWrite | StoredFile>> : M["array"] extends true ? TsOf<Fld>[] | PrimitiveListUpdate<TsOf<Fld>> : Nullable<M, TsOf<Fld> | (IsNum<M> extends true ? UpdateNumValue : never)>;
84
85
  type UpdateOf<S, F extends Fields> = {
85
86
  [K in Exclude<keyof F, IdKeys<F>>]?: FieldUpdate<S, F[K]>;
@@ -103,7 +104,8 @@ type IdWhere<S, N extends keyof S> = {
103
104
  export type Where<S, N extends keyof S> = WhereValue<WhereFieldsOf<S, FieldsOf<S[N]>> & IdWhere<S, N>>;
104
105
  type StructWhereFields<S, F extends Fields> = WhereFieldsOf<S, F>;
105
106
  type OrderKeys<S, N extends keyof S> = (ValueKeys<FieldsOf<S[N]>> | keyof Id<S, N>) & keyof Row<S, N>;
106
- /** ONE field and its direction: the engine sorts by a single field, so a second key is a type error. */
107
+ /** One field and its direction. Several keys are several of these, in an array (`$order: [{ createdAt: "desc" },
108
+ * { id: "asc" }]`) or chained `.order()` calls — an object with two keys is refused, since JSON promises no key order. */
107
109
  export type Order<S, N extends keyof S> = {
108
110
  [K in OrderKeys<S, N>]: {
109
111
  [P in K]: "asc" | "desc";
@@ -114,7 +116,7 @@ export type Order<S, N extends keyof S> = {
114
116
  /** The query object — marcidb's wire form: a select shape plus the `$`-clauses. */
115
117
  export type QueryObject<S, N extends keyof S> = Select<S, N> & {
116
118
  $where?: Where<S, N>;
117
- $order?: Order<S, N>;
119
+ $order?: Order<S, N> | Order<S, N>[];
118
120
  $limit?: number;
119
121
  $skip?: number;
120
122
  $cursor?: Id<S, N>;
@@ -128,7 +130,7 @@ export type AggregateQuery<S, N extends keyof S> = {
128
130
  $max?: (ValueKeys<FieldsOf<S[N]>> | keyof Id<S, N>) & string;
129
131
  };
130
132
  /** The per-model type bag marcidb's generic `Query<T>` is parametrised by (what its codegen emits as `UserTypes`). */
131
- export type Types<S, N extends keyof S> = {
133
+ export type Types<S, N extends keyof S, FW = FileWrite> = {
132
134
  name: N & string;
133
135
  model: Row<S, N>;
134
136
  id: Id<S, N>;
@@ -137,7 +139,7 @@ export type Types<S, N extends keyof S> = {
137
139
  query: QueryObject<S, N>;
138
140
  where: Where<S, N>;
139
141
  order: Order<S, N>;
140
- insert: Insert<S, N>;
142
+ insert: Insert<S, N, FW>;
141
143
  update: Update<S, N>;
142
144
  aggregate: AggregateQuery<S, N>;
143
145
  reindex: false;
@@ -148,7 +150,7 @@ export type Types<S, N extends keyof S> = {
148
150
  * `updateMany(data)`/`deleteMany()` write through the chain's `where`; a query placed in another query's
149
151
  * select is a sub-query. `Sel` is the projection: id + scalars until `select(...)` sets a shape.
150
152
  */
151
- export type Query<S, N extends keyof S, Sel = ScalarSelect<S, N>> = MarciQuery<Types<S, N>, Sel>;
153
+ export type Query<S, N extends keyof S, Sel = ScalarSelect<S, N>, FW = FileWrite> = MarciQuery<Types<S, N, FW>, Sel>;
152
154
  /** The rows a query with projection `Q` resolves to (no field keys = id + scalars; sub-queries resolved). */
153
155
  export type Result<S, N extends keyof S, Q> = Rows<Types<S, N>, Q>;
154
156
  /** `db.<model>` — the root query of a model. */
@@ -168,7 +170,18 @@ type DbBase<S extends Schema> = {
168
170
  }>;
169
171
  /** The `.marci` schema text this db syncs with. */
170
172
  readonly $schema: string;
173
+ /**
174
+ * The first rows of this database: run by the host ONCE, on a database that is empty, wherever the
175
+ * database opens (a check, a dev backend, the first deploy). Part of the definition, like sign-in:
176
+ * written on `defineDb(...)` itself. Inside, a file field takes a resource of the project — the path
177
+ * of a picture the design shows (`photo: row.photo`) — and stores it the way an upload is stored.
178
+ */
179
+ seed(fn: (db: SeedDb<S>) => Promise<void>): Db<S>;
171
180
  };
181
+ /** The db a seed writes through: the same queries, a file field taking a resource of the project (its path) too. */
182
+ export type SeedDb<S extends Schema> = {
183
+ [N in keyof S & string as Uncapitalize<N>]: Query<Full<S>, N, ScalarSelect<Full<S>, N>, FileWrite | string>;
184
+ } & Pick<DbBase<S>, "$transaction">;
172
185
  export type Db<S extends Schema> = DbBase<S> & {
173
186
  /**
174
187
  * Sign-in for this database: `model` is the project's model of a user. Adds the platform's `Session`
@@ -180,8 +193,9 @@ export type Db<S extends Schema> = DbBase<S> & {
180
193
  model: N;
181
194
  }): DbWithAuth<S, N>;
182
195
  };
183
- export type DbWithAuth<S extends Schema, N extends keyof S & string> = DbBase<S> & {
196
+ export type DbWithAuth<S extends Schema, N extends keyof S & string> = Omit<DbBase<S>, "seed"> & {
184
197
  readonly auth: Auth<S, N>;
198
+ seed(fn: (db: SeedDb<S>) => Promise<void>): DbWithAuth<S, N>;
185
199
  };
186
200
  /**
187
201
  * The user of the current request, as a query over the project's user model: `select(shape)` picks the
@@ -1,6 +1,9 @@
1
1
  /**
2
2
  * `throw new ApiError(404, "Not found")` inside an endpoint → the client's promise rejects with the
3
- * same status + message. Any other throw is a 500 (message hidden, logged to the server console).
3
+ * same status + message. A write the database refused for a reason a person could have caused reaches
4
+ * the app the same way, in the database's words: a taken unique value, a pointer at no row, a delete
5
+ * other rows forbid are 409, a row that is gone 404. Any other throw is a 500 (message hidden, logged
6
+ * to the server console).
4
7
  */
5
8
  export declare class ApiError extends Error {
6
9
  readonly status: number;
@@ -124,6 +124,10 @@ export type FilesSeam = {
124
124
  /** The id of the `File` row behind an upload of the current request; undefined for anything else —
125
125
  * a value the app sent as JSON is never an upload, whatever it looks like. */
126
126
  uploaded(value: unknown): string | undefined;
127
+ /** While a seed runs: the id of a `File` row made from a resource of the project named by its path
128
+ * (what a path literal of the design is in the server bundle); undefined for a path that is no file
129
+ * of the project. Absent outside a seed: a string the app sent is never a file. */
130
+ resource?(path: string): Promise<string | undefined>;
127
131
  url(id: string, name: string): string;
128
132
  /** Make the upload `id` fit for a `t.image()` field (its bytes are rewritten in place) and say what
129
133
  * it is now. Throws an `ApiError` for what is no picture. */
@@ -3,3 +3,9 @@ export { ApiError } from "./errors";
3
3
  export { channel } from "./channel";
4
4
  export { request } from "./context";
5
5
  export type { StoredFile, StoredImage } from "./files/models";
6
+ /** The value an SVG source is to the client (`assetIcon(…)` compiles to `SvgSource(…)`): a shared module of
7
+ * the design that names an icon per row loads on the server too. The server draws nothing with it. */
8
+ export declare const SvgSource: (svg: string) => {
9
+ svg: string;
10
+ tintColor: string | null;
11
+ };
@@ -27,6 +27,9 @@ export type Color = ColorInput | null;
27
27
  /** Style props every element accepts. */
28
28
  export type BaseStyle = {
29
29
  opacity?: number | `${number}`;
30
+ /** A CSS-style list of `translate` / `scale` / `rotate`. A `%` in a translate is a percent of the
31
+ * element's own box (`translate(-50%, -50%)` centres it on its corner), kept as a percent: it
32
+ * follows the box through every layout. */
30
33
  transform?: string | null;
31
34
  /** Pivot of `transform` (CSS `transform-origin` subset): one or two values, each a keyword
32
35
  * (`left`/`center`/`right`, `top`/`center`/`bottom`), a percentage of the box, or a px length
@@ -6,20 +6,14 @@ export type UIVirtualizedListStyle = DrawableStyle & ElementStyle & {
6
6
  * matter how the history is scrolled. */
7
7
  keyboardDismissMode?: "interactive" | "scroll" | "none";
8
8
  };
9
- export interface VListConfig<T> {
10
- /** The rows, read reactively: the list follows the signals this reads, the way a container's
11
- * function children do. Rows are told apart by `keyOf`; a row whose item is another object
12
- * re-renders. Appended rows and rows added at the start are told from a rewrite (a chat at
13
- * its bottom follows what is appended; older history above moves nothing). */
14
- data: () => T[];
9
+ interface VListBaseConfig<T> {
15
10
  /** Stable unique key of an item. */
16
11
  keyOf: (item: T) => string;
17
12
  /** Subtree factory — called when the item enters the window; the subtree lives until it
18
13
  * leaves. */
19
14
  render: (item: T) => UINodeChild;
20
15
  /** Height estimate (px) used before the first measurement — a number, or a per-item function
21
- * (chat: `msg => 40 + Math.ceil(msg.text.length / 35) * 20`). Computed once when the item is
22
- * added. */
16
+ * (chat: `msg => 40 + Math.ceil(msg.text.length / 35) * 20`). Read when the item is added. */
23
17
  estimatedHeight: number | ((item: T) => number);
24
18
  /** Extra px around the viewport kept mounted. Default: one viewport height. */
25
19
  overscan?: number;
@@ -27,9 +21,23 @@ export interface VListConfig<T> {
27
21
  * list sits at the bottom. */
28
22
  inverted?: boolean;
29
23
  }
30
- /** A windowed (virtualized) list: only the items near the viewport are mounted — for long feeds
31
- * and chats. Fed by `data`, a function over signals. */
32
- export interface UIVirtualizedList<T = unknown> extends UIElementBase<UIVirtualizedListStyle> {
24
+ /** A list fed by `data`: the rows are read reactively, the way a container's function children
25
+ * are. Such a list has no imperative feed (`append` and the rest are not on its type). */
26
+ export interface VListBoundConfig<T> extends VListBaseConfig<T> {
27
+ /** The rows, read reactively: the list follows the signals this reads. Rows are told apart by
28
+ * `keyOf`; a row whose item is another object re-renders. The change is told from the keys:
29
+ * rows gone are removals, new rows are inserts where they stand (a chat at its bottom follows
30
+ * an append; rows above the reader move nothing), a reorder rewrites the keys. */
31
+ data: () => T[];
32
+ }
33
+ /** A list fed by hand: `setData` / `append` / `prepend` / `update` / `removeByKey`. */
34
+ export interface VListFedConfig<T> extends VListBaseConfig<T> {
35
+ data?: undefined;
36
+ }
37
+ export type VListConfig<T> = VListBoundConfig<T> | VListFedConfig<T>;
38
+ /** What both forms of the list share: the items held, scrolling, the scroll / refresh / edge
39
+ * callbacks. */
40
+ export interface UIVirtualizedListBase<T = unknown> extends UIElementBase<UIVirtualizedListStyle> {
33
41
  readonly type: "vlist";
34
42
  /** Number of data items (mounted or not). */
35
43
  readonly itemCount: number;
@@ -51,7 +59,29 @@ export interface UIVirtualizedList<T = unknown> extends UIElementBase<UIVirtuali
51
59
  /** Same, for the top edge — chat history loading. */
52
60
  onStartReached(thresholdPx: number, callback: () => void): this;
53
61
  }
54
- export declare class VListElement<T> extends Element<"vlist"> implements UIVirtualizedList<T> {
62
+ /** A virtualized list fed by `data` (a function over signals): write the signal, the list follows. */
63
+ export interface UIBoundVirtualizedList<T = unknown> extends UIVirtualizedListBase<T> {
64
+ }
65
+ /** A virtualized list fed by hand. Each call goes straight to the runtime: `append` is O(1). */
66
+ export interface UIFedVirtualizedList<T = unknown> extends UIVirtualizedListBase<T> {
67
+ /** Replace the data wholesale — diffed by key like a `data` write (vanished keys are removed,
68
+ * new ones inserted where they stand, a reorder rewrites). */
69
+ setData(items: T[]): this;
70
+ /** Append items to the end (auto-scrolls when `inverted` and resting at the bottom). */
71
+ append(...items: T[]): this;
72
+ /** Insert at the start WITHOUT a scroll jump (the runtime compensates the offset). */
73
+ prepend(...items: T[]): this;
74
+ /** Update items with the same keys: mounted ones re-render; the cached height persists until
75
+ * re-measured. */
76
+ update(...items: T[]): this;
77
+ /** Remove items by key. */
78
+ removeByKey(...keys: string[]): this;
79
+ }
80
+ /** A windowed (virtualized) list: only the items near the viewport are mounted — for long feeds
81
+ * and chats. Fed by `data`, a function over signals, or by hand (`append` and the rest); which
82
+ * one is the config's and the type's. */
83
+ export type UIVirtualizedList<T = unknown> = UIBoundVirtualizedList<T> | UIFedVirtualizedList<T>;
84
+ export declare class VListElement<T> extends Element<"vlist"> implements UIFedVirtualizedList<T> {
55
85
  private readonly sl;
56
86
  private readonly erl;
57
87
  private readonly strl;
@@ -64,15 +94,28 @@ export declare class VListElement<T> extends Element<"vlist"> implements UIVirtu
64
94
  private estimate;
65
95
  private readonly _overscan;
66
96
  private readonly _inverted;
97
+ private readonly _bound;
67
98
  private _endThreshold;
68
99
  private _startThreshold;
69
100
  private _inited;
70
101
  constructor(config: VListConfig<T>);
71
102
  private _ensure;
72
- /** The data as it is now against what the runtime holds: rows added at the end or at the start
73
- * are inserts (the runtime anchors the scroll: a chat follows an append, history above moves
74
- * nothing), rows gone are removals, anything else a rewrite of the keys (measured heights stay). */
103
+ /** The two feeds are exclusive per list: a hand-made change to a list `data` feeds would be
104
+ * undone by the binding's next run, so it is refused and logged. */
105
+ private _fed;
106
+ /** The data as it is now against what the runtime holds. Keys gone are removals, keys new are
107
+ * inserts where they stand in the new order — both anchored by the runtime (a chat follows an
108
+ * append, rows above the reader move nothing) — as long as the keys kept keep their order;
109
+ * a reorder rewrites the keys (measured heights stay). A mounted row whose item is another
110
+ * object re-renders. */
75
111
  private _apply;
112
+ /** The new rows of `items` (keys not held yet, repeats dropped), registered. */
113
+ private _register;
114
+ setData(items: T[]): this;
115
+ append(...items: T[]): this;
116
+ prepend(...items: T[]): this;
117
+ update(...items: T[]): this;
118
+ removeByKey(...keys: string[]): this;
76
119
  get itemCount(): number;
77
120
  getItem(key: string): T | undefined;
78
121
  scrollTo(offset: number, animated?: boolean): void;
@@ -83,6 +126,9 @@ export declare class VListElement<T> extends Element<"vlist"> implements UIVirtu
83
126
  onEndReached(thresholdPx: number, callback: () => void): this;
84
127
  onStartReached(thresholdPx: number, callback: () => void): this;
85
128
  }
86
- /** Create a virtualized list from a config (`data` + `keyOf` + `render` + `estimatedHeight`
87
- * required). Give it a size (`flexGrow: 1` as a screen body). */
88
- export declare function UIVirtualizedList<T>(config: VListConfig<T>): UIVirtualizedList<T>;
129
+ /** Create a virtualized list (`keyOf` + `render` + `estimatedHeight` required). With `data` the
130
+ * list follows the signals it reads and has no hand feed; without it, feed it with `setData` /
131
+ * `append` / `prepend` / `update` / `removeByKey`. Give it a size (`flexGrow: 1` as a screen body). */
132
+ export declare function UIVirtualizedList<T>(config: VListBoundConfig<T>): UIBoundVirtualizedList<T>;
133
+ export declare function UIVirtualizedList<T>(config: VListFedConfig<T>): UIFedVirtualizedList<T>;
134
+ export {};
@@ -1,4 +1,4 @@
1
- export declare const SDK_VERSION = "2.2.3";
1
+ export declare const SDK_VERSION = "2.2.4";
2
2
  /** The major of a semver string, or null when it is not one. */
3
3
  export declare const sdkMajor: (version: string | null | undefined) => number | null;
4
4
  /** The `// sdk: <version>` line of a compiled bundle's header (sdk/src/compile/header.ts), read from