@heswell/vuu-data-engine-local 4.0.0 → 4.1.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
@@ -85,24 +85,15 @@ const DEFAULT_RANGE_LIMITS = {
85
85
  };
86
86
  const tableKey = ({ module, table })=>`${module}:${table}`;
87
87
  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
- };
88
+ const createJoinTable = (joinTable, table1, table2, joinColumn, joinTypeOrOptions = "inner")=>{
89
+ const { joinType = "inner", columns, schema } = "string" == typeof joinTypeOrOptions ? {
90
+ joinType: joinTypeOrOptions
91
+ } : joinTypeOrOptions;
104
92
  const engineJoinTable = new JoinTable({
105
- schema: combinedSchema,
93
+ schema: buildJoinSchema(joinTable, table1.schema, table2.schema, {
94
+ columns,
95
+ schema
96
+ }),
106
97
  baseTable: table1.engineTable,
107
98
  joinTable: table2.engineTable,
108
99
  leftColumn: joinColumn,
@@ -111,6 +102,49 @@ const createJoinTable = (joinTable, table1, table2, joinColumn, joinType = "inne
111
102
  });
112
103
  return new Table_Table(engineJoinTable);
113
104
  };
105
+ const buildJoinSchema = (joinTable, schema1, schema2, { columns, schema })=>{
106
+ const name = `${joinTable.module}:${joinTable.table}`;
107
+ const fail = (message)=>{
108
+ throw Error(`[createJoinTable] ${name}: ${message}`);
109
+ };
110
+ if (columns && schema) fail("pass either columns or schema, not both");
111
+ if (schema) {
112
+ const { module, table } = schema.table;
113
+ if (module !== joinTable.module || table !== joinTable.table) fail(`schema is for table ${module}:${table}`);
114
+ if (schema.key !== schema1.key) fail(`schema key ${schema.key} must be the key of ${schema1.table.table} (${schema1.key})`);
115
+ }
116
+ const sourceColumns = new Set([
117
+ ...schema1.columns,
118
+ ...schema2.columns
119
+ ].map((col)=>col.name));
120
+ const columnNames = schema?.columns.map((col)=>col.name) ?? columns ?? Array.from(sourceColumns).sort();
121
+ const seen = new Set();
122
+ for (const columnName of columnNames){
123
+ if (!sourceColumns.has(columnName)) fail(`unknown column ${columnName}, not in ${schema1.table.table} or ${schema2.table.table}`);
124
+ if (seen.has(columnName)) fail(`duplicate column ${columnName}`);
125
+ seen.add(columnName);
126
+ }
127
+ if (!seen.has(schema1.key)) fail(`columns must include the key column ${schema1.key}`);
128
+ const joinColumns = columnNames.map((columnName)=>({
129
+ name: columnName,
130
+ serverDataType: getServerDataType(columnName, schema1, schema2)
131
+ }));
132
+ if (schema) {
133
+ schema.columns.forEach(({ name: columnName, serverDataType }, i)=>{
134
+ if (serverDataType !== joinColumns[i].serverDataType) fail(`column ${columnName} is declared ${serverDataType} but is ${joinColumns[i].serverDataType} in the source table`);
135
+ });
136
+ return {
137
+ ...schema,
138
+ rangeLimits: schema.rangeLimits ?? DEFAULT_RANGE_LIMITS
139
+ };
140
+ }
141
+ return {
142
+ key: schema1.key,
143
+ table: joinTable,
144
+ rangeLimits: DEFAULT_RANGE_LIMITS,
145
+ columns: joinColumns
146
+ };
147
+ };
114
148
  class TableContainer {
115
149
  #tables = new Map();
116
150
  createTable = (schema, data = [], dataMap = buildDataColumnMapFromSchema(schema), updateGenerator)=>{
@@ -118,8 +152,8 @@ class TableContainer {
118
152
  this.addTable(table);
119
153
  return table;
120
154
  };
121
- createJoinTable(joinTable, table1, table2, joinColumn, joinType = "inner") {
122
- const newTable = createJoinTable(joinTable, this.getTable(table1), this.getTable(table2), joinColumn, joinType);
155
+ createJoinTable(joinTable, table1, table2, joinColumn, joinTypeOrOptions = "inner") {
156
+ const newTable = createJoinTable(joinTable, this.getTable(table1), this.getTable(table2), joinColumn, joinTypeOrOptions);
123
157
  this.addTable(newTable);
124
158
  return newTable;
125
159
  }
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,29 @@ 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
+
154
178
  ### Ranges
155
179
 
156
180
  `subscribe({ range })`, `dataSource.range = …` and `setRange` accept a vuu-utils `Range` or
package/index.js CHANGED
@@ -147,12 +147,12 @@ class RowKeys {
147
147
  return key;
148
148
  }
149
149
  }
150
- const toRange = (range)=>"function" == typeof range.equals && void 0 !== range.withBuffer ? range : Range(range.from, range.to);
151
- const { KEY: KEY } = metadataKeys;
152
150
  const nextFrame = (callback)=>{
153
- if ("function" == typeof globalThis.requestAnimationFrame) globalThis.requestAnimationFrame(callback);
151
+ if ("function" == typeof globalThis.requestAnimationFrame) globalThis.requestAnimationFrame(()=>callback());
154
152
  else globalThis.setTimeout(callback, 0);
155
153
  };
154
+ const toRange = (range)=>"function" == typeof range.equals && void 0 !== range.withBuffer ? range : Range(range.from, range.to);
155
+ const { KEY: KEY } = metadataKeys;
156
156
  const buildTableSchema = (columns, keyColumn)=>({
157
157
  columns: columns.map(toSchemaColumn),
158
158
  key: keyColumn ?? columns[0].name,
@@ -1228,7 +1228,7 @@ class VuuModule {
1228
1228
  handlePostSubscribeActivities = ({ clientViewportId, tableSchema })=>{
1229
1229
  const visualLinks = this.getVisualLinks(tableSchema.table.table);
1230
1230
  const { dataSource } = this.getSubscriptionByViewport(clientViewportId);
1231
- requestAnimationFrame(()=>{
1231
+ nextFrame(()=>{
1232
1232
  dataSource.links = visualLinks;
1233
1233
  });
1234
1234
  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.1.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",
@@ -9,13 +9,35 @@ import type { UpdateGenerator } from "../../rowUpdates";
9
9
  * whichever VuuModule returns it from `tables`.
10
10
  */
11
11
  export declare const createTable: (schema: TableSchema, data?: Array<Array<VuuRowDataItemType | bigint>>, dataMap?: ColumnMap, updateGenerator?: UpdateGenerator) => Table;
12
+ export interface CreateJoinTableOptions {
13
+ /** Defaults to "inner". */
14
+ joinType?: JoinType;
15
+ /**
16
+ * Ordered column names of the join table, possibly a subset of the columns
17
+ * of the two source tables. Must include the key column of table1. Types
18
+ * are taken from the source tables. Defaults to every column of both
19
+ * tables, in alphabetical order.
20
+ */
21
+ columns?: readonly string[];
22
+ /**
23
+ * Declared schema of the join table, e.g. the schema a Vuu server would
24
+ * return in TABLE_META_RESP. Its column order is preserved; every column
25
+ * must exist in one of the source tables, with the same serverDataType, and
26
+ * its key must be the key of table1. Cannot be combined with `columns`.
27
+ */
28
+ schema?: TableSchema;
29
+ }
12
30
  /**
13
31
  * Create a materialized join of two tables, backed by an engine JoinTable,
14
32
  * maintained incrementally as either source changes. `joinType` defaults to
15
33
  * "inner" (only rows with a match in table2). With "leftOuter" every table1
16
34
  * row is present and unmatched table2 columns are null, as in vuu-server.
35
+ *
36
+ * The fifth argument is either a JoinType or a CreateJoinTableOptions, which
37
+ * can also control the columns (and their order) of the join table. Where a
38
+ * column exists in both tables, the value comes from table1.
17
39
  */
18
- export declare const createJoinTable: (joinTable: VuuTable, table1: Table, table2: Table, joinColumn: string, joinType?: JoinType) => Table;
40
+ export declare const createJoinTable: (joinTable: VuuTable, table1: Table, table2: Table, joinColumn: string, joinTypeOrOptions?: JoinType | CreateJoinTableOptions) => Table;
19
41
  /**
20
42
  * Tables, keyed by `{module, table}`. Each LocalEngine owns one, populated
21
43
  * from the tables of its registered modules. Tables created through
@@ -28,7 +50,7 @@ export declare class TableContainer {
28
50
  * Create a join of two tables already in this container. Pass full
29
51
  * `{module, table}` references; a bare name is accepted only if unique.
30
52
  */
31
- createJoinTable(joinTable: VuuTable, table1: VuuTable | string, table2: VuuTable | string, joinColumn: string, joinType?: JoinType): Table;
53
+ createJoinTable(joinTable: VuuTable, table1: VuuTable | string, table2: VuuTable | string, joinColumn: string, joinTypeOrOptions?: JoinType | CreateJoinTableOptions): Table;
32
54
  /**
33
55
  * Add a table, keyed by `as` if given, otherwise by its schema's
34
56
  * `{module, table}`.
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, 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";
@@ -0,0 +1 @@
1
+ export declare const nextFrame: (callback: () => void) => void;