@heswell/vuu-data-engine-local 4.0.0 → 4.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/91.js CHANGED
@@ -1,9 +1,15 @@
1
1
  import { JoinTable, Table } from "@heswell/vuu-table";
2
2
  import { EventEmitter } from "@vuu-ui/vuu-utils";
3
+ const defaultRangeLimits = new WeakMap();
4
+ const setDefaultRangeLimits = (table, rangeLimits)=>{
5
+ defaultRangeLimits.set(table, rangeLimits);
6
+ };
3
7
  class Table_Table extends EventEmitter {
4
8
  engineTable;
5
9
  #dataMap;
10
+ #destroyed = false;
6
11
  #indexOfKey;
12
+ #listener;
7
13
  #updateColumnName;
8
14
  constructor(schemaOrEngineTable, data = [], dataMap, updateGenerator){
9
15
  super();
@@ -11,12 +17,13 @@ class Table_Table extends EventEmitter {
11
17
  this.#dataMap = dataMap ?? this.engineTable.columnMap;
12
18
  this.#indexOfKey = this.engineTable.indexOfKeyField;
13
19
  for (const row of data)this.engineTable.insert(row);
14
- this.engineTable.addListener({
20
+ this.#listener = {
15
21
  onInsert: (_rowIdx, row)=>this.emit("insert", row),
16
22
  onUpdate: (_rowIdx, row)=>this.emit("update", row, this.#updateColumnName),
17
23
  onDelete: (_rowIdx, row)=>this.emit("delete", String(row[this.#indexOfKey])),
18
24
  onClear: ()=>void 0
19
- });
25
+ };
26
+ this.engineTable.addListener(this.#listener);
20
27
  updateGenerator?.setTable(this);
21
28
  updateGenerator?.setRange({
22
29
  from: 0,
@@ -32,6 +39,18 @@ class Table_Table extends EventEmitter {
32
39
  get schema() {
33
40
  return this.engineTable.schema;
34
41
  }
42
+ get rangeLimits() {
43
+ return this.schema.rangeLimits ?? defaultRangeLimits.get(this);
44
+ }
45
+ get destroyed() {
46
+ return this.#destroyed;
47
+ }
48
+ destroy() {
49
+ if (this.#destroyed) return;
50
+ this.#destroyed = true;
51
+ this.engineTable.removeListener(this.#listener);
52
+ if (this.engineTable instanceof JoinTable) this.engineTable.destroy();
53
+ }
35
54
  get name() {
36
55
  return this.engineTable.name;
37
56
  }
@@ -85,49 +104,88 @@ const DEFAULT_RANGE_LIMITS = {
85
104
  };
86
105
  const tableKey = ({ module, table })=>`${module}:${table}`;
87
106
  const createTable = (schema, data = [], dataMap = buildDataColumnMapFromSchema(schema), updateGenerator)=>new Table_Table(schema, data, dataMap, updateGenerator);
88
- const createJoinTable = (joinTable, table1, table2, joinColumn, joinType = "inner")=>{
89
- const { schema: schema1 } = table1;
90
- const { schema: schema2 } = table2;
91
- const combinedColumns = new Set([
92
- ...schema1.columns,
93
- ...schema2.columns
94
- ].map((col)=>col.name).sort());
95
- const combinedSchema = {
96
- key: schema1.key,
97
- table: joinTable,
98
- rangeLimits: DEFAULT_RANGE_LIMITS,
99
- columns: Array.from(combinedColumns).map((columnName)=>({
100
- name: columnName,
101
- serverDataType: getServerDataType(columnName, schema1, schema2)
102
- }))
103
- };
107
+ const createJoinTable = (joinTable, table1, table2, joinColumn, joinTypeOrOptions = "inner")=>{
108
+ const { joinType = "inner", columns, schema } = "string" == typeof joinTypeOrOptions ? {
109
+ joinType: joinTypeOrOptions
110
+ } : joinTypeOrOptions;
104
111
  const engineJoinTable = new JoinTable({
105
- schema: combinedSchema,
112
+ schema: buildJoinSchema(joinTable, table1.schema, table2.schema, {
113
+ columns,
114
+ schema
115
+ }),
106
116
  baseTable: table1.engineTable,
107
117
  joinTable: table2.engineTable,
108
118
  leftColumn: joinColumn,
109
119
  rightColumn: joinColumn,
110
120
  joinType
111
121
  });
112
- return new Table_Table(engineJoinTable);
122
+ const table = new Table_Table(engineJoinTable);
123
+ setDefaultRangeLimits(table, DEFAULT_RANGE_LIMITS);
124
+ return table;
125
+ };
126
+ const buildJoinSchema = (joinTable, schema1, schema2, { columns, schema })=>{
127
+ const name = `${joinTable.module}:${joinTable.table}`;
128
+ const fail = (message)=>{
129
+ throw Error(`[createJoinTable] ${name}: ${message}`);
130
+ };
131
+ if (columns && schema) fail("pass either columns or schema, not both");
132
+ if (schema) {
133
+ const { module, table } = schema.table;
134
+ if (module !== joinTable.module || table !== joinTable.table) fail(`schema is for table ${module}:${table}`);
135
+ if (schema.key !== schema1.key) fail(`schema key ${schema.key} must be the key of ${schema1.table.table} (${schema1.key})`);
136
+ }
137
+ const sourceColumns = new Set([
138
+ ...schema1.columns,
139
+ ...schema2.columns
140
+ ].map((col)=>col.name));
141
+ const columnNames = schema?.columns.map((col)=>col.name) ?? columns ?? Array.from(sourceColumns).sort();
142
+ const seen = new Set();
143
+ for (const columnName of columnNames){
144
+ if (!sourceColumns.has(columnName)) fail(`unknown column ${columnName}, not in ${schema1.table.table} or ${schema2.table.table}`);
145
+ if (seen.has(columnName)) fail(`duplicate column ${columnName}`);
146
+ seen.add(columnName);
147
+ }
148
+ if (!seen.has(schema1.key)) fail(`columns must include the key column ${schema1.key}`);
149
+ const joinColumns = columnNames.map((columnName)=>({
150
+ name: columnName,
151
+ serverDataType: getServerDataType(columnName, schema1, schema2)
152
+ }));
153
+ if (schema) {
154
+ schema.columns.forEach(({ name: columnName, serverDataType }, i)=>{
155
+ if (serverDataType !== joinColumns[i].serverDataType) fail(`column ${columnName} is declared ${serverDataType} but is ${joinColumns[i].serverDataType} in the source table`);
156
+ });
157
+ return schema;
158
+ }
159
+ return {
160
+ key: schema1.key,
161
+ table: joinTable,
162
+ rangeLimits: DEFAULT_RANGE_LIMITS,
163
+ columns: joinColumns
164
+ };
113
165
  };
114
166
  class TableContainer {
115
167
  #tables = new Map();
168
+ #created = new Set();
116
169
  createTable = (schema, data = [], dataMap = buildDataColumnMapFromSchema(schema), updateGenerator)=>{
117
170
  const table = createTable(schema, data, dataMap, updateGenerator);
118
171
  this.addTable(table);
172
+ this.#created.add(table);
119
173
  return table;
120
174
  };
121
- createJoinTable(joinTable, table1, table2, joinColumn, joinType = "inner") {
122
- const newTable = createJoinTable(joinTable, this.getTable(table1), this.getTable(table2), joinColumn, joinType);
175
+ createJoinTable(joinTable, table1, table2, joinColumn, joinTypeOrOptions = "inner") {
176
+ const newTable = createJoinTable(joinTable, this.getTable(table1), this.getTable(table2), joinColumn, joinTypeOrOptions);
123
177
  this.addTable(newTable);
178
+ this.#created.add(newTable);
124
179
  return newTable;
125
180
  }
126
181
  addTable(table, as = table.schema.table) {
127
182
  this.#tables.set(tableKey(as), table);
128
183
  }
129
184
  removeTable(table) {
130
- return this.#tables.delete(tableKey(table));
185
+ const key = tableKey(table);
186
+ const found = this.#tables.get(key);
187
+ if (found) this.#created.delete(found);
188
+ return this.#tables.delete(key);
131
189
  }
132
190
  hasTable(table) {
133
191
  return this.#tables.has(tableKey(table));
@@ -147,6 +205,8 @@ class TableContainer {
147
205
  return Array.from(this.#tables.values());
148
206
  }
149
207
  clear() {
208
+ for (const table of this.#created)table.destroy();
209
+ this.#created.clear();
150
210
  this.#tables.clear();
151
211
  }
152
212
  }
package/README.md CHANGED
@@ -33,8 +33,9 @@ a second set of React contexts). They are `@vuu-ui/vuu-utils` and `@vuu-ui/vuu-f
33
33
  ## Usage
34
34
 
35
35
  Importing the package has no side effects: no modules, tables or timers are created
36
- until you ask for them. The main entry needs no DOM, so it runs in plain Node or Bun. The
37
- React bindings (`createDataSourceProvider`, `createLocalVuuServer`,
36
+ until you ask for them. The main entry needs no DOM, so it runs in plain Node or Bun with
37
+ no shims: it uses `requestAnimationFrame` when present and falls back to `setTimeout`
38
+ otherwise. The React bindings (`createDataSourceProvider`, `createLocalVuuServer`,
38
39
  `LocalDataSourceProvider`) are in `@heswell/vuu-data-engine-local/react`, because
39
40
  `@vuu-ui/core` needs a DOM (`ResizeObserver`) at import time.
40
41
 
@@ -151,6 +152,49 @@ changes. Pass `"leftOuter"` to keep every `table1` row. For a row with no match,
151
152
  Sort places nulls first when ascending; relational filters (`<`, `>=`, …) never match
152
153
  null; and aggregations skip nulls.
153
154
 
155
+ By default the join table has every column of both tables, in alphabetical order. To
156
+ control the columns, pass an options object as the fifth argument instead of a join type:
157
+
158
+ ```ts
159
+ // an ordered list of column names, possibly a subset (must include table1's key)
160
+ createJoinTable(joinTable, instruments, prices, "ric", {
161
+ joinType: "leftOuter",
162
+ columns: ["ric", "exchange", "bid", "ask"],
163
+ });
164
+
165
+ // or a declared TableSchema for the join table, e.g. the schema vuu-server returns
166
+ createJoinTable(instrumentPricesSchema.table, instruments, prices, "ric", {
167
+ joinType: "leftOuter",
168
+ schema: instrumentPricesSchema,
169
+ });
170
+ ```
171
+
172
+ `columns` and `schema` are mutually exclusive, and `joinType` still defaults to `"inner"`.
173
+ Column types come from the source tables; a column in both tables takes its value from
174
+ `table1`. Unknown or duplicate columns, a missing key column, a declared `serverDataType`
175
+ that differs from the source column, or a `schema` whose `table` or `key` does not match
176
+ (`joinTable`, `table1`'s key) throw an error naming the join table and the problem.
177
+
178
+ With `schema`, the join's `Table.schema` is exactly the declared schema (no keys added), so
179
+ it matches `getTableSchema` / TABLE_META_RESP and can be published as a module's
180
+ `schemas`. If the declared schema has no `rangeLimits`, the engine default
181
+ (`maxRangeEnd: 1_000_000`, `maxRangeWidth: 1_000`) still applies, exposed as
182
+ `table.rangeLimits` rather than added to the schema. Joins created without `schema`
183
+ (default or `columns`) generate a schema that includes those default `rangeLimits`.
184
+
185
+ `createJoinTable` returns a `JoinTableHandle` (`Table<JoinTable>`), whose `engineTable` is
186
+ typed as the `@heswell/vuu-table` `JoinTable`. Call `table.destroy()` to detach a join from
187
+ its source tables, after which source changes no longer propagate to it. `destroy()` is
188
+ available on every `Table` and is safe to call more than once. You rarely need to call it
189
+ yourself:
190
+
191
+ - `module.dispose()` (called by `engine.dispose()` for modules the engine owns) destroys the
192
+ join tables the module publishes.
193
+ - `tableContainer.clear()` (also called by `engine.dispose()`) destroys tables created by
194
+ that container's `createTable` / `createJoinTable`.
195
+ - `engine.unregister()` and `tableContainer.removeTable()` do not destroy tables, as they
196
+ may still be in use.
197
+
154
198
  ### Ranges
155
199
 
156
200
  `subscribe({ range })`, `dataSource.range = …` and `setRange` accept a vuu-utils `Range` or
package/index.js CHANGED
@@ -1,3 +1,4 @@
1
+ import { JoinTable } from "@heswell/vuu-table";
1
2
  import { Clock, EventEmitter, NULL_RANGE, Range, StaleUpdateError, assertExpectedSessionTable, filterAsQuery, isAddRowRpcRequest, isBeginEditSessionRpcRequest, isConfigChanged, isCreateSessionTableRpcRequest, isDeleteSelectedRowsRpcRequest, isEditCellRpcRequest, isEndEditSessionRpcRequest, isRpcSuccess, isTypeaheadRequest, isUndoRowChangeRpcRequest, metadataKeys, sessionDataSourceConfig, toSchemaColumn, uuid, vanillaConfig, withConfigDefaults } from "@vuu-ui/vuu-utils";
2
3
  import { inMemoryDataEngine } from "@heswell/vuu-viewport";
3
4
  import { parseFilter } from "@vuu-ui/vuu-filter-parser";
@@ -147,12 +148,12 @@ class RowKeys {
147
148
  return key;
148
149
  }
149
150
  }
150
- const toRange = (range)=>"function" == typeof range.equals && void 0 !== range.withBuffer ? range : Range(range.from, range.to);
151
- const { KEY: KEY } = metadataKeys;
152
151
  const nextFrame = (callback)=>{
153
- if ("function" == typeof globalThis.requestAnimationFrame) globalThis.requestAnimationFrame(callback);
152
+ if ("function" == typeof globalThis.requestAnimationFrame) globalThis.requestAnimationFrame(()=>callback());
154
153
  else globalThis.setTimeout(callback, 0);
155
154
  };
155
+ const toRange = (range)=>"function" == typeof range.equals && void 0 !== range.withBuffer ? range : Range(range.from, range.to);
156
+ const { KEY: KEY } = metadataKeys;
156
157
  const buildTableSchema = (columns, keyColumn)=>({
157
158
  columns: columns.map(toSchemaColumn),
158
159
  key: keyColumn ?? columns[0].name,
@@ -362,7 +363,8 @@ class EngineDataSource extends EventEmitter {
362
363
  this.clientCallback = callback;
363
364
  this.viewport = viewport;
364
365
  this.#status = "subscribed";
365
- if (this.tableSchema.rangeLimits) this.#maxRangeEnd = this.tableSchema.rangeLimits.maxRangeEnd;
366
+ const rangeLimits = this.#table.rangeLimits;
367
+ if (rangeLimits) this.#maxRangeEnd = rangeLimits.maxRangeEnd;
366
368
  let config = this._config;
367
369
  const hasConfigProps = aggregations || columns || filterSpec || groupBy || sort;
368
370
  if (hasConfigProps) config = {
@@ -1160,6 +1162,7 @@ class VuuModule {
1160
1162
  if (this.#engine === engine) this.#engine = void 0;
1161
1163
  }
1162
1164
  dispose() {
1165
+ for (const table of Object.values(this.tables))if (table.engineTable instanceof JoinTable) table.destroy();
1163
1166
  this.#sessionTableMap = {};
1164
1167
  this.#sessionSourceTableMap = {};
1165
1168
  this.#sessionSnapshots.clear();
@@ -1228,7 +1231,7 @@ class VuuModule {
1228
1231
  handlePostSubscribeActivities = ({ clientViewportId, tableSchema })=>{
1229
1232
  const visualLinks = this.getVisualLinks(tableSchema.table.table);
1230
1233
  const { dataSource } = this.getSubscriptionByViewport(clientViewportId);
1231
- requestAnimationFrame(()=>{
1234
+ nextFrame(()=>{
1232
1235
  dataSource.links = visualLinks;
1233
1236
  });
1234
1237
  if (this.visualLinks && dataSource.tableSchema) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@heswell/vuu-data-engine-local",
3
- "version": "4.0.0",
3
+ "version": "4.2.0",
4
4
  "description": "In-browser Vuu server (VuuModule, tables, EngineDataSource) built on @heswell/vuu-table and @heswell/vuu-viewport",
5
5
  "main": "./index.js",
6
6
  "module": "./index.js",
@@ -95,7 +95,7 @@ export declare class EngineDataSource extends EventEmitter<DataSourceEvents> imp
95
95
  get size(): number;
96
96
  get table(): import("@vuu-ui/vuu-data-types").TableSchemaTable;
97
97
  /** The table backing this dataSource */
98
- get dataTable(): Table;
98
+ get dataTable(): Table<import("@heswell/vuu-table").Table>;
99
99
  get columnMap(): ColumnMap;
100
100
  /**
101
101
  * Flat (ungrouped) rows can be resolved at any index. Grouped rows are
package/src/Table.d.ts CHANGED
@@ -4,6 +4,12 @@ import type { VuuRowDataItemType } from "@vuu-ui/vuu-protocol-types";
4
4
  import { type ColumnMap, EventEmitter } from "@vuu-ui/vuu-utils";
5
5
  import type { UpdateGenerator } from "./rowUpdates";
6
6
  export type TableRow = Array<VuuRowDataItemType | bigint>;
7
+ export type RangeLimits = NonNullable<TableSchema["rangeLimits"]>;
8
+ /**
9
+ * @internal Range limits applied when the table's schema declares none,
10
+ * without adding them to the (exposed) schema.
11
+ */
12
+ export declare const setDefaultRangeLimits: (table: Table<EngineTable>, rangeLimits: RangeLimits) => void;
7
13
  export type TableEvents = {
8
14
  delete: (key: string) => void;
9
15
  insert: (row: TableRow) => void;
@@ -17,14 +23,30 @@ export type TableEvents = {
17
23
  *
18
24
  * Events are emitted from engine table listeners, so changes applied
19
25
  * directly to the engine table (e.g. by a JoinTable) are also observed.
26
+ *
27
+ * `E` is the type of the engine table, `JoinTable` for tables created by
28
+ * `createJoinTable` (see `JoinTableHandle`).
20
29
  */
21
- export declare class Table extends EventEmitter<TableEvents> {
30
+ export declare class Table<E extends EngineTable = EngineTable> extends EventEmitter<TableEvents> {
22
31
  #private;
23
- readonly engineTable: EngineTable;
24
- constructor(schemaOrEngineTable: TableSchema | EngineTable, data?: TableRow[], dataMap?: ColumnMap, updateGenerator?: UpdateGenerator);
32
+ readonly engineTable: E;
33
+ constructor(schemaOrEngineTable: TableSchema | E, data?: TableRow[], dataMap?: ColumnMap, updateGenerator?: UpdateGenerator);
25
34
  get data(): TableRow[];
26
35
  get map(): ColumnMap;
27
36
  get schema(): TableSchema;
37
+ /**
38
+ * The range limits in effect: those of the schema if it declares any,
39
+ * otherwise any engine default (applied to join tables), else undefined
40
+ * (unlimited). Never added to `schema`.
41
+ */
42
+ get rangeLimits(): RangeLimits | undefined;
43
+ get destroyed(): boolean;
44
+ /**
45
+ * Release the table. For a join table this detaches the join from its
46
+ * source tables, so source changes no longer propagate. This table stops
47
+ * emitting events. Safe to call on any table, and more than once.
48
+ */
49
+ destroy(): void;
28
50
  get name(): string;
29
51
  get rowCount(): number;
30
52
  delete(key: string): void;
@@ -43,7 +43,7 @@ export declare class BasketModule extends VuuModule<BasketsTableName> {
43
43
  priceStrategyType: undefined;
44
44
  };
45
45
  get menuServices(): Record<BasketsTableName, RpcMenuService[] | undefined> | undefined;
46
- get tables(): Record<BasketsTableName, Table>;
46
+ get tables(): Record<BasketsTableName, Table<import("@heswell/vuu-table").Table>>;
47
47
  get visualLinks(): {
48
48
  basketConstituent: {
49
49
  fromColumn: string;
@@ -55,8 +55,9 @@ export declare abstract class VuuModule<T extends string = string> implements IV
55
55
  detachEngine(engine: LocalEngine): void;
56
56
  /**
57
57
  * Release resources (timers, providers). Called by `LocalEngine.dispose`
58
- * for modules it owns. Subclasses that start timers should override,
59
- * calling super.dispose().
58
+ * for modules it owns. Join tables published by the module are destroyed,
59
+ * detaching them from their source tables. Subclasses that start timers
60
+ * should override, calling super.dispose().
60
61
  */
61
62
  dispose(): void;
62
63
  protected abstract menus?: Record<T, VuuMenu | undefined> | undefined;
@@ -174,9 +175,9 @@ export declare abstract class VuuModule<T extends string = string> implements IV
174
175
  private addRow;
175
176
  private endEditSession;
176
177
  private applyBulkEdits;
177
- protected createSessionTable(sourceTable: Table, sessionTableName: string, editSessionMode: EditSessionMode | CopyOption, dataSource: ModuleDataSource, sessionType?: SessionType): Table;
178
- protected createSessionTableWithAllRows({ data, schema }: Table, _sessionTableName: string): Table;
179
- protected createEmptySessionTable({ schema }: Table, sessionTableName: string, sessionType?: SessionType): Table;
180
- protected createSessionTableFromSelectedRows({ data, map, schema }: Table, sessionTableName: string, dataSource: ModuleDataSource): Table;
178
+ protected createSessionTable(sourceTable: Table, sessionTableName: string, editSessionMode: EditSessionMode | CopyOption, dataSource: ModuleDataSource, sessionType?: SessionType): Table<import("@heswell/vuu-table").Table>;
179
+ protected createSessionTableWithAllRows({ data, schema }: Table, _sessionTableName: string): Table<import("@heswell/vuu-table").Table>;
180
+ protected createEmptySessionTable({ schema }: Table, sessionTableName: string, sessionType?: SessionType): Table<import("@heswell/vuu-table").Table>;
181
+ protected createSessionTableFromSelectedRows({ data, map, schema }: Table, sessionTableName: string, dataSource: ModuleDataSource): Table<import("@heswell/vuu-table").Table>;
181
182
  }
182
183
  export {};
@@ -1,39 +1,77 @@
1
- import { type JoinType } from "@heswell/vuu-table";
1
+ import { JoinTable, type JoinType } from "@heswell/vuu-table";
2
2
  import type { TableSchema } from "@vuu-ui/vuu-data-types";
3
3
  import { Table } from "../../Table";
4
4
  import type { VuuRowDataItemType, VuuTable } from "@vuu-ui/vuu-protocol-types";
5
5
  import type { ColumnMap } from "@vuu-ui/vuu-utils";
6
6
  import type { UpdateGenerator } from "../../rowUpdates";
7
+ /**
8
+ * A Table backed by an engine `JoinTable`, as returned by `createJoinTable`.
9
+ * `destroy()` detaches the join from its source tables.
10
+ */
11
+ export type JoinTableHandle = Table<JoinTable>;
7
12
  /**
8
13
  * Create a table. The table is not registered anywhere; it belongs to
9
14
  * whichever VuuModule returns it from `tables`.
10
15
  */
11
- export declare const createTable: (schema: TableSchema, data?: Array<Array<VuuRowDataItemType | bigint>>, dataMap?: ColumnMap, updateGenerator?: UpdateGenerator) => Table;
16
+ export declare const createTable: (schema: TableSchema, data?: Array<Array<VuuRowDataItemType | bigint>>, dataMap?: ColumnMap, updateGenerator?: UpdateGenerator) => Table<import("@heswell/vuu-table").Table>;
17
+ export interface CreateJoinTableOptions {
18
+ /** Defaults to "inner". */
19
+ joinType?: JoinType;
20
+ /**
21
+ * Ordered column names of the join table, possibly a subset of the columns
22
+ * of the two source tables. Must include the key column of table1. Types
23
+ * are taken from the source tables. Defaults to every column of both
24
+ * tables, in alphabetical order.
25
+ */
26
+ columns?: readonly string[];
27
+ /**
28
+ * Declared schema of the join table, e.g. the schema a Vuu server would
29
+ * return in TABLE_META_RESP. Its column order is preserved; every column
30
+ * must exist in one of the source tables, with the same serverDataType, and
31
+ * its key must be the key of table1. Cannot be combined with `columns`.
32
+ * The join's `schema` is exactly this schema; if it declares no
33
+ * `rangeLimits`, the engine default limits are applied without being added
34
+ * to the schema.
35
+ */
36
+ schema?: TableSchema;
37
+ }
12
38
  /**
13
39
  * Create a materialized join of two tables, backed by an engine JoinTable,
14
40
  * maintained incrementally as either source changes. `joinType` defaults to
15
41
  * "inner" (only rows with a match in table2). With "leftOuter" every table1
16
42
  * row is present and unmatched table2 columns are null, as in vuu-server.
43
+ *
44
+ * The fifth argument is either a JoinType or a CreateJoinTableOptions, which
45
+ * can also control the columns (and their order) of the join table. Where a
46
+ * column exists in both tables, the value comes from table1.
47
+ *
48
+ * Call `destroy()` on the returned table to detach it from its source tables.
49
+ * Tables published by a module are destroyed when the module is disposed.
17
50
  */
18
- export declare const createJoinTable: (joinTable: VuuTable, table1: Table, table2: Table, joinColumn: string, joinType?: JoinType) => Table;
51
+ export declare const createJoinTable: (joinTable: VuuTable, table1: Table, table2: Table, joinColumn: string, joinTypeOrOptions?: JoinType | CreateJoinTableOptions) => JoinTableHandle;
19
52
  /**
20
53
  * Tables, keyed by `{module, table}`. Each LocalEngine owns one, populated
21
54
  * from the tables of its registered modules. Tables created through
22
- * `createTable` / `createJoinTable` are added to this container.
55
+ * `createTable` / `createJoinTable` are added to this container, and are
56
+ * destroyed by `clear()`.
23
57
  */
24
58
  export declare class TableContainer {
25
59
  #private;
26
- createTable: (schema: TableSchema, data?: Array<Array<VuuRowDataItemType | bigint>>, dataMap?: ColumnMap, updateGenerator?: UpdateGenerator) => Table;
60
+ createTable: (schema: TableSchema, data?: Array<Array<VuuRowDataItemType | bigint>>, dataMap?: ColumnMap, updateGenerator?: UpdateGenerator) => Table<import("@heswell/vuu-table").Table>;
27
61
  /**
28
62
  * Create a join of two tables already in this container. Pass full
29
63
  * `{module, table}` references; a bare name is accepted only if unique.
30
64
  */
31
- createJoinTable(joinTable: VuuTable, table1: VuuTable | string, table2: VuuTable | string, joinColumn: string, joinType?: JoinType): Table;
65
+ createJoinTable(joinTable: VuuTable, table1: VuuTable | string, table2: VuuTable | string, joinColumn: string, joinTypeOrOptions?: JoinType | CreateJoinTableOptions): JoinTableHandle;
32
66
  /**
33
67
  * Add a table, keyed by `as` if given, otherwise by its schema's
34
68
  * `{module, table}`.
35
69
  */
36
70
  addTable(table: Table, as?: VuuTable): void;
71
+ /**
72
+ * Remove a table from the container. The table is not destroyed, it may
73
+ * still be in use elsewhere; call its `destroy()` if not.
74
+ */
37
75
  removeTable(table: VuuTable): boolean;
38
76
  hasTable(table: VuuTable): boolean;
39
77
  /**
@@ -44,5 +82,10 @@ export declare class TableContainer {
44
82
  getTable(table: VuuTable | string): Table;
45
83
  getTable<T>(table: VuuTable | string): T;
46
84
  get tables(): Table[];
85
+ /**
86
+ * Remove every table, destroying those created by this container's
87
+ * `createTable` / `createJoinTable` (detaching join tables from their
88
+ * sources). Tables added with `addTable` are not destroyed.
89
+ */
47
90
  clear(): void;
48
91
  }
package/src/index.d.ts CHANGED
@@ -4,7 +4,7 @@ export * from "./core/module/VuuModule";
4
4
  export * from "./core/module/edit-hooks";
5
5
  export { type AnyVuuModule, createLocalEngine, ensureVuuModule, getDefaultEngine, LocalEngine, type LocalEngineOptions, resetDefaultEngine, } from "./core/engine/LocalEngine";
6
6
  export * from "./core/user/CurrentUser";
7
- export { createJoinTable, createTable, TableContainer, } from "./core/table/TableContainer";
7
+ export { type CreateJoinTableOptions, createJoinTable, createTable, type JoinTableHandle, TableContainer, } from "./core/table/TableContainer";
8
8
  export { tableContainer } from "./core/table/defaultTableContainer";
9
9
  export { registerDemoModules, type DemoModules } from "./demo-modules";
10
10
  export { BaseUpdateGenerator } from "./UpdateGenerator";
@@ -15,7 +15,7 @@ export * from "./simul";
15
15
  export * from "./basket";
16
16
  export * from "./module-admin";
17
17
  export * from "./user-admin";
18
- export { buildDataColumnMap, buildDataColumnMapFromSchema, Table, type TableEvents, } from "./Table";
18
+ export { buildDataColumnMap, buildDataColumnMapFromSchema, type RangeLimits, Table, type TableEvents, } from "./Table";
19
19
  export * from "./test";
20
20
  export * from "./EngineDataSource";
21
21
  export * from "./ModuleDataSource";
@@ -0,0 +1 @@
1
+ export declare const nextFrame: (callback: () => void) => void;
@@ -35,6 +35,6 @@ export declare class NotificationModule extends VuuModule<NotificationsTableName
35
35
  get menuServices(): undefined;
36
36
  get schemas(): Record<"notifications", TableSchema>;
37
37
  get services(): Record<"notifications", RpcService[]>;
38
- get tables(): Record<"notifications", Table>;
38
+ get tables(): Record<"notifications", Table<import("@heswell/vuu-table").Table>>;
39
39
  get visualLinks(): undefined;
40
40
  }
@@ -2,5 +2,5 @@ import type { VuuRowDataItemType } from "@vuu-ui/vuu-protocol-types";
2
2
  import { Table } from "./Table";
3
3
  import type { TableSchema } from "@vuu-ui/vuu-data-types";
4
4
  export declare const sessionTableRow: (row: Array<bigint | VuuRowDataItemType>, schema: TableSchema) => (bigint | VuuRowDataItemType)[];
5
- export declare const createSessionTableFromSelectedRows: (table: Table, selectedRowIds: string[]) => Table;
5
+ export declare const createSessionTableFromSelectedRows: (table: Table, selectedRowIds: string[]) => Table<import("@heswell/vuu-table").Table>;
6
6
  export declare const sessionTableSchema: (schema: TableSchema) => TableSchema;
@@ -24,7 +24,7 @@ export declare class SimulModule extends VuuModule<SimulTableName> {
24
24
  get editServices(): undefined;
25
25
  get menuServices(): Record<SimulTableName, RpcMenuService[] | undefined> | undefined;
26
26
  get services(): Record<SimulTableName, RpcService[] | undefined> | undefined;
27
- get tables(): Record<SimulTableName, Table>;
27
+ get tables(): Record<SimulTableName, Table<import("@heswell/vuu-table").Table>>;
28
28
  get visualLinks(): Record<SimulTableName, VuuLink[] | undefined>;
29
29
  cancelOrder: MenuServiceHandler;
30
30
  startOrders: ServiceHandler;
@@ -1 +1 @@
1
- export declare const createInstrumentsExtendedTable: () => import("../..").Table;
1
+ export declare const createInstrumentsExtendedTable: () => import("../..").Table<import("@heswell/vuu-table").Table>;
@@ -31,4 +31,4 @@ export declare const InstrumentColumnMap: {
31
31
  };
32
32
  export declare const getInstrumentsData: () => InstrumentsDataRow[];
33
33
  export declare const getRic: (defaultRic: string) => string;
34
- export declare const createInstrumentsTable: () => import("../..").Table;
34
+ export declare const createInstrumentsTable: () => import("../..").Table<import("@heswell/vuu-table").Table>;
@@ -32,4 +32,4 @@ export declare const OrderColumnMap: {
32
32
  readonly trader: 8;
33
33
  };
34
34
  export declare const getOrdersData: () => OrdersDataRow[];
35
- export declare const createOrdersTable: () => import("../..").Table;
35
+ export declare const createOrdersTable: () => import("../..").Table<import("@heswell/vuu-table").Table>;
@@ -10,6 +10,6 @@ export declare class TestModule extends VuuModule<TestTableName> {
10
10
  get services(): undefined;
11
11
  get menuServices(): Record<TestTableName, RpcMenuService[] | undefined> | undefined;
12
12
  get schemas(): Record<TestTableName, TableSchema>;
13
- get tables(): Record<TestTableName, Table>;
13
+ get tables(): Record<TestTableName, Table<import("@heswell/vuu-table").Table>>;
14
14
  get visualLinks(): undefined;
15
15
  }
@@ -23,7 +23,7 @@ export declare class UserAdminModule extends VuuModule<UserAdminTableName> {
23
23
  get menuServices(): undefined;
24
24
  get services(): Record<UserAdminTableName, RpcService[]>;
25
25
  reconcile(): Promise<void>;
26
- protected createSessionTable(sourceTable: Table, sessionTableName: string, editSessionMode: import("@vuu-ui/vuu-data-types").EditSessionMode | import("@vuu-ui/vuu-data-types").CopyOption, dataSource: import("../ModuleDataSource").ModuleDataSource, sessionType?: import("@vuu-ui/vuu-data-types").SessionType): Table;
26
+ protected createSessionTable(sourceTable: Table, sessionTableName: string, editSessionMode: import("@vuu-ui/vuu-data-types").EditSessionMode | import("@vuu-ui/vuu-data-types").CopyOption, dataSource: import("../ModuleDataSource").ModuleDataSource, sessionType?: import("@vuu-ui/vuu-data-types").SessionType): Table<import("@heswell/vuu-table").Table>;
27
27
  protected beforeSessionSave(sourceTable: Table, sessionTable: Table): Promise<void>;
28
28
  protected afterSessionSave(sourceTable: Table, sessionTable: Table): Promise<void>;
29
29
  private saveGroupsFromSession;