@protobase/schema 0.13.0 → 0.13.4
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/app-address.d.ts +2 -0
- package/dist/app-address.js +18 -1
- package/dist/field.d.ts +1 -1
- package/dist/file-field.d.ts +11 -1
- package/dist/file-field.js +21 -1
- package/dist/files/file-options.d.ts +23 -0
- package/dist/files/file-options.js +28 -1
- package/dist/index.d.ts +3 -3
- package/dist/index.js +2 -2
- package/dist/model.d.ts +18 -2
- package/dist/resource-model.js +15 -2
- package/dist/view-displays.d.ts +4 -3
- package/dist/view-displays.js +4 -3
- package/package.json +1 -1
package/dist/app-address.d.ts
CHANGED
|
@@ -13,6 +13,8 @@ export declare const addressWords: {
|
|
|
13
13
|
readonly display: "display";
|
|
14
14
|
/** Where a list's filters sit: `bar` (the default) or `panel`. */
|
|
15
15
|
readonly filterLayout: "layout";
|
|
16
|
+
/** Where a list's map display looks: `latitude,longitude,zoom`. */
|
|
17
|
+
readonly map: "map";
|
|
16
18
|
/** The second path segment that opens a resource's create form instead of a record. */
|
|
17
19
|
readonly create: "new";
|
|
18
20
|
};
|
package/dist/app-address.js
CHANGED
|
@@ -13,6 +13,8 @@ const addressWords = {
|
|
|
13
13
|
display: "display",
|
|
14
14
|
/** Where a list's filters sit: `bar` (the default) or `panel`. */
|
|
15
15
|
filterLayout: "layout",
|
|
16
|
+
/** Where a list's map display looks: `latitude,longitude,zoom`. */
|
|
17
|
+
map: "map",
|
|
16
18
|
/** The second path segment that opens a resource's create form instead of a record. */
|
|
17
19
|
create: "new"
|
|
18
20
|
};
|
|
@@ -22,12 +24,25 @@ const listWords = [
|
|
|
22
24
|
"orderBy",
|
|
23
25
|
"filterLayout"
|
|
24
26
|
];
|
|
27
|
+
const mapText = (view) => `${view.latitude.toFixed(5)},${view.longitude.toFixed(5)},${view.zoom.toFixed(2)}`;
|
|
28
|
+
const inRange = (value, limit, low = -limit) => Number.isFinite(value) && value >= low && value <= limit;
|
|
29
|
+
const readMap = (text) => {
|
|
30
|
+
const parts = text?.split(",");
|
|
31
|
+
if (parts?.length !== 3 || parts.some((part) => part.trim() === "")) return void 0;
|
|
32
|
+
const [latitude, longitude, zoom] = parts.map(Number);
|
|
33
|
+
return inRange(latitude, 90) && inRange(longitude, 180) && inRange(zoom, 22, 0) ? {
|
|
34
|
+
latitude,
|
|
35
|
+
longitude,
|
|
36
|
+
zoom
|
|
37
|
+
} : void 0;
|
|
38
|
+
};
|
|
25
39
|
const listQuery = (address) => {
|
|
26
40
|
const query = new URLSearchParams();
|
|
27
41
|
for (const name of listWords) {
|
|
28
42
|
const value = address[name];
|
|
29
43
|
if (value !== void 0 && value !== "") query.set(addressWords[name], value);
|
|
30
44
|
}
|
|
45
|
+
if (address.map) query.set(addressWords.map, mapText(address.map));
|
|
31
46
|
const text = query.toString();
|
|
32
47
|
return text ? `?${text}` : "";
|
|
33
48
|
};
|
|
@@ -85,13 +100,15 @@ const readAppPath = (pathAndQuery, pages) => {
|
|
|
85
100
|
const filter = params.get(addressWords.filter) || void 0;
|
|
86
101
|
const orderBy = params.get(addressWords.orderBy) || void 0;
|
|
87
102
|
const layout = filterLayout(params.get(addressWords.filterLayout));
|
|
103
|
+
const map = readMap(params.get(addressWords.map));
|
|
88
104
|
return {
|
|
89
105
|
kind: "list",
|
|
90
106
|
resource: first,
|
|
91
107
|
...display && { display },
|
|
92
108
|
...filter && { filter },
|
|
93
109
|
...orderBy && { orderBy },
|
|
94
|
-
...layout && { filterLayout: layout }
|
|
110
|
+
...layout && { filterLayout: layout },
|
|
111
|
+
...map && { map }
|
|
95
112
|
};
|
|
96
113
|
};
|
|
97
114
|
//#endregion
|
package/dist/field.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
+
import { FileOptions } from "./files/file-options.js";
|
|
1
2
|
import { FieldType } from "./model.js";
|
|
2
3
|
import { FieldAccess } from "./access/types.js";
|
|
3
|
-
import { FileOptions } from "./files/file-options.js";
|
|
4
4
|
import { z } from "zod";
|
|
5
5
|
//#region src/field.d.ts
|
|
6
6
|
export type Constraint = 'min' | 'max' | 'regex';
|
package/dist/file-field.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
+
import { FileProcessor, ImageVariant, ImageVariantOptions } from "./files/file-options.js";
|
|
1
2
|
import { FieldAccess } from "./access/types.js";
|
|
2
|
-
import { FileProcessor } from "./files/file-options.js";
|
|
3
3
|
import { Field, FieldMeta } from "./field.js";
|
|
4
4
|
//#region src/file-field.d.ts
|
|
5
5
|
/** `f.file()`: a text column holding a file's URI, uploaded through `:upload`. */
|
|
@@ -17,6 +17,16 @@ export declare class FileField<V> extends Field<V> {
|
|
|
17
17
|
* file: `{ image_width: imageSize('width') }`. Clearing the file clears them.
|
|
18
18
|
*/
|
|
19
19
|
derive(processors: Record<string, FileProcessor>): FileField<V>;
|
|
20
|
+
/**
|
|
21
|
+
* Computes a ThumbHash of each image into a read-only text field (`photo_placeholder`), which the admin app shows
|
|
22
|
+
* while the image loads. Other files leave it null.
|
|
23
|
+
*/
|
|
24
|
+
placeholder(field: string): FileField<V>;
|
|
25
|
+
/**
|
|
26
|
+
* Resized copies of each image, stored beside it under these names: `{ card: { width: 640, height: 480, fit: 'cover',
|
|
27
|
+
* format: 'webp' } }`. `{ original }` stores that image instead of the upload. Other files get none.
|
|
28
|
+
*/
|
|
29
|
+
variants(variants: Record<string, ImageVariant>, options?: ImageVariantOptions): FileField<V>;
|
|
20
30
|
optional(): FileField<V | null>;
|
|
21
31
|
readOnly(): FileField<V>;
|
|
22
32
|
column(name: string): FileField<V>;
|
package/dist/file-field.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Field } from "./field.js";
|
|
2
|
-
import { checkedAccept, checkedProvider, parseSize } from "./files/file-options.js";
|
|
2
|
+
import { checkedAccept, checkedProvider, checkedVariant, checkedVariants, parseSize } from "./files/file-options.js";
|
|
3
3
|
//#region src/file-field.ts
|
|
4
4
|
/** `f.file()`: a text column holding a file's URI, uploaded through `:upload`. */
|
|
5
5
|
var FileField = class FileField extends Field {
|
|
@@ -29,6 +29,26 @@ var FileField = class FileField extends Field {
|
|
|
29
29
|
...processors
|
|
30
30
|
} });
|
|
31
31
|
}
|
|
32
|
+
/**
|
|
33
|
+
* Computes a ThumbHash of each image into a read-only text field (`photo_placeholder`), which the admin app shows
|
|
34
|
+
* while the image loads. Other files leave it null.
|
|
35
|
+
*/
|
|
36
|
+
placeholder(field) {
|
|
37
|
+
return this.withFile({ placeholder: field });
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Resized copies of each image, stored beside it under these names: `{ card: { width: 640, height: 480, fit: 'cover',
|
|
41
|
+
* format: 'webp' } }`. `{ original }` stores that image instead of the upload. Other files get none.
|
|
42
|
+
*/
|
|
43
|
+
variants(variants, options = {}) {
|
|
44
|
+
return this.withFile({
|
|
45
|
+
variants: {
|
|
46
|
+
...this.meta.file.variants,
|
|
47
|
+
...checkedVariants(variants)
|
|
48
|
+
},
|
|
49
|
+
...options.original && { original: checkedVariant("The original", options.original) }
|
|
50
|
+
});
|
|
51
|
+
}
|
|
32
52
|
optional() {
|
|
33
53
|
return this.with({ nullable: true });
|
|
34
54
|
}
|
|
@@ -18,12 +18,35 @@ export type FileProcessor = {
|
|
|
18
18
|
accepts?: readonly string[];
|
|
19
19
|
run: (file: FileProcessorInput) => unknown;
|
|
20
20
|
};
|
|
21
|
+
/** The formats an image variant is written in. */
|
|
22
|
+
export declare const imageFormats: readonly ["webp", "avif", "jpeg", "png"];
|
|
23
|
+
export type ImageFormat = (typeof imageFormats)[number];
|
|
24
|
+
/**
|
|
25
|
+
* A resized copy of an uploaded image: at most `width` by `height` pixels, never enlarged. `inside` (the default) keeps
|
|
26
|
+
* the whole image within that box, `cover` fills the box and crops what is outside it. `quality` is 1 to 100, the
|
|
27
|
+
* format's default when omitted.
|
|
28
|
+
*/
|
|
29
|
+
export type ImageVariant = {
|
|
30
|
+
width?: number;
|
|
31
|
+
height?: number;
|
|
32
|
+
fit?: 'inside' | 'cover';
|
|
33
|
+
format: ImageFormat;
|
|
34
|
+
quality?: number;
|
|
35
|
+
};
|
|
36
|
+
/** `.variants()`' second argument: `original` replaces the upload by that image, so the upload itself is not kept. */
|
|
37
|
+
export type ImageVariantOptions = {
|
|
38
|
+
original?: ImageVariant;
|
|
39
|
+
};
|
|
21
40
|
/** A file field's settings, as the builder collects them. */
|
|
22
41
|
export type FileOptions = {
|
|
23
42
|
accept: string[];
|
|
24
43
|
maxSize: number;
|
|
25
44
|
provider: string;
|
|
26
45
|
derive?: Record<string, FileProcessor>;
|
|
46
|
+
/** The read-only text field each image's ThumbHash goes to (`.placeholder()`). */
|
|
47
|
+
placeholder?: string;
|
|
48
|
+
variants?: Record<string, ImageVariant>;
|
|
49
|
+
original?: ImageVariant;
|
|
27
50
|
};
|
|
28
51
|
/** Bytes from a number or text such as `10 MB` (decimal units) or `5 MiB` (binary units). */
|
|
29
52
|
export declare const parseSize: (size: number | string) => number;
|
|
@@ -1,4 +1,11 @@
|
|
|
1
1
|
//#region src/files/file-options.ts
|
|
2
|
+
/** The formats an image variant is written in. */
|
|
3
|
+
const imageFormats = [
|
|
4
|
+
"webp",
|
|
5
|
+
"avif",
|
|
6
|
+
"jpeg",
|
|
7
|
+
"png"
|
|
8
|
+
];
|
|
2
9
|
const defaultMaxSize = 5e7;
|
|
3
10
|
const units = {
|
|
4
11
|
b: 1,
|
|
@@ -43,5 +50,25 @@ const checkedProvider = (name) => {
|
|
|
43
50
|
if (reservedProviders.has(name)) throw new Error(`"${name}" cannot name a file provider: stored values would read as ${name}: links`);
|
|
44
51
|
return name;
|
|
45
52
|
};
|
|
53
|
+
const variantNamePattern = /^[a-z][a-z0-9_]{0,31}$/;
|
|
54
|
+
const fits = ["inside", "cover"];
|
|
55
|
+
const checkedBound = (label, value) => {
|
|
56
|
+
if (value !== void 0 && (!Number.isSafeInteger(value) || value < 1 || value > 16384)) throw new Error(`${label} must be a whole number of pixels from 1 to 16384, not ${value}`);
|
|
57
|
+
};
|
|
58
|
+
/** A variant as written, checked: a known format and fit, and bounds and a quality in range. */
|
|
59
|
+
const checkedVariant = (label, variant) => {
|
|
60
|
+
if (!imageFormats.includes(variant.format)) throw new Error(`${label}: "${variant.format}" is not an image format; use ${imageFormats.join(", ")}`);
|
|
61
|
+
if (variant.fit !== void 0 && !fits.includes(variant.fit)) throw new Error(`${label}: fit is "inside" or "cover", not "${variant.fit}"`);
|
|
62
|
+
if (variant.fit === "cover" && (variant.width === void 0 || variant.height === void 0)) throw new Error(`${label}: fit "cover" needs both a width and a height`);
|
|
63
|
+
checkedBound(`${label}: width`, variant.width);
|
|
64
|
+
checkedBound(`${label}: height`, variant.height);
|
|
65
|
+
if (variant.quality !== void 0 && (!Number.isSafeInteger(variant.quality) || variant.quality < 1 || variant.quality > 100)) throw new Error(`${label}: quality must be a whole number from 1 to 100, not ${variant.quality}`);
|
|
66
|
+
return { ...variant };
|
|
67
|
+
};
|
|
68
|
+
/** Variants by name: snake_case names, since each one is part of its file's path. */
|
|
69
|
+
const checkedVariants = (variants) => Object.fromEntries(Object.entries(variants).map(([name, variant]) => {
|
|
70
|
+
if (!variantNamePattern.test(name)) throw new Error(`"${name}" cannot name an image variant: lowercase letters, digits and underscores, starting with a letter`);
|
|
71
|
+
return [name, checkedVariant(`Image variant "${name}"`, variant)];
|
|
72
|
+
}));
|
|
46
73
|
//#endregion
|
|
47
|
-
export { checkedAccept, checkedProvider, defaultMaxSize, parseSize };
|
|
74
|
+
export { checkedAccept, checkedProvider, checkedVariant, checkedVariants, defaultMaxSize, imageFormats, parseSize };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { FileProcessor, FileProcessorInput, ImageFormat, ImageVariant, ImageVariantOptions, checkedAccept, checkedProvider, imageFormats, parseSize } from "./files/file-options.js";
|
|
2
|
+
import { ActionModel, ActionRun, AppAddress, BadgeShape, BottomBarItemModel, BottomBarModel, CardPhotosModel, ChartGranularity, ChartModel, ChartRange, CheckedFilter, CompareOp, CustomSectionModel, DatePreset, DurationLiteral, DurationUnit, EventModel, EventOperation, FieldFormat, FieldModel, FieldPath, FieldType, FieldViewModel, FieldWidgetModel, FileFieldModel, FilterError, FilterErrorCode, FilterExpr, FilterLayout, FilterOf, FilterValue, FilterWidgetModel, JsonValue, KeyValue, LayoutItemModel, ListDisplayModel, ListModel, ListQuery, Literal, MapSectionModel, MapView, NavModel, NavRecentModel, NowValue, OrderByItem, RelatedModel, ResourceModel, SaveFeedback, SearchMatch, SearchResultModel, SortSpec, Span, StatusTone, UserMenuItemModel, UserMenuModel, ViewModel } from "./model.js";
|
|
2
3
|
import { AccessAction, AccessContext, AccessFn, AccessResult, AccessUser, FieldAccess, FieldRule } from "./access/types.js";
|
|
3
|
-
import { FileProcessor, FileProcessorInput, checkedAccept, checkedProvider, parseSize } from "./files/file-options.js";
|
|
4
4
|
import { Field } from "./field.js";
|
|
5
5
|
import { FileField } from "./file-field.js";
|
|
6
6
|
import { f } from "./fields.js";
|
|
@@ -44,4 +44,4 @@ import "./access/index.js";
|
|
|
44
44
|
import { AssistantAction, AssistantActionRequest, AssistantCardPart, AssistantDiff, AssistantEvent, AssistantMessage, AssistantMessageRequest, AssistantPart, AssistantState, AssistantStep, AssistantStepState, AssistantTablePart, AssistantTextPart, AssistantTone, AssistantWidgetPart } from "./assistant/protocol.js";
|
|
45
45
|
import { applyAssistantEvent, emptyAssistantState } from "./assistant/apply-event.js";
|
|
46
46
|
import { readEventStream } from "./assistant/event-stream.js";
|
|
47
|
-
export { type AccessAction, type AccessContext, type AccessFn, type AccessResult, type AccessSource, type AccessUser, type ActionModel, type ActionRun, type AddressCheck, 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 Capability, type CapabilityAction, type CapabilityLimit, type CapabilityScope, type CardPhotosModel, type ChartGranularity, type ChartModel, type ChartRange, type CheckResult, type CheckedFilter, type CompareOp, type ComponentRef, type CustomSectionModel, type DatePreset, type DurationLiteral, type DurationUnit, type EvaluateOptions, type EventActor, type EventContext, type EventHelpers, type EventMatch, type EventModel, type EventOperation, type EventSpec, type Field, type FieldAccess, type FieldFormat, type FieldModel, type FieldPath, type FieldRefValue, type FieldRule, type FieldType, type FieldViewModel, type FieldWidgetModel, type FileField, type FileFieldModel, type FileProcessor, type FileProcessorInput, type FilterError, type FilterErrorCode, type FilterExpr, type FilterLayout, type FilterLimits, type FilterOf, type FilterParse, type FilterValue, type FilterWidgetModel, type InferRecord, type JsonValue, type KeyType, type KeyValue, type LayoutItemModel, type ListDisplayModel, type ListModel, type ListQuery, type Literal, type MapSectionModel, type NavModel, type NavRecentModel, type NowValue, type OrderByCheck, type OrderByItem, type OrderByParse, type PluginHost, type PluginRequirement, type PropShapes, type RecordKey, type RefShape, type RelatedModel, type ResolveOptions, type ResolvedAccess, type ResourceModel, type RoleEntry, type RoleLimit, type Roles, type RolesOptions, type RowFilterOperation, type Rule, type SaveFeedback, type SearchMatch, type SearchResultModel, type SearchTerm, type SortSpec, type Span, type StatusTone, type UserMenuItemModel, type UserMenuModel, type UserMenuSource, type ViewModel, type ViewProps, accessWarnings, addressErrors, addressPlaceholders, addressWords, anonymousRole, appHrefError, appPath, applyAssistantEvent, bottomBar, bottomBarErrors, bottomBarFor, checkEventFilter, checkFilter, checkOrderBy, checkRecord, checkedAccept, checkedProvider, createWhere, decodeKey, defaultRecentLimit, defineRoles, displayFields, emptyAssistantState, encodeKey, enumLabel, evaluateFilter, f, fieldRef, fieldRefsIn, fieldsUsedBy, fillAddress, hrefPlaceholders, inRange, isFieldRef, keyTypes, layout as l, leavesApp, markFieldRefs, matchedSearchField, matchesSearch, maxBottomBarItems, navRecentErrors, parseFilter, parseOrderBy, parseSize, pickView, pluginProblem, printFilter, printOrderBy, pruneByShape, readAppPath, readEventStream, redactSecrets, resolveAccess, resource, restrictModel, rule, searchTerms, shapes, statusTones, tableDisplay, termMatches, unmarkFieldRefs, userMenu, view, webhookResources, where };
|
|
47
|
+
export { type AccessAction, type AccessContext, type AccessFn, type AccessResult, type AccessSource, type AccessUser, type ActionModel, type ActionRun, type AddressCheck, 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 Capability, type CapabilityAction, type CapabilityLimit, type CapabilityScope, type CardPhotosModel, type ChartGranularity, type ChartModel, type ChartRange, type CheckResult, type CheckedFilter, type CompareOp, type ComponentRef, type CustomSectionModel, type DatePreset, type DurationLiteral, type DurationUnit, type EvaluateOptions, type EventActor, type EventContext, type EventHelpers, type EventMatch, type EventModel, type EventOperation, type EventSpec, type Field, type FieldAccess, type FieldFormat, type FieldModel, type FieldPath, type FieldRefValue, type FieldRule, type FieldType, type FieldViewModel, type FieldWidgetModel, type FileField, type FileFieldModel, type FileProcessor, type FileProcessorInput, type FilterError, type FilterErrorCode, type FilterExpr, type FilterLayout, type FilterLimits, type FilterOf, type FilterParse, type FilterValue, type FilterWidgetModel, type ImageFormat, type ImageVariant, type ImageVariantOptions, type InferRecord, type JsonValue, type KeyType, type KeyValue, type LayoutItemModel, type ListDisplayModel, type ListModel, type ListQuery, type Literal, type MapSectionModel, type MapView, type NavModel, type NavRecentModel, type NowValue, type OrderByCheck, type OrderByItem, type OrderByParse, type PluginHost, type PluginRequirement, type PropShapes, type RecordKey, type RefShape, type RelatedModel, type ResolveOptions, type ResolvedAccess, type ResourceModel, type RoleEntry, type RoleLimit, type Roles, type RolesOptions, type RowFilterOperation, type Rule, type SaveFeedback, type SearchMatch, type SearchResultModel, type SearchTerm, type SortSpec, type Span, type StatusTone, type UserMenuItemModel, type UserMenuModel, type UserMenuSource, type ViewModel, type ViewProps, accessWarnings, addressErrors, addressPlaceholders, addressWords, anonymousRole, appHrefError, appPath, applyAssistantEvent, bottomBar, bottomBarErrors, bottomBarFor, checkEventFilter, checkFilter, checkOrderBy, checkRecord, checkedAccept, checkedProvider, createWhere, decodeKey, defaultRecentLimit, defineRoles, displayFields, emptyAssistantState, encodeKey, enumLabel, evaluateFilter, f, fieldRef, fieldRefsIn, fieldsUsedBy, fillAddress, hrefPlaceholders, imageFormats, inRange, isFieldRef, keyTypes, layout as l, leavesApp, markFieldRefs, matchedSearchField, matchesSearch, maxBottomBarItems, navRecentErrors, parseFilter, parseOrderBy, parseSize, pickView, pluginProblem, printFilter, printOrderBy, pruneByShape, readAppPath, readEventStream, redactSecrets, resolveAccess, resource, restrictModel, rule, searchTerms, shapes, statusTones, tableDisplay, termMatches, unmarkFieldRefs, userMenu, view, webhookResources, where };
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { checkedAccept, checkedProvider, parseSize } from "./files/file-options.js";
|
|
1
|
+
import { checkedAccept, checkedProvider, imageFormats, parseSize } from "./files/file-options.js";
|
|
2
2
|
import { f } from "./fields.js";
|
|
3
3
|
import { parseFilter } from "./filter/parse.js";
|
|
4
4
|
import { checkFilter } from "./filter/check.js";
|
|
@@ -34,4 +34,4 @@ import { pickView } from "./access/pick-view.js";
|
|
|
34
34
|
import { restrictModel } from "./access/restrict.js";
|
|
35
35
|
import { applyAssistantEvent, emptyAssistantState } from "./assistant/apply-event.js";
|
|
36
36
|
import { readEventStream } from "./assistant/event-stream.js";
|
|
37
|
-
export { accessWarnings, addressErrors, addressPlaceholders, addressWords, anonymousRole, appHrefError, appPath, applyAssistantEvent, bottomBar, bottomBarErrors, bottomBarFor, checkEventFilter, checkFilter, checkOrderBy, checkRecord, checkedAccept, checkedProvider, createWhere, decodeKey, defaultRecentLimit, defineRoles, displayFields, emptyAssistantState, encodeKey, enumLabel, evaluateFilter, f, fieldRef, fieldRefsIn, fieldsUsedBy, fillAddress, hrefPlaceholders, inRange, isFieldRef, keyTypes, layout as l, leavesApp, markFieldRefs, matchedSearchField, matchesSearch, maxBottomBarItems, navRecentErrors, parseFilter, parseOrderBy, parseSize, pickView, pluginProblem, printFilter, printOrderBy, pruneByShape, readAppPath, readEventStream, redactSecrets, resolveAccess, resource, restrictModel, rule, searchTerms, shapes, statusTones, tableDisplay, termMatches, unmarkFieldRefs, userMenu, view, webhookResources, where };
|
|
37
|
+
export { accessWarnings, addressErrors, addressPlaceholders, addressWords, anonymousRole, appHrefError, appPath, applyAssistantEvent, bottomBar, bottomBarErrors, bottomBarFor, checkEventFilter, checkFilter, checkOrderBy, checkRecord, checkedAccept, checkedProvider, createWhere, decodeKey, defaultRecentLimit, defineRoles, displayFields, emptyAssistantState, encodeKey, enumLabel, evaluateFilter, f, fieldRef, fieldRefsIn, fieldsUsedBy, fillAddress, hrefPlaceholders, imageFormats, inRange, isFieldRef, keyTypes, layout as l, leavesApp, markFieldRefs, matchedSearchField, matchesSearch, maxBottomBarItems, navRecentErrors, parseFilter, parseOrderBy, parseSize, pickView, pluginProblem, printFilter, printOrderBy, pruneByShape, readAppPath, readEventStream, redactSecrets, resolveAccess, resource, restrictModel, rule, searchTerms, shapes, statusTones, tableDisplay, termMatches, unmarkFieldRefs, userMenu, view, webhookResources, where };
|
package/dist/model.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { ImageVariant } from "./files/file-options.js";
|
|
1
2
|
//#region src/model.d.ts
|
|
2
3
|
export type FieldType = 'text' | 'integer' | 'bigint' | 'decimal' | 'boolean' | 'date' | 'timestamp' | 'uuid' | 'enum' | 'json' | 'relation' | 'file' | 'currency' | 'country';
|
|
3
4
|
export type FieldModel = {
|
|
@@ -22,14 +23,21 @@ export type FieldModel = {
|
|
|
22
23
|
} | {
|
|
23
24
|
db: true;
|
|
24
25
|
};
|
|
25
|
-
/** A file field's allowed types (empty: any), largest upload in bytes, provider, and
|
|
26
|
+
/** A file field's allowed types (empty: any), largest upload in bytes, provider, and what each upload writes. */
|
|
26
27
|
file?: FileFieldModel;
|
|
27
28
|
};
|
|
28
29
|
export type FileFieldModel = {
|
|
29
30
|
accept: string[];
|
|
30
31
|
maxSize: number;
|
|
31
32
|
provider: string;
|
|
33
|
+
/** The fields each upload writes: those of `.derive()` and the placeholder's. */
|
|
32
34
|
derive?: string[];
|
|
35
|
+
/** The text field holding each image's ThumbHash, base64 (`.placeholder()`). */
|
|
36
|
+
placeholder?: string;
|
|
37
|
+
/** The resized copies stored beside each image, by name (`.variants()`). */
|
|
38
|
+
variants?: Record<string, ImageVariant>;
|
|
39
|
+
/** What is stored instead of an uploaded image, when the upload is not kept. */
|
|
40
|
+
original?: ImageVariant;
|
|
33
41
|
};
|
|
34
42
|
/** `digitsEnd`: only the end of the value's digits, spaces, `+` and dashes ignored (phone numbers). */
|
|
35
43
|
export type SearchMatch = 'digitsEnd';
|
|
@@ -85,10 +93,17 @@ export type ListQuery = {
|
|
|
85
93
|
limit: number;
|
|
86
94
|
};
|
|
87
95
|
export type FilterLayout = 'bar' | 'panel';
|
|
96
|
+
/** Where a list's map display looks: the centre in decimal degrees, and MapLibre's zoom, from 0 to 22. */
|
|
97
|
+
export type MapView = {
|
|
98
|
+
latitude: number;
|
|
99
|
+
longitude: number;
|
|
100
|
+
zoom: number;
|
|
101
|
+
};
|
|
88
102
|
/**
|
|
89
103
|
* 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
104
|
* 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' }`).
|
|
105
|
+
* form's `values` fill in fields of the new record by name, as text (`{ listing_id: '42' }`). A list's `map` is where its
|
|
106
|
+
* map display looks.
|
|
92
107
|
*/
|
|
93
108
|
export type AppAddress = {
|
|
94
109
|
kind: 'list';
|
|
@@ -97,6 +112,7 @@ export type AppAddress = {
|
|
|
97
112
|
orderBy?: string;
|
|
98
113
|
display?: string;
|
|
99
114
|
filterLayout?: FilterLayout;
|
|
115
|
+
map?: MapView;
|
|
100
116
|
} | {
|
|
101
117
|
kind: 'record';
|
|
102
118
|
resource: string;
|
package/dist/resource-model.js
CHANGED
|
@@ -10,6 +10,7 @@ const parseTable = (table) => {
|
|
|
10
10
|
};
|
|
11
11
|
throw new Error(`Invalid table "${table}", expected "table" or "schema.table"`);
|
|
12
12
|
};
|
|
13
|
+
const writtenFields = (file) => [...Object.keys(file.derive ?? {}), ...file.placeholder === void 0 ? [] : [file.placeholder]];
|
|
13
14
|
const fieldModel = (name, field) => {
|
|
14
15
|
const { meta } = field;
|
|
15
16
|
const column = meta.column ?? snakeCase(name);
|
|
@@ -34,7 +35,10 @@ const fieldModel = (name, field) => {
|
|
|
34
35
|
accept: [...meta.file.accept],
|
|
35
36
|
maxSize: meta.file.maxSize,
|
|
36
37
|
provider: meta.file.provider,
|
|
37
|
-
...
|
|
38
|
+
...writtenFields(meta.file).length > 0 && { derive: writtenFields(meta.file) },
|
|
39
|
+
...meta.file.placeholder !== void 0 && { placeholder: meta.file.placeholder },
|
|
40
|
+
...meta.file.variants && Object.keys(meta.file.variants).length > 0 && { variants: structuredClone(meta.file.variants) },
|
|
41
|
+
...meta.file.original && { original: { ...meta.file.original } }
|
|
38
42
|
} }
|
|
39
43
|
};
|
|
40
44
|
};
|
|
@@ -84,8 +88,17 @@ const fileProblems = (model) => Object.values(model.fields).filter((field) => fi
|
|
|
84
88
|
if (!derived) return [`derives "${target}", which is not a field`];
|
|
85
89
|
if (target === field.name || derived.file) return [`derives "${target}", which is a file field`];
|
|
86
90
|
return derived.readOnly ? [] : [`derives "${target}", which must be readOnly()`];
|
|
87
|
-
})
|
|
91
|
+
}),
|
|
92
|
+
...placeholderProblems(model, field)
|
|
88
93
|
].map((problem) => `file field "${field.name}" ${problem}`));
|
|
94
|
+
const placeholderProblems = (model, field) => {
|
|
95
|
+
const target = field.file.placeholder;
|
|
96
|
+
if (target === void 0) return [];
|
|
97
|
+
const derive = field.file.derive ?? [];
|
|
98
|
+
if (derive.indexOf(target) !== derive.lastIndexOf(target)) return [`has "${target}" as its placeholder and derives it as well`];
|
|
99
|
+
const placeholder = Object.hasOwn(model.fields, target) ? model.fields[target] : void 0;
|
|
100
|
+
return placeholder && !placeholder.file && placeholder.type !== "text" ? [`has "${target}" as its placeholder, which must be a text field`] : [];
|
|
101
|
+
};
|
|
89
102
|
const buildResourceModel = (state) => {
|
|
90
103
|
const model = assembleModel(state);
|
|
91
104
|
const events = state.events && buildEventModels(model, state.events);
|
package/dist/view-displays.d.ts
CHANGED
|
@@ -40,9 +40,10 @@ export declare const displays: {
|
|
|
40
40
|
photos?: CardPhotosModel;
|
|
41
41
|
}) => ListDisplayModel;
|
|
42
42
|
/**
|
|
43
|
-
* A map of the records at their `latitude` and `longitude`, in decimal degrees, drawn with the app's
|
|
44
|
-
*
|
|
45
|
-
*
|
|
43
|
+
* A map of the records at their `latitude` and `longitude`, filterable fields in decimal degrees, drawn with the app's
|
|
44
|
+
* map style. It asks for the records in its view as it pans and zooms: from far out a count for each part of the map,
|
|
45
|
+
* which zooms in when chosen, closer in the records. A marker stands for the records that lie close together at the
|
|
46
|
+
* zoom; choosing it shows their card at the map's bottom, with arrows through them. The card is the one of the cards display named `cards`, the list's first cards display
|
|
46
47
|
* when it names none, or the record's title alone without one. Named `map` unless `name` says otherwise.
|
|
47
48
|
*/
|
|
48
49
|
map: (fields: {
|
package/dist/view-displays.js
CHANGED
|
@@ -38,9 +38,10 @@ const displays = {
|
|
|
38
38
|
...o.photos && { photos: { ...o.photos } }
|
|
39
39
|
}),
|
|
40
40
|
/**
|
|
41
|
-
* A map of the records at their `latitude` and `longitude`, in decimal degrees, drawn with the app's
|
|
42
|
-
*
|
|
43
|
-
*
|
|
41
|
+
* A map of the records at their `latitude` and `longitude`, filterable fields in decimal degrees, drawn with the app's
|
|
42
|
+
* map style. It asks for the records in its view as it pans and zooms: from far out a count for each part of the map,
|
|
43
|
+
* which zooms in when chosen, closer in the records. A marker stands for the records that lie close together at the
|
|
44
|
+
* zoom; choosing it shows their card at the map's bottom, with arrows through them. The card is the one of the cards display named `cards`, the list's first cards display
|
|
44
45
|
* when it names none, or the record's title alone without one. Named `map` unless `name` says otherwise.
|
|
45
46
|
*/
|
|
46
47
|
map: (fields, o = {}) => ({
|