@protobase/schema 0.23.4 → 0.23.6
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.
- package/dist/computed/computed-problems.js +51 -0
- package/dist/computed/formula.d.ts +30 -0
- package/dist/computed/formula.js +162 -0
- package/dist/computed/rollup.d.ts +27 -0
- package/dist/field.d.ts +29 -0
- package/dist/field.js +36 -0
- package/dist/index.d.ts +2 -1
- package/dist/internal.d.ts +3 -1
- package/dist/internal.js +3 -1
- package/dist/locale.d.ts +11 -0
- package/dist/locale.js +13 -0
- package/dist/model.d.ts +12 -0
- package/dist/resource-model.js +9 -1
- package/package.json +1 -1
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { formulaFields, formulaNullable, parseFormula } from "./formula.js";
|
|
2
|
+
//#region src/computed/computed-problems.ts
|
|
3
|
+
const numberTypes = [
|
|
4
|
+
"integer",
|
|
5
|
+
"bigint",
|
|
6
|
+
"decimal"
|
|
7
|
+
];
|
|
8
|
+
const writtenProblems = (model, field) => [
|
|
9
|
+
...field.sensitive ? ["cannot be sensitive"] : [],
|
|
10
|
+
...field.ai ? ["cannot be an AI field"] : [],
|
|
11
|
+
...field.file ? ["cannot be a file"] : [],
|
|
12
|
+
...field.default ? ["cannot have a default, as the database computes it"] : [],
|
|
13
|
+
...[
|
|
14
|
+
...model.primaryKey,
|
|
15
|
+
model.tenant,
|
|
16
|
+
model.owner,
|
|
17
|
+
model.softDelete
|
|
18
|
+
].includes(field.name) ? ["cannot be a key, tenant, owner or soft delete field"] : []
|
|
19
|
+
];
|
|
20
|
+
const rollupProblems = (model, field) => {
|
|
21
|
+
const { fn } = field.rollup;
|
|
22
|
+
return [
|
|
23
|
+
...field.formula !== void 0 ? ["cannot be a formula too"] : [],
|
|
24
|
+
...numberTypes.includes(field.type) ? [] : [`is ${field.type}, not an integer, bigint or decimal`],
|
|
25
|
+
...fn === "count" && field.type === "decimal" ? ["counts, so it is an integer or bigint"] : [],
|
|
26
|
+
...fn === "avg" && field.type !== "decimal" ? ["takes an average, so it is a decimal"] : [],
|
|
27
|
+
...(fn === "avg" || fn === "min" || fn === "max") && !field.nullable ? [`must be optional(), as its ${fn} is empty without records`] : [],
|
|
28
|
+
...writtenProblems(model, field)
|
|
29
|
+
].map((problem) => `rollup field "${field.name}" ${problem}`);
|
|
30
|
+
};
|
|
31
|
+
const formulaProblems = (model, field) => {
|
|
32
|
+
const node = parseFormula(field.formula);
|
|
33
|
+
const read = (name) => Object.hasOwn(model.fields, name) ? model.fields[name] : void 0;
|
|
34
|
+
return [
|
|
35
|
+
...numberTypes.includes(field.type) ? [] : [`is ${field.type}, not an integer, bigint or decimal`],
|
|
36
|
+
...formulaFields(node).flatMap((name) => {
|
|
37
|
+
const other = read(name);
|
|
38
|
+
if (!other) return [`reads "${name}", which is not a field`];
|
|
39
|
+
if (name === field.name) return [`reads "${name}", itself`];
|
|
40
|
+
if (other.formula !== void 0) return [`reads "${name}", which is a formula too`];
|
|
41
|
+
if (other.sensitive) return [`reads "${name}", which is sensitive`];
|
|
42
|
+
return numberTypes.includes(other.type) ? [] : [`reads "${name}", which is ${other.type}, not a number`];
|
|
43
|
+
}),
|
|
44
|
+
...!field.nullable && formulaNullable(node, (name) => read(name)?.nullable ?? false) ? ["must be optional(), or coalesce() the optional fields it reads"] : [],
|
|
45
|
+
...writtenProblems(model, field)
|
|
46
|
+
].map((problem) => `formula field "${field.name}" ${problem}`);
|
|
47
|
+
};
|
|
48
|
+
/** What is wrong with a resource's rollup and formula fields, as far as the resource alone tells. */
|
|
49
|
+
const computedProblems = (model) => Object.values(model.fields).flatMap((field) => [...field.rollup ? rollupProblems(model, field) : [], ...field.formula !== void 0 ? formulaProblems(model, field) : []]);
|
|
50
|
+
//#endregion
|
|
51
|
+
export { computedProblems };
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
//#region src/computed/formula.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* A formula as {@link parseFormula} reads it: numbers, the record's fields, `+ - * /`, a leading minus, parentheses,
|
|
4
|
+
* `coalesce(a, b, ...)` and `round(x)` or `round(x, digits)`.
|
|
5
|
+
*/
|
|
6
|
+
export type FormulaNode = {
|
|
7
|
+
kind: 'number';
|
|
8
|
+
value: string;
|
|
9
|
+
} | {
|
|
10
|
+
kind: 'field';
|
|
11
|
+
name: string;
|
|
12
|
+
} | {
|
|
13
|
+
kind: 'negate';
|
|
14
|
+
operand: FormulaNode;
|
|
15
|
+
} | {
|
|
16
|
+
kind: 'binary';
|
|
17
|
+
op: '+' | '-' | '*' | '/';
|
|
18
|
+
left: FormulaNode;
|
|
19
|
+
right: FormulaNode;
|
|
20
|
+
} | {
|
|
21
|
+
kind: 'coalesce';
|
|
22
|
+
args: FormulaNode[];
|
|
23
|
+
} | {
|
|
24
|
+
kind: 'round';
|
|
25
|
+
value: FormulaNode;
|
|
26
|
+
digits?: number;
|
|
27
|
+
};
|
|
28
|
+
/** Reads a formula, or throws naming what it cannot read. */
|
|
29
|
+
export declare const parseFormula: (source: string) => FormulaNode;
|
|
30
|
+
//#endregion
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
//#region src/computed/formula.ts
|
|
2
|
+
const tokenPattern = /\s*(?:(\d+(?:\.\d+)?)|([A-Za-z_][A-Za-z0-9_]*)|([-+*/(),]))/y;
|
|
3
|
+
const tokenize = (source) => {
|
|
4
|
+
const tokens = [];
|
|
5
|
+
tokenPattern.lastIndex = 0;
|
|
6
|
+
while (tokenPattern.lastIndex < source.length) {
|
|
7
|
+
const at = tokenPattern.lastIndex;
|
|
8
|
+
const match = tokenPattern.exec(source);
|
|
9
|
+
if (!match) {
|
|
10
|
+
const rest = source.slice(at);
|
|
11
|
+
if (rest.trim() === "") break;
|
|
12
|
+
const skipped = rest.length - rest.trimStart().length;
|
|
13
|
+
throw new Error(`Formula "${source}": unexpected "${rest[skipped]}" at ${at + skipped + 1}`);
|
|
14
|
+
}
|
|
15
|
+
const [whole, number, name, symbol] = match;
|
|
16
|
+
const text = number ?? name ?? symbol;
|
|
17
|
+
tokens.push({
|
|
18
|
+
kind: number !== void 0 ? "number" : name !== void 0 ? "name" : "symbol",
|
|
19
|
+
text,
|
|
20
|
+
at: at + whole.length - text.length
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
return tokens;
|
|
24
|
+
};
|
|
25
|
+
/** Reads a formula, or throws naming what it cannot read. */
|
|
26
|
+
const parseFormula = (source) => {
|
|
27
|
+
const tokens = tokenize(source);
|
|
28
|
+
let position = 0;
|
|
29
|
+
const fail = (what) => {
|
|
30
|
+
const token = tokens[position];
|
|
31
|
+
throw new Error(`Formula "${source}": ${what}${token ? ` at ${token.at + 1}, found "${token.text}"` : " at the end"}`);
|
|
32
|
+
};
|
|
33
|
+
const peek = (text) => tokens[position]?.kind === "symbol" && tokens[position].text === text;
|
|
34
|
+
const expect = (text) => {
|
|
35
|
+
if (!peek(text)) fail(`expected "${text}"`);
|
|
36
|
+
position += 1;
|
|
37
|
+
};
|
|
38
|
+
const args = () => {
|
|
39
|
+
expect("(");
|
|
40
|
+
const list = [sum()];
|
|
41
|
+
while (peek(",")) {
|
|
42
|
+
position += 1;
|
|
43
|
+
list.push(sum());
|
|
44
|
+
}
|
|
45
|
+
expect(")");
|
|
46
|
+
return list;
|
|
47
|
+
};
|
|
48
|
+
const call = (name) => {
|
|
49
|
+
if (name === "coalesce") {
|
|
50
|
+
const list = args();
|
|
51
|
+
if (list.length < 2) fail("coalesce() takes at least two values");
|
|
52
|
+
return {
|
|
53
|
+
kind: "coalesce",
|
|
54
|
+
args: list
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
const list = args();
|
|
58
|
+
const [value, digits] = list;
|
|
59
|
+
if (list.length > 2) fail("round() takes a value and at most a number of digits");
|
|
60
|
+
if (digits === void 0) return {
|
|
61
|
+
kind: "round",
|
|
62
|
+
value
|
|
63
|
+
};
|
|
64
|
+
if (digits.kind !== "number" || digits.value.includes(".")) fail("round()'s digits must be a whole number");
|
|
65
|
+
return {
|
|
66
|
+
kind: "round",
|
|
67
|
+
value,
|
|
68
|
+
digits: Number(digits.value)
|
|
69
|
+
};
|
|
70
|
+
};
|
|
71
|
+
const operand = () => {
|
|
72
|
+
const token = tokens[position];
|
|
73
|
+
if (!token) return fail("expected a number, a field or \"(\"");
|
|
74
|
+
if (token.kind === "number") {
|
|
75
|
+
position += 1;
|
|
76
|
+
return {
|
|
77
|
+
kind: "number",
|
|
78
|
+
value: token.text
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
if (token.kind === "name") {
|
|
82
|
+
position += 1;
|
|
83
|
+
if (!peek("(")) return {
|
|
84
|
+
kind: "field",
|
|
85
|
+
name: token.text
|
|
86
|
+
};
|
|
87
|
+
if (token.text !== "coalesce" && token.text !== "round") {
|
|
88
|
+
position -= 1;
|
|
89
|
+
return fail(`unknown function "${token.text}", only coalesce() and round()`);
|
|
90
|
+
}
|
|
91
|
+
return call(token.text);
|
|
92
|
+
}
|
|
93
|
+
if (token.text === "-") {
|
|
94
|
+
position += 1;
|
|
95
|
+
return {
|
|
96
|
+
kind: "negate",
|
|
97
|
+
operand: operand()
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
if (token.text === "(") {
|
|
101
|
+
position += 1;
|
|
102
|
+
const inner = sum();
|
|
103
|
+
expect(")");
|
|
104
|
+
return inner;
|
|
105
|
+
}
|
|
106
|
+
return fail("expected a number, a field or \"(\"");
|
|
107
|
+
};
|
|
108
|
+
const product = () => {
|
|
109
|
+
let node = operand();
|
|
110
|
+
while (peek("*") || peek("/")) {
|
|
111
|
+
const op = tokens[position].text;
|
|
112
|
+
position += 1;
|
|
113
|
+
node = {
|
|
114
|
+
kind: "binary",
|
|
115
|
+
op,
|
|
116
|
+
left: node,
|
|
117
|
+
right: operand()
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
return node;
|
|
121
|
+
};
|
|
122
|
+
const sum = () => {
|
|
123
|
+
let node = product();
|
|
124
|
+
while (peek("+") || peek("-")) {
|
|
125
|
+
const op = tokens[position].text;
|
|
126
|
+
position += 1;
|
|
127
|
+
node = {
|
|
128
|
+
kind: "binary",
|
|
129
|
+
op,
|
|
130
|
+
left: node,
|
|
131
|
+
right: product()
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
return node;
|
|
135
|
+
};
|
|
136
|
+
const node = sum();
|
|
137
|
+
if (position < tokens.length) fail("expected an operator");
|
|
138
|
+
return node;
|
|
139
|
+
};
|
|
140
|
+
/** The fields a formula reads, each once, in the order it names them. */
|
|
141
|
+
const formulaFields = (node) => {
|
|
142
|
+
const names = (part) => {
|
|
143
|
+
if (part.kind === "field") return [part.name];
|
|
144
|
+
if (part.kind === "number") return [];
|
|
145
|
+
if (part.kind === "negate") return names(part.operand);
|
|
146
|
+
if (part.kind === "binary") return [...names(part.left), ...names(part.right)];
|
|
147
|
+
if (part.kind === "coalesce") return part.args.flatMap(names);
|
|
148
|
+
return names(part.value);
|
|
149
|
+
};
|
|
150
|
+
return [...new Set(names(node))];
|
|
151
|
+
};
|
|
152
|
+
/** Whether a formula can be empty: when a field it needs is, unless `coalesce()` has a value that is not. */
|
|
153
|
+
const formulaNullable = (node, nullable) => {
|
|
154
|
+
if (node.kind === "field") return nullable(node.name);
|
|
155
|
+
if (node.kind === "number") return false;
|
|
156
|
+
if (node.kind === "negate") return formulaNullable(node.operand, nullable);
|
|
157
|
+
if (node.kind === "binary") return formulaNullable(node.left, nullable) || formulaNullable(node.right, nullable);
|
|
158
|
+
if (node.kind === "coalesce") return node.args.every((arg) => formulaNullable(arg, nullable));
|
|
159
|
+
return formulaNullable(node.value, nullable);
|
|
160
|
+
};
|
|
161
|
+
//#endregion
|
|
162
|
+
export { formulaFields, formulaNullable, parseFormula };
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
//#region src/computed/rollup.d.ts
|
|
2
|
+
/** What a rollup computes over the other resource's records: how many there are, or the sum, average, least or greatest of a field. */
|
|
3
|
+
export type RollupFn = 'count' | 'sum' | 'avg' | 'min' | 'max';
|
|
4
|
+
/** Which records a rollup reads (`.rollup()`). */
|
|
5
|
+
export type RollupSource = {
|
|
6
|
+
/** The resource whose records it reads. */
|
|
7
|
+
from: string;
|
|
8
|
+
/** The relation field of `from` that points at this resource: the records whose `via` is this record count. */
|
|
9
|
+
via: string;
|
|
10
|
+
};
|
|
11
|
+
/** A rollup that counts the records, or with `field` those where it is not empty. */
|
|
12
|
+
export type RollupCount = RollupSource & {
|
|
13
|
+
/** Counts the records. */
|
|
14
|
+
fn: 'count';
|
|
15
|
+
/** The field of `from` that must not be empty for a record to count. */
|
|
16
|
+
field?: string;
|
|
17
|
+
};
|
|
18
|
+
/** A rollup of the values of a field of the records. */
|
|
19
|
+
export type RollupValue = RollupSource & {
|
|
20
|
+
/** The sum, average, least or greatest value of `field`. */
|
|
21
|
+
fn: Exclude<RollupFn, 'count'>;
|
|
22
|
+
/** The number field of `from` it takes. */
|
|
23
|
+
field: string;
|
|
24
|
+
};
|
|
25
|
+
/** A rollup's source and what it computes (`.rollup()`). */
|
|
26
|
+
export type Rollup = RollupCount | RollupValue;
|
|
27
|
+
//#endregion
|
package/dist/field.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { Rollup } from "./computed/rollup.js";
|
|
1
2
|
import { FileOptions } from "./files/file-options.js";
|
|
2
3
|
import { FieldType } from "./model.js";
|
|
3
4
|
import { FieldAccess } from "./access/types.js";
|
|
@@ -62,6 +63,10 @@ export type FieldMeta = {
|
|
|
62
63
|
/** The prompt the job fills it from. */
|
|
63
64
|
prompt: string;
|
|
64
65
|
};
|
|
66
|
+
/** What the database keeps it from: the records of another resource (`.rollup()`). */
|
|
67
|
+
rollup?: Rollup;
|
|
68
|
+
/** The expression the database computes it from, over the record's other fields (`.formula()`). */
|
|
69
|
+
formula?: string;
|
|
65
70
|
};
|
|
66
71
|
/** A field type: its name, the constraints it takes and how it builds the schema that checks a value. */
|
|
67
72
|
export type FieldTypeDef = {
|
|
@@ -127,6 +132,30 @@ export declare class Field<V> {
|
|
|
127
132
|
ai(o: {
|
|
128
133
|
prompt: string;
|
|
129
134
|
}): Field<V>;
|
|
135
|
+
/**
|
|
136
|
+
* The database keeps the field: a column a trigger on `from`'s table sets to `fn` of the records whose `via` points at
|
|
137
|
+
* this record, in the transaction that writes them. `count` and `sum` are 0 without records; `avg`, `min` and `max`
|
|
138
|
+
* are empty, so their fields are `.optional()`. Empty values are left out, as in SQL. Read-only; `protobase migration`
|
|
139
|
+
* writes the column and its trigger. See [Rollups and formulas](website/src/content/docs/reference/data-config.mdx#rollups-and-formulas).
|
|
140
|
+
*
|
|
141
|
+
* @example
|
|
142
|
+
* ```ts
|
|
143
|
+
* total: f.decimal({ precision: 12, scale: 2 }).rollup({ from: 'order_lines', via: 'order_id', fn: 'sum', field: 'amount' }),
|
|
144
|
+
* ```
|
|
145
|
+
*/
|
|
146
|
+
rollup(o: Rollup): Field<V>;
|
|
147
|
+
/**
|
|
148
|
+
* The database computes the field from the record's other number fields: a generated column. `expression` takes
|
|
149
|
+
* numbers, fields, `+ - * /`, parentheses, `coalesce(a, b, ...)` and `round(x, digits)`, computed exactly as decimals and
|
|
150
|
+
* stored as the field's type. Read-only; `protobase migration` writes the column. See
|
|
151
|
+
* [Rollups and formulas](website/src/content/docs/reference/data-config.mdx#rollups-and-formulas).
|
|
152
|
+
*
|
|
153
|
+
* @example
|
|
154
|
+
* ```ts
|
|
155
|
+
* amount: f.decimal({ precision: 12, scale: 2 }).formula('round(quantity * unit_price, 2)'),
|
|
156
|
+
* ```
|
|
157
|
+
*/
|
|
158
|
+
formula(expression: string): Field<V>;
|
|
130
159
|
/** A copy of the field with `patch` applied, which every option returns. */
|
|
131
160
|
protected with(patch: Partial<FieldMeta>): Field<V>;
|
|
132
161
|
private constrain;
|
package/dist/field.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { promptError } from "./ai-prompt.js";
|
|
2
|
+
import { parseFormula } from "./computed/formula.js";
|
|
2
3
|
import { z } from "zod";
|
|
3
4
|
//#region src/field.ts
|
|
4
5
|
const fieldRegistry = z.registry();
|
|
@@ -95,6 +96,41 @@ var Field = class Field {
|
|
|
95
96
|
if (error) throw new Error(`.ai(): ${error}`);
|
|
96
97
|
return this.with({ ai: { prompt: o.prompt } });
|
|
97
98
|
}
|
|
99
|
+
/**
|
|
100
|
+
* The database keeps the field: a column a trigger on `from`'s table sets to `fn` of the records whose `via` points at
|
|
101
|
+
* this record, in the transaction that writes them. `count` and `sum` are 0 without records; `avg`, `min` and `max`
|
|
102
|
+
* are empty, so their fields are `.optional()`. Empty values are left out, as in SQL. Read-only; `protobase migration`
|
|
103
|
+
* writes the column and its trigger. See [Rollups and formulas](website/src/content/docs/reference/data-config.mdx#rollups-and-formulas).
|
|
104
|
+
*
|
|
105
|
+
* @example
|
|
106
|
+
* ```ts
|
|
107
|
+
* total: f.decimal({ precision: 12, scale: 2 }).rollup({ from: 'order_lines', via: 'order_id', fn: 'sum', field: 'amount' }),
|
|
108
|
+
* ```
|
|
109
|
+
*/
|
|
110
|
+
rollup(o) {
|
|
111
|
+
return this.with({
|
|
112
|
+
rollup: { ...o },
|
|
113
|
+
readOnly: true
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* The database computes the field from the record's other number fields: a generated column. `expression` takes
|
|
118
|
+
* numbers, fields, `+ - * /`, parentheses, `coalesce(a, b, ...)` and `round(x, digits)`, computed exactly as decimals and
|
|
119
|
+
* stored as the field's type. Read-only; `protobase migration` writes the column. See
|
|
120
|
+
* [Rollups and formulas](website/src/content/docs/reference/data-config.mdx#rollups-and-formulas).
|
|
121
|
+
*
|
|
122
|
+
* @example
|
|
123
|
+
* ```ts
|
|
124
|
+
* amount: f.decimal({ precision: 12, scale: 2 }).formula('round(quantity * unit_price, 2)'),
|
|
125
|
+
* ```
|
|
126
|
+
*/
|
|
127
|
+
formula(expression) {
|
|
128
|
+
parseFormula(expression);
|
|
129
|
+
return this.with({
|
|
130
|
+
formula: expression,
|
|
131
|
+
readOnly: true
|
|
132
|
+
});
|
|
133
|
+
}
|
|
98
134
|
/** A copy of the field with `patch` applied, which every option returns. */
|
|
99
135
|
with(patch) {
|
|
100
136
|
return new Field(this.def, {
|
package/dist/index.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { Rollup, RollupCount, RollupFn, RollupSource, RollupValue } from "./computed/rollup.js";
|
|
1
2
|
import { FileOptions, FileProcessor, FileProcessorInput, ImageFormat, ImageVariant, ImageVariantOptions, imageFormats } from "./files/file-options.js";
|
|
2
3
|
import { ActionInputKind, ActionInputModel, ActionModel, ActionRun, AppAddress, BadgeShape, BottomBarItemModel, BottomBarModel, CalendarView, CardFavoriteModel, CardPhotosModel, ChartGranularity, ChartModel, ChartRange, CheckedFilter, CommentsSectionModel, CompareOp, CreateModel, CustomSectionModel, DatePreset, DurationLiteral, DurationUnit, EventModel, EventOperation, ExportModel, FieldFormat, FieldModel, FieldPath, FieldType, FieldViewModel, FieldWidgetModel, FileFieldModel, FilterError, FilterErrorCode, FilterExpr, FilterLayout, FilterOf, FilterValue, FilterWidgetModel, FormModel, GalleryModel, GroupChartModel, GroupItemModel, GroupModel, HelpWhen, HistorySectionModel, JsonValue, KeyValue, LayoutItemModel, ListDisplayModel, ListModel, ListQuery, Literal, MapSectionModel, MapView, NamedListModel, NavModel, NavRecentModel, NowValue, OrderByItem, PeriodChartModel, RecordMode, RecordWhen, RelatedColumnModel, RelatedModel, ResourceModel, SaveFeedback, SaveSearchModel, SearchMatch, SearchResultModel, SearchesOfModel, SectionModel, SectionPresentation, ShowWhen, SignInPromptModel, SignInViewModel, SortSpec, Span, StatusTone, TabItemModel, TabModel, UserMenuItemModel, UserMenuModel, ViewModel } from "./model.js";
|
|
3
4
|
import { AccessAction, AccessContext, AccessFn, AccessResult, AccessUser, FieldAccess, FieldRule } from "./access/types.js";
|
|
@@ -42,4 +43,4 @@ import { AccessSource, ResolveOptions, ResolvedAccess, RowFilterOperation, acces
|
|
|
42
43
|
import { restrictModel } from "./access/restrict.js";
|
|
43
44
|
import "./access/index.js";
|
|
44
45
|
import { AssistantAction, AssistantActionRequest, AssistantCardPart, AssistantDiff, AssistantEvent, AssistantMessage, AssistantMessageRequest, AssistantPart, AssistantState, AssistantStep, AssistantStepState, AssistantTablePart, AssistantTextPart, AssistantTone, AssistantWidgetPart } from "./assistant/protocol.js";
|
|
45
|
-
export { type Access, type AccessAction, type AccessContext, type AccessFn, type AccessResult, type AccessSource, type AccessUser, type ActionInputKind, type ActionInputModel, type ActionModel, type ActionRun, type AnyField, type AppAddress, type AssistantAction, type AssistantActionRequest, type AssistantCardPart, type AssistantDiff, type AssistantEvent, type AssistantMessage, type AssistantMessageRequest, type AssistantPart, type AssistantState, type AssistantStep, type AssistantStepState, type AssistantTablePart, type AssistantTextPart, type AssistantTone, type AssistantWidgetPart, type BadgeShape, type BottomBarItemModel, type BottomBarModel, type BottomBarNames, type BottomBarSource, type CalendarView, type Capability, type CapabilityAction, type CapabilityLimit, type CapabilityScope, type CardFavoriteModel, type CardPhotosModel, type ChartGranularity, type ChartInput, type ChartModel, type ChartRange, type CheckResult, type CheckedFilter, type CommentsSectionModel, type CompareOp, type ComponentRef, type Constraint, type CreateModel, type CreatedOptions, type CustomSectionModel, type DatePreset, type DeletedOptions, type DurationLiteral, type DurationUnit, type EvaluateOptions, type EventActor, type EventContext, type EventHelpers, type EventMatch, type EventModel, type EventOperation, type EventSpec, type ExportModel, type Field, type FieldAccess, type FieldFormat, type FieldMeta, type FieldModel, type FieldPath, type FieldRefValue, type FieldRefs, type FieldRule, type FieldType, type FieldTypeDef, type FieldView, type FieldViewModel, type FieldViews, type FieldWidgetModel, type FieldsInput, type FileField, type FileFieldModel, type FileOptions, type FileProcessor, type FileProcessorInput, type FilterError, type FilterErrorCode, type FilterExpr, type FilterLayout, type FilterLimits, type FilterOf, type FilterParse, type FilterValue, type FilterWidgetModel, type FormAddress, type FormInput, type FormModel, type GalleryModel, type Grant, type GroupChartInput, type GroupChartModel, type GroupItemModel, type GroupModel, type HeldCapability, type HelpWhen, type HistorySectionModel, type ImageFormat, type ImageVariant, type ImageVariantOptions, type InferRecord, type Issue, type JsonValue, type KeyOf, type KeyRefs, type KeyType, type KeyValue, type LayoutItemModel, type ListColumn, type ListDisplayModel, type ListModel, type ListQuery, type Literal, type MapSectionModel, type MapView, type NamedListModel, type NavModel, type NavRecentModel, type NowValue, type OrderByCheck, type OrderByItem, type OrderByParse, type PeriodChartInput, type PeriodChartModel, type RecordKey, type RecordMode, type RecordOf, type RecordWhen, type Ref, type Refs, type RelatedColumnModel, type RelatedModel, type RelationField, type ResolveOptions, type ResolvedAccess, type ResourceBuilder, type ResourceModel, type RoleEntry, type RoleLimit, type Roles, type RolesOptions, type RowFilterOperation, type Rule, type SaveFeedback, type SaveSearchModel, type SearchMatch, type SearchRef, type SearchRefs, type SearchResultModel, type SearchesOfModel, type SectionModel, type SectionPresentation, type ShowWhen, type SignInAddress, type SignInPromptModel, type SignInStep, type SignInViewModel, type SortSpec, type Span, type StatusTone, type TabItemModel, type TabModel, type UndeletedOptions, type UpdatedOptions, type UserMenuItemModel, type UserMenuModel, type UserMenuSource, type Validator, type ViewBuilder, type ViewModel, type ViewProps, accessWarnings, addressWords, anonymousRole, appPath, bottomBar, bottomBarErrors, checkFilter, checkOrderBy, checkRecord, createWhere, defineRoles, enumLabel, evaluateFilter, f, fieldRef, fieldsUsedBy, fitsKeyType, formPath, formRoute, imageFormats, isRecordCondition, keyTypes, layout as l, maxBottomBarItems, parseFilter, parseOrderBy, pickView, printFilter, printOrderBy, readAppPath, readFormAddress, readSignInAddress, resolveAccess, resource, restrictModel, rule, signInParams, signInPath, signInRoute, signInSteps, userMenu, view, where };
|
|
46
|
+
export { type Access, type AccessAction, type AccessContext, type AccessFn, type AccessResult, type AccessSource, type AccessUser, type ActionInputKind, type ActionInputModel, type ActionModel, type ActionRun, type AnyField, type AppAddress, type AssistantAction, type AssistantActionRequest, type AssistantCardPart, type AssistantDiff, type AssistantEvent, type AssistantMessage, type AssistantMessageRequest, type AssistantPart, type AssistantState, type AssistantStep, type AssistantStepState, type AssistantTablePart, type AssistantTextPart, type AssistantTone, type AssistantWidgetPart, type BadgeShape, type BottomBarItemModel, type BottomBarModel, type BottomBarNames, type BottomBarSource, type CalendarView, type Capability, type CapabilityAction, type CapabilityLimit, type CapabilityScope, type CardFavoriteModel, type CardPhotosModel, type ChartGranularity, type ChartInput, type ChartModel, type ChartRange, type CheckResult, type CheckedFilter, type CommentsSectionModel, type CompareOp, type ComponentRef, type Constraint, type CreateModel, type CreatedOptions, type CustomSectionModel, type DatePreset, type DeletedOptions, type DurationLiteral, type DurationUnit, type EvaluateOptions, type EventActor, type EventContext, type EventHelpers, type EventMatch, type EventModel, type EventOperation, type EventSpec, type ExportModel, type Field, type FieldAccess, type FieldFormat, type FieldMeta, type FieldModel, type FieldPath, type FieldRefValue, type FieldRefs, type FieldRule, type FieldType, type FieldTypeDef, type FieldView, type FieldViewModel, type FieldViews, type FieldWidgetModel, type FieldsInput, type FileField, type FileFieldModel, type FileOptions, type FileProcessor, type FileProcessorInput, type FilterError, type FilterErrorCode, type FilterExpr, type FilterLayout, type FilterLimits, type FilterOf, type FilterParse, type FilterValue, type FilterWidgetModel, type FormAddress, type FormInput, type FormModel, type GalleryModel, type Grant, type GroupChartInput, type GroupChartModel, type GroupItemModel, type GroupModel, type HeldCapability, type HelpWhen, type HistorySectionModel, type ImageFormat, type ImageVariant, type ImageVariantOptions, type InferRecord, type Issue, type JsonValue, type KeyOf, type KeyRefs, type KeyType, type KeyValue, type LayoutItemModel, type ListColumn, type ListDisplayModel, type ListModel, type ListQuery, type Literal, type MapSectionModel, type MapView, type NamedListModel, type NavModel, type NavRecentModel, type NowValue, type OrderByCheck, type OrderByItem, type OrderByParse, type PeriodChartInput, type PeriodChartModel, type RecordKey, type RecordMode, type RecordOf, type RecordWhen, type Ref, type Refs, type RelatedColumnModel, type RelatedModel, type RelationField, type ResolveOptions, type ResolvedAccess, type ResourceBuilder, type ResourceModel, type RoleEntry, type RoleLimit, type Roles, type RolesOptions, type Rollup, type RollupCount, type RollupFn, type RollupSource, type RollupValue, type RowFilterOperation, type Rule, type SaveFeedback, type SaveSearchModel, type SearchMatch, type SearchRef, type SearchRefs, type SearchResultModel, type SearchesOfModel, type SectionModel, type SectionPresentation, type ShowWhen, type SignInAddress, type SignInPromptModel, type SignInStep, type SignInViewModel, type SortSpec, type Span, type StatusTone, type TabItemModel, type TabModel, type UndeletedOptions, type UpdatedOptions, type UserMenuItemModel, type UserMenuModel, type UserMenuSource, type Validator, type ViewBuilder, type ViewModel, type ViewProps, accessWarnings, addressWords, anonymousRole, appPath, bottomBar, bottomBarErrors, checkFilter, checkOrderBy, checkRecord, createWhere, defineRoles, enumLabel, evaluateFilter, f, fieldRef, fieldsUsedBy, fitsKeyType, formPath, formRoute, imageFormats, isRecordCondition, keyTypes, layout as l, maxBottomBarItems, parseFilter, parseOrderBy, pickView, printFilter, printOrderBy, readAppPath, readFormAddress, readSignInAddress, resolveAccess, resource, restrictModel, rule, signInParams, signInPath, signInRoute, signInSteps, userMenu, view, where };
|
package/dist/internal.d.ts
CHANGED
|
@@ -15,9 +15,11 @@ import { redactSecrets } from "./redact-secrets.js";
|
|
|
15
15
|
import { updateSetErrors, updateValues } from "./update-set.js";
|
|
16
16
|
import { addressPlaceholders, appHrefError, fillAddress, hrefField, hrefPlaceholders, leavesApp, linkActionError } from "./address-template.js";
|
|
17
17
|
import { promptError, promptFields, renderPrompt } from "./ai-prompt.js";
|
|
18
|
+
import { FormulaNode, parseFormula } from "./computed/formula.js";
|
|
18
19
|
import { AddressCheck, addressErrors } from "./address-errors.js";
|
|
19
20
|
import { applyAssistantEvent, emptyAssistantState } from "./assistant/apply-event.js";
|
|
20
21
|
import { readEventStream } from "./assistant/event-stream.js";
|
|
21
22
|
import { layoutItems } from "./layout-items.js";
|
|
22
23
|
import { viewOfList } from "./named-list.js";
|
|
23
|
-
|
|
24
|
+
import { Locale, isLocale, locales, pickLocale } from "./locale.js";
|
|
25
|
+
export { type AddressCheck, type FormulaNode, type Locale, type PluginHost, type PluginRequirement, type PropShapes, type RefShape, type SearchTerm, actionInputErrors, addressErrors, addressPlaceholders, appHrefError, applyAssistantEvent, boardErrors, bottomBarFor, calendarErrors, cardPhotoLimit, chartFields, checkEventFilter, checkedAccept, checkedProvider, decodeKey, defaultRecentLimit, displayFields, emptyAssistantState, encodeKey, fieldRefsIn, fillAddress, hrefField, hrefPlaceholders, inRange, isFieldRef, isGroupChart, isLocale, layoutItems, leavesApp, linkActionError, locales, markFieldRefs, matchedSearchField, matchesSearch, navRecentErrors, parseFormula, parseSize, pickLocale, pluginProblem, promptError, promptFields, pruneByShape, readEventStream, redactSecrets, renderPrompt, searchTerms, shapes, shownInputs, statusTones, tableDisplay, termMatches, unmarkFieldRefs, updateSetErrors, updateValues, viewOfList, webhookResources };
|
package/dist/internal.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { addressPlaceholders, appHrefError, fillAddress, hrefField, hrefPlaceholders, leavesApp, linkActionError } from "./address-template.js";
|
|
2
2
|
import { promptError, promptFields, renderPrompt } from "./ai-prompt.js";
|
|
3
|
+
import { parseFormula } from "./computed/formula.js";
|
|
3
4
|
import { checkedAccept, checkedProvider, parseSize } from "./files/file-options.js";
|
|
4
5
|
import { matchedSearchField, matchesSearch, searchTerms, termMatches } from "./search-match.js";
|
|
5
6
|
import { checkEventFilter } from "./events/event-models.js";
|
|
@@ -20,4 +21,5 @@ import { redactSecrets } from "./redact-secrets.js";
|
|
|
20
21
|
import { addressErrors } from "./address-errors.js";
|
|
21
22
|
import { applyAssistantEvent, emptyAssistantState } from "./assistant/apply-event.js";
|
|
22
23
|
import { readEventStream } from "./assistant/event-stream.js";
|
|
23
|
-
|
|
24
|
+
import { isLocale, locales, pickLocale } from "./locale.js";
|
|
25
|
+
export { actionInputErrors, addressErrors, addressPlaceholders, appHrefError, applyAssistantEvent, boardErrors, bottomBarFor, calendarErrors, cardPhotoLimit, chartFields, checkEventFilter, checkedAccept, checkedProvider, decodeKey, defaultRecentLimit, displayFields, emptyAssistantState, encodeKey, fieldRefsIn, fillAddress, hrefField, hrefPlaceholders, inRange, isFieldRef, isGroupChart, isLocale, layoutItems, leavesApp, linkActionError, locales, markFieldRefs, matchedSearchField, matchesSearch, navRecentErrors, parseFormula, parseSize, pickLocale, pluginProblem, promptError, promptFields, pruneByShape, readEventStream, redactSecrets, renderPrompt, searchTerms, shapes, shownInputs, statusTones, tableDisplay, termMatches, unmarkFieldRefs, updateSetErrors, updateValues, viewOfList, webhookResources };
|
package/dist/locale.d.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
//#region src/locale.d.ts
|
|
2
|
+
/** The languages Protobase's built-in screens come in. */
|
|
3
|
+
export declare const locales: readonly ["en", "nl"];
|
|
4
|
+
export type Locale = (typeof locales)[number];
|
|
5
|
+
export declare const isLocale: (value: unknown) => value is Locale;
|
|
6
|
+
/**
|
|
7
|
+
* The first of `preferred`, language tags such as the browser's `nl-BE` or `en`, whose language the screens come in;
|
|
8
|
+
* English when none is.
|
|
9
|
+
*/
|
|
10
|
+
export declare const pickLocale: (preferred: readonly string[]) => Locale;
|
|
11
|
+
//#endregion
|
package/dist/locale.js
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
//#region src/locale.ts
|
|
2
|
+
/** The languages Protobase's built-in screens come in. */
|
|
3
|
+
const locales = ["en", "nl"];
|
|
4
|
+
const isLocale = (value) => locales.includes(value);
|
|
5
|
+
/**
|
|
6
|
+
* The first of `preferred`, language tags such as the browser's `nl-BE` or `en`, whose language the screens come in;
|
|
7
|
+
* English when none is.
|
|
8
|
+
*/
|
|
9
|
+
const pickLocale = (preferred) => {
|
|
10
|
+
return preferred.map((tag) => tag.split("-")[0].toLowerCase()).find(isLocale) ?? "en";
|
|
11
|
+
};
|
|
12
|
+
//#endregion
|
|
13
|
+
export { isLocale, locales, pickLocale };
|
package/dist/model.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { Rollup } from "./computed/rollup.js";
|
|
1
2
|
import { ImageVariant } from "./files/file-options.js";
|
|
2
3
|
//#region src/model.d.ts
|
|
3
4
|
/** A field's storage type, as `f.*` declares it; see [Field types](website/src/content/docs/reference/data-config.mdx#field-types). */
|
|
@@ -49,6 +50,17 @@ export type FieldModel = {
|
|
|
49
50
|
/** The prompt, with `{field}` placeholders. */
|
|
50
51
|
prompt: string;
|
|
51
52
|
};
|
|
53
|
+
/** The database keeps it from the records of another resource (`.rollup()`). */
|
|
54
|
+
rollup?: Rollup;
|
|
55
|
+
/** The database computes it from the record's other fields (`.formula()`). */
|
|
56
|
+
formula?: string;
|
|
57
|
+
/** A rollup's or formula's decimal, for the column `protobase migration` adds. */
|
|
58
|
+
decimal?: {
|
|
59
|
+
/** Its number of digits. */
|
|
60
|
+
precision: number;
|
|
61
|
+
/** Its number of digits after the point. */
|
|
62
|
+
scale: number;
|
|
63
|
+
};
|
|
52
64
|
};
|
|
53
65
|
/** A file field's settings, as `/meta` gives them; see [Files](website/src/content/docs/reference/files.mdx). */
|
|
54
66
|
export type FileFieldModel = {
|
package/dist/resource-model.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { promptFields } from "./ai-prompt.js";
|
|
2
2
|
import { buildEventModels } from "./events/event-models.js";
|
|
3
|
+
import { computedProblems } from "./computed/computed-problems.js";
|
|
3
4
|
import { snakeCase } from "./naming.js";
|
|
4
5
|
//#region src/resource-model.ts
|
|
5
6
|
const parseTable = (table) => {
|
|
@@ -42,7 +43,13 @@ const fieldModel = (name, field) => {
|
|
|
42
43
|
...meta.file.original && { original: { ...meta.file.original } },
|
|
43
44
|
...meta.file.external && { external: true }
|
|
44
45
|
} },
|
|
45
|
-
...meta.ai && { ai: { prompt: meta.ai.prompt } }
|
|
46
|
+
...meta.ai && { ai: { prompt: meta.ai.prompt } },
|
|
47
|
+
...meta.rollup && { rollup: { ...meta.rollup } },
|
|
48
|
+
...meta.formula !== void 0 && { formula: meta.formula },
|
|
49
|
+
...(meta.rollup || meta.formula !== void 0) && meta.precision !== void 0 && { decimal: {
|
|
50
|
+
precision: meta.precision,
|
|
51
|
+
scale: meta.scale
|
|
52
|
+
} }
|
|
46
53
|
};
|
|
47
54
|
};
|
|
48
55
|
const assembleModel = (state) => {
|
|
@@ -143,6 +150,7 @@ const buildResourceModel = (state) => {
|
|
|
143
150
|
...fileProblems(model),
|
|
144
151
|
...positionProblems(model),
|
|
145
152
|
...aiProblems(model),
|
|
153
|
+
...computedProblems(model),
|
|
146
154
|
...events?.problems ?? []
|
|
147
155
|
];
|
|
148
156
|
if (problems.length > 0) throw new Error(`Resource "${model.name}": ${problems.join("; ")}`);
|