@svgrid/enterprise 3.1.8 → 3.1.9

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 (268) hide show
  1. package/README.md +1 -0
  2. package/dist/SvAlertRuleEditor.svelte +16 -5
  3. package/dist/SvGridAlerts.svelte +8 -0
  4. package/dist/SvGridAlerts.svelte.d.ts +4 -0
  5. package/dist/SvGridBoard.svelte +69 -10
  6. package/dist/SvGridBulkEditDrawer.svelte +13 -5
  7. package/dist/SvGridGallery.svelte +286 -0
  8. package/dist/SvGridGallery.svelte.d.ts +30 -0
  9. package/dist/SvGridScheduler.svelte +33 -11
  10. package/dist/SvGridSelectionBar.svelte +47 -0
  11. package/dist/advanced-filter/expr-columns-from-grid.d.ts +1 -0
  12. package/dist/advanced-filter/expr-columns-from-grid.js +11 -7
  13. package/dist/advanced-filter-enable.d.ts +6 -0
  14. package/dist/advanced-filter-enable.js +8 -0
  15. package/dist/alerts/alert-engine.d.ts +7 -0
  16. package/dist/alerts/alert-engine.js +5 -2
  17. package/dist/alerts/condition-editor.d.ts +26 -0
  18. package/dist/alerts/condition-editor.js +17 -0
  19. package/dist/base/index.d.ts +20 -0
  20. package/dist/base/index.js +20 -0
  21. package/dist/bulk-edit.d.ts +11 -0
  22. package/dist/bulk-edit.js +18 -1
  23. package/dist/cdn/svgrid-enterprise.svelte-external.js +11197 -10639
  24. package/dist/designer/assets/GridFooter-Cu8_yBlQ.js +2 -0
  25. package/dist/designer/assets/GridMenus-BUjkkAU2.js +6 -0
  26. package/dist/designer/assets/PinSplitter-BgTpgh1d.css +1 -0
  27. package/dist/designer/assets/PinSplitter-C5AwA1xV.js +1 -0
  28. package/dist/designer/assets/SvGrid.helpers-CIrmIo9U.js +1 -0
  29. package/dist/designer/assets/SvGridCellEditor-CJxThKL_.js +4 -0
  30. package/dist/designer/assets/SvListBox-CS7oUAiX.js +2 -0
  31. package/dist/designer/assets/conditional-formatting-MDWbF8_Y.js +1 -0
  32. package/dist/designer/assets/grid-extension.svelte-BZ7AqyJI.js +1 -0
  33. package/dist/designer/assets/{index-Obk8sI77.js → index-BPU2KNc5.js} +307 -307
  34. package/dist/designer/assets/index-BmvG99kR.css +1 -0
  35. package/dist/designer/assets/sparkline-C-NcnB2i.js +1 -0
  36. package/dist/designer/assets/src-C_U1NKOY.js +58 -0
  37. package/dist/designer/assets/sv-grid-scrollbar-B9N6Wuj0.js +109 -0
  38. package/dist/designer/assets/validate-H5u2HEQh.js +1 -0
  39. package/dist/designer/index.html +11 -7
  40. package/dist/expressions/compile.js +10 -0
  41. package/dist/expressions/evaluate.js +18 -0
  42. package/dist/expressions/expression-types.d.ts +10 -0
  43. package/dist/expressions/kinds.d.ts +28 -0
  44. package/dist/expressions/kinds.js +19 -0
  45. package/dist/expressions/parse.js +9 -1
  46. package/dist/fields/AttachmentCell.svelte +242 -0
  47. package/dist/fields/AttachmentCell.svelte.d.ts +5 -0
  48. package/dist/fields/AttachmentDialog.svelte +357 -0
  49. package/dist/fields/AttachmentDialog.svelte.d.ts +13 -0
  50. package/dist/fields/AttachmentEditor.svelte +41 -0
  51. package/dist/fields/AttachmentEditor.svelte.d.ts +11 -0
  52. package/dist/fields/AttachmentPreview.svelte +72 -0
  53. package/dist/fields/AttachmentPreview.svelte.d.ts +8 -0
  54. package/dist/fields/BarcodeCell.svelte +20 -0
  55. package/dist/fields/BarcodeCell.svelte.d.ts +5 -0
  56. package/dist/fields/BaseAutomations.svelte +558 -0
  57. package/dist/fields/BaseAutomations.svelte.d.ts +18 -0
  58. package/dist/fields/BaseChartView.svelte +47 -0
  59. package/dist/fields/BaseChartView.svelte.d.ts +8 -0
  60. package/dist/fields/BaseTableView.svelte +185 -0
  61. package/dist/fields/BaseTableView.svelte.d.ts +37 -0
  62. package/dist/fields/BaseTabs.svelte +308 -0
  63. package/dist/fields/BaseTabs.svelte.d.ts +21 -0
  64. package/dist/fields/ButtonCell.svelte +47 -0
  65. package/dist/fields/ButtonCell.svelte.d.ts +20 -0
  66. package/dist/fields/CheckboxCell.svelte +46 -0
  67. package/dist/fields/CheckboxCell.svelte.d.ts +14 -0
  68. package/dist/fields/DeleteFieldConfirm.svelte +80 -0
  69. package/dist/fields/DeleteFieldConfirm.svelte.d.ts +8 -0
  70. package/dist/fields/DeleteRecordConfirm.svelte +48 -0
  71. package/dist/fields/DeleteRecordConfirm.svelte.d.ts +9 -0
  72. package/dist/fields/FieldCell.svelte +80 -0
  73. package/dist/fields/FieldCell.svelte.d.ts +9 -0
  74. package/dist/fields/FieldEditor.svelte +321 -0
  75. package/dist/fields/FieldEditor.svelte.d.ts +10 -0
  76. package/dist/fields/FieldInput.svelte +188 -0
  77. package/dist/fields/FieldInput.svelte.d.ts +29 -0
  78. package/dist/fields/FieldsHeaderEnd.svelte +146 -0
  79. package/dist/fields/FieldsHeaderEnd.svelte.d.ts +6 -0
  80. package/dist/fields/FootRowAdder.svelte +8 -0
  81. package/dist/fields/FootRowAdder.svelte.d.ts +6 -0
  82. package/dist/fields/GroupHeader.svelte +127 -0
  83. package/dist/fields/GroupHeader.svelte.d.ts +24 -0
  84. package/dist/fields/LinkCell.svelte +46 -0
  85. package/dist/fields/LinkCell.svelte.d.ts +9 -0
  86. package/dist/fields/LinkPickerEditor.svelte +172 -0
  87. package/dist/fields/LinkPickerEditor.svelte.d.ts +9 -0
  88. package/dist/fields/LinkPreview.svelte +60 -0
  89. package/dist/fields/LinkPreview.svelte.d.ts +7 -0
  90. package/dist/fields/LinkedRecordView.svelte +94 -0
  91. package/dist/fields/LinkedRecordView.svelte.d.ts +9 -0
  92. package/dist/fields/LinkedRecordsCell.svelte +59 -0
  93. package/dist/fields/LinkedRecordsCell.svelte.d.ts +11 -0
  94. package/dist/fields/LongTextEditor.svelte +157 -0
  95. package/dist/fields/LongTextEditor.svelte.d.ts +11 -0
  96. package/dist/fields/NumberEditor.svelte +309 -0
  97. package/dist/fields/NumberEditor.svelte.d.ts +12 -0
  98. package/dist/fields/OptionsEditor.svelte +117 -0
  99. package/dist/fields/OptionsEditor.svelte.d.ts +11 -0
  100. package/dist/fields/PercentCell.svelte +21 -0
  101. package/dist/fields/PercentCell.svelte.d.ts +8 -0
  102. package/dist/fields/PropertyInput.svelte +204 -0
  103. package/dist/fields/PropertyInput.svelte.d.ts +11 -0
  104. package/dist/fields/RatingCell.svelte +75 -0
  105. package/dist/fields/RatingCell.svelte.d.ts +16 -0
  106. package/dist/fields/RatingEditor.svelte +82 -0
  107. package/dist/fields/RatingEditor.svelte.d.ts +9 -0
  108. package/dist/fields/RecordView.svelte +310 -0
  109. package/dist/fields/RecordView.svelte.d.ts +11 -0
  110. package/dist/fields/RichTextCell.svelte +30 -0
  111. package/dist/fields/RichTextCell.svelte.d.ts +5 -0
  112. package/dist/fields/RowAdder.svelte +93 -0
  113. package/dist/fields/RowAdder.svelte.d.ts +7 -0
  114. package/dist/fields/RowHeader.svelte +99 -0
  115. package/dist/fields/RowHeader.svelte.d.ts +11 -0
  116. package/dist/fields/SelectCell.svelte +35 -0
  117. package/dist/fields/SelectCell.svelte.d.ts +15 -0
  118. package/dist/fields/SelectEditor.svelte +350 -0
  119. package/dist/fields/SelectEditor.svelte.d.ts +17 -0
  120. package/dist/fields/SummaryCell.svelte +160 -0
  121. package/dist/fields/SummaryCell.svelte.d.ts +11 -0
  122. package/dist/fields/SvBase.svelte +545 -0
  123. package/dist/fields/SvBase.svelte.d.ts +30 -0
  124. package/dist/fields/SvBaseForm.svelte +295 -0
  125. package/dist/fields/SvBaseForm.svelte.d.ts +22 -0
  126. package/dist/fields/SvBaseViewBar.svelte +644 -0
  127. package/dist/fields/SvBaseViewBar.svelte.d.ts +26 -0
  128. package/dist/fields/UserCell.svelte +39 -0
  129. package/dist/fields/UserCell.svelte.d.ts +9 -0
  130. package/dist/fields/UserFace.svelte +30 -0
  131. package/dist/fields/UserFace.svelte.d.ts +12 -0
  132. package/dist/fields/activity.svelte.d.ts +50 -0
  133. package/dist/fields/activity.svelte.js +110 -0
  134. package/dist/fields/attachments.d.ts +95 -0
  135. package/dist/fields/attachments.js +145 -0
  136. package/dist/fields/automation-outbound.d.ts +46 -0
  137. package/dist/fields/automation-outbound.js +61 -0
  138. package/dist/fields/automation-server.d.ts +53 -0
  139. package/dist/fields/automation-server.js +333 -0
  140. package/dist/fields/automations.svelte.d.ts +208 -0
  141. package/dist/fields/automations.svelte.js +437 -0
  142. package/dist/fields/bar-request.svelte.d.ts +16 -0
  143. package/dist/fields/bar-request.svelte.js +15 -0
  144. package/dist/fields/base-alerts.d.ts +24 -0
  145. package/dist/fields/base-alerts.js +64 -0
  146. package/dist/fields/base-connect.d.ts +82 -0
  147. package/dist/fields/base-connect.js +271 -0
  148. package/dist/fields/base-schema.d.ts +104 -0
  149. package/dist/fields/base-schema.js +266 -0
  150. package/dist/fields/base-sql.d.ts +62 -0
  151. package/dist/fields/base-sql.js +251 -0
  152. package/dist/fields/base-sync.d.ts +142 -0
  153. package/dist/fields/base-sync.js +499 -0
  154. package/dist/fields/base-tables.d.ts +89 -0
  155. package/dist/fields/base-tables.js +349 -0
  156. package/dist/fields/builtin-types.d.ts +13 -0
  157. package/dist/fields/builtin-types.js +773 -0
  158. package/dist/fields/button.d.ts +2 -0
  159. package/dist/fields/button.js +17 -0
  160. package/dist/fields/chart-view.d.ts +35 -0
  161. package/dist/fields/chart-view.js +154 -0
  162. package/dist/fields/column-order.d.ts +6 -0
  163. package/dist/fields/column-order.js +12 -0
  164. package/dist/fields/convert.d.ts +16 -0
  165. package/dist/fields/convert.js +176 -0
  166. package/dist/fields/copy-text.d.ts +13 -0
  167. package/dist/fields/copy-text.js +45 -0
  168. package/dist/fields/editor-request.svelte.d.ts +71 -0
  169. package/dist/fields/editor-request.svelte.js +52 -0
  170. package/dist/fields/field-ops.d.ts +72 -0
  171. package/dist/fields/field-ops.js +334 -0
  172. package/dist/fields/filter-model.d.ts +84 -0
  173. package/dist/fields/filter-model.js +446 -0
  174. package/dist/fields/format.d.ts +109 -0
  175. package/dist/fields/format.js +288 -0
  176. package/dist/fields/formula.d.ts +50 -0
  177. package/dist/fields/formula.js +530 -0
  178. package/dist/fields/grid-access.d.ts +60 -0
  179. package/dist/fields/grid-access.js +120 -0
  180. package/dist/fields/index.d.ts +34 -0
  181. package/dist/fields/index.js +312 -0
  182. package/dist/fields/linked-fields.d.ts +8 -0
  183. package/dist/fields/linked-fields.js +40 -0
  184. package/dist/fields/links.d.ts +10 -0
  185. package/dist/fields/links.js +46 -0
  186. package/dist/fields/name-list.d.ts +16 -0
  187. package/dist/fields/name-list.js +49 -0
  188. package/dist/fields/palette.d.ts +47 -0
  189. package/dist/fields/palette.js +69 -0
  190. package/dist/fields/record-delete.svelte.d.ts +12 -0
  191. package/dist/fields/record-delete.svelte.js +5 -0
  192. package/dist/fields/record-menu.d.ts +9 -0
  193. package/dist/fields/record-menu.js +78 -0
  194. package/dist/fields/registry.svelte.d.ts +176 -0
  195. package/dist/fields/registry.svelte.js +51 -0
  196. package/dist/fields/rich-text.d.ts +23 -0
  197. package/dist/fields/rich-text.js +48 -0
  198. package/dist/fields/row-color-choice.svelte.d.ts +31 -0
  199. package/dist/fields/row-color-choice.svelte.js +25 -0
  200. package/dist/fields/row-colors.d.ts +36 -0
  201. package/dist/fields/row-colors.js +90 -0
  202. package/dist/fields/row-height.d.ts +28 -0
  203. package/dist/fields/row-height.js +50 -0
  204. package/dist/fields/sources.svelte.d.ts +80 -0
  205. package/dist/fields/sources.svelte.js +131 -0
  206. package/dist/fields/summaries.d.ts +18 -0
  207. package/dist/fields/summaries.js +138 -0
  208. package/dist/fields/view-model.d.ts +129 -0
  209. package/dist/fields/view-model.js +492 -0
  210. package/dist/fields/viewbar/ChartPanel.svelte +61 -0
  211. package/dist/fields/viewbar/ChartPanel.svelte.d.ts +9 -0
  212. package/dist/fields/viewbar/ColorPanel.svelte +112 -0
  213. package/dist/fields/viewbar/ColorPanel.svelte.d.ts +9 -0
  214. package/dist/fields/viewbar/ConditionValue.svelte +153 -0
  215. package/dist/fields/viewbar/ConditionValue.svelte.d.ts +15 -0
  216. package/dist/fields/viewbar/FieldIcon.svelte +18 -0
  217. package/dist/fields/viewbar/FieldIcon.svelte.d.ts +9 -0
  218. package/dist/fields/viewbar/FilterPanel.svelte +192 -0
  219. package/dist/fields/viewbar/FilterPanel.svelte.d.ts +13 -0
  220. package/dist/fields/viewbar/GanttPanel.svelte +60 -0
  221. package/dist/fields/viewbar/GanttPanel.svelte.d.ts +10 -0
  222. package/dist/fields/viewbar/HideFieldsPanel.svelte +104 -0
  223. package/dist/fields/viewbar/HideFieldsPanel.svelte.d.ts +7 -0
  224. package/dist/fields/viewbar/SortPanel.svelte +85 -0
  225. package/dist/fields/viewbar/SortPanel.svelte.d.ts +8 -0
  226. package/dist/fields/viewbar/ViewsMenu.svelte +250 -0
  227. package/dist/fields/viewbar/ViewsMenu.svelte.d.ts +15 -0
  228. package/dist/fields/viewbar/panel-focus.d.ts +3 -0
  229. package/dist/fields/viewbar/panel-focus.js +22 -0
  230. package/dist/fields/viewbar/sort-state.d.ts +20 -0
  231. package/dist/fields/viewbar/sort-state.js +24 -0
  232. package/dist/gallery.d.ts +7 -0
  233. package/dist/gallery.js +36 -0
  234. package/dist/gantt/SvGridGantt.svelte +88 -16
  235. package/dist/gantt/gantt-config.d.ts +28 -0
  236. package/dist/index.d.ts +3 -0
  237. package/dist/index.js +6 -0
  238. package/dist/install.js +3 -0
  239. package/dist/license-core.d.ts +3 -3
  240. package/dist/license.js +1 -0
  241. package/dist/node/studio.js +105 -4
  242. package/dist/scheduler-config.d.ts +11 -0
  243. package/dist/sources/introspect-supabase.js +4 -1
  244. package/dist/sources/rest.js +6 -2
  245. package/dist/studio/ui-components.generated.js +93 -1
  246. package/dist/upgrade-prompt.d.ts +1 -1
  247. package/dist/version.d.ts +2 -2
  248. package/dist/version.js +2 -2
  249. package/dist/wc/GridFooter-f5K0aLe_.js +145 -0
  250. package/dist/wc/GridMenus-FtSN_8t9.js +1369 -0
  251. package/dist/wc/PinSplitter-D-qtDhSM.js +132 -0
  252. package/dist/wc/{SvGrid.helpers-zBkDTryR.js → SvGrid.helpers-B2GEKrxS.js} +84 -36
  253. package/dist/wc/{SvGridCellEditor-CzUKEbkI.js → SvGridCellEditor-B_cLJrsp.js} +20 -9
  254. package/dist/wc/conditional-formatting-BXis_oP1.js +189 -0
  255. package/dist/wc/grid-extension.svelte-Bufq4B4C.js +20 -0
  256. package/dist/wc/sv-sheet-element.js +7556 -7617
  257. package/dist/wc/svelte-virtualizer.svelte-ehwOupl-.js +360 -0
  258. package/dist/wc/{validate-43C03DLP.js → validate-BaMO8UPE.js} +1 -1
  259. package/package.json +14 -3
  260. package/dist/designer/assets/GridMenus-CvcASlKv.js +0 -8
  261. package/dist/designer/assets/SvGrid.helpers-DE5mmORW.js +0 -1
  262. package/dist/designer/assets/SvGridCellEditor-C7NWcnNC.js +0 -4
  263. package/dist/designer/assets/SvListBox-BlmmzYcU.js +0 -110
  264. package/dist/designer/assets/index-KmSEY7Lb.css +0 -1
  265. package/dist/designer/assets/src-DqHCqori.js +0 -58
  266. package/dist/designer/assets/validate-B71dE1Cs.js +0 -1
  267. package/dist/wc/GridMenus-fWbBUiRH.js +0 -1088
  268. package/dist/wc/svelte-virtualizer.svelte-Dntdh8Ms.js +0 -581
@@ -0,0 +1,499 @@
1
+ import { colId, rowIdOf, syncInverse } from './base-tables.js';
2
+ const isJunction = (l) => 'through' in l;
3
+ const ids = (v) => (Array.isArray(v) ? v.map(String) : v == null || v === '' ? [] : [String(v)]);
4
+ const request = (endRow, filter = {}) => ({
5
+ startRow: 0,
6
+ endRow,
7
+ pageIndex: 0,
8
+ pageSize: endRow,
9
+ sortModel: [],
10
+ filterModel: filter,
11
+ });
12
+ /**
13
+ * A value as the page keeps it: a date column read as a JS Date (PGlite,
14
+ * node-postgres) becomes 'YYYY-MM-DD', or a full ISO time when it has one.
15
+ */
16
+ function fromDb(v) {
17
+ if (v instanceof Date) {
18
+ const iso = v.toISOString();
19
+ return iso.endsWith('T00:00:00.000Z') ? iso.slice(0, 10) : iso;
20
+ }
21
+ return v;
22
+ }
23
+ /**
24
+ * A base table's source from any data source with the grid's contract
25
+ * (`createSupabaseDataSource`, `createSqlDataSource`, `createRestDataSource`,
26
+ * `createInMemoryDataSource`, or your own).
27
+ *
28
+ * ```ts
29
+ * source: baseSource(createSupabaseDataSource({ client, table: 'tasks', schema }), {
30
+ * links: { project: { column: 'project_id' } },
31
+ * realtime: (onChange) => createSupabaseRealtime({ client, table: 'tasks', onChange }).unsubscribe,
32
+ * })
33
+ * ```
34
+ */
35
+ export function baseSource(ds, options = {}) {
36
+ const limit = options.limit ?? 10000;
37
+ const idField = options.idField ?? 'id';
38
+ const links = options.links ?? {};
39
+ const rename = options.columns ?? {};
40
+ const back = new Map(Object.entries(rename).map(([f, c]) => [c, f]));
41
+ const fks = new Map(Object.entries(links).filter((e) => !isJunction(e[1])));
42
+ const junctions = Object.entries(links).filter((e) => isJunction(e[1]));
43
+ const fkColumns = new Map([...fks].map(([field, l]) => [l.column, field]));
44
+ /** A row as the database has it, as the page keeps it (junction links aside). */
45
+ function toPage(r) {
46
+ const row = {};
47
+ for (const [k, v] of Object.entries(r ?? {})) {
48
+ const fk = fkColumns.get(k);
49
+ if (fk)
50
+ row[fk] = v == null ? [] : [String(v)];
51
+ else
52
+ row[back.get(k) ?? k] = fromDb(v);
53
+ }
54
+ row.id = String(r?.[idField] ?? row.id ?? '');
55
+ return row;
56
+ }
57
+ // Junction row ids by "from|to", to delete a pair.
58
+ const pairs = new Map();
59
+ const pairKey = (a, b) => `${a}|${b}`;
60
+ const src = {
61
+ limit,
62
+ links,
63
+ skip: new Set(options.skip ?? []),
64
+ toPage,
65
+ async load() {
66
+ const res = await ds.getRows(request(limit));
67
+ const out = res.rows.map((r) => toPage(r));
68
+ for (const [field, j] of junctions) {
69
+ const seen = new Map();
70
+ pairs.set(field, seen);
71
+ const jr = await j.through.getRows(request(limit * 20));
72
+ const byFrom = new Map();
73
+ for (const p of jr.rows) {
74
+ const a = String(p[j.from]);
75
+ const b = String(p[j.to]);
76
+ seen.set(pairKey(a, b), String(p[j.idField ?? 'id']));
77
+ const list = byFrom.get(a);
78
+ if (list)
79
+ list.push(b);
80
+ else
81
+ byFrom.set(a, [b]);
82
+ }
83
+ for (const row of out)
84
+ row[field] = byFrom.get(String(row.id)) ?? [];
85
+ }
86
+ return out;
87
+ },
88
+ async insert(row) {
89
+ const { stored, later } = split(row);
90
+ const created = (await ds.createRow(stored));
91
+ const id = String(created?.[idField] ?? row.id);
92
+ for (const [field, to] of later)
93
+ await addPairs(field, id, to);
94
+ return { ...row, id };
95
+ },
96
+ async update(id, patch, before) {
97
+ const { stored, later } = split(patch);
98
+ if (Object.keys(stored).length)
99
+ await ds.updateRow(id, stored);
100
+ for (const [field, to] of later) {
101
+ const was = new Set(ids(before[field]));
102
+ const now = new Set(to);
103
+ await addPairs(field, id, to.filter((x) => !was.has(x)));
104
+ await dropPairs(field, id, [...was].filter((x) => !now.has(x)));
105
+ }
106
+ },
107
+ async remove(id, before) {
108
+ for (const [field] of junctions)
109
+ await dropPairs(field, id, ids(before[field]));
110
+ await ds.deleteRow(id);
111
+ },
112
+ ...(options.realtime
113
+ ? {
114
+ subscribe: (onChange) => {
115
+ const stop = options.realtime(onChange);
116
+ return typeof stop === 'function' ? stop : () => stop.unsubscribe();
117
+ },
118
+ }
119
+ : {}),
120
+ };
121
+ /** A record's fields as columns, and its junction links to write after it. */
122
+ function split(row) {
123
+ const stored = {};
124
+ const later = [];
125
+ for (const [k, v] of Object.entries(row)) {
126
+ const link = links[k];
127
+ if (link && isJunction(link))
128
+ later.push([k, ids(v)]);
129
+ else if (link) {
130
+ const first = ids(v)[0];
131
+ stored[link.column] = first == null ? null : linkedId(k, first);
132
+ }
133
+ else if (k === 'id') {
134
+ if (!options.serverIds)
135
+ stored[idField] = v;
136
+ }
137
+ else
138
+ stored[rename[k] ?? k] = v;
139
+ }
140
+ return { stored, later };
141
+ }
142
+ // A linked record's database id. The writer sets `ids` and knows the table.
143
+ const linkedTable = new Map();
144
+ function linkedId(field, id) {
145
+ const table = linkedTable.get(field);
146
+ return table && src.ids ? src.ids.toServer(table, id) : id;
147
+ }
148
+ async function addPairs(field, from, to) {
149
+ const j = links[field];
150
+ const seen = pairs.get(field) ?? new Map();
151
+ pairs.set(field, seen);
152
+ for (const x of to) {
153
+ const b = linkedId(field, x);
154
+ const made = (await j.through.createRow({ [j.from]: from, [j.to]: b }));
155
+ seen.set(pairKey(from, b), String(made?.[j.idField ?? 'id'] ?? ''));
156
+ }
157
+ }
158
+ async function dropPairs(field, from, to) {
159
+ const j = links[field];
160
+ const seen = pairs.get(field);
161
+ for (const x of to) {
162
+ const b = linkedId(field, x);
163
+ const pid = seen?.get(pairKey(from, b));
164
+ if (!pid)
165
+ continue;
166
+ await j.through.deleteRow(pid);
167
+ seen.delete(pairKey(from, b));
168
+ }
169
+ }
170
+ // The writer tells the source which table each link field points at.
171
+ ;
172
+ src.linkTables = linkedTable;
173
+ return src;
174
+ }
175
+ // ---- The writer -------------------------------------------------------------
176
+ /** Field types worked out in the page: never written. */
177
+ const COMPUTED = new Set(['formula', 'lookup', 'rollup', 'count', 'button']);
178
+ const clone = (r) => {
179
+ const out = {};
180
+ for (const [k, v] of Object.entries(r))
181
+ out[k] = Array.isArray(v) ? [...v] : v && typeof v === 'object' ? JSON.parse(JSON.stringify(v)) : v;
182
+ return out;
183
+ };
184
+ const same = (a, b) => {
185
+ if (a === b)
186
+ return true;
187
+ if ((a == null || a === '') && (b == null || b === ''))
188
+ return true;
189
+ if (Array.isArray(a) || Array.isArray(b) || (a && typeof a === 'object') || (b && typeof b === 'object'))
190
+ return JSON.stringify(a ?? []) === JSON.stringify(b ?? []);
191
+ return false;
192
+ };
193
+ /** Whether a link field is the one worked out from the other side (that side stores the pairs). */
194
+ function derivedLink(tables, def) {
195
+ const inverse = def.fieldProps?.inverse;
196
+ if (!inverse)
197
+ return false;
198
+ const other = tables.find((t) => t.id === def.fieldProps?.table);
199
+ const otherDef = other?.columns.find((d) => colId(d) === inverse);
200
+ const otherField = otherDef ? String(otherDef.field ?? colId(otherDef)) : '';
201
+ return !!other?.source?.links?.[otherField];
202
+ }
203
+ /** The fields of a table its source writes. */
204
+ function storedFields(tables, t) {
205
+ const src = t.source;
206
+ return t.columns
207
+ .filter((d) => d.field != null && !COMPUTED.has(d.fieldType) && !src.skip.has(String(d.field)))
208
+ .filter((d) => !(d.fieldType === 'link' && !src.links[String(d.field)] && derivedLink(tables, d)))
209
+ .map((d) => String(d.field));
210
+ }
211
+ export function createBaseSync(options) {
212
+ // What the database holds, per table, by the record's page id.
213
+ const synced = new Map();
214
+ // Page id <-> database id, per table, for records the database gave its own id.
215
+ const toServerIds = new Map();
216
+ const toLocalIds = new Map();
217
+ const pendingInsert = new Set();
218
+ const stops = [];
219
+ let queue = Promise.resolve();
220
+ let pending = 0;
221
+ // The last failed write: the status keeps saying so until a write goes through.
222
+ let failure = null;
223
+ let disposed = false;
224
+ const status = (s) => options.onStatus?.(s);
225
+ const ids = {
226
+ toServer: (table, id) => toServerIds.get(table)?.get(id) ?? id,
227
+ toLocal: (table, id) => toLocalIds.get(table)?.get(id) ?? id,
228
+ };
229
+ const key = (table, id) => `${table}:${id}`;
230
+ function wire(t) {
231
+ const src = t.source;
232
+ src.ids = ids;
233
+ const linkTables = src.linkTables;
234
+ if (linkTables)
235
+ for (const d of t.columns)
236
+ if (d.fieldType === 'link' && d.fieldProps?.table)
237
+ linkTables.set(String(d.field), String(d.fieldProps.table));
238
+ }
239
+ /** Records read from a source, with database ids turned into page ids. */
240
+ async function read(t) {
241
+ const rows = await t.source.load();
242
+ const linkTo = new Map(t.columns.filter((d) => d.fieldType === 'link').map((d) => [String(d.field), String(d.fieldProps?.table ?? '')]));
243
+ return rows.map((r) => {
244
+ const row = { ...r, id: ids.toLocal(t.id, String(r.id)) };
245
+ for (const [field, table] of linkTo)
246
+ if (field in row && Array.isArray(row[field]))
247
+ row[field] = row[field].map((x) => ids.toLocal(table, x));
248
+ return row;
249
+ });
250
+ }
251
+ /** Fill in the fields that link back to a table from its rows. */
252
+ function linkBack(list, tableId) {
253
+ let next = list;
254
+ const t = next.find((x) => x.id === tableId);
255
+ for (const d of t?.columns ?? [])
256
+ if (d.fieldType === 'link' && d.fieldProps?.inverse && t?.source?.links[String(d.field)])
257
+ next = syncInverse(next, tableId, colId(d));
258
+ return next;
259
+ }
260
+ /** Load every table that has a source, then fill in the fields that link back. */
261
+ async function load() {
262
+ const list = options.getTables() ?? [];
263
+ const withSource = list.filter((t) => t.source);
264
+ if (!withSource.length)
265
+ return;
266
+ status({ state: 'loading' });
267
+ for (const t of withSource)
268
+ wire(t);
269
+ const loaded = new Map();
270
+ for (const t of withSource) {
271
+ try {
272
+ const rows = await read(t);
273
+ loaded.set(t.id, rows);
274
+ if (rows.length >= t.source.limit)
275
+ status({ state: 'limited', table: t.name, limit: t.source.limit });
276
+ }
277
+ catch (e) {
278
+ failure = report(t, 'load', e);
279
+ }
280
+ }
281
+ if (disposed)
282
+ return;
283
+ let next = (options.getTables() ?? []).map((t) => (loaded.has(t.id) ? { ...t, data: loaded.get(t.id) } : t));
284
+ for (const t of withSource)
285
+ next = linkBack(next, t.id);
286
+ for (const t of next)
287
+ if (loaded.has(t.id))
288
+ remember(next, t);
289
+ options.apply(next);
290
+ for (const t of withSource) {
291
+ const stop = t.source.subscribe?.(() => refresh(t.id));
292
+ if (stop)
293
+ stops.push(stop);
294
+ }
295
+ if (pending === 0)
296
+ status(failure ?? { state: 'idle' });
297
+ }
298
+ function remember(list, t) {
299
+ const fields = storedFields(list, t);
300
+ const map = new Map();
301
+ for (const r of t.data)
302
+ if (r)
303
+ map.set(rowIdOf(t, r), pick(r, fields));
304
+ synced.set(t.id, map);
305
+ }
306
+ const pick = (r, fields) => {
307
+ const out = {};
308
+ for (const f of fields)
309
+ if (f in r)
310
+ out[f] = Array.isArray(r[f]) ? [...r[f]] : r[f] && typeof r[f] === 'object' ? clone({ v: r[f] }).v : r[f];
311
+ return out;
312
+ };
313
+ /** Compare the tables with what the database holds, and queue what differs. */
314
+ function push(list) {
315
+ if (!list || disposed)
316
+ return;
317
+ for (const t of list) {
318
+ const before = synced.get(t.id);
319
+ if (!t.source || !before)
320
+ continue;
321
+ const fields = storedFields(list, t);
322
+ const seen = new Set();
323
+ for (const r of t.data) {
324
+ if (!r)
325
+ continue;
326
+ const id = rowIdOf(t, r);
327
+ seen.add(id);
328
+ const was = before.get(id);
329
+ if (!was) {
330
+ if (pendingInsert.has(key(t.id, id)))
331
+ continue;
332
+ const row = pick(r, fields);
333
+ before.set(id, row);
334
+ pendingInsert.add(key(t.id, id));
335
+ enqueue(t, 'insert', id, async () => {
336
+ const stored = await t.source.insert({ ...row, id });
337
+ const dbId = String(stored.id ?? id);
338
+ if (dbId !== id) {
339
+ const out = toServerIds.get(t.id) ?? new Map();
340
+ out.set(id, dbId);
341
+ toServerIds.set(t.id, out);
342
+ const inn = toLocalIds.get(t.id) ?? new Map();
343
+ inn.set(dbId, id);
344
+ toLocalIds.set(t.id, inn);
345
+ }
346
+ pendingInsert.delete(key(t.id, id));
347
+ }, () => {
348
+ pendingInsert.delete(key(t.id, id));
349
+ before.delete(id);
350
+ undo(t.id, (rows) => rows.filter((x) => x && rowIdOf(t, x) !== id));
351
+ });
352
+ continue;
353
+ }
354
+ const patch = {};
355
+ for (const f of fields)
356
+ if (!same(r[f], was[f]))
357
+ patch[f] = Array.isArray(r[f]) ? [...r[f]] : r[f];
358
+ if (!Object.keys(patch).length)
359
+ continue;
360
+ const prev = { ...was };
361
+ Object.assign(was, pick(patch, Object.keys(patch)));
362
+ enqueue(t, 'update', id, () => t.source.update(ids.toServer(t.id, id), patch, prev), () => {
363
+ Object.assign(was, prev);
364
+ undo(t.id, (rows) => rows.map((x) => (x && rowIdOf(t, x) === id ? { ...x, ...pick(prev, Object.keys(patch)) } : x)));
365
+ });
366
+ }
367
+ for (const [id, was] of [...before]) {
368
+ if (seen.has(id))
369
+ continue;
370
+ before.delete(id);
371
+ enqueue(t, 'delete', id, () => t.source.remove(ids.toServer(t.id, id), was), () => {
372
+ before.set(id, was);
373
+ undo(t.id, (rows) => [...rows, { ...was, id }]);
374
+ });
375
+ }
376
+ }
377
+ }
378
+ function enqueue(t, action, _id, run, rollback) {
379
+ pending += 1;
380
+ status({ state: 'saving', pending });
381
+ queue = queue.then(async () => {
382
+ if (disposed)
383
+ return;
384
+ try {
385
+ await run();
386
+ failure = null;
387
+ }
388
+ catch (e) {
389
+ rollback();
390
+ failure = report(t, action, e);
391
+ }
392
+ finally {
393
+ pending -= 1;
394
+ if (pending === 0 && !disposed)
395
+ status(failure ?? { state: 'idle' });
396
+ else if (!disposed)
397
+ status({ state: 'saving', pending });
398
+ }
399
+ });
400
+ }
401
+ function report(t, action, e) {
402
+ const message = e instanceof Error ? e.message : String(e);
403
+ const s = { state: 'error', message, table: t.name };
404
+ status(s);
405
+ options.onError?.({ table: t.name, action, message });
406
+ return s;
407
+ }
408
+ /** Put a table's rows back as the database has them, without writing. */
409
+ function undo(tableId, change) {
410
+ const list = options.getTables();
411
+ if (!list)
412
+ return;
413
+ let next = list.map((t) => (t.id === tableId ? { ...t, data: change(t.data) } : t));
414
+ next = linkBack(next, tableId);
415
+ options.apply(next);
416
+ }
417
+ // Changes made elsewhere: read the table again and take what moved.
418
+ const timers = new Map();
419
+ function refresh(tableId) {
420
+ clearTimeout(timers.get(tableId));
421
+ timers.set(tableId, setTimeout(() => void reload(tableId), 120));
422
+ }
423
+ async function reload(tableId) {
424
+ const t0 = options.getTables()?.find((t) => t.id === tableId);
425
+ if (!t0?.source || disposed)
426
+ return;
427
+ // Our own writes first: what they changed is already in the page.
428
+ await queue;
429
+ let rows;
430
+ try {
431
+ rows = await read(t0);
432
+ }
433
+ catch (e) {
434
+ report(t0, 'load', e);
435
+ return;
436
+ }
437
+ const list = options.getTables();
438
+ const t = list?.find((x) => x.id === tableId);
439
+ const before = synced.get(tableId);
440
+ if (!list || !t || !before || disposed)
441
+ return;
442
+ const fields = storedFields(list, t);
443
+ const fresh = new Map(rows.map((r) => [String(r.id), r]));
444
+ let changed = false;
445
+ const data = [];
446
+ for (const r of t.data) {
447
+ if (!r)
448
+ continue;
449
+ const id = rowIdOf(t, r);
450
+ const server = fresh.get(id);
451
+ if (!server) {
452
+ if (pendingInsert.has(key(tableId, id)))
453
+ data.push(r);
454
+ else {
455
+ before.delete(id);
456
+ changed = true;
457
+ }
458
+ continue;
459
+ }
460
+ fresh.delete(id);
461
+ const moved = fields.filter((f) => f in server && !same(server[f], before.get(id)?.[f]));
462
+ if (moved.length) {
463
+ changed = true;
464
+ const next = { ...r };
465
+ for (const f of moved)
466
+ next[f] = server[f];
467
+ before.set(id, { ...before.get(id), ...pick(server, moved) });
468
+ data.push(next);
469
+ }
470
+ else
471
+ data.push(r);
472
+ }
473
+ for (const [id, r] of fresh) {
474
+ changed = true;
475
+ before.set(id, pick(r, fields));
476
+ data.push(r);
477
+ }
478
+ if (!changed)
479
+ return;
480
+ let next = list.map((x) => (x.id === tableId ? { ...x, data } : x));
481
+ next = linkBack(next, tableId);
482
+ options.apply(next);
483
+ }
484
+ return {
485
+ load,
486
+ push,
487
+ /** Read a table again now (after a change you know of). */
488
+ refresh,
489
+ /** Resolves when every queued write has finished. */
490
+ settled: () => queue,
491
+ dispose() {
492
+ disposed = true;
493
+ for (const stop of stops)
494
+ stop();
495
+ for (const t of timers.values())
496
+ clearTimeout(t);
497
+ },
498
+ };
499
+ }
@@ -0,0 +1,89 @@
1
+ import type { BaseAutomation } from './automations.svelte.js';
2
+ import type { BaseSource } from './base-sync.js';
3
+ import type { FieldView } from './view-model.js';
4
+ type Row = Record<string, unknown>;
5
+ type Def = Record<string, any>;
6
+ /** One table of a base. */
7
+ export type BaseTable = {
8
+ /** Unique across the app: link fields name it in `fieldProps.table`. */
9
+ id: string;
10
+ name: string;
11
+ data: Row[];
12
+ /** Columns declared by field type (`fieldType`, `fieldProps`). */
13
+ columns: Def[];
14
+ views?: FieldView[];
15
+ /** The field that names a record. Default: the first column. */
16
+ primaryField?: string;
17
+ /** A record's id. Default: its `id` property. */
18
+ getRowId?: (row: Row) => string;
19
+ /**
20
+ * Where the records live: a database or an API (`baseSource`). The table
21
+ * loads from it and writes every change back.
22
+ */
23
+ source?: BaseSource;
24
+ /** What happens when records are added, changed or match a condition (the view bar's Automations). */
25
+ automations?: BaseAutomation[];
26
+ /** Grid props for this table only (they win over the ones on `<SvBase>`). */
27
+ props?: Record<string, unknown>;
28
+ };
29
+ export declare const colId: (d: Def) => string;
30
+ export declare const rowIdOf: (t: BaseTable, r: Row) => string;
31
+ export declare const primaryOf: (t: BaseTable) => string;
32
+ /**
33
+ * The field that links back from `def`'s table, made when `def` points at
34
+ * another table of the base and has none: a link field in that table named
35
+ * after this one, many records per record. Both fields name each other.
36
+ */
37
+ export declare function addInverse(tables: BaseTable[], tableId: string, fieldId: string): BaseTable[];
38
+ /**
39
+ * Make the field that links back agree with `fieldId`'s values: a record of
40
+ * the other table lists the records that link to it. Ids it already lists
41
+ * keep their place, new ones go at the end. When the field back holds one
42
+ * record (`multiple: false`), it keeps the one that linked last, and the
43
+ * records it was linked from before let go of it, so both sides agree.
44
+ */
45
+ export declare function syncInverse(tables: BaseTable[], tableId: string, fieldId: string): BaseTable[];
46
+ /**
47
+ * What is wrong with the links of the tables as loaded: an `inverse` that
48
+ * names no link field, and two sides of a link that disagree. The base does
49
+ * not rewrite loaded data, so it reports instead (once per message, in the
50
+ * console; SvBase calls this when it mounts).
51
+ */
52
+ export declare function checkLinks(tables: BaseTable[], warn?: (message: string) => void): string[];
53
+ /** Every link field of a table brings its other side up to date (after records are added or deleted). */
54
+ export declare function syncAllInverses(tables: BaseTable[], tableId: string): BaseTable[];
55
+ /** Remove the field that links back from `def` (its column and its values). */
56
+ export declare function dropInverse(tables: BaseTable[], def: Def | undefined): BaseTable[];
57
+ /**
58
+ * A field of `tableId` changed from `prev` to its current column. A link that
59
+ * now points at another table loses the field back in the old one, its values
60
+ * (ids of the old table's records) and gets a field back in the new one; a
61
+ * link turned into another type loses the field back.
62
+ */
63
+ export declare function fieldChanged(tables: BaseTable[], tableId: string, prev: Def | undefined, fieldId: string): BaseTable[];
64
+ /** The lookups, rollups and counts of a table (or a grid's columns) that read its link field `link`. */
65
+ export declare function linkReaders(t: {
66
+ columns: ReadonlyArray<Def>;
67
+ }, link: Def): Def[];
68
+ /**
69
+ * Link field `def` of `tableId` was deleted (it is gone from the columns
70
+ * already): the lookups, rollups and counts that read it go too, and so do
71
+ * its field back, with its values, and the ones that read that.
72
+ */
73
+ export declare function removeLinkField(tables: BaseTable[], tableId: string, def: Def | undefined): BaseTable[];
74
+ /**
75
+ * Delete a table, and every link field of another table that points at it
76
+ * (with its values), with the lookups, rollups and counts that read those.
77
+ */
78
+ export declare function removeTable(tables: BaseTable[], tableId: string): BaseTable[];
79
+ export { newRecordId } from './field-ops.js';
80
+ /** A table name not used yet: "Table 2", "Table 3". */
81
+ export declare function freeTableName(tables: ReadonlyArray<BaseTable>, base?: string): string;
82
+ /** A new table as a base starts one: Name, Notes and Status, three empty records, a grid view. */
83
+ export declare function blankTable(tables: ReadonlyArray<BaseTable>, name?: string): BaseTable;
84
+ /**
85
+ * A copy of a table: its fields, records and views, under a new name. Its
86
+ * link fields point at the same records; addInverse gives each one a field
87
+ * back once the copy is in the base.
88
+ */
89
+ export declare function copyTable(tables: ReadonlyArray<BaseTable>, source: BaseTable): BaseTable;