@svgrid/enterprise 3.1.7 → 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 (270) 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 +11375 -10805
  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-DtBIaQno.js → index-BPU2KNc5.js} +396 -395
  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 +141 -20
  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/emit-project.js +25 -6
  246. package/dist/studio/sveltekit3.js +13 -7
  247. package/dist/studio/ui-components.generated.js +94 -2
  248. package/dist/upgrade-prompt.d.ts +1 -1
  249. package/dist/version.d.ts +2 -2
  250. package/dist/version.js +2 -2
  251. package/dist/wc/GridFooter-f5K0aLe_.js +145 -0
  252. package/dist/wc/GridMenus-FtSN_8t9.js +1369 -0
  253. package/dist/wc/PinSplitter-D-qtDhSM.js +132 -0
  254. package/dist/wc/{SvGrid.helpers-zBkDTryR.js → SvGrid.helpers-B2GEKrxS.js} +84 -36
  255. package/dist/wc/{SvGridCellEditor-CzUKEbkI.js → SvGridCellEditor-B_cLJrsp.js} +20 -9
  256. package/dist/wc/conditional-formatting-BXis_oP1.js +189 -0
  257. package/dist/wc/grid-extension.svelte-Bufq4B4C.js +20 -0
  258. package/dist/wc/sv-sheet-element.js +7561 -7618
  259. package/dist/wc/svelte-virtualizer.svelte-ehwOupl-.js +360 -0
  260. package/dist/wc/{validate-43C03DLP.js → validate-BaMO8UPE.js} +1 -1
  261. package/package.json +14 -3
  262. package/dist/designer/assets/GridMenus-CvcASlKv.js +0 -8
  263. package/dist/designer/assets/SvGrid.helpers-DE5mmORW.js +0 -1
  264. package/dist/designer/assets/SvGridCellEditor-C7NWcnNC.js +0 -4
  265. package/dist/designer/assets/SvListBox-BlmmzYcU.js +0 -110
  266. package/dist/designer/assets/index-KmSEY7Lb.css +0 -1
  267. package/dist/designer/assets/src-DqHCqori.js +0 -58
  268. package/dist/designer/assets/validate-B71dE1Cs.js +0 -1
  269. package/dist/wc/GridMenus-fWbBUiRH.js +0 -1088
  270. package/dist/wc/svelte-virtualizer.svelte-Dntdh8Ms.js +0 -581
@@ -0,0 +1,62 @@
1
+ /**
2
+ * A base as Postgres SQL: the tables, their columns, foreign keys and
3
+ * junction tables, and the records, ready to run on Postgres, Supabase or
4
+ * PGlite.
5
+ *
6
+ * Everything SQL has no column type for rides along in comments
7
+ * (`COMMENT ON ... IS 'svgrid:{...}'`): a select's options and colors, a
8
+ * number shown as currency, the formula, lookup, rollup and count fields, the
9
+ * views and the automations. `baseFromDatabase` (and `createBaseEndpoint`)
10
+ * read them back, so the database opens as the same base, not a generic one.
11
+ * A database without the comments still opens: they only add to what is
12
+ * read from the columns.
13
+ */
14
+ import { type BaseTable } from './base-tables.js';
15
+ type Def = Record<string, any>;
16
+ /** What a table comment holds. */
17
+ export type BaseTableComment = {
18
+ v: 1;
19
+ /** The table's name in the base ("Key results"). */
20
+ name: string;
21
+ /** Where its tab was. */
22
+ position?: number;
23
+ primaryField?: string;
24
+ /** The fields in their order. */
25
+ order: string[];
26
+ /** Fields with no column of their own: computed ones, buttons, links kept elsewhere. */
27
+ extra: Def[];
28
+ views?: unknown[];
29
+ automations?: unknown[];
30
+ };
31
+ /** What a junction table's comment holds: whose link it keeps. */
32
+ export type BaseJunctionComment = {
33
+ v: 1;
34
+ junction: {
35
+ owner: string;
36
+ field: string;
37
+ from: string;
38
+ to: string;
39
+ };
40
+ };
41
+ export declare const SQL_COMMENT_PREFIX = "svgrid:";
42
+ /** `fixedOn` -> `fixed_on`, `Key results` -> `key_results`. */
43
+ export declare function sqlName(s: string): string;
44
+ export type BaseToSqlOptions = {
45
+ /** Dates within this many days of `today` are written as `current_date + n`, so seed data stays current (default 400; 0 writes every date as it is). */
46
+ relativeDays?: number;
47
+ today?: Date;
48
+ /** Leave the records out: tables only. */
49
+ data?: boolean;
50
+ /** A first line saying what this is. */
51
+ title?: string;
52
+ };
53
+ /**
54
+ * The SQL that makes a database holding these tables and records.
55
+ *
56
+ * ```ts
57
+ * const sql = baseToSql(tables) // run it with psql, the Supabase SQL editor, or PGlite
58
+ * const again = await baseFromDatabase({ execute }) // the same base, from the database
59
+ * ```
60
+ */
61
+ export declare function baseToSql(tables: ReadonlyArray<BaseTable>, options?: BaseToSqlOptions): string;
62
+ export {};
@@ -0,0 +1,251 @@
1
+ /**
2
+ * A base as Postgres SQL: the tables, their columns, foreign keys and
3
+ * junction tables, and the records, ready to run on Postgres, Supabase or
4
+ * PGlite.
5
+ *
6
+ * Everything SQL has no column type for rides along in comments
7
+ * (`COMMENT ON ... IS 'svgrid:{...}'`): a select's options and colors, a
8
+ * number shown as currency, the formula, lookup, rollup and count fields, the
9
+ * views and the automations. `baseFromDatabase` (and `createBaseEndpoint`)
10
+ * read them back, so the database opens as the same base, not a generic one.
11
+ * A database without the comments still opens: they only add to what is
12
+ * read from the columns.
13
+ */
14
+ import { colId, primaryOf, rowIdOf } from './base-tables.js';
15
+ import { fillFieldColumn } from './registry.svelte.js';
16
+ export const SQL_COMMENT_PREFIX = 'svgrid:';
17
+ /** `fixedOn` -> `fixed_on`, `Key results` -> `key_results`. */
18
+ export function sqlName(s) {
19
+ const out = String(s)
20
+ .replace(/([a-z0-9])([A-Z])/g, '$1_$2')
21
+ .replace(/[^A-Za-z0-9]+/g, '_')
22
+ .replace(/^_+|_+$/g, '')
23
+ .toLowerCase();
24
+ return /^[a-z_]/.test(out) ? out : `t_${out}`;
25
+ }
26
+ const COMPUTED = new Set(['formula', 'lookup', 'rollup', 'count', 'button']);
27
+ const quote = (name) => `"${name.replace(/"/g, '""')}"`;
28
+ const str = (s) => `'${s.replace(/'/g, "''")}'`;
29
+ const ids = (v) => (Array.isArray(v) ? v.map(String) : v == null || v === '' ? [] : [String(v)]);
30
+ function sqlType(d) {
31
+ switch (d.fieldType) {
32
+ case 'number':
33
+ case 'currency':
34
+ case 'percent':
35
+ case 'rating':
36
+ case 'duration':
37
+ case 'autoNumber':
38
+ return 'double precision';
39
+ case 'checkbox':
40
+ return 'boolean';
41
+ case 'date':
42
+ return d.fieldProps?.time ? 'timestamptz' : 'date';
43
+ case 'createdTime':
44
+ case 'lastModifiedTime':
45
+ return 'timestamptz';
46
+ case 'multiSelect':
47
+ return 'text[]';
48
+ case 'user':
49
+ case 'createdBy':
50
+ case 'lastModifiedBy':
51
+ return d.fieldProps?.multiple ? 'text[]' : 'text';
52
+ case 'attachment':
53
+ return 'jsonb';
54
+ default:
55
+ return typeOfData(d);
56
+ }
57
+ }
58
+ const dataTypes = new WeakMap();
59
+ /**
60
+ * Any other type (a registered one) by what its filled column holds, as the
61
+ * built-ins map: a number, a true / false, a date with or without the time.
62
+ */
63
+ function typeOfData(d) {
64
+ let t = dataTypes.get(d);
65
+ if (t === undefined) {
66
+ const kind = fillFieldColumn(d).cellDataType;
67
+ t = kind === 'number' ? 'double precision' : kind === 'boolean' ? 'boolean' : kind === 'date' ? 'timestamptz' : kind === 'dateString' ? 'date' : 'text';
68
+ dataTypes.set(d, t);
69
+ }
70
+ return t;
71
+ }
72
+ const DAY = 86400000;
73
+ /**
74
+ * The SQL that makes a database holding these tables and records.
75
+ *
76
+ * ```ts
77
+ * const sql = baseToSql(tables) // run it with psql, the Supabase SQL editor, or PGlite
78
+ * const again = await baseFromDatabase({ execute }) // the same base, from the database
79
+ * ```
80
+ */
81
+ export function baseToSql(tables, options = {}) {
82
+ const today = options.today ?? new Date();
83
+ const todayUtc = Date.UTC(today.getFullYear(), today.getMonth(), today.getDate());
84
+ const relative = options.relativeDays ?? 400;
85
+ const used = new Set();
86
+ const nameOf = new Map();
87
+ for (const t of tables) {
88
+ let n = sqlName(t.name || t.id);
89
+ while (used.has(n))
90
+ n = `${n}_2`;
91
+ used.add(n);
92
+ nameOf.set(t.id, n);
93
+ }
94
+ const tableOf = (id) => tables.find((t) => t.id === id);
95
+ const fieldOf = (t, id) => t.columns.find((d) => colId(d) === id);
96
+ /** Who keeps a link: a foreign key on the side that links to one record, else a junction on the first table. */
97
+ function storage(t, d) {
98
+ const target = tableOf(String(d.fieldProps?.table ?? ''));
99
+ const back = d.fieldProps?.inverse && target ? fieldOf(target, String(d.fieldProps.inverse)) : undefined;
100
+ const many = d.fieldProps?.multiple !== false;
101
+ if (!back || target === t)
102
+ return many ? 'junction' : 'fk';
103
+ const backMany = back.fieldProps?.multiple !== false;
104
+ if (!many)
105
+ return 'fk';
106
+ if (!backMany)
107
+ return 'derived';
108
+ return tables.indexOf(t) <= tables.indexOf(target) ? 'junction' : 'derived';
109
+ }
110
+ // Rewrite table ids to the SQL names in what a comment keeps.
111
+ const remap = (value) => JSON.parse(JSON.stringify(value ?? null), (k, v) => (k === 'table' && typeof v === 'string' && nameOf.has(v) ? nameOf.get(v) : v));
112
+ const plans = [];
113
+ const junctions = [];
114
+ for (const t of tables) {
115
+ const name = nameOf.get(t.id);
116
+ const columns = [];
117
+ const extra = [];
118
+ const taken = new Set(['id']);
119
+ const column = (base) => {
120
+ let c = sqlName(base);
121
+ while (taken.has(c))
122
+ c = `${c}_2`;
123
+ taken.add(c);
124
+ return c;
125
+ };
126
+ for (const d of t.columns) {
127
+ if (d.field == null)
128
+ continue;
129
+ const field = String(d.field);
130
+ if (COMPUTED.has(String(d.fieldType))) {
131
+ extra.push(d);
132
+ continue;
133
+ }
134
+ if (d.fieldType === 'link') {
135
+ const how = storage(t, d);
136
+ const target = tableOf(String(d.fieldProps?.table ?? ''));
137
+ if (!target)
138
+ continue;
139
+ if (how === 'fk')
140
+ columns.push({ def: d, column: column(`${field}_id`), fk: nameOf.get(target.id) });
141
+ else {
142
+ extra.push(d);
143
+ if (how === 'junction') {
144
+ const from = `${sqlName(name).replace(/s$/, '')}_id`;
145
+ let to = `${sqlName(field).replace(/s$/, '')}_id`;
146
+ if (to === from)
147
+ to = `${to}_2`;
148
+ junctions.push({ name: `${name}_${sqlName(field)}`, owner: t, field, from, to, target });
149
+ }
150
+ }
151
+ continue;
152
+ }
153
+ columns.push({ def: d, column: column(field) });
154
+ }
155
+ // The database makes the key of a record added later: a Supabase insert
156
+ // carries no id. The records below bring their own.
157
+ const lines = [` "id" text primary key default gen_random_uuid()::text`, ...columns.map((c) => ` ${quote(c.column)} ${c.fk ? 'text' : sqlType(c.def)}`)];
158
+ plans.push({ table: t, columns, extra, sql: `create table ${quote(name)} (\n${lines.join(',\n')}\n);` });
159
+ }
160
+ const out = [];
161
+ out.push(`-- ${options.title ?? 'A base'}, as Postgres tables. Made by baseToSql (@svgrid/enterprise/base);`);
162
+ out.push(`-- the svgrid: comments keep what SQL has no type for, so baseFromDatabase opens it as the same base.`);
163
+ out.push('');
164
+ for (const p of plans)
165
+ out.push(p.sql);
166
+ for (const j of junctions) {
167
+ out.push(`create table ${quote(j.name)} (\n "id" serial primary key,\n ${quote(j.from)} text not null,\n ${quote(j.to)} text not null\n);`);
168
+ }
169
+ out.push('');
170
+ // Comments: what each field, table and junction is in the base.
171
+ for (const p of plans) {
172
+ const name = nameOf.get(p.table.id);
173
+ for (const c of p.columns) {
174
+ out.push(`comment on column ${quote(name)}.${quote(c.column)} is ${str(SQL_COMMENT_PREFIX + JSON.stringify(remap(c.def)))};`);
175
+ }
176
+ const comment = {
177
+ v: 1,
178
+ name: p.table.name,
179
+ position: plans.indexOf(p),
180
+ primaryField: primaryOf(p.table),
181
+ order: p.table.columns.filter((d) => d.field != null).map((d) => String(d.field)),
182
+ extra: remap(p.extra),
183
+ ...(p.table.views ? { views: remap(p.table.views) } : {}),
184
+ ...(p.table.automations?.length ? { automations: remap(p.table.automations) } : {}),
185
+ };
186
+ out.push(`comment on table ${quote(name)} is ${str(SQL_COMMENT_PREFIX + JSON.stringify(comment))};`);
187
+ }
188
+ for (const j of junctions) {
189
+ const c = { v: 1, junction: { owner: nameOf.get(j.owner.id), field: j.field, from: j.from, to: j.to } };
190
+ out.push(`comment on table ${quote(j.name)} is ${str(SQL_COMMENT_PREFIX + JSON.stringify(c))};`);
191
+ }
192
+ // The records.
193
+ if (options.data !== false) {
194
+ const value = (d, v) => {
195
+ if (v == null || v === '' || (Array.isArray(v) && v.length === 0 && sqlType(d) !== 'text[]' && sqlType(d) !== 'jsonb'))
196
+ return 'null';
197
+ const type = sqlType(d);
198
+ if (type === 'jsonb') {
199
+ // A blob: link lives only in the page that made it: leave such files out.
200
+ const kept = Array.isArray(v) ? v.filter((x) => !(x && typeof x === 'object' && String(x.url ?? '').startsWith('blob:'))) : v;
201
+ return `${str(JSON.stringify(kept))}::jsonb`;
202
+ }
203
+ if (type === 'text[]')
204
+ return `array[${ids(v).map(str).join(', ')}]::text[]`;
205
+ if (type === 'boolean')
206
+ return v ? 'true' : 'false';
207
+ if (type === 'double precision')
208
+ return Number.isFinite(Number(v)) ? String(Number(v)) : 'null';
209
+ if (type === 'date' && typeof v === 'string' && /^\d{4}-\d{2}-\d{2}$/.test(v) && relative > 0) {
210
+ const n = Math.round((Date.parse(`${v}T00:00:00Z`) - todayUtc) / DAY);
211
+ if (Math.abs(n) <= relative)
212
+ return n === 0 ? 'current_date' : `current_date ${n > 0 ? '+' : '-'} ${Math.abs(n)}`;
213
+ }
214
+ return str(String(v));
215
+ };
216
+ out.push('');
217
+ for (const p of plans) {
218
+ const rows = p.table.data.filter(Boolean);
219
+ if (!rows.length)
220
+ continue;
221
+ const cols = ['id', ...p.columns.map((c) => c.column)];
222
+ const tuples = rows.map((r) => {
223
+ const vals = [str(rowIdOf(p.table, r)), ...p.columns.map((c) => (c.fk ? (ids(r[String(c.def.field)])[0] != null ? str(ids(r[String(c.def.field)])[0]) : 'null') : value(c.def, r[String(c.def.field)])))];
224
+ return ` (${vals.join(', ')})`;
225
+ });
226
+ out.push(`insert into ${quote(nameOf.get(p.table.id))} (${cols.map(quote).join(', ')}) values\n${tuples.join(',\n')};`);
227
+ }
228
+ for (const j of junctions) {
229
+ const pairs = [];
230
+ for (const r of j.owner.data)
231
+ if (r)
232
+ for (const to of ids(r[j.field]))
233
+ pairs.push(` (${str(rowIdOf(j.owner, r))}, ${str(to)})`);
234
+ if (pairs.length)
235
+ out.push(`insert into ${quote(j.name)} (${quote(j.from)}, ${quote(j.to)}) values\n${pairs.join(',\n')};`);
236
+ }
237
+ }
238
+ // Foreign keys last, once every record is in: links may point either way.
239
+ out.push('');
240
+ for (const p of plans) {
241
+ const name = nameOf.get(p.table.id);
242
+ for (const c of p.columns)
243
+ if (c.fk)
244
+ out.push(`alter table ${quote(name)} add foreign key (${quote(c.column)}) references ${quote(c.fk)}("id") on delete set null;`);
245
+ }
246
+ for (const j of junctions) {
247
+ out.push(`alter table ${quote(j.name)} add foreign key (${quote(j.from)}) references ${quote(nameOf.get(j.owner.id))}("id") on delete cascade;`);
248
+ out.push(`alter table ${quote(j.name)} add foreign key (${quote(j.to)}) references ${quote(nameOf.get(j.target.id))}("id") on delete cascade;`);
249
+ }
250
+ return out.join('\n') + '\n';
251
+ }
@@ -0,0 +1,142 @@
1
+ /**
2
+ * A base on a database: each table with a `source` loads its records from
3
+ * it and writes every change back.
4
+ *
5
+ * A base filters, groups, colors and sums in the page, so a table loads in
6
+ * full (up to `limit` records) and the writer keeps a copy of what the
7
+ * database holds. After each change it compares the tables with that copy
8
+ * and sends what differs: an insert for a new record, an update with the
9
+ * changed fields, a delete. Writes go one at a time, in order, so an update
10
+ * never overtakes the insert of its record. A write that fails is undone in
11
+ * the page and reported.
12
+ *
13
+ * Link fields: a link to one record is a foreign key column, a link to many
14
+ * records goes through a junction table, and the field that links back is
15
+ * worked out in the page from the other side, so it is never stored.
16
+ *
17
+ * Record ids: a new record gets an id in the page at once. When the database
18
+ * gives it another one (a serial key, a default uuid), the writer keeps both
19
+ * and translates, so the grid never sees a record change its id under an
20
+ * open editor.
21
+ */
22
+ import type { ServerDataSource } from '@svgrid/grid';
23
+ import { type BaseTable } from './base-tables.js';
24
+ type Row = Record<string, unknown>;
25
+ /** A link field kept as a foreign key: the column holds the linked record's id. */
26
+ export type ForeignKeyLink = {
27
+ column: string;
28
+ };
29
+ /** A link field kept in a junction table: one row per pair. */
30
+ export type JunctionLink = {
31
+ through: ServerDataSource<any>;
32
+ /** The junction column with this table's record id. */
33
+ from: string;
34
+ /** The junction column with the linked record's id. */
35
+ to: string;
36
+ /** The junction table's own key (default `id`). */
37
+ idField?: string;
38
+ };
39
+ /** What a base table reads and writes. `baseSource` makes one from any data source. */
40
+ export type BaseSource = {
41
+ /** Every record, as the page keeps them (link fields as lists of ids). */
42
+ load(): Promise<Row[]>;
43
+ /**
44
+ * One row as the database has it (a webhook's `record`), as the page keeps
45
+ * it: a foreign key becomes a link, renamed columns their fields. Links
46
+ * kept in a junction table are not in such a row.
47
+ */
48
+ toPage(row: Row): Row;
49
+ /** Store a new record; resolves with the record as stored (its id may be the database's). */
50
+ insert(row: Row): Promise<Row>;
51
+ update(id: string, patch: Row, before: Row): Promise<void>;
52
+ remove(id: string, before: Row): Promise<void>;
53
+ /** Hear of changes made elsewhere (another user, another tab); returns the unsubscribe. */
54
+ subscribe?(onChange: () => void): () => void;
55
+ /** The most records `load` reads. */
56
+ limit: number;
57
+ /** The link fields it stores, by field. */
58
+ links: Record<string, ForeignKeyLink | JunctionLink>;
59
+ /** Fields it does not store, besides computed ones and the fields that link back. */
60
+ skip: ReadonlySet<string>;
61
+ /** Turn a linked record's page id into its database id, and back (set by the writer). */
62
+ ids?: {
63
+ toServer: (table: string, id: string) => string;
64
+ toLocal: (table: string, id: string) => string;
65
+ };
66
+ };
67
+ export type BaseSourceOptions = {
68
+ /** The most records to load (default 10,000). A base works on all of them in the page. */
69
+ limit?: number;
70
+ /** The record's key column (default `id`). */
71
+ idField?: string;
72
+ /**
73
+ * The database makes the key (a serial column, a default uuid): a new
74
+ * record is inserted without the page's id, and the writer uses the one it
75
+ * gets back. By default the page's id is the key.
76
+ */
77
+ serverIds?: boolean;
78
+ /** Column names that differ from the field names: `{ dueDate: 'due_date' }`. */
79
+ columns?: Record<string, string>;
80
+ /** How link fields are stored: `{ project: { column: 'project_id' } }`, or a junction. */
81
+ links?: Record<string, ForeignKeyLink | JunctionLink>;
82
+ /** Fields kept in the page only. */
83
+ skip?: string[];
84
+ /**
85
+ * Changes made elsewhere: call `onChange` when the table changed, and return
86
+ * a function that stops listening. With Supabase:
87
+ * `(onChange) => createSupabaseRealtime({ client, table, onChange }).unsubscribe`.
88
+ */
89
+ realtime?: (onChange: () => void) => (() => void) | {
90
+ unsubscribe: () => void;
91
+ };
92
+ };
93
+ /**
94
+ * A base table's source from any data source with the grid's contract
95
+ * (`createSupabaseDataSource`, `createSqlDataSource`, `createRestDataSource`,
96
+ * `createInMemoryDataSource`, or your own).
97
+ *
98
+ * ```ts
99
+ * source: baseSource(createSupabaseDataSource({ client, table: 'tasks', schema }), {
100
+ * links: { project: { column: 'project_id' } },
101
+ * realtime: (onChange) => createSupabaseRealtime({ client, table: 'tasks', onChange }).unsubscribe,
102
+ * })
103
+ * ```
104
+ */
105
+ export declare function baseSource(ds: ServerDataSource<any>, options?: BaseSourceOptions): BaseSource;
106
+ export type BaseSyncStatus = {
107
+ state: 'idle';
108
+ } | {
109
+ state: 'loading';
110
+ } | {
111
+ state: 'saving';
112
+ pending: number;
113
+ } | {
114
+ state: 'error';
115
+ message: string;
116
+ table: string;
117
+ } | {
118
+ state: 'limited';
119
+ table: string;
120
+ limit: number;
121
+ };
122
+ export type BaseSyncOptions = {
123
+ getTables: () => BaseTable[] | undefined;
124
+ /** Put tables in the page without writing them (loaded, undone, changed elsewhere). */
125
+ apply: (tables: BaseTable[]) => void;
126
+ onStatus?: (status: BaseSyncStatus) => void;
127
+ onError?: (error: {
128
+ table: string;
129
+ action: 'insert' | 'update' | 'delete' | 'load';
130
+ message: string;
131
+ }) => void;
132
+ };
133
+ export declare function createBaseSync(options: BaseSyncOptions): {
134
+ load: () => Promise<void>;
135
+ push: (list: BaseTable[] | undefined) => void;
136
+ /** Read a table again now (after a change you know of). */
137
+ refresh: (tableId: string) => void;
138
+ /** Resolves when every queued write has finished. */
139
+ settled: () => Promise<void>;
140
+ dispose(): void;
141
+ };
142
+ export {};