@peers-app/peers-sdk 0.8.23 → 0.9.1

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.
Files changed (59) hide show
  1. package/dist/context/user-context-singleton.d.ts +1 -0
  2. package/dist/context/user-context-singleton.js +8 -0
  3. package/dist/context/user-context.js +3 -0
  4. package/dist/data/assistants.d.ts +1 -1
  5. package/dist/data/assistants.js +1 -1
  6. package/dist/data/change-tracking.d.ts +8 -0
  7. package/dist/data/change-tracking.js +18 -0
  8. package/dist/data/channels.d.ts +2 -2
  9. package/dist/data/channels.js +3 -2
  10. package/dist/data/data-locks.test.js +8 -4
  11. package/dist/data/devices.d.ts +2 -2
  12. package/dist/data/devices.js +3 -2
  13. package/dist/data/embeddings.d.ts +1 -1
  14. package/dist/data/embeddings.js +2 -1
  15. package/dist/data/files/file.types.d.ts +2 -2
  16. package/dist/data/files/file.types.js +3 -2
  17. package/dist/data/groups.d.ts +1 -1
  18. package/dist/data/groups.js +1 -1
  19. package/dist/data/index.d.ts +1 -0
  20. package/dist/data/index.js +1 -0
  21. package/dist/data/messages.d.ts +5 -5
  22. package/dist/data/messages.js +5 -5
  23. package/dist/data/orm/dependency-injection.test.js +5 -2
  24. package/dist/data/orm/sql-db.d.ts +3 -1
  25. package/dist/data/orm/sql.data-source.test.js +5 -3
  26. package/dist/data/orm/table-container.d.ts +33 -0
  27. package/dist/data/orm/table-container.js +202 -8
  28. package/dist/data/orm/table-container.test.d.ts +1 -0
  29. package/dist/data/orm/table-container.test.js +163 -0
  30. package/dist/data/orm/table.d.ts +1 -0
  31. package/dist/data/orm/table.js +14 -2
  32. package/dist/data/orm/types.d.ts +2 -1
  33. package/dist/data/orm/types.js +14 -5
  34. package/dist/data/orm/types.test.js +136 -1
  35. package/dist/data/packages.d.ts +5 -5
  36. package/dist/data/packages.js +6 -5
  37. package/dist/data/peer-events/peer-event-handlers.d.ts +3 -3
  38. package/dist/data/peer-events/peer-event-handlers.js +4 -3
  39. package/dist/data/peer-events/peer-event-types.d.ts +1 -1
  40. package/dist/data/peer-events/peer-event-types.js +2 -1
  41. package/dist/data/persistent-vars.d.ts +1 -1
  42. package/dist/data/persistent-vars.js +2 -1
  43. package/dist/data/table-definitions-table.d.ts +31 -0
  44. package/dist/data/table-definitions-table.js +28 -0
  45. package/dist/data/tool-tests.d.ts +2 -2
  46. package/dist/data/tool-tests.js +3 -2
  47. package/dist/data/tools.d.ts +1 -1
  48. package/dist/data/tools.js +2 -1
  49. package/dist/data/user-trust-levels.d.ts +1 -1
  50. package/dist/data/user-trust-levels.js +2 -1
  51. package/dist/data/users.d.ts +1 -2
  52. package/dist/data/users.js +3 -4
  53. package/dist/data/workflow-logs.d.ts +2 -2
  54. package/dist/data/workflow-runs.d.ts +2 -2
  55. package/dist/keys.js +7 -1
  56. package/dist/types/workflow.d.ts +7 -7
  57. package/dist/types/workflow.js +6 -5
  58. package/dist/types/zod-types.js +2 -0
  59. package/package.json +1 -1
@@ -1,15 +1,56 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.TableContainer = void 0;
4
+ exports.checkVersionedUpdate = checkVersionedUpdate;
4
5
  const types_1 = require("./types");
5
6
  const table_1 = require("./table");
6
7
  const table_definitions_system_1 = require("./table-definitions.system");
7
8
  const utils_1 = require("../../utils");
9
+ const rpc_types_1 = require("../../rpc-types");
10
+ /**
11
+ * Compare two table definition versions and decide whether to update.
12
+ * @returns 'update' when incoming version is strictly newer, 'skip' otherwise.
13
+ * Logs an error if same version but different definitions (caller should bump versionNumber).
14
+ */
15
+ function checkVersionedUpdate(existingVersion, incomingVersion, existingMetaData, incomingMetaData) {
16
+ if (existingVersion < incomingVersion) {
17
+ return 'update';
18
+ }
19
+ if (existingVersion === incomingVersion) {
20
+ const sameDefinition = (0, utils_1.simpleObjectHash)(existingMetaData) === (0, utils_1.simpleObjectHash)(incomingMetaData);
21
+ if (!sameDefinition) {
22
+ const tableName = (0, utils_1.getFullTableName)(existingMetaData);
23
+ console.error(`Table "${tableName}" has two different definitions with the same versionNumber (${existingMetaData.versionNumber}). Skipping update. Increment versionNumber when changing table definitions.`);
24
+ }
25
+ }
26
+ return 'skip';
27
+ // commenting this out for now, it's too likely to end up in an infinite loop
28
+ // if (existingVersion > incomingVersion) {
29
+ // return 'skip';
30
+ // }
31
+ // if (existingVersion === incomingVersion) {
32
+ // const sameDefinition = simpleObjectHash(existingMetaData) === simpleObjectHash(incomingMetaData);
33
+ // if (sameDefinition) {
34
+ // return 'skip'; // same version, same definition — nothing to do
35
+ // }
36
+ // if (!incomingVersion) {
37
+ // return 'update-warn-unversioned';
38
+ // }
39
+ // return 'skip-warn-conflict';
40
+ // }
41
+ // return 'update'; // incoming is strictly newer
42
+ }
8
43
  class TableContainer {
9
44
  tableFactory;
10
45
  groupId;
11
46
  tableDefinitions = {};
12
47
  tableInstances = {};
48
+ savingDefinitions = false;
49
+ // Fallback tier: bare-bones definitions from synced TableDefinitionRecords (no custom constructors).
50
+ // These live in a separate map so they never block full instances from being created.
51
+ fallbackDefinitions = {};
52
+ fallbackInstances = {};
53
+ fallbackSubscribed = false;
13
54
  constructor(tableFactory, groupId) {
14
55
  this.tableFactory = tableFactory;
15
56
  this.groupId = groupId;
@@ -26,6 +67,10 @@ class TableContainer {
26
67
  return;
27
68
  }
28
69
  if (existing && opts?.overwrite) {
70
+ const existingDef = this.tableDefinitions[tableName];
71
+ if (existingDef && (0, utils_1.simpleObjectHash)(existingDef.metaData) === (0, utils_1.simpleObjectHash)(tableDefinition.metaData)) {
72
+ return; // definition hasn't changed, no need to overwrite
73
+ }
29
74
  console.warn(`Overwriting table definition for ${tableName}.`);
30
75
  delete this.tableInstances[tableName];
31
76
  }
@@ -63,34 +108,183 @@ class TableContainer {
63
108
  const TableClass = tableConstructor || registeredDef?.tableConstructor || (table_1.Table);
64
109
  const table = this.tableFactory(metaData, schema, TableClass, this.groupId);
65
110
  this.tableInstances[tableName] = table;
111
+ this.saveTableDefinition(metaData);
66
112
  }
67
113
  return this.tableInstances[tableName];
68
114
  }
115
+ /**
116
+ * Auto-save table definition to the TableDefinitions system table.
117
+ * Only runs on server side, only for tables with a tableId (skips system tables),
118
+ * and skips saving the TableDefinitions table itself to avoid recursion.
119
+ */
120
+ saveTableDefinition(metaData) {
121
+ if (rpc_types_1.isClient)
122
+ return;
123
+ if (!metaData.tableId)
124
+ return; // system tables don't need to be saved
125
+ if (metaData.name === 'TableDefinitions')
126
+ return; // avoid recursion
127
+ if (this.savingDefinitions)
128
+ return; // guard against re-entrant calls
129
+ this.savingDefinitions = true;
130
+ const tableName = (0, utils_1.getFullTableName)(metaData);
131
+ try {
132
+ // Lazy import to avoid circular dependency at module load time
133
+ const { TableDefinitions } = require('../table-definitions-table');
134
+ const tableDefsTable = TableDefinitions();
135
+ const record = {
136
+ tableId: metaData.tableId,
137
+ name: tableName,
138
+ metaData: metaData,
139
+ versionNumber: metaData.versionNumber ?? 0,
140
+ };
141
+ // Fire-and-forget: check if existing record has a newer versionNumber
142
+ tableDefsTable.get(metaData.tableId).then(existing => {
143
+ if (existing) {
144
+ const result = checkVersionedUpdate(existing.versionNumber, record.versionNumber, existing.metaData, record.metaData);
145
+ if (result === 'skip') {
146
+ return;
147
+ }
148
+ }
149
+ return tableDefsTable.save(record, { restoreIfDeleted: true, saveAsSnapshot: true });
150
+ }).catch(err => {
151
+ console.warn(`Failed to save table definition for ${tableName}:`, err);
152
+ }).finally(() => {
153
+ this.savingDefinitions = false;
154
+ });
155
+ }
156
+ catch (err) {
157
+ console.warn(`Failed to save table definition for ${tableName}:`, err);
158
+ this.savingDefinitions = false;
159
+ }
160
+ }
69
161
  getTableByName(tableName) {
162
+ // 1. Check for a full instance (from code/packages)
70
163
  if (this.tableInstances[tableName]) {
71
- // If the table is already instantiated, return it
72
164
  return this.tableInstances[tableName];
73
165
  }
166
+ // 2. Check for a full definition (code-registered or system) and instantiate it
74
167
  const tableDefinition = this.tableDefinitions[tableName] || table_definitions_system_1.systemTableDefinitions[tableName];
75
- if (!tableDefinition) {
76
- throw new Error(`There is no instantiated table or registered definition for ${tableName}`);
168
+ if (tableDefinition) {
169
+ const { metaData, schema, tableConstructor } = tableDefinition;
170
+ return this.getTable(metaData, schema, tableConstructor);
171
+ }
172
+ // 3. Check for a fallback instance (from synced TableDefinitionRecords)
173
+ if (this.fallbackInstances[tableName]) {
174
+ return this.fallbackInstances[tableName];
175
+ }
176
+ // 4. Check for a fallback definition and create a fallback instance
177
+ const fallbackDef = this.fallbackDefinitions[tableName];
178
+ if (fallbackDef) {
179
+ const { metaData, schema } = fallbackDef;
180
+ const table = this.tableFactory(metaData, schema ?? (0, types_1.fieldsToSchema)(metaData.fields), table_1.Table, this.groupId);
181
+ this.fallbackInstances[tableName] = table;
182
+ return table;
183
+ }
184
+ throw new Error(`There is no instantiated table or registered definition for ${tableName}`);
185
+ }
186
+ /**
187
+ * Lazily look up a fallback table by name from the TableDefinitions system table.
188
+ * On first call, subscribes to TableDefinitions.dataChanged so future synced
189
+ * records are auto-registered (and removed on delete).
190
+ * Returns the table instance if found, or null if no definition exists.
191
+ */
192
+ async getFallbackTable(tableName) {
193
+ // Already have an instance or definition from a previous lookup
194
+ if (this.fallbackInstances[tableName])
195
+ return this.fallbackInstances[tableName];
196
+ if (this.fallbackDefinitions[tableName]) {
197
+ const { metaData, schema } = this.fallbackDefinitions[tableName];
198
+ const table = this.tableFactory(metaData, schema ?? (0, types_1.fieldsToSchema)(metaData.fields), table_1.Table, this.groupId);
199
+ this.fallbackInstances[tableName] = table;
200
+ return table;
201
+ }
202
+ const tableDefsTable = this.tableInstances['TableDefinitions'];
203
+ if (!tableDefsTable)
204
+ return null;
205
+ // Subscribe once so future changes are picked up automatically
206
+ if (!this.fallbackSubscribed) {
207
+ this.fallbackSubscribed = true;
208
+ tableDefsTable.dataChanged.subscribe((event) => {
209
+ if (event.op === 'delete') {
210
+ const md = event.dataObject?.metaData;
211
+ if (md) {
212
+ const name = (0, utils_1.getFullTableName)(md);
213
+ delete this.fallbackDefinitions[name];
214
+ delete this.fallbackInstances[name];
215
+ }
216
+ return;
217
+ }
218
+ this.registerFromTableDefinitionRecord(event.dataObject);
219
+ });
220
+ }
221
+ // Query for this specific table definition
222
+ const record = await tableDefsTable.findOne({ name: tableName });
223
+ if (!record)
224
+ return null;
225
+ this.registerFromTableDefinitionRecord(record);
226
+ // Now instantiate via getTableByName which checks fallbackDefinitions
227
+ return this.getTableByName(tableName);
228
+ }
229
+ /**
230
+ * Register a table definition from a synced TableDefinitions record.
231
+ * These go into the fallback tier (no custom constructor) so they never
232
+ * block full code-defined instances from being created later.
233
+ *
234
+ * Called automatically by the getFallbackTable dataChanged subscription.
235
+ * External code should not need to call this directly.
236
+ */
237
+ registerFromTableDefinitionRecord(record) {
238
+ const metaData = record.metaData;
239
+ if (!metaData?.tableId || !metaData?.name || !metaData?.fields) {
240
+ console.warn(`Invalid table definition record, skipping:`, record);
241
+ return;
242
+ }
243
+ const tableName = (0, utils_1.getFullTableName)(metaData);
244
+ // Don't overwrite a full (code-registered) definition -- that always takes precedence
245
+ const fullDef = this.tableDefinitions[tableName] || table_definitions_system_1.systemTableDefinitions[tableName];
246
+ if (fullDef) {
247
+ return; // full definition exists, no need for a fallback
77
248
  }
78
- const { metaData, schema, tableConstructor } = tableDefinition;
79
- return this.getTable(metaData, schema, tableConstructor);
249
+ const existing = this.fallbackDefinitions[tableName];
250
+ if (existing) {
251
+ const existingVersion = existing.metaData.versionNumber ?? 0;
252
+ const incomingVersion = record.versionNumber ?? 0;
253
+ const result = checkVersionedUpdate(existingVersion, incomingVersion, existing.metaData, metaData);
254
+ if (result === 'skip') {
255
+ return;
256
+ }
257
+ // Remove old fallback instance so it gets re-created with the new schema
258
+ delete this.fallbackInstances[tableName];
259
+ }
260
+ const schema = (0, types_1.fieldsToSchema)(metaData.fields);
261
+ this.fallbackDefinitions[tableName] = { metaData, schema };
80
262
  }
81
263
  /**
82
264
  * This ensures all tables have been new'ed up and then returns the entire list of them.
265
+ * Full (code-defined) instances take precedence over fallback (synced-definition) instances.
83
266
  *
84
267
  * This is primarily intended to be used by the peer-device sync operation which needs all
85
268
  * tables to be able to apply changes correctly.
86
269
  */
87
270
  getAllTables() {
88
- const allDefinitions = [...Object.values(table_definitions_system_1.systemTableDefinitions), ...Object.values(this.tableDefinitions)];
89
- for (const tableDefinition of allDefinitions) {
271
+ // 1. Instantiate all full definitions (system + code-registered)
272
+ const fullDefinitions = [...Object.values(table_definitions_system_1.systemTableDefinitions), ...Object.values(this.tableDefinitions)];
273
+ for (const tableDefinition of fullDefinitions) {
90
274
  const { metaData, schema, tableConstructor } = tableDefinition;
91
275
  this.getTable(metaData, schema, tableConstructor);
92
276
  }
93
- return Object.values(this.tableInstances);
277
+ // 2. Instantiate fallback definitions that don't already have a full instance
278
+ for (const [tableName, fallbackDef] of Object.entries(this.fallbackDefinitions)) {
279
+ if (!this.tableInstances[tableName] && !this.fallbackInstances[tableName]) {
280
+ const { metaData, schema } = fallbackDef;
281
+ const table = this.tableFactory(metaData, schema ?? (0, types_1.fieldsToSchema)(metaData.fields), table_1.Table, this.groupId);
282
+ this.fallbackInstances[tableName] = table;
283
+ }
284
+ }
285
+ // 3. Merge: full instances take precedence, then add fallbacks for tables not in full
286
+ const result = { ...this.fallbackInstances, ...this.tableInstances };
287
+ return Object.values(result);
94
288
  }
95
289
  }
96
290
  exports.TableContainer = TableContainer;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,163 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const zod_1 = require("zod");
4
+ const table_container_1 = require("./table-container");
5
+ const event_registry_1 = require("./event-registry");
6
+ const client_proxy_data_source_1 = require("./client-proxy.data-source");
7
+ const table_1 = require("./table");
8
+ const field_type_1 = require("../../types/field-type");
9
+ const utils_1 = require("../../utils");
10
+ describe('checkVersionedUpdate', () => {
11
+ const metaA = { name: 'TestTable', fields: [{ name: 'id', type: 'string' }] };
12
+ const metaAReordered = { fields: [{ name: 'id', type: 'string' }], name: 'TestTable' };
13
+ const metaB = { name: 'TestTable', fields: [{ name: 'id', type: 'number' }] };
14
+ it('should skip when existing version is strictly newer', () => {
15
+ expect((0, table_container_1.checkVersionedUpdate)(200, 100, metaA, metaB)).toBe('skip');
16
+ });
17
+ it('should update when incoming version is strictly newer', () => {
18
+ expect((0, table_container_1.checkVersionedUpdate)(100, 200, metaA, metaB)).toBe('update');
19
+ });
20
+ it('should skip when same non-zero version and same definition', () => {
21
+ expect((0, table_container_1.checkVersionedUpdate)(100, 100, metaA, metaA)).toBe('skip');
22
+ });
23
+ it('should skip when same non-zero version and same definition with different key order', () => {
24
+ expect((0, table_container_1.checkVersionedUpdate)(100, 100, metaA, metaAReordered)).toBe('skip');
25
+ });
26
+ it('should skip when same non-zero version but different definitions', () => {
27
+ expect((0, table_container_1.checkVersionedUpdate)(100, 100, metaA, metaB)).toBe('skip');
28
+ });
29
+ it('should skip when both versions are 0 and definitions are the same', () => {
30
+ expect((0, table_container_1.checkVersionedUpdate)(0, 0, metaA, metaA)).toBe('skip');
31
+ });
32
+ it('should skip when both versions are 0 and definitions are the same with different key order', () => {
33
+ expect((0, table_container_1.checkVersionedUpdate)(0, 0, metaA, metaAReordered)).toBe('skip');
34
+ });
35
+ it('should skip when both versions are 0 and definitions differ', () => {
36
+ expect((0, table_container_1.checkVersionedUpdate)(0, 0, metaA, metaB)).toBe('skip');
37
+ });
38
+ it('should update when incoming is newer regardless of definition content', () => {
39
+ expect((0, table_container_1.checkVersionedUpdate)(50, 100, metaA, metaA)).toBe('update');
40
+ expect((0, table_container_1.checkVersionedUpdate)(50, 100, metaA, metaB)).toBe('update');
41
+ });
42
+ it('should skip when existing is newer regardless of definition content', () => {
43
+ expect((0, table_container_1.checkVersionedUpdate)(100, 50, metaA, metaA)).toBe('skip');
44
+ expect((0, table_container_1.checkVersionedUpdate)(100, 50, metaA, metaB)).toBe('skip');
45
+ });
46
+ });
47
+ // --- Tiered table instance tests ---
48
+ // Custom Table subclass for testing
49
+ class CustomTestTable extends table_1.Table {
50
+ customMethod() {
51
+ return 'custom';
52
+ }
53
+ }
54
+ function createTestHarness() {
55
+ const mockDataContext = { dataContextId: 'test-context' };
56
+ const eventRegistry = new event_registry_1.EventRegistry(mockDataContext);
57
+ const tableFactory = jest.fn().mockImplementation((metaData, schema, TableClass) => {
58
+ const deps = {
59
+ dataSource: new client_proxy_data_source_1.ClientProxyDataSource(metaData, schema, 'test-context'),
60
+ eventRegistry,
61
+ };
62
+ return new (TableClass || table_1.Table)(metaData, deps);
63
+ });
64
+ const container = new table_container_1.TableContainer(tableFactory);
65
+ const tableId = (0, utils_1.newid)();
66
+ const testMetaData = {
67
+ tableId,
68
+ name: 'TestPkgTable',
69
+ description: 'A package-defined table',
70
+ primaryKeyName: 'id',
71
+ fields: [
72
+ { name: 'id', type: field_type_1.FieldType.id },
73
+ { name: 'title', type: field_type_1.FieldType.string },
74
+ ],
75
+ };
76
+ const testSchema = zod_1.z.object({
77
+ id: zod_1.z.string(),
78
+ title: zod_1.z.string(),
79
+ });
80
+ const defRecord = {
81
+ tableId,
82
+ name: `TestPkgTable_${tableId}`,
83
+ metaData: testMetaData,
84
+ versionNumber: 0,
85
+ };
86
+ return { container, testMetaData, testSchema, defRecord, tableFactory };
87
+ }
88
+ describe('Tiered table instances', () => {
89
+ it('bare-bones fallback instance should NOT block full instance with custom constructor', () => {
90
+ const { container, testMetaData, testSchema, defRecord } = createTestHarness();
91
+ // Simulate sync: register from a TableDefinitionRecord (no custom constructor)
92
+ // (method is private — normally auto-called via dataChanged subscription)
93
+ container.registerFromTableDefinitionRecord(defRecord);
94
+ // Sync code calls getTableByName to get a working instance
95
+ const fallbackTable = container.getTableByName(defRecord.name);
96
+ expect(fallbackTable).toBeInstanceOf(table_1.Table);
97
+ expect(fallbackTable).not.toBeInstanceOf(CustomTestTable);
98
+ // Later, package code calls getTable with the full custom constructor
99
+ const fullTable = container.getTable(testMetaData, testSchema, CustomTestTable);
100
+ // The full instance should be a CustomTestTable, NOT the bare-bones Table
101
+ expect(fullTable).toBeInstanceOf(CustomTestTable);
102
+ expect(fullTable.customMethod()).toBe('custom');
103
+ });
104
+ it('getTableByName should return full instance when one exists', () => {
105
+ const { container, testMetaData, testSchema, defRecord } = createTestHarness();
106
+ // Register fallback definition (private — normally auto-called)
107
+ container.registerFromTableDefinitionRecord(defRecord);
108
+ // Package code creates the full instance first
109
+ const fullTable = container.getTable(testMetaData, testSchema, CustomTestTable);
110
+ expect(fullTable).toBeInstanceOf(CustomTestTable);
111
+ // getTableByName should return the full instance, not create a new bare one
112
+ const byName = container.getTableByName(defRecord.name);
113
+ expect(byName).toBe(fullTable);
114
+ expect(byName).toBeInstanceOf(CustomTestTable);
115
+ });
116
+ it('getTableByName should return fallback instance when no full instance exists', () => {
117
+ const { container, defRecord } = createTestHarness();
118
+ // Only register from definition record -- no full definition available (private — normally auto-called)
119
+ container.registerFromTableDefinitionRecord(defRecord);
120
+ // Should return a bare-bones Table instance
121
+ const table = container.getTableByName(defRecord.name);
122
+ expect(table).toBeInstanceOf(table_1.Table);
123
+ expect(table).not.toBeInstanceOf(CustomTestTable);
124
+ });
125
+ it('getAllTables should prefer full instances over fallback instances', () => {
126
+ const { container, testMetaData, testSchema, defRecord, tableFactory } = createTestHarness();
127
+ // Create a second table that will only have a fallback definition
128
+ const tableId2 = (0, utils_1.newid)();
129
+ const metaData2 = {
130
+ tableId: tableId2,
131
+ name: 'OtherTable',
132
+ description: 'Another table',
133
+ primaryKeyName: 'id',
134
+ fields: [
135
+ { name: 'id', type: field_type_1.FieldType.id },
136
+ { name: 'value', type: field_type_1.FieldType.string },
137
+ ],
138
+ };
139
+ const defRecord2 = {
140
+ tableId: tableId2,
141
+ name: `OtherTable_${tableId2}`,
142
+ metaData: metaData2,
143
+ versionNumber: 0,
144
+ };
145
+ // Register both as fallback definitions (private — normally auto-called)
146
+ container.registerFromTableDefinitionRecord(defRecord);
147
+ container.registerFromTableDefinitionRecord(defRecord2);
148
+ // Create full instance for only the first table
149
+ container.getTable(testMetaData, testSchema, CustomTestTable);
150
+ // getAllTables should include both tables
151
+ const allTables = container.getAllTables();
152
+ const tableNames = allTables.map(t => t.tableName);
153
+ expect(tableNames).toContain(defRecord.name);
154
+ expect(tableNames).toContain(defRecord2.name);
155
+ // The first table should be the full CustomTestTable instance
156
+ const firstTable = allTables.find(t => t.tableName === defRecord.name);
157
+ expect(firstTable).toBeInstanceOf(CustomTestTable);
158
+ // The second table should be a bare-bones Table (fallback)
159
+ const secondTable = allTables.find(t => t.tableName === defRecord2.name);
160
+ expect(secondTable).toBeInstanceOf(table_1.Table);
161
+ expect(secondTable).not.toBeInstanceOf(CustomTestTable);
162
+ });
163
+ });
@@ -10,6 +10,7 @@ export declare class Table<T extends {
10
10
  readonly tableName: string;
11
11
  readonly primaryKeyName: string;
12
12
  readonly schema: z.AnyZodObject;
13
+ private readonly codeSchema?;
13
14
  protected readonly dataChangedEmitter: Emitter<IDataChangedEvent<T>>;
14
15
  readonly dataChanged: Event<IDataChangedEvent<T>>;
15
16
  private readonly localOperationCache;
@@ -14,6 +14,7 @@ class Table {
14
14
  tableName;
15
15
  primaryKeyName;
16
16
  schema;
17
+ codeSchema;
17
18
  dataChangedEmitter;
18
19
  dataChanged;
19
20
  localOperationCache = new Map();
@@ -23,7 +24,8 @@ class Table {
23
24
  constructor(metaData, deps) {
24
25
  this.metaData = metaData;
25
26
  this.dataSource = deps.dataSource;
26
- this.schema = deps.schema || (0, types_1.fieldsToSchema)(metaData.fields);
27
+ this.codeSchema = deps.schema;
28
+ this.schema = (0, types_1.fieldsToSchema)(metaData.fields, deps.schema);
27
29
  this.groupId = deps.groupId;
28
30
  this.tableName = (0, utils_1.getFullTableName)(metaData);
29
31
  this.primaryKeyName = metaData.primaryKeyName;
@@ -43,7 +45,7 @@ class Table {
43
45
  // Find any underlying SubscribableDataSource to intercept its events
44
46
  let subscribableDataSource = undefined;
45
47
  let _dataSource = this.dataSource;
46
- while (!subscribableDataSource && _dataSource) {
48
+ while (_dataSource) {
47
49
  if (!subscribableDataSource && _dataSource instanceof subscribable_data_source_1.SubscribableDataSource) {
48
50
  subscribableDataSource = _dataSource;
49
51
  }
@@ -219,6 +221,16 @@ class Table {
219
221
  initRecord(data = {}) {
220
222
  const defaults = {};
221
223
  for (const field of this.metaData.fields) {
224
+ // Apply code schema first (has original defaults like () => new Date())
225
+ if (this.codeSchema?.shape?.[field.name]) {
226
+ const codeValue = this.codeSchema.shape[field.name].safeParse(data[field.name]);
227
+ if (codeValue.success) {
228
+ // @ts-ignore
229
+ defaults[field.name] = codeValue.data;
230
+ continue;
231
+ }
232
+ }
233
+ // Fall back to merged schema (fields-derived + code schema intersection)
222
234
  const zodField = this.schema.shape[field.name];
223
235
  const value = zodField.safeParse(data[field.name]);
224
236
  if (value.success) {
@@ -15,6 +15,7 @@ export interface ITableMetaData {
15
15
  }[];
16
16
  iconClassName?: string;
17
17
  localOnly?: boolean;
18
+ versionNumber?: number;
18
19
  }
19
- export declare function fieldsToSchema(fields: IField[]): z.AnyZodObject;
20
+ export declare function fieldsToSchema(fields: IField[], codeSchema?: z.AnyZodObject): z.AnyZodObject;
20
21
  export declare function schemaToFields(schema: z.AnyZodObject): IField[];
@@ -5,7 +5,7 @@ exports.schemaToFields = schemaToFields;
5
5
  const zod_1 = require("zod");
6
6
  const field_type_1 = require("../../types/field-type");
7
7
  const utils_1 = require("../../utils");
8
- function fieldsToSchema(fields) {
8
+ function fieldsToSchema(fields, codeSchema) {
9
9
  let argsSchema = zod_1.z.object({});
10
10
  for (const argSchema of fields) {
11
11
  const { name, description, type, isArray, defaultValue, optional } = argSchema;
@@ -13,6 +13,7 @@ function fieldsToSchema(fields) {
13
13
  switch (type) {
14
14
  case 'id':
15
15
  propertyType = zod_1.z.string().refine((id => (0, utils_1.isid)(id)), { message: 'Invalid id' });
16
+ propertyType._def._fieldType = field_type_1.FieldType.id;
16
17
  break;
17
18
  case 'string':
18
19
  propertyType = zod_1.z.string();
@@ -43,6 +44,9 @@ function fieldsToSchema(fields) {
43
44
  propertyType = propertyType.default(defaultValue);
44
45
  }
45
46
  propertyType = propertyType.describe(description);
47
+ if (codeSchema?.shape?.[name]) {
48
+ propertyType = zod_1.z.intersection(propertyType, codeSchema.shape[name]);
49
+ }
46
50
  const newProperty = { [name]: propertyType };
47
51
  argsSchema = argsSchema.extend(newProperty);
48
52
  }
@@ -55,14 +59,22 @@ function schemaToFields(schema) {
55
59
  let type = field_type_1.FieldType.any;
56
60
  let _def = field._def;
57
61
  let isArray = undefined;
62
+ let brandedFieldType;
58
63
  while (true) {
59
64
  if (_def.typeName === 'ZodArray') {
60
65
  isArray = true;
61
66
  }
67
+ if (_def._fieldType) {
68
+ brandedFieldType = _def._fieldType;
69
+ }
62
70
  if (_def.innerType) {
63
71
  _def = _def.innerType._def;
64
72
  continue;
65
73
  }
74
+ if (_def.schema) {
75
+ _def = _def.schema._def;
76
+ continue;
77
+ }
66
78
  if (_def.type) {
67
79
  _def = _def.type._def;
68
80
  continue;
@@ -71,7 +83,7 @@ function schemaToFields(schema) {
71
83
  }
72
84
  switch (_def.typeName) {
73
85
  case 'ZodString':
74
- type = field_type_1.FieldType.string;
86
+ type = brandedFieldType || field_type_1.FieldType.string;
75
87
  break;
76
88
  case 'ZodNumber':
77
89
  type = field_type_1.FieldType.number;
@@ -93,9 +105,6 @@ function schemaToFields(schema) {
93
105
  default:
94
106
  type = field_type_1.FieldType.any;
95
107
  }
96
- if ((name === 'id' || name.endsWith('Id')) && type === 'string') {
97
- type = field_type_1.FieldType.id;
98
- }
99
108
  if (!isArray && field._def.typeName === 'ZodArray') {
100
109
  isArray = true;
101
110
  // isArray = field._def.typeName === 'ZodArray' || undefined;