@protobase/schema 0.21.0 → 0.21.2
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/access/restrict.d.ts +1 -1
- package/dist/access/restrict.js +5 -3
- package/dist/fields/decimal.js +7 -1
- package/dist/model.d.ts +7 -0
- package/dist/resource-model.d.ts +4 -0
- package/dist/resource-model.js +19 -1
- package/dist/resource.d.ts +14 -0
- package/dist/resource.js +18 -0
- package/package.json +1 -1
|
@@ -4,7 +4,7 @@ import { ResolvedAccess } from "./resolve.js";
|
|
|
4
4
|
/**
|
|
5
5
|
* The resource as one user may see it: only readable fields remain, and a field is `readOnly`
|
|
6
6
|
* unless it is writable on create or update. `search` (and `searchMatch`) shrinks to readable fields, and `tenant`,
|
|
7
|
-
* `softDelete` and `owner` are dropped when their field is hidden. The event catalogue, whose conditions may use any
|
|
7
|
+
* `softDelete` and `owner` are dropped when their field is hidden, `position` when its field or its `within` is. The event catalogue, whose conditions may use any
|
|
8
8
|
* field, is dropped. Key columns always stay,
|
|
9
9
|
* because records are addressed by them.
|
|
10
10
|
*
|
package/dist/access/restrict.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* The resource as one user may see it: only readable fields remain, and a field is `readOnly`
|
|
4
4
|
* unless it is writable on create or update. `search` (and `searchMatch`) shrinks to readable fields, and `tenant`,
|
|
5
|
-
* `softDelete` and `owner` are dropped when their field is hidden. The event catalogue, whose conditions may use any
|
|
5
|
+
* `softDelete` and `owner` are dropped when their field is hidden, `position` when its field or its `within` is. The event catalogue, whose conditions may use any
|
|
6
6
|
* field, is dropped. Key columns always stay,
|
|
7
7
|
* because records are addressed by them.
|
|
8
8
|
*
|
|
@@ -17,10 +17,11 @@ const restrictModel = (model, access) => {
|
|
|
17
17
|
const keep = (name) => name !== void 0 && visible.has(name) ? name : void 0;
|
|
18
18
|
const search = model.search?.filter((name) => visible.has(name));
|
|
19
19
|
const searchMatch = model.searchMatch && Object.entries(model.searchMatch).filter(([name]) => visible.has(name));
|
|
20
|
-
const { search: _search, searchMatch: _searchMatch, tenant: _tenant, softDelete: _softDelete, owner: _owner, events: _events, ...rest } = model;
|
|
20
|
+
const { search: _search, searchMatch: _searchMatch, tenant: _tenant, softDelete: _softDelete, owner: _owner, position: _position, events: _events, ...rest } = model;
|
|
21
21
|
const tenant = keep(model.tenant);
|
|
22
22
|
const softDelete = keep(model.softDelete);
|
|
23
23
|
const owner = keep(model.owner);
|
|
24
|
+
const position = model.position && keep(model.position.field) && (model.position.within === void 0 || keep(model.position.within)) ? model.position : void 0;
|
|
24
25
|
return {
|
|
25
26
|
...rest,
|
|
26
27
|
fields: Object.fromEntries(Object.entries(model.fields).filter(([name]) => visible.has(name)).map(([name, field]) => [name, {
|
|
@@ -31,7 +32,8 @@ const restrictModel = (model, access) => {
|
|
|
31
32
|
...searchMatch?.length && { searchMatch: Object.fromEntries(searchMatch) },
|
|
32
33
|
...tenant && { tenant },
|
|
33
34
|
...softDelete && { softDelete },
|
|
34
|
-
...owner && { owner }
|
|
35
|
+
...owner && { owner },
|
|
36
|
+
...position && { position: { ...position } }
|
|
35
37
|
};
|
|
36
38
|
};
|
|
37
39
|
//#endregion
|
package/dist/fields/decimal.js
CHANGED
|
@@ -15,7 +15,13 @@ const decimal = {
|
|
|
15
15
|
const fraction = match[2] ?? "";
|
|
16
16
|
if (!(match[1] || fraction)) return false;
|
|
17
17
|
return integer.length <= maxInteger && fraction.length <= maxFraction;
|
|
18
|
-
}, {
|
|
18
|
+
}, {
|
|
19
|
+
message: `Expected a decimal string with at most ${o.precision} digits, ${o.scale} after the point`,
|
|
20
|
+
params: {
|
|
21
|
+
precision: o.precision,
|
|
22
|
+
scale: o.scale
|
|
23
|
+
}
|
|
24
|
+
});
|
|
19
25
|
}
|
|
20
26
|
};
|
|
21
27
|
//#endregion
|
package/dist/model.d.ts
CHANGED
|
@@ -94,6 +94,13 @@ export type ResourceModel = {
|
|
|
94
94
|
searchMatch?: Record<string, SearchMatch>;
|
|
95
95
|
/** The field holding the owning user's id; `.own` capability scopes filter on it. */
|
|
96
96
|
owner?: string;
|
|
97
|
+
/** The integer field holding the records' order by hand (`.position()`), 1 the first. */
|
|
98
|
+
position?: {
|
|
99
|
+
/** The integer field, 1 the first record. */
|
|
100
|
+
field: string;
|
|
101
|
+
/** The relation whose records each have an order of their own, as an invoice's items do; one order for all records when omitted. */
|
|
102
|
+
within?: string;
|
|
103
|
+
};
|
|
97
104
|
/** The events its changes raise (`.events()`), the catalogue subscriptions pick from. */
|
|
98
105
|
events?: EventModel[];
|
|
99
106
|
/** Whether every change of its records is kept, with who made it (`.history()`). */
|
package/dist/resource-model.d.ts
CHANGED
package/dist/resource-model.js
CHANGED
|
@@ -58,7 +58,8 @@ const assembleModel = (state) => {
|
|
|
58
58
|
...state.search && { search: [...state.search] },
|
|
59
59
|
...state.searchMatch && Object.keys(state.searchMatch).length > 0 && { searchMatch: { ...state.searchMatch } },
|
|
60
60
|
...state.owner && { owner: state.owner },
|
|
61
|
-
...state.history && { history: true }
|
|
61
|
+
...state.history && { history: true },
|
|
62
|
+
...state.position && { position: { ...state.position } }
|
|
62
63
|
};
|
|
63
64
|
};
|
|
64
65
|
const sensitiveProblems = (model) => Object.values(model.fields).filter((field) => field.sensitive).flatMap((field) => [
|
|
@@ -101,12 +102,29 @@ const placeholderProblems = (model, field) => {
|
|
|
101
102
|
const placeholder = Object.hasOwn(model.fields, target) ? model.fields[target] : void 0;
|
|
102
103
|
return placeholder && !placeholder.file && placeholder.type !== "text" ? [`has "${target}" as its placeholder, which must be a text field`] : [];
|
|
103
104
|
};
|
|
105
|
+
const positionProblems = (model) => {
|
|
106
|
+
if (!model.position) return [];
|
|
107
|
+
const { field: name, within } = model.position;
|
|
108
|
+
const field = Object.hasOwn(model.fields, name) ? model.fields[name] : void 0;
|
|
109
|
+
if (!field) return [`position "${name}" is not a field`];
|
|
110
|
+
const parent = within !== void 0 && Object.hasOwn(model.fields, within) ? model.fields[within] : void 0;
|
|
111
|
+
if (within !== void 0 && !parent) return [`position "${name}" is within "${within}", which is not a field`];
|
|
112
|
+
return [
|
|
113
|
+
...field.type !== "integer" && field.type !== "bigint" ? [`position "${name}" is ${field.type}, not an integer`] : [],
|
|
114
|
+
...field.sortable ? [] : [`position "${name}" is not sortable, and the list in its order sorts by it`],
|
|
115
|
+
...model.primaryKey.includes(name) ? [`position "${name}" is part of the key, which a move would change`] : [],
|
|
116
|
+
...within === name ? [`position "${name}" cannot be within itself`] : [],
|
|
117
|
+
...parent && parent.type !== "relation" ? [`position "${name}" is within "${within}", which is ${parent.type}, not a relation`] : [],
|
|
118
|
+
...parent && !parent.filterable ? [`position "${name}" is within "${within}", which is not filterable, and each parent's records are found by it`] : []
|
|
119
|
+
];
|
|
120
|
+
};
|
|
104
121
|
const buildResourceModel = (state) => {
|
|
105
122
|
const model = assembleModel(state);
|
|
106
123
|
const events = state.events && buildEventModels(model, state.events);
|
|
107
124
|
const problems = [
|
|
108
125
|
...sensitiveProblems(model),
|
|
109
126
|
...fileProblems(model),
|
|
127
|
+
...positionProblems(model),
|
|
110
128
|
...events?.problems ?? []
|
|
111
129
|
];
|
|
112
130
|
if (problems.length > 0) throw new Error(`Resource "${model.name}": ${problems.join("; ")}`);
|
package/dist/resource.d.ts
CHANGED
|
@@ -68,6 +68,20 @@ export declare class ResourceBuilder<F extends FieldsInput = {}, K = never> {
|
|
|
68
68
|
search(pick: (r: SearchRefs<F>) => SearchRef[]): ResourceBuilder<F, K>;
|
|
69
69
|
/** The field holding the owning user's id, which `.own` capability scopes filter on. */
|
|
70
70
|
owner(pick: (r: FieldRefs<F>) => Ref): ResourceBuilder<F, K>;
|
|
71
|
+
/**
|
|
72
|
+
* The integer field holding the records' order, 1 the first, which a list's **Sort** button changes by dragging the
|
|
73
|
+
* records; a move renumbers them 1, 2, 3, ... in one batch `reorder`. With `within`, a relation, each parent's records
|
|
74
|
+
* have an order of their own, as an invoice's items do, sorted in the parent's related section. The field must be
|
|
75
|
+
* sortable and `within` filterable. See [Sorting by hand](website/src/content/docs/reference/ui-config.mdx#sorting-by-hand).
|
|
76
|
+
*
|
|
77
|
+
* @example
|
|
78
|
+
* ```ts
|
|
79
|
+
* .position((r) => r.position, { within: (r) => r.invoice_id })
|
|
80
|
+
* ```
|
|
81
|
+
*/
|
|
82
|
+
position(pick: (r: FieldRefs<F>) => Ref, o?: {
|
|
83
|
+
within?: (r: FieldRefs<F>) => Ref;
|
|
84
|
+
}): ResourceBuilder<F, K>;
|
|
71
85
|
/**
|
|
72
86
|
* The events changes to its records raise, captured in the database for every write, from the API or elsewhere. See
|
|
73
87
|
* [Declaring events](website/src/content/docs/reference/events.mdx#declaring-events).
|
package/dist/resource.js
CHANGED
|
@@ -78,6 +78,24 @@ var ResourceBuilder = class ResourceBuilder {
|
|
|
78
78
|
return this.next({ owner: pick(this.refs()).name });
|
|
79
79
|
}
|
|
80
80
|
/**
|
|
81
|
+
* The integer field holding the records' order, 1 the first, which a list's **Sort** button changes by dragging the
|
|
82
|
+
* records; a move renumbers them 1, 2, 3, ... in one batch `reorder`. With `within`, a relation, each parent's records
|
|
83
|
+
* have an order of their own, as an invoice's items do, sorted in the parent's related section. The field must be
|
|
84
|
+
* sortable and `within` filterable. See [Sorting by hand](website/src/content/docs/reference/ui-config.mdx#sorting-by-hand).
|
|
85
|
+
*
|
|
86
|
+
* @example
|
|
87
|
+
* ```ts
|
|
88
|
+
* .position((r) => r.position, { within: (r) => r.invoice_id })
|
|
89
|
+
* ```
|
|
90
|
+
*/
|
|
91
|
+
position(pick, o = {}) {
|
|
92
|
+
const within = o.within?.(this.refs()).name;
|
|
93
|
+
return this.next({ position: {
|
|
94
|
+
field: pick(this.refs()).name,
|
|
95
|
+
...within !== void 0 && { within }
|
|
96
|
+
} });
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
81
99
|
* The events changes to its records raise, captured in the database for every write, from the API or elsewhere. See
|
|
82
100
|
* [Declaring events](website/src/content/docs/reference/events.mdx#declaring-events).
|
|
83
101
|
*
|