@fieldia/core 0.24.0 → 0.25.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (117) hide show
  1. package/README.md +77 -1
  2. package/package.json +1 -1
  3. package/schema/page.schema.json +951 -380
  4. package/src/index.d.mts +5 -2
  5. package/src/index.d.ts +5 -2
  6. package/src/index.js +14 -1
  7. package/src/index.js.map +1 -1
  8. package/src/index.mjs +6 -2
  9. package/src/index.mjs.map +1 -1
  10. package/src/lib/format/actions.d.mts +31 -1
  11. package/src/lib/format/actions.d.ts +31 -1
  12. package/src/lib/format/actions.js +1 -0
  13. package/src/lib/format/actions.js.map +1 -1
  14. package/src/lib/format/actions.mjs +1 -0
  15. package/src/lib/format/actions.mjs.map +1 -1
  16. package/src/lib/format/duplicate-kinds.d.mts +10 -0
  17. package/src/lib/format/duplicate-kinds.d.ts +10 -0
  18. package/src/lib/format/duplicate-kinds.js +14 -0
  19. package/src/lib/format/duplicate-kinds.js.map +1 -0
  20. package/src/lib/format/duplicate-kinds.mjs +10 -0
  21. package/src/lib/format/duplicate-kinds.mjs.map +1 -0
  22. package/src/lib/format/duplicates.d.mts +22 -0
  23. package/src/lib/format/duplicates.d.ts +22 -0
  24. package/src/lib/format/duplicates.js +24 -0
  25. package/src/lib/format/duplicates.js.map +1 -0
  26. package/src/lib/format/duplicates.mjs +21 -0
  27. package/src/lib/format/duplicates.mjs.map +1 -0
  28. package/src/lib/format/field.d.mts +236 -0
  29. package/src/lib/format/field.d.ts +236 -0
  30. package/src/lib/format/field.js +4 -3
  31. package/src/lib/format/field.js.map +1 -1
  32. package/src/lib/format/field.mjs +2 -1
  33. package/src/lib/format/field.mjs.map +1 -1
  34. package/src/lib/format/import-fields.d.mts +18 -0
  35. package/src/lib/format/import-fields.d.ts +18 -0
  36. package/src/lib/format/import-fields.js +26 -0
  37. package/src/lib/format/import-fields.js.map +1 -0
  38. package/src/lib/format/import-fields.mjs +21 -0
  39. package/src/lib/format/import-fields.mjs.map +1 -0
  40. package/src/lib/format/layout.d.mts +5453 -1522
  41. package/src/lib/format/layout.d.ts +5453 -1522
  42. package/src/lib/format/layout.js +90 -32
  43. package/src/lib/format/layout.js.map +1 -1
  44. package/src/lib/format/layout.mjs +89 -31
  45. package/src/lib/format/layout.mjs.map +1 -1
  46. package/src/lib/format/layout.types.d.mts +5 -2
  47. package/src/lib/format/layout.types.d.ts +5 -2
  48. package/src/lib/format/page.d.mts +961 -127
  49. package/src/lib/format/page.d.ts +961 -127
  50. package/src/lib/format/page.js +14 -0
  51. package/src/lib/format/page.js.map +1 -1
  52. package/src/lib/format/page.mjs +14 -0
  53. package/src/lib/format/page.mjs.map +1 -1
  54. package/src/lib/format/references.d.mts +29 -0
  55. package/src/lib/format/references.d.ts +29 -0
  56. package/src/lib/format/references.js +172 -24
  57. package/src/lib/format/references.js.map +1 -1
  58. package/src/lib/format/references.mjs +172 -24
  59. package/src/lib/format/references.mjs.map +1 -1
  60. package/src/lib/format/translate.js +8 -4
  61. package/src/lib/format/translate.js.map +1 -1
  62. package/src/lib/format/translate.mjs +8 -4
  63. package/src/lib/format/translate.mjs.map +1 -1
  64. package/src/lib/format/upgrade.d.mts +5 -3
  65. package/src/lib/format/upgrade.d.ts +5 -3
  66. package/src/lib/format/upgrade.js +5 -3
  67. package/src/lib/format/upgrade.js.map +1 -1
  68. package/src/lib/format/upgrade.mjs +5 -3
  69. package/src/lib/format/upgrade.mjs.map +1 -1
  70. package/src/lib/format/version.d.mts +2 -2
  71. package/src/lib/format/version.d.ts +2 -2
  72. package/src/lib/format/version.js +2 -2
  73. package/src/lib/format/version.js.map +1 -1
  74. package/src/lib/format/version.mjs +2 -2
  75. package/src/lib/format/version.mjs.map +1 -1
  76. package/src/lib/record/data-source.d.mts +121 -0
  77. package/src/lib/record/data-source.d.ts +121 -0
  78. package/src/lib/record/data-source.js +16 -0
  79. package/src/lib/record/data-source.js.map +1 -1
  80. package/src/lib/record/data-source.mjs +15 -0
  81. package/src/lib/record/data-source.mjs.map +1 -1
  82. package/src/lib/record/form.d.mts +45 -1
  83. package/src/lib/record/form.d.ts +45 -1
  84. package/src/lib/record/form.js +53 -0
  85. package/src/lib/record/form.js.map +1 -1
  86. package/src/lib/record/form.mjs +53 -0
  87. package/src/lib/record/form.mjs.map +1 -1
  88. package/src/lib/record/memory-data-source.d.mts +12 -5
  89. package/src/lib/record/memory-data-source.d.ts +12 -5
  90. package/src/lib/record/memory-data-source.js +61 -0
  91. package/src/lib/record/memory-data-source.js.map +1 -1
  92. package/src/lib/record/memory-data-source.mjs +62 -1
  93. package/src/lib/record/memory-data-source.mjs.map +1 -1
  94. package/src/lib/record/memory-duplicates.d.mts +19 -0
  95. package/src/lib/record/memory-duplicates.d.ts +19 -0
  96. package/src/lib/record/memory-duplicates.js +78 -0
  97. package/src/lib/record/memory-duplicates.js.map +1 -0
  98. package/src/lib/record/memory-duplicates.mjs +74 -0
  99. package/src/lib/record/memory-duplicates.mjs.map +1 -0
  100. package/src/lib/record/run.d.mts +31 -1
  101. package/src/lib/record/run.d.ts +31 -1
  102. package/src/lib/record/run.js +15 -1
  103. package/src/lib/record/run.js.map +1 -1
  104. package/src/lib/record/run.mjs +15 -1
  105. package/src/lib/record/run.mjs.map +1 -1
  106. package/src/lib/record/waits.d.mts +32 -0
  107. package/src/lib/record/waits.d.ts +32 -0
  108. package/src/lib/record/waits.js +89 -0
  109. package/src/lib/record/waits.js.map +1 -0
  110. package/src/lib/record/waits.mjs +85 -0
  111. package/src/lib/record/waits.mjs.map +1 -0
  112. package/src/runtime.d.mts +10 -7
  113. package/src/runtime.d.ts +10 -7
  114. package/src/runtime.js +8 -3
  115. package/src/runtime.js.map +1 -1
  116. package/src/runtime.mjs +2 -1
  117. package/src/runtime.mjs.map +1 -1
@@ -14,6 +14,7 @@ import { rolesAllow } from './roles.mjs';
14
14
  import { compileFieldTone, compileTable } from './cells.mjs';
15
15
  import { fill, MESSAGES } from './messages.mjs';
16
16
  import { loadedRecordOf, saveProblemOf, } from './data-source.mjs';
17
+ import { waitingOn, workedOutFrom } from './waits.mjs';
17
18
  import { createUploads, fileBytesIn, uploadGoing, withoutFileBytes } from './uploads.mjs';
18
19
  import { hostScheduler } from './scheduler.mjs';
19
20
  import { createMoments, createRunner } from './run.mjs';
@@ -158,6 +159,8 @@ function innerForm(options, within, place, outer) {
158
159
  const editingNow = () => editing && !(started && state.can?.edit === false);
159
160
  const about = () => ({ recordId: started ? state.recordId : (options.recordId ?? null), user: options.user, editing: editingNow(), context: options.context, parent: parentContext });
160
161
  const computed = compileComputed(page, today, about);
162
+ /** Each value worked out from others — by the page, or by the app as the lines change — and what it waits for. */
163
+ const workedOut = workedOutFrom(page, !!options.dataSource?.onchange && page.data.kind === 'record', !!options.parent);
161
164
  const setWhen = compileSetWhen(page, today, about);
162
165
  /** The saved forms placed on the page, by the part's id: each one's answers sit under its name, as JSON. */
163
166
  const parts = new Map();
@@ -317,6 +320,13 @@ function innerForm(options, within, place, outer) {
317
320
  * for is still the one shown, as it was. A save's answer, a call's, an onchange's, a name looked up, a line's
318
321
  * dialog, a record step's — none is laid on another record, nor over the values the person's Discard let go.
319
322
  */
323
+ /** The data source with a member a record's page asks of it, and the page's model: refused where either is missing. */
324
+ function recordSource(member) {
325
+ const source = options.dataSource;
326
+ if (page.data.kind !== 'record' || !source?.[member])
327
+ throw new Error(`The data source has no ${member}()`);
328
+ return [source, page.data.model];
329
+ }
320
330
  function shownNow() {
321
331
  const at = generation;
322
332
  const reset = resets;
@@ -2020,6 +2030,13 @@ function innerForm(options, within, place, outer) {
2020
2030
  leave: (how) => Promise.resolve(letGo(how)),
2021
2031
  leaveNow: (how = {}) => (outer ? outer.leaveNow(how) : atOnce(how)),
2022
2032
  recordShown: shownNow,
2033
+ relatedFilter(id) {
2034
+ const node = index.get(id)?.source;
2035
+ if (node?.type !== 'related' || state.recordId == null)
2036
+ return null;
2037
+ const related = node;
2038
+ return [{ field: related.field, op: '=', value: state.recordId }, ...(related.filter ? resolveFilter(related.filter, context()) : [])];
2039
+ },
2023
2040
  post,
2024
2041
  setEditing(next) {
2025
2042
  if (next === editing)
@@ -2032,6 +2049,10 @@ function innerForm(options, within, place, outer) {
2032
2049
  loadChoices,
2033
2050
  loadDefinitions,
2034
2051
  addDefinition,
2052
+ waitsFor(name) {
2053
+ const from = workedOut.get(name);
2054
+ return from ? waitingOn(page, from, state.values, state.unread) : null;
2055
+ },
2035
2056
  problem(name) {
2036
2057
  fieldDef(name);
2037
2058
  // Only a field someone can see is asked anything, and required wherever it is shown so.
@@ -2110,6 +2131,7 @@ function innerForm(options, within, place, outer) {
2110
2131
  save: () => track(save()),
2111
2132
  canUpload: () => typeof options.dataSource?.upload === 'function',
2112
2133
  today,
2134
+ recordReads: (id, values, fields) => recordContext(values, fields ?? page.fields, { recordId: id, user: options.user, editing: false, context: options.context }),
2113
2135
  canFindSlots: () => typeof options.dataSource?.slots === 'function',
2114
2136
  findSlots(field, ask) {
2115
2137
  const source = options.dataSource;
@@ -2130,6 +2152,28 @@ function innerForm(options, within, place, outer) {
2130
2152
  values: structuredCopy(state.values),
2131
2153
  });
2132
2154
  },
2155
+ canFindDuplicates: () => !!(page.data.kind === 'record' && page.duplicates?.fields?.length && options.dataSource?.duplicates),
2156
+ async findDuplicates() {
2157
+ const [source, model] = recordSource('duplicates');
2158
+ const fields = (page.duplicates?.fields ?? []).filter((name) => page.fields[name] && !isEmpty(page.fields[name], state.values[name]));
2159
+ const recordId = state.recordId;
2160
+ // Nothing typed in any of them: nothing asked.
2161
+ if (!fields.length)
2162
+ return [];
2163
+ const { matches } = await source.duplicates({ model, recordId, values: structuredCopy(state.values), fields });
2164
+ // Never the record itself, whatever the source says; the likest first where it scores them.
2165
+ const others = (matches ?? []).filter((match) => match && String(match.id) !== String(recordId));
2166
+ return others.every((match) => typeof match.score === 'number') ? others.sort((a, b) => b.score - a.score) : others;
2167
+ },
2168
+ canMerge: () => !!(page.data.kind === 'record' && options.dataSource?.merge && options.dataSource.load),
2169
+ async readRecord(id) {
2170
+ const [source, model] = recordSource('load');
2171
+ return loadedRecordOf(await source.load({ model, id, fields: page.fields })).values;
2172
+ },
2173
+ async merge(request) {
2174
+ const [source, model] = recordSource('merge');
2175
+ return source.merge({ model, keepId: request.keepId, mergeIds: [...request.mergeIds], values: structuredCopy(request.values), fields: page.fields });
2176
+ },
2133
2177
  upload: (field, files, how = {}) => uploads.start(field, files, how),
2134
2178
  uploading: (field) => state.uploads.filter((upload) => upload.field === field && !upload.line),
2135
2179
  cancelUpload(id) {
@@ -2806,6 +2850,12 @@ function indexLayout(root) {
2806
2850
  else if (node.type === 'form') {
2807
2851
  add(node, parent, 'form', { invisible: node.invisible, readonly: node.readonly });
2808
2852
  }
2853
+ else if (node.type === 'related') {
2854
+ add(node, parent, 'other', { invisible: node.invisible });
2855
+ // A row's buttons: their roles here, hidden with the list, their condition read by it on each row's record.
2856
+ for (const button of node.rowButtons ?? [])
2857
+ add(button, node.id, 'button', {});
2858
+ }
2809
2859
  else {
2810
2860
  add(node, parent, 'other', { invisible: node.invisible });
2811
2861
  }
@@ -2832,6 +2882,9 @@ function indexLayout(root) {
2832
2882
  // A list holds no fields of its own record: only the buttons for what is selected.
2833
2883
  for (const button of root.actions ?? [])
2834
2884
  add(button, root.id, 'button', { invisible: button.invisible });
2885
+ // A row's buttons: their roles here, their condition read by the list on each row's record, which the form does not hold.
2886
+ for (const button of root.rowButtons ?? [])
2887
+ add(button, root.id, 'button', {});
2835
2888
  }
2836
2889
  else {
2837
2890
  // The title and statusbar show fields too, so they are validated like