@getaster/data 0.0.0-stage → 0.1.0-preview.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 (84) hide show
  1. package/LICENSE +202 -0
  2. package/NOTICE +10 -0
  3. package/README.md +6 -2
  4. package/dist/authentication.d.ts +42 -0
  5. package/dist/authentication.js +121 -0
  6. package/dist/changes/changeset.d.ts +47 -0
  7. package/dist/changes/changeset.js +75 -0
  8. package/dist/collection/aggregate.d.ts +22 -0
  9. package/dist/collection/aggregate.js +94 -0
  10. package/dist/collection/collection.d.ts +96 -0
  11. package/dist/collection/collection.js +588 -0
  12. package/dist/collection/grouping.d.ts +75 -0
  13. package/dist/collection/grouping.js +190 -0
  14. package/dist/collection/groups.d.ts +31 -0
  15. package/dist/collection/groups.js +117 -0
  16. package/dist/collection/summary.d.ts +17 -0
  17. package/dist/collection/summary.js +103 -0
  18. package/dist/composition/activity.d.ts +36 -0
  19. package/dist/composition/activity.js +60 -0
  20. package/dist/composition/command.d.ts +22 -0
  21. package/dist/composition/command.js +57 -0
  22. package/dist/composition/lifetime.d.ts +11 -0
  23. package/dist/composition/lifetime.js +33 -0
  24. package/dist/composition/sessions.d.ts +64 -0
  25. package/dist/composition/sessions.js +209 -0
  26. package/dist/context.d.ts +76 -0
  27. package/dist/context.js +211 -0
  28. package/dist/criteria/ast.d.ts +79 -0
  29. package/dist/criteria/ast.js +1 -0
  30. package/dist/criteria/compile.d.ts +37 -0
  31. package/dist/criteria/compile.js +233 -0
  32. package/dist/criteria/evaluate.d.ts +20 -0
  33. package/dist/criteria/evaluate.js +171 -0
  34. package/dist/criteria/filter-draft.d.ts +79 -0
  35. package/dist/criteria/filter-draft.js +320 -0
  36. package/dist/criteria/relative.d.ts +13 -0
  37. package/dist/criteria/relative.js +82 -0
  38. package/dist/criteria/values.d.ts +20 -0
  39. package/dist/criteria/values.js +283 -0
  40. package/dist/edit/edit-model.d.ts +152 -0
  41. package/dist/edit/edit-model.js +620 -0
  42. package/dist/edit/list-edit.d.ts +116 -0
  43. package/dist/edit/list-edit.js +332 -0
  44. package/dist/edit/record-edit.d.ts +93 -0
  45. package/dist/edit/record-edit.js +317 -0
  46. package/dist/export/csv.d.ts +25 -0
  47. package/dist/export/csv.js +75 -0
  48. package/dist/index.d.ts +38 -0
  49. package/dist/index.js +35 -0
  50. package/dist/odata/errors.d.ts +25 -0
  51. package/dist/odata/errors.js +56 -0
  52. package/dist/odata/source.d.ts +65 -0
  53. package/dist/odata/source.js +193 -0
  54. package/dist/progress/status.d.ts +29 -0
  55. package/dist/progress/status.js +105 -0
  56. package/dist/schema/csdl.d.ts +13 -0
  57. package/dist/schema/csdl.js +531 -0
  58. package/dist/schema/model.d.ts +132 -0
  59. package/dist/schema/model.js +1 -0
  60. package/dist/scope/filters.d.ts +130 -0
  61. package/dist/scope/filters.js +349 -0
  62. package/dist/scope/publications.d.ts +27 -0
  63. package/dist/scope/publications.js +74 -0
  64. package/dist/scope/related-aggregate.d.ts +9 -0
  65. package/dist/scope/related-aggregate.js +67 -0
  66. package/dist/scope/scope.d.ts +181 -0
  67. package/dist/scope/scope.js +574 -0
  68. package/dist/selection/selection.d.ts +56 -0
  69. package/dist/selection/selection.js +137 -0
  70. package/dist/store/keys.d.ts +10 -0
  71. package/dist/store/keys.js +21 -0
  72. package/dist/store/store.d.ts +72 -0
  73. package/dist/store/store.js +143 -0
  74. package/dist/template/template.d.ts +19 -0
  75. package/dist/template/template.js +186 -0
  76. package/dist/transport.d.ts +28 -0
  77. package/dist/transport.js +20 -0
  78. package/dist/tree/tree.d.ts +117 -0
  79. package/dist/tree/tree.js +435 -0
  80. package/dist/validate/criteria-rule.d.ts +28 -0
  81. package/dist/validate/criteria-rule.js +22 -0
  82. package/dist/validate/validate.d.ts +13 -0
  83. package/dist/validate/validate.js +123 -0
  84. package/package.json +32 -4
@@ -0,0 +1,283 @@
1
+ /** A criteria problem: unknown field, a value that doesn't fit its field, or a bad stored value. */
2
+ export class CriteriaError extends Error {
3
+ constructor(message) {
4
+ super(message);
5
+ this.name = "CriteriaError";
6
+ }
7
+ }
8
+ const INT_RANGES = {
9
+ "Edm.Byte": [0, 255],
10
+ "Edm.SByte": [-128, 127],
11
+ "Edm.Int16": [-32768, 32767],
12
+ "Edm.Int32": [-2147483648, 2147483647],
13
+ };
14
+ const RE_INT64 = /^-?\d+$/;
15
+ const RE_DECIMAL = /^-?\d+(\.\d+)?([eE][+-]?\d+)?$/;
16
+ const RE_DATE = /^(-?\d{4,})-(\d{2})-(\d{2})$/;
17
+ const RE_TIME = /^(\d{2}):(\d{2})(?::(\d{2})(?:\.(\d{1,12}))?)?$/;
18
+ const RE_DTO = /^(-?\d{4,})-(\d{2})-(\d{2})[Tt](\d{2}):(\d{2})(?::(\d{2})(?:\.(\d{1,12}))?)?(?:[Zz]|([+-])(\d{2}):(\d{2}))$/;
19
+ const RE_GUID = /^[0-9a-fA-F]{8}(-[0-9a-fA-F]{4}){3}-[0-9a-fA-F]{12}$/;
20
+ const RE_DURATION = /^(-)?P(?:(\d+)D)?(?:T(?:(\d+)H)?(?:(\d+)M)?(?:(\d+)(?:\.(\d{1,12}))?S)?)?$/;
21
+ export function isFloatType(field) {
22
+ return field.type === "Edm.Single" || field.type === "Edm.Double";
23
+ }
24
+ /** Throws `CriteriaError` unless `value` is a legal JS value for `field`'s Edm type. */
25
+ export function assertLiteral(value, field) {
26
+ if (value === null)
27
+ return;
28
+ const bad = (why) => {
29
+ throw new CriteriaError(`${field.name}: ${why} (${field.type}), got ${JSON.stringify(value)}`);
30
+ };
31
+ const t = field.type;
32
+ switch (t) {
33
+ case "Edm.String":
34
+ if (typeof value !== "string")
35
+ bad("expected a string");
36
+ return;
37
+ case "Edm.Boolean":
38
+ if (typeof value !== "boolean")
39
+ bad("expected a boolean");
40
+ return;
41
+ case "Edm.Byte":
42
+ case "Edm.SByte":
43
+ case "Edm.Int16":
44
+ case "Edm.Int32": {
45
+ const [lo, hi] = INT_RANGES[t];
46
+ if (typeof value !== "number" || !Number.isInteger(value) || value < lo || value > hi) {
47
+ bad("expected an integer in range");
48
+ }
49
+ return;
50
+ }
51
+ case "Edm.Single":
52
+ case "Edm.Double":
53
+ if (typeof value !== "number")
54
+ bad("expected a number");
55
+ return;
56
+ case "Edm.Int64":
57
+ if (typeof value !== "string" || !RE_INT64.test(value))
58
+ bad("expected an integer string");
59
+ return;
60
+ case "Edm.Decimal":
61
+ if (typeof value !== "string" || !RE_DECIMAL.test(value))
62
+ bad("expected a decimal string");
63
+ return;
64
+ case "Edm.Date":
65
+ if (typeof value !== "string" || !RE_DATE.test(value))
66
+ bad("expected YYYY-MM-DD");
67
+ return;
68
+ case "Edm.TimeOfDay":
69
+ if (typeof value !== "string" || !RE_TIME.test(value))
70
+ bad("expected hh:mm[:ss[.f]]");
71
+ return;
72
+ case "Edm.DateTimeOffset":
73
+ if (typeof value !== "string" || !RE_DTO.test(value))
74
+ bad("expected an RFC 3339 timestamp with offset");
75
+ return;
76
+ case "Edm.Guid":
77
+ if (typeof value !== "string" || !RE_GUID.test(value))
78
+ bad("expected a GUID");
79
+ return;
80
+ case "Edm.Duration":
81
+ if (typeof value !== "string" || !RE_DURATION.test(value) || value === "P" || value === "-P") {
82
+ bad("expected an ISO 8601 day-time duration");
83
+ }
84
+ return;
85
+ case "Edm.Binary":
86
+ throw new CriteriaError(`${field.name}: Edm.Binary values are not supported in criteria`);
87
+ }
88
+ }
89
+ const RE_NUM = /^([+-]?)(\d*)(?:\.(\d*))?(?:[eE]([+-]?\d+))?$/;
90
+ function parseDec(v, field) {
91
+ const s = typeof v === "number" ? String(v) : v;
92
+ const m = RE_NUM.exec(s);
93
+ if (!m || (m[2] === "" && (m[3] ?? "") === "")) {
94
+ throw new CriteriaError(`${field.name}: not a number: ${JSON.stringify(v)}`);
95
+ }
96
+ const frac = m[3] ?? "";
97
+ let mant = BigInt(`${m[2] || "0"}${frac}`);
98
+ if (m[1] === "-")
99
+ mant = -mant;
100
+ const exp = Number(m[4] ?? 0) - frac.length;
101
+ return exp <= 0 ? { m: mant, e: -exp } : { m: mant * 10n ** BigInt(exp), e: 0 };
102
+ }
103
+ function cmpDec(a, b) {
104
+ const e = Math.max(a.e, b.e);
105
+ const x = a.m * 10n ** BigInt(e - a.e);
106
+ const y = b.m * 10n ** BigInt(e - b.e);
107
+ return x < y ? -1 : x > y ? 1 : 0;
108
+ }
109
+ function cmpNum(a, b) {
110
+ if (Number.isNaN(a) || Number.isNaN(b))
111
+ return undefined;
112
+ return a < b ? -1 : a > b ? 1 : 0;
113
+ }
114
+ function toNumber(v, field) {
115
+ if (typeof v === "number")
116
+ return v;
117
+ if (typeof v === "string" && RE_NUM.test(v))
118
+ return Number(v);
119
+ throw new CriteriaError(`${field.name}: expected a number, got ${JSON.stringify(v)}`);
120
+ }
121
+ // ---- temporal parsing, memoised: sorting parses each value many times ----
122
+ const memo = new Map();
123
+ function cached(kind, s, parse) {
124
+ const k = `${kind}\0${s}`;
125
+ const hit = memo.get(k);
126
+ if (hit !== undefined)
127
+ return hit;
128
+ const v = parse();
129
+ if (memo.size >= 20000)
130
+ memo.clear();
131
+ memo.set(k, v);
132
+ return v;
133
+ }
134
+ function daysFromCivil(y, m, d) {
135
+ const yy = m <= 2 ? y - 1 : y;
136
+ const era = Math.floor(yy / 400);
137
+ const yoe = yy - era * 400;
138
+ const doy = Math.floor((153 * (m + (m > 2 ? -3 : 9)) + 2) / 5) + d - 1;
139
+ const doe = yoe * 365 + Math.floor(yoe / 4) - Math.floor(yoe / 100) + doy;
140
+ return era * 146097 + doe - 719468;
141
+ }
142
+ function nanos(frac) {
143
+ return frac === undefined ? 0 : Number(frac.padEnd(12, "0").slice(0, 9));
144
+ }
145
+ function bad(field, v) {
146
+ throw new CriteriaError(`${field.name}: invalid ${field.type} value ${JSON.stringify(v)}`);
147
+ }
148
+ /** `[days since epoch]` for a Date. */
149
+ function dateKey(v, field) {
150
+ if (typeof v !== "string")
151
+ return bad(field, v);
152
+ return cached("d", v, () => {
153
+ const m = RE_DATE.exec(v);
154
+ if (!m)
155
+ return bad(field, v);
156
+ return daysFromCivil(Number(m[1]), Number(m[2]), Number(m[3]));
157
+ });
158
+ }
159
+ function timeKey(v, field) {
160
+ if (typeof v !== "string")
161
+ return bad(field, v);
162
+ return cached("t", v, () => {
163
+ const m = RE_TIME.exec(v);
164
+ if (!m)
165
+ return bad(field, v);
166
+ // A leap second (:60) sorts after :59 and its fraction, as in the engine.
167
+ return (Number(m[1]) * 3600 + Number(m[2]) * 60 + Number(m[3] ?? 0)) * 1e9 + nanos(m[4]);
168
+ });
169
+ }
170
+ /** `[UTC seconds, nanoseconds]`: orders by instant, ignoring the offset. */
171
+ function instantKey(v, field) {
172
+ if (typeof v !== "string")
173
+ return bad(field, v);
174
+ return cached("i", v, () => {
175
+ const m = RE_DTO.exec(v);
176
+ if (!m)
177
+ return bad(field, v);
178
+ const offset = m[8] === undefined ? 0 : (m[8] === "-" ? -1 : 1) * (Number(m[9]) * 3600 + Number(m[10]) * 60);
179
+ const secs = daysFromCivil(Number(m[1]), Number(m[2]), Number(m[3])) * 86400 +
180
+ Number(m[4]) * 3600 +
181
+ Number(m[5]) * 60 +
182
+ Number(m[6] ?? 0) -
183
+ offset;
184
+ return [secs, nanos(m[7])];
185
+ });
186
+ }
187
+ function durationKey(v, field) {
188
+ if (typeof v !== "string")
189
+ return bad(field, v);
190
+ return cached("u", v, () => {
191
+ const m = RE_DURATION.exec(v);
192
+ if (!m || v === "P" || v === "-P")
193
+ return bad(field, v);
194
+ let ns = BigInt(m[2] ?? 0) * 86400000000000n +
195
+ BigInt(m[3] ?? 0) * 3600000000000n +
196
+ BigInt(m[4] ?? 0) * 60000000000n +
197
+ BigInt(m[5] ?? 0) * 1000000000n +
198
+ BigInt(nanos(m[6]));
199
+ if (m[1] === "-")
200
+ ns = -ns;
201
+ return ns;
202
+ });
203
+ }
204
+ /** Unicode code point order: what the engine's byte-wise UTF-8 comparison gives. */
205
+ export function compareCodePoints(a, b) {
206
+ const n = Math.min(a.length, b.length);
207
+ for (let i = 0; i < n; i++) {
208
+ const x = a.charCodeAt(i);
209
+ const y = b.charCodeAt(i);
210
+ if (x === y)
211
+ continue;
212
+ // UTF-16 order differs from code point order only when a surrogate meets a
213
+ // BMP unit at or above U+E000: surrogates stand for code points >= U+10000.
214
+ const xs = x >= 0xd800 && x <= 0xdfff;
215
+ const ys = y >= 0xd800 && y <= 0xdfff;
216
+ if (xs !== ys)
217
+ return xs ? 1 : -1;
218
+ return x < y ? -1 : 1;
219
+ }
220
+ return a.length === b.length ? 0 : a.length < b.length ? -1 : 1;
221
+ }
222
+ function sign(x) {
223
+ return x < 0 ? -1 : x > 0 ? 1 : 0;
224
+ }
225
+ /**
226
+ * Orders two non-null values of `field`'s type; `undefined` when unordered
227
+ * (a NaN is involved).
228
+ */
229
+ export function ordering(a, b, field) {
230
+ switch (field.type) {
231
+ case "Edm.String":
232
+ if (typeof a !== "string" || typeof b !== "string")
233
+ return bad(field, typeof a !== "string" ? a : b);
234
+ return compareCodePoints(a, b);
235
+ case "Edm.Boolean":
236
+ if (typeof a !== "boolean" || typeof b !== "boolean")
237
+ return bad(field, typeof a !== "boolean" ? a : b);
238
+ return a === b ? 0 : a ? 1 : -1;
239
+ case "Edm.Byte":
240
+ case "Edm.SByte":
241
+ case "Edm.Int16":
242
+ case "Edm.Int32":
243
+ case "Edm.Single":
244
+ case "Edm.Double":
245
+ return cmpNum(toNumber(a, field), toNumber(b, field));
246
+ case "Edm.Int64":
247
+ case "Edm.Decimal": {
248
+ // A non-integer JS number is a double, which makes the comparison floating point.
249
+ const fa = typeof a === "number" && !Number.isInteger(a);
250
+ const fb = typeof b === "number" && !Number.isInteger(b);
251
+ if (fa || fb)
252
+ return cmpNum(toNumber(a, field), toNumber(b, field));
253
+ return cmpDec(parseDec(a, field), parseDec(b, field));
254
+ }
255
+ case "Edm.Date":
256
+ return sign(dateKey(a, field) - dateKey(b, field));
257
+ case "Edm.TimeOfDay":
258
+ return sign(timeKey(a, field) - timeKey(b, field));
259
+ case "Edm.DateTimeOffset": {
260
+ const [s1, n1] = instantKey(a, field);
261
+ const [s2, n2] = instantKey(b, field);
262
+ return s1 !== s2 ? sign(s1 - s2) : sign(n1 - n2);
263
+ }
264
+ case "Edm.Duration":
265
+ return sign(durationKey(a, field) - durationKey(b, field));
266
+ case "Edm.Guid":
267
+ if (typeof a !== "string" || typeof b !== "string")
268
+ return bad(field, typeof a !== "string" ? a : b);
269
+ return compareCodePoints(a.toLowerCase(), b.toLowerCase());
270
+ case "Edm.Binary":
271
+ throw new CriteriaError(`${field.name}: Edm.Binary values are not supported in criteria`);
272
+ }
273
+ }
274
+ /** Whether a number value is NaN (sorts after everything, equal to itself). */
275
+ export function isNaNValue(v) {
276
+ return typeof v === "number" && Number.isNaN(v);
277
+ }
278
+ /** A comparison's value, which must be plain by now: relative dates are resolved before compiling or evaluating. */
279
+ export function plain(v) {
280
+ if (typeof v === "object" && v !== null)
281
+ throw new CriteriaError("a relative date reached a query unresolved; resolve it at fetch time (resolveRelative)");
282
+ return v;
283
+ }
@@ -0,0 +1,152 @@
1
+ import { type ReadonlySignal } from "@preact/signals-core";
2
+ import { type ChangeSet } from "../changes/changeset.ts";
3
+ import type { DataContext, Saved } from "../context.ts";
4
+ import type { StoredRecord } from "../store/store.ts";
5
+ import type { Entity, EntityType, Value } from "../schema/model.ts";
6
+ import { type Case, type Explanation, type FieldState, type PageScope, type StateRules } from "../scope/scope.ts";
7
+ import { type KeyText } from "../store/keys.ts";
8
+ import { type FieldErrors } from "../validate/validate.ts";
9
+ /**
10
+ * An application rule: errors by field for the current values. A rule's
11
+ * message wins over the metadata's for the same field, so it can say what
12
+ * the metadata can't ("already a member") or say it better for the screen.
13
+ */
14
+ export type Rule = (values: Entity) => Readonly<Record<string, string | undefined>>;
15
+ export interface EditOptions {
16
+ readonly rules?: readonly Rule[];
17
+ }
18
+ /**
19
+ * A field's page rules. With `clearWhenHidden`, hiding a shown field clears
20
+ * its value. `derived` gives the field a value from the page: cases for
21
+ * `scope.select`, or a signal computed in code, in the field's JSON form.
22
+ * On a read-only field it always replaces the value; on an editable one it's
23
+ * a suggestion that stops once the person changes the field.
24
+ */
25
+ export interface FieldRules extends StateRules {
26
+ readonly clearWhenHidden?: boolean;
27
+ readonly derived?: readonly Case[] | ReadonlySignal<unknown>;
28
+ }
29
+ /** An edit's working state, enough to resume it after a reload. Plain data. */
30
+ export interface EditDraft {
31
+ readonly key?: KeyText;
32
+ readonly baseline: Entity;
33
+ readonly values: Entity;
34
+ readonly etag?: string;
35
+ readonly forced: readonly string[];
36
+ /** Fields with a derived value that the person changed, so the suggestion no longer applies. */
37
+ readonly typed?: readonly string[];
38
+ }
39
+ export interface Conflict {
40
+ /** The record as it now is on the server. */
41
+ readonly theirs: Entity;
42
+ /** Fields both sides changed since editing began. */
43
+ readonly fields: readonly string[];
44
+ }
45
+ /**
46
+ * Uncommitted values for one record, apart from the stored record.
47
+ *
48
+ * Invariant: `baseline` is the stored record the edit started from (or the
49
+ * defaults for a new one); a field is pending when its value differs from
50
+ * the baseline, or when pending is forced on.
51
+ *
52
+ * A field hidden on the stored record (`StoredRecord.hidden`) is read-only,
53
+ * never pending and never sent: its `null` isn't a value the person has, so
54
+ * neither a change, a check nor a save may act on it. Every diff below goes
55
+ * through `#diff`, which leaves hidden fields out.
56
+ */
57
+ export declare class EditModel {
58
+ #private;
59
+ readonly entitySet: string;
60
+ readonly type: EntityType;
61
+ readonly key: ReadonlySignal<KeyText | undefined>;
62
+ readonly baseline: ReadonlySignal<Entity>;
63
+ readonly values: ReadonlySignal<Entity>;
64
+ /** Fields the server hides from this caller on the record; their value is unknown, not empty. */
65
+ readonly hidden: ReadonlySignal<ReadonlySet<string>>;
66
+ readonly errors: ReadonlySignal<FieldErrors>;
67
+ /** A failure that isn't about one field. */
68
+ readonly formError: ReadonlySignal<string | undefined>;
69
+ readonly busy: ReadonlySignal<boolean>;
70
+ readonly conflict: ReadonlySignal<Conflict | undefined>;
71
+ /** The record was deleted, by someone else or elsewhere in this client. Saving is refused. */
72
+ readonly deleted: ReadonlySignal<boolean>;
73
+ readonly isNew: ReadonlySignal<boolean>;
74
+ readonly dirty: ReadonlySignal<boolean>;
75
+ /** Whether the current values would pass `validate()`, without showing errors. */
76
+ readonly valid: ReadonlySignal<boolean>;
77
+ /** Edits an existing record when `key` is given, otherwise a new one starting from `defaults`. */
78
+ constructor(ctx: DataContext, set: string, key?: KeyText, defaults?: Entity, options?: EditOptions);
79
+ /**
80
+ * A change shown as it was made: `before` is the baseline and `after` the
81
+ * values, so pending marks exactly what changed. Every field is read-only
82
+ * and nothing is saved.
83
+ */
84
+ static replay(ctx: DataContext, set: string, before: Entity, after: Entity, forced?: readonly string[]): EditModel;
85
+ /** A past change shown over its old values (`replay`), not an edit. */
86
+ get replaying(): boolean;
87
+ /** The ETag of the version the edit started from. */
88
+ get etag(): string | undefined;
89
+ /**
90
+ * Takes what was saved elsewhere (a deep update of a parent) as the new
91
+ * starting point: the saved record if given, otherwise the current values.
92
+ */
93
+ accept(saved?: StoredRecord): void;
94
+ /** Shows errors the server gave for these fields, such as a deep update's errors on a child. */
95
+ showErrors(errors: FieldErrors): void;
96
+ /** The working state, for keeping across a reload. */
97
+ draft(): EditDraft;
98
+ /**
99
+ * Takes up a draft of this record. If the stored record has moved on since,
100
+ * that's handled as any external change: followed where it touches nothing
101
+ * edited, otherwise a conflict.
102
+ */
103
+ resume(draft: EditDraft): void;
104
+ get(name: string): Value | undefined;
105
+ /**
106
+ * Sets a value. A field that's read-only keeps its value and shows the
107
+ * reason as its error. Once a field shows an error, the error follows the
108
+ * value: it changes with it and clears as soon as the value is valid.
109
+ * Empty text is null on a field marked `emptyAsNull`.
110
+ */
111
+ set(name: string, entered: Value): void;
112
+ /**
113
+ * Page rules for some fields, named `<target>.<field>` in the scope's
114
+ * `explain`. Call once, before the fields are shown.
115
+ */
116
+ applyRules(scope: PageScope, target: string, rules: Readonly<Record<string, FieldRules>>): void;
117
+ /**
118
+ * Whether the field holds its derived value: suggested or replaced, not
119
+ * typed over. False for a field without one.
120
+ */
121
+ derived(name: string): ReadonlySignal<boolean>;
122
+ /** The page rules behind a field's state, with what they read now. */
123
+ explain(name: string): Explanation[];
124
+ /**
125
+ * A field's state: its page rules, then what the metadata and the record
126
+ * say. Computed fields are read-only, immutable ones once saved, and every
127
+ * field of a deleted record; a non-nullable field is required.
128
+ */
129
+ state(name: string): ReadonlySignal<FieldState>;
130
+ pending(name: string): ReadonlySignal<boolean>;
131
+ forcePending(name: string, on: boolean): void;
132
+ /** Puts the starting value back; a derived field takes suggestions again from the next change. */
133
+ revert(name?: string): void;
134
+ /** Validates every field and shows the errors; returns whether the values can be saved. */
135
+ validate(): boolean;
136
+ /** The change a save would make, or `undefined` when nothing changed. */
137
+ changes(): ChangeSet | undefined;
138
+ /**
139
+ * Validates, then creates or updates. Field errors from the server land on
140
+ * their fields. On a 412 the newer record is loaded: when the other change
141
+ * touched none of the fields edited here, the edit is rebased onto it and
142
+ * sent once more; otherwise `conflict` is set. Returns `undefined` when
143
+ * nothing was saved, and then `errors`, `formError`, `conflict` or
144
+ * `deleted` says why.
145
+ */
146
+ save(): Promise<Saved | undefined>;
147
+ /** Takes the server's record as the new baseline and keeps local changes on top. */
148
+ rebase(): void;
149
+ /** Drops local changes in favour of the server's record. */
150
+ discard(): void;
151
+ dispose(): void;
152
+ }