@protobase/schema 0.12.8 → 0.12.10
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/address-errors.d.ts +2 -1
- package/dist/address-errors.js +3 -1
- package/dist/address-template.d.ts +1 -1
- package/dist/address-template.js +6 -1
- package/dist/app-address.js +13 -5
- package/dist/model.d.ts +4 -1
- package/dist/view-displays.d.ts +4 -2
- package/dist/view-displays.js +5 -2
- package/dist/view.js +1 -0
- package/package.json +1 -1
package/dist/address-errors.d.ts
CHANGED
|
@@ -10,7 +10,8 @@ export type AddressCheck = {
|
|
|
10
10
|
};
|
|
11
11
|
/**
|
|
12
12
|
* What is wrong with a link's address against these models: an unknown resource or page, a `{field}` the record it sits
|
|
13
|
-
* on does not have,
|
|
13
|
+
* on does not have, a filter or order naming fields the resource does not have, or create values for fields it does not
|
|
14
|
+
* have. Empty when it can be followed.
|
|
14
15
|
*/
|
|
15
16
|
export declare const addressErrors: (to: AppAddress, { models, pages, source }: AddressCheck) => string[];
|
|
16
17
|
//#endregion
|
package/dist/address-errors.js
CHANGED
|
@@ -31,7 +31,8 @@ const orderErrors = (model, orderBy) => {
|
|
|
31
31
|
};
|
|
32
32
|
/**
|
|
33
33
|
* What is wrong with a link's address against these models: an unknown resource or page, a `{field}` the record it sits
|
|
34
|
-
* on does not have,
|
|
34
|
+
* on does not have, a filter or order naming fields the resource does not have, or create values for fields it does not
|
|
35
|
+
* have. Empty when it can be followed.
|
|
35
36
|
*/
|
|
36
37
|
const addressErrors = (to, { models, pages, source }) => {
|
|
37
38
|
const placeholders = addressPlaceholders(to).flatMap((name) => {
|
|
@@ -45,6 +46,7 @@ const addressErrors = (to, { models, pages, source }) => {
|
|
|
45
46
|
case "create": {
|
|
46
47
|
const target = Object.hasOwn(models, to.resource) ? models[to.resource] : void 0;
|
|
47
48
|
if (!target) return [`unknown resource "${to.resource}"`];
|
|
49
|
+
if (to.kind === "create") return [...placeholders, ...Object.keys(to.values ?? {}).filter((name) => !Object.hasOwn(target.fields, name)).map((name) => `values: unknown field "${name}" on ${to.resource}`)];
|
|
48
50
|
if (to.kind !== "list") return placeholders;
|
|
49
51
|
return [
|
|
50
52
|
...placeholders,
|
|
@@ -4,7 +4,7 @@ import { AppAddress } from "./model.js";
|
|
|
4
4
|
export declare const addressPlaceholders: (to: AppAddress) => string[];
|
|
5
5
|
/** The fields an href takes from the record, from its `{field}` placeholders. */
|
|
6
6
|
export declare const hrefPlaceholders: (href: string) => string[];
|
|
7
|
-
/** `to` with every `{field}` in its key, filter
|
|
7
|
+
/** `to` with every `{field}` in its key, filter, order or values replaced by the record's value; a missing value becomes empty. */
|
|
8
8
|
export declare const fillAddress: (to: AppAddress, record?: Record<string, unknown>) => AppAddress;
|
|
9
9
|
/**
|
|
10
10
|
* Whether an href leaves the app: it starts with a scheme (`https:`, `mailto:`) or `//`. A path inside the app is an
|
package/dist/address-template.js
CHANGED
|
@@ -4,6 +4,7 @@ const names = (text) => [...text.matchAll(placeholder)].map((match) => match[1])
|
|
|
4
4
|
const templated = (to) => {
|
|
5
5
|
if (to.kind === "record") return (Array.isArray(to.key) ? to.key : [to.key]).filter((part) => typeof part === "string");
|
|
6
6
|
if (to.kind === "list") return [to.filter, to.orderBy].filter((part) => part !== void 0);
|
|
7
|
+
if (to.kind === "create") return Object.values(to.values ?? {});
|
|
7
8
|
return [];
|
|
8
9
|
};
|
|
9
10
|
/** The fields whose values a link's address takes from the record, from its `{field}` placeholders. */
|
|
@@ -11,7 +12,7 @@ const addressPlaceholders = (to) => [...new Set(templated(to).flatMap(names))];
|
|
|
11
12
|
/** The fields an href takes from the record, from its `{field}` placeholders. */
|
|
12
13
|
const hrefPlaceholders = (href) => [...new Set(names(href))];
|
|
13
14
|
const fill = (text, record) => text.replace(placeholder, (_, name) => record[name] === null || record[name] === void 0 ? "" : String(record[name]));
|
|
14
|
-
/** `to` with every `{field}` in its key, filter
|
|
15
|
+
/** `to` with every `{field}` in its key, filter, order or values replaced by the record's value; a missing value becomes empty. */
|
|
15
16
|
const fillAddress = (to, record = {}) => {
|
|
16
17
|
if (to.kind === "record") {
|
|
17
18
|
const part = (value) => typeof value === "string" ? fill(value, record) : value;
|
|
@@ -20,6 +21,10 @@ const fillAddress = (to, record = {}) => {
|
|
|
20
21
|
key: Array.isArray(to.key) ? to.key.map(part) : part(to.key)
|
|
21
22
|
};
|
|
22
23
|
}
|
|
24
|
+
if (to.kind === "create") return to.values ? {
|
|
25
|
+
...to,
|
|
26
|
+
values: Object.fromEntries(Object.entries(to.values).map(([name, value]) => [name, fill(value, record)]))
|
|
27
|
+
} : to;
|
|
23
28
|
if (to.kind !== "list") return to;
|
|
24
29
|
return {
|
|
25
30
|
...to,
|
package/dist/app-address.js
CHANGED
|
@@ -35,7 +35,7 @@ const listQuery = (address) => {
|
|
|
35
35
|
const appPath = (address, basePath = "") => {
|
|
36
36
|
switch (address.kind) {
|
|
37
37
|
case "page": return `${basePath}/${encodeURIComponent(address.page)}`;
|
|
38
|
-
case "create": return `${basePath}/${encodeURIComponent(address.resource)}/${addressWords.create}`;
|
|
38
|
+
case "create": return `${basePath}/${encodeURIComponent(address.resource)}/${addressWords.create}${valuesQuery(address.values)}`;
|
|
39
39
|
case "record": return `${basePath}/${encodeURIComponent(address.resource)}/${encodeURIComponent(encodeKey(address.key))}`;
|
|
40
40
|
case "list": return `${basePath}/${encodeURIComponent(address.resource)}${listQuery(address)}`;
|
|
41
41
|
}
|
|
@@ -48,6 +48,10 @@ const decodeSegments = (pathname) => {
|
|
|
48
48
|
throw error;
|
|
49
49
|
}
|
|
50
50
|
};
|
|
51
|
+
const valuesQuery = (values = {}) => {
|
|
52
|
+
const text = new URLSearchParams(Object.entries(values).filter(([, value]) => value !== "")).toString();
|
|
53
|
+
return text ? `?${text}` : "";
|
|
54
|
+
};
|
|
51
55
|
const filterLayout = (value) => value === "bar" || value === "panel" ? value : void 0;
|
|
52
56
|
/**
|
|
53
57
|
* The address `pathAndQuery` (below the base path) shows, or `undefined` for anything else: the root, the app's own
|
|
@@ -63,10 +67,14 @@ const readAppPath = (pathAndQuery, pages) => {
|
|
|
63
67
|
kind: "page",
|
|
64
68
|
page: first
|
|
65
69
|
} : void 0;
|
|
66
|
-
if (second === addressWords.create)
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
+
if (second === addressWords.create) {
|
|
71
|
+
const values = Object.fromEntries([...url.searchParams].filter(([, value]) => value !== ""));
|
|
72
|
+
return {
|
|
73
|
+
kind: "create",
|
|
74
|
+
resource: first,
|
|
75
|
+
...Object.keys(values).length > 0 && { values }
|
|
76
|
+
};
|
|
77
|
+
}
|
|
70
78
|
if (second !== void 0) return {
|
|
71
79
|
kind: "record",
|
|
72
80
|
resource: first,
|
package/dist/model.d.ts
CHANGED
|
@@ -87,7 +87,8 @@ export type ListQuery = {
|
|
|
87
87
|
export type FilterLayout = 'bar' | 'panel';
|
|
88
88
|
/**
|
|
89
89
|
* A place in the app: a resource's list, one of its records, its create form, or a composed page. A record's `key` is
|
|
90
|
-
* its one value or its parts, as the record API takes them; `readAppPath` gives it back as the API's text form.
|
|
90
|
+
* its one value or its parts, as the record API takes them; `readAppPath` gives it back as the API's text form. A create
|
|
91
|
+
* form's `values` fill in fields of the new record by name, as text (`{ listing_id: '42' }`).
|
|
91
92
|
*/
|
|
92
93
|
export type AppAddress = {
|
|
93
94
|
kind: 'list';
|
|
@@ -103,6 +104,7 @@ export type AppAddress = {
|
|
|
103
104
|
} | {
|
|
104
105
|
kind: 'create';
|
|
105
106
|
resource: string;
|
|
107
|
+
values?: Readonly<Record<string, string>>;
|
|
106
108
|
} | {
|
|
107
109
|
kind: 'page';
|
|
108
110
|
page: string;
|
|
@@ -136,6 +138,7 @@ export type ListDisplayModel = {
|
|
|
136
138
|
name: string;
|
|
137
139
|
label?: string;
|
|
138
140
|
icon?: string;
|
|
141
|
+
image?: string;
|
|
139
142
|
title?: string;
|
|
140
143
|
subtitle?: string;
|
|
141
144
|
badge?: string;
|
package/dist/view-displays.d.ts
CHANGED
|
@@ -17,12 +17,14 @@ type Naming = {
|
|
|
17
17
|
export declare const tableDisplay = "table";
|
|
18
18
|
export declare const displays: {
|
|
19
19
|
/**
|
|
20
|
-
* A card per record: `
|
|
21
|
-
*
|
|
20
|
+
* A card per record: `image`, a file field shown as a picture across the card's top, then `title` (default: the
|
|
21
|
+
* view's title) over `subtitle`, `badge` drawn as a badge with its value labels, and a few small `meta` fields such as
|
|
22
|
+
* dates. With `card`, a component of the project or a plugin draws each
|
|
22
23
|
* card's content instead, from the row of those fields (`{ row }`); without it, or when it throws, the card is the
|
|
23
24
|
* built-in one. Named `cards` unless `name` says otherwise.
|
|
24
25
|
*/
|
|
25
26
|
cards: (fields: {
|
|
27
|
+
image?: Ref;
|
|
26
28
|
title?: Ref;
|
|
27
29
|
subtitle?: Ref;
|
|
28
30
|
badge?: Ref;
|
package/dist/view-displays.js
CHANGED
|
@@ -13,8 +13,9 @@ const customComponent = (component, where) => {
|
|
|
13
13
|
};
|
|
14
14
|
const displays = {
|
|
15
15
|
/**
|
|
16
|
-
* A card per record: `
|
|
17
|
-
*
|
|
16
|
+
* A card per record: `image`, a file field shown as a picture across the card's top, then `title` (default: the
|
|
17
|
+
* view's title) over `subtitle`, `badge` drawn as a badge with its value labels, and a few small `meta` fields such as
|
|
18
|
+
* dates. With `card`, a component of the project or a plugin draws each
|
|
18
19
|
* card's content instead, from the row of those fields (`{ row }`); without it, or when it throws, the card is the
|
|
19
20
|
* built-in one. Named `cards` unless `name` says otherwise.
|
|
20
21
|
*/
|
|
@@ -22,6 +23,7 @@ const displays = {
|
|
|
22
23
|
kind: "cards",
|
|
23
24
|
name: o.name ?? "cards",
|
|
24
25
|
...naming(o),
|
|
26
|
+
...fields.image && { image: fields.image.name },
|
|
25
27
|
...fields.title && { title: fields.title.name },
|
|
26
28
|
...fields.subtitle && { subtitle: fields.subtitle.name },
|
|
27
29
|
...fields.badge && { badge: fields.badge.name },
|
|
@@ -55,6 +57,7 @@ const displayErrors = (list) => {
|
|
|
55
57
|
};
|
|
56
58
|
/** Every field a display names: what it shows, and the field references in a custom display's props. */
|
|
57
59
|
const displayFields = (display) => display.kind === "cards" ? [
|
|
60
|
+
display.image,
|
|
58
61
|
display.title,
|
|
59
62
|
display.subtitle,
|
|
60
63
|
display.badge,
|
package/dist/view.js
CHANGED
|
@@ -97,6 +97,7 @@ var ViewBuilder = class ViewBuilder {
|
|
|
97
97
|
for (const display of resource ? list?.displays ?? [] : []) {
|
|
98
98
|
const unknown = displayFields(display).find((name) => !Object.hasOwn(resource.fields, name));
|
|
99
99
|
if (unknown !== void 0) throw new Error(`View "${this.state.resource}": display "${display.name}" names field "${unknown}", which the resource does not have`);
|
|
100
|
+
if (display.kind === "cards" && display.image !== void 0 && resource.fields[display.image].type !== "file") throw new Error(`View "${this.state.resource}": display "${display.name}" shows "${display.image}" as its image, which is not a file field`);
|
|
100
101
|
}
|
|
101
102
|
const recentErrors = resource && nav?.recent ? navRecentErrors(nav.recent, resource) : [];
|
|
102
103
|
if (recentErrors.length > 0) throw new Error(`View "${this.state.resource}": nav.recent ${recentErrors.join("; ")}`);
|