@brydio/manifest 0.1.0-alpha.0
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/LICENSE +21 -0
- package/package.json +27 -0
- package/src/base.d.ts +73 -0
- package/src/base.js +61 -0
- package/src/bundle.d.ts +52 -0
- package/src/bundle.js +95 -0
- package/src/define.d.ts +49 -0
- package/src/define.js +21 -0
- package/src/document-limits.d.ts +21 -0
- package/src/document-limits.js +21 -0
- package/src/field-types.d.ts +157 -0
- package/src/field-types.js +298 -0
- package/src/grants.d.ts +20 -0
- package/src/grants.js +30 -0
- package/src/index.d.ts +17 -0
- package/src/index.js +17 -0
- package/src/migrations.d.ts +142 -0
- package/src/migrations.js +322 -0
- package/src/schema.d.ts +381 -0
- package/src/schema.js +375 -0
- package/src/sdk.d.ts +30 -0
- package/src/sdk.js +77 -0
- package/src/secrets.d.ts +32 -0
- package/src/secrets.js +81 -0
- package/src/tools.d.ts +21 -0
- package/src/tools.js +31 -0
- package/src/validate.d.ts +27 -0
- package/src/validate.js +29 -0
|
@@ -0,0 +1,298 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
/**
|
|
3
|
+
* The bounds a schema and a record are held to (A3-F01-S03).
|
|
4
|
+
*
|
|
5
|
+
* `stringChars` is 1,000 (E1, 16 Sep: the feature file wins over contracts'
|
|
6
|
+
* old 512), the same as `bry-input`'s `INPUT_MAX`, so nothing a person can
|
|
7
|
+
* type into a text field is refused by the store.
|
|
8
|
+
*/
|
|
9
|
+
export const FIELD_LIMITS = {
|
|
10
|
+
/** Collections one app may keep. */
|
|
11
|
+
collections: 20,
|
|
12
|
+
/** Fields one collection may have. */
|
|
13
|
+
fields: 40,
|
|
14
|
+
/** Values one choice may allow. */
|
|
15
|
+
enumValues: 50,
|
|
16
|
+
/** Characters in one of a choice's values. */
|
|
17
|
+
enumValueChars: 64,
|
|
18
|
+
/** Characters in a `string` value, the same as `bry-input` lets a person type. */
|
|
19
|
+
stringChars: 1_000,
|
|
20
|
+
/** Characters in a `text` value. */
|
|
21
|
+
textChars: 100_000,
|
|
22
|
+
/** Entries in a `string[]` value. */
|
|
23
|
+
listEntries: 100,
|
|
24
|
+
/** Characters in a collection, label, field or screen name. */
|
|
25
|
+
nameChars: 40,
|
|
26
|
+
};
|
|
27
|
+
/**
|
|
28
|
+
* The colours a `token` field may hold, by design-token name.
|
|
29
|
+
*
|
|
30
|
+
* The status roles `DESIGN_SYSTEM.md` already gives a solid, a text, a tint
|
|
31
|
+
* and an edge, plus the brand and a neutral. A label picks one by name and
|
|
32
|
+
* the host draws it in the current theme, so an app never holds a colour
|
|
33
|
+
* value it could get wrong in dark mode (ADR-A13). `@brydio/manifest` carries
|
|
34
|
+
* the same list.
|
|
35
|
+
*/
|
|
36
|
+
export const COLOUR_TOKENS = ['neutral', 'brand', 'success', 'warn', 'danger'];
|
|
37
|
+
/**
|
|
38
|
+
* Names Brydio keeps on every record, or that the generated tools already
|
|
39
|
+
* use for their own arguments. A field called `version` would be ambiguous in
|
|
40
|
+
* `update_issue`, and one called `instance` would shadow ADR-A09's argument.
|
|
41
|
+
*/
|
|
42
|
+
export const RESERVED_FIELDS = new Set([
|
|
43
|
+
'id',
|
|
44
|
+
'version',
|
|
45
|
+
'instance',
|
|
46
|
+
'createdAt',
|
|
47
|
+
'createdBy',
|
|
48
|
+
'updatedAt',
|
|
49
|
+
'deletedAt',
|
|
50
|
+
]);
|
|
51
|
+
/** A field, collection or label name: an identifier the tools can use as-is. */
|
|
52
|
+
export const FIELD_NAME = /^[a-z][a-zA-Z0-9_]*$/;
|
|
53
|
+
export const COLLECTION_NAME = /^[a-z][a-z0-9_]*$/;
|
|
54
|
+
/**
|
|
55
|
+
* Kept in the plain `fields` column: filterable and sortable. Words never
|
|
56
|
+
* are: `string`, `text` and `string[]` live only in the encrypted body
|
|
57
|
+
* (A3-F01-S02, A3-F02-S01; E1, 16 Sep), so a list of labels is found by
|
|
58
|
+
* search, not by a filter.
|
|
59
|
+
*/
|
|
60
|
+
const STRUCTURED = new Set([
|
|
61
|
+
'enum',
|
|
62
|
+
'member',
|
|
63
|
+
'project',
|
|
64
|
+
'date',
|
|
65
|
+
'number',
|
|
66
|
+
'boolean',
|
|
67
|
+
'token',
|
|
68
|
+
]);
|
|
69
|
+
/** Only the words in these may one day be searched (A3-F01-S02). */
|
|
70
|
+
const SEARCHABLE = new Set(['string', 'text', 'string[]']);
|
|
71
|
+
const SCALARS = new Set(['string', 'text', 'member', 'project', 'date', 'number', 'boolean', 'token']);
|
|
72
|
+
/** A field type the manifest wrote that Brydio does not have, or a bad list. */
|
|
73
|
+
export class FieldTypeInvalid extends Error {
|
|
74
|
+
code;
|
|
75
|
+
constructor(code, message) {
|
|
76
|
+
super(message);
|
|
77
|
+
this.code = code;
|
|
78
|
+
this.name = 'FieldTypeInvalid';
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* One field's manifest spelling, as a type.
|
|
83
|
+
*
|
|
84
|
+
* Throws `FieldTypeInvalid` with a code rather than returning null, because
|
|
85
|
+
* "unknown type" and "a choice with 51 values" are different mistakes and the
|
|
86
|
+
* import report has to say which.
|
|
87
|
+
*/
|
|
88
|
+
export function parseFieldType(raw) {
|
|
89
|
+
if (Array.isArray(raw))
|
|
90
|
+
return parseEnumeration(raw);
|
|
91
|
+
if (raw && typeof raw === 'object')
|
|
92
|
+
return parseFieldObject(raw);
|
|
93
|
+
if (typeof raw !== 'string') {
|
|
94
|
+
throw new FieldTypeInvalid('data_field_type_unknown', 'A field type is a name such as "string?" or a list of allowed values.');
|
|
95
|
+
}
|
|
96
|
+
const optional = raw.endsWith('?');
|
|
97
|
+
const name = optional ? raw.slice(0, -1) : raw;
|
|
98
|
+
if (name === 'string[]')
|
|
99
|
+
return { kind: 'string[]', optional };
|
|
100
|
+
if (name === 'token')
|
|
101
|
+
return { kind: 'token', optional, values: COLOUR_TOKENS };
|
|
102
|
+
if (SCALARS.has(name))
|
|
103
|
+
return { kind: name, optional };
|
|
104
|
+
throw new FieldTypeInvalid('data_field_type_unknown', `"${raw}" is not a field type.`);
|
|
105
|
+
}
|
|
106
|
+
const FIELD_KEYS = new Set(['type', 'optional', 'default', 'labels']);
|
|
107
|
+
/** How long a choice value's label may be. */
|
|
108
|
+
export const LABEL_CHARS = 60;
|
|
109
|
+
/** The object form: the short form under `type`, and what it may add. */
|
|
110
|
+
function parseFieldObject(raw) {
|
|
111
|
+
const unknown = Object.keys(raw).find(key => !FIELD_KEYS.has(key));
|
|
112
|
+
if (unknown) {
|
|
113
|
+
throw new FieldTypeInvalid('data_field_key_unknown', `"${unknown}" is not something a field may say; use type, optional, default and labels.`);
|
|
114
|
+
}
|
|
115
|
+
if (typeof raw.type !== 'string' && !Array.isArray(raw.type)) {
|
|
116
|
+
throw new FieldTypeInvalid('data_field_type_unknown', 'A field written as an object names its type under "type".');
|
|
117
|
+
}
|
|
118
|
+
if (raw.optional !== undefined && typeof raw.optional !== 'boolean') {
|
|
119
|
+
throw new FieldTypeInvalid('data_field_key_unknown', '"optional" is true or false.');
|
|
120
|
+
}
|
|
121
|
+
const inner = parseFieldType(raw.type);
|
|
122
|
+
const type = { ...inner, optional: inner.optional || raw.optional === true, ...labelsOf(inner, raw) };
|
|
123
|
+
if (!('default' in raw))
|
|
124
|
+
return type;
|
|
125
|
+
const value = raw.default;
|
|
126
|
+
if (type.kind !== 'enum' && type.kind !== 'boolean') {
|
|
127
|
+
throw new FieldTypeInvalid('data_default_not_allowed', `Only a choice or a boolean may have a default, not a ${type.kind}.`);
|
|
128
|
+
}
|
|
129
|
+
if (!type.optional) {
|
|
130
|
+
throw new FieldTypeInvalid('data_default_on_required', 'A field with a default is one a create may leave out: add "optional": true (or write "boolean?").');
|
|
131
|
+
}
|
|
132
|
+
if (type.kind === 'enum' ? typeof value !== 'string' || !(type.values ?? []).includes(value) : typeof value !== 'boolean') {
|
|
133
|
+
throw new FieldTypeInvalid('data_default_invalid', type.kind === 'enum'
|
|
134
|
+
? `The default must be one of ${quoted(type.values ?? [])}.`
|
|
135
|
+
: 'The default of a boolean is true or false.');
|
|
136
|
+
}
|
|
137
|
+
return { ...type, default: value };
|
|
138
|
+
}
|
|
139
|
+
/** A choice's `labels`, checked: one short label per value it names, and only values the choice has. */
|
|
140
|
+
function labelsOf(type, raw) {
|
|
141
|
+
if (!('labels' in raw))
|
|
142
|
+
return {};
|
|
143
|
+
if (type.kind !== 'enum') {
|
|
144
|
+
throw new FieldTypeInvalid('data_labels_not_allowed', `Only a choice may label its values, not a ${type.kind}.`);
|
|
145
|
+
}
|
|
146
|
+
const labels = raw.labels;
|
|
147
|
+
if (!labels || typeof labels !== 'object' || Array.isArray(labels)) {
|
|
148
|
+
throw new FieldTypeInvalid('data_label_invalid', '"labels" maps each value to how it reads, such as { "todo": "To do" }.');
|
|
149
|
+
}
|
|
150
|
+
for (const [value, label] of Object.entries(labels)) {
|
|
151
|
+
if (!(type.values ?? []).includes(value)) {
|
|
152
|
+
throw new FieldTypeInvalid('data_label_unknown_value', `"${value}" is not one of ${quoted(type.values ?? [])}, so it can't have a label.`);
|
|
153
|
+
}
|
|
154
|
+
if (typeof label !== 'string' || !label.trim() || label.length > LABEL_CHARS) {
|
|
155
|
+
throw new FieldTypeInvalid('data_label_invalid', `The label for "${value}" is 1 to ${LABEL_CHARS} characters of text.`);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
return { labels: { ...labels } };
|
|
159
|
+
}
|
|
160
|
+
/** How a choice's value reads to a person: its label, or the value itself. */
|
|
161
|
+
export const labelOfValue = (type, value) => type.labels?.[value] ?? value;
|
|
162
|
+
function parseEnumeration(values) {
|
|
163
|
+
if (!values.length) {
|
|
164
|
+
throw new FieldTypeInvalid('data_enum_empty', 'A choice needs at least one allowed value.');
|
|
165
|
+
}
|
|
166
|
+
if (values.length > FIELD_LIMITS.enumValues) {
|
|
167
|
+
throw new FieldTypeInvalid('data_enum_too_many', `A choice may have at most ${FIELD_LIMITS.enumValues} values.`);
|
|
168
|
+
}
|
|
169
|
+
for (const value of values) {
|
|
170
|
+
if (typeof value !== 'string' || !value.trim() || value.length > FIELD_LIMITS.enumValueChars) {
|
|
171
|
+
throw new FieldTypeInvalid('data_enum_value_invalid', `Each allowed value is a word or two of text, at most ${FIELD_LIMITS.enumValueChars} characters.`);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
if (new Set(values).size !== values.length) {
|
|
175
|
+
throw new FieldTypeInvalid('data_enum_duplicate', 'A choice names the same value twice.');
|
|
176
|
+
}
|
|
177
|
+
return { kind: 'enum', optional: false, values: values };
|
|
178
|
+
}
|
|
179
|
+
/** True for a type whose value lands in the plain `fields` column. */
|
|
180
|
+
export const isStructured = (type) => STRUCTURED.has(type.kind);
|
|
181
|
+
/** True for a type a list can be ordered by: structured, and one value. */
|
|
182
|
+
export const isSortable = (type) => STRUCTURED.has(type.kind);
|
|
183
|
+
export const isSearchable = (type) => SEARCHABLE.has(type.kind);
|
|
184
|
+
/**
|
|
185
|
+
* The names of a schema's structured fields: the ones kept in plain beside the
|
|
186
|
+
* encrypted body, and so the ones a list may filter on.
|
|
187
|
+
*
|
|
188
|
+
* One function, imported by the store and by the tools, so what is written
|
|
189
|
+
* plainly and what may be filtered cannot drift apart (A3-F01's note). Accepts
|
|
190
|
+
* the manifest's spelling or parsed types.
|
|
191
|
+
*/
|
|
192
|
+
export function structuredFields(schema) {
|
|
193
|
+
return Object.entries(schema)
|
|
194
|
+
.filter(([, raw]) => isStructured(asType(raw)))
|
|
195
|
+
.map(([name]) => name);
|
|
196
|
+
}
|
|
197
|
+
const asType = (raw) => raw && typeof raw === 'object' && !Array.isArray(raw) && 'kind' in raw && !('type' in raw)
|
|
198
|
+
? raw
|
|
199
|
+
: parseFieldType(raw);
|
|
200
|
+
const ISO_DATE = /^\d{4}-\d{2}-\d{2}(T\d{2}:\d{2}(:\d{2}(\.\d{1,6})?)?(Z|[+-]\d{2}:\d{2}))?$/;
|
|
201
|
+
/**
|
|
202
|
+
* What one value of this type may be, as zod.
|
|
203
|
+
*
|
|
204
|
+
* The same schema checks a record in the store and describes the argument to
|
|
205
|
+
* the model, so the tool never accepts something the store then refuses.
|
|
206
|
+
* Always the required form: callers add `.optional()` where they mean it.
|
|
207
|
+
*/
|
|
208
|
+
export function valueSchema(type) {
|
|
209
|
+
switch (type.kind) {
|
|
210
|
+
case 'string':
|
|
211
|
+
return z.string().max(FIELD_LIMITS.stringChars);
|
|
212
|
+
case 'text':
|
|
213
|
+
return z.string().max(FIELD_LIMITS.textChars);
|
|
214
|
+
case 'enum':
|
|
215
|
+
case 'token':
|
|
216
|
+
return z.enum(type.values);
|
|
217
|
+
case 'member':
|
|
218
|
+
case 'project':
|
|
219
|
+
return z.string().min(1).max(200);
|
|
220
|
+
case 'date':
|
|
221
|
+
return z
|
|
222
|
+
.string()
|
|
223
|
+
.regex(ISO_DATE)
|
|
224
|
+
.refine(value => !Number.isNaN(Date.parse(value)));
|
|
225
|
+
case 'number':
|
|
226
|
+
return z.number().finite();
|
|
227
|
+
case 'boolean':
|
|
228
|
+
return z.boolean();
|
|
229
|
+
case 'string[]':
|
|
230
|
+
return z.array(z.string().max(FIELD_LIMITS.stringChars)).max(FIELD_LIMITS.listEntries);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
/**
|
|
234
|
+
* Why a value is not one of this type, in words the assistant can repeat to
|
|
235
|
+
* the person, naming the field. Null when it is fine.
|
|
236
|
+
*/
|
|
237
|
+
export function valueProblem(field, type, value) {
|
|
238
|
+
if (valueSchema(type).safeParse(value).success)
|
|
239
|
+
return null;
|
|
240
|
+
switch (type.kind) {
|
|
241
|
+
case 'string':
|
|
242
|
+
return typeof value === 'string'
|
|
243
|
+
? `${field} is longer than ${FIELD_LIMITS.stringChars.toLocaleString('en-GB')} characters.`
|
|
244
|
+
: `${field} must be text.`;
|
|
245
|
+
case 'text':
|
|
246
|
+
return typeof value === 'string'
|
|
247
|
+
? `${field} is longer than ${FIELD_LIMITS.textChars.toLocaleString('en-GB')} characters.`
|
|
248
|
+
: `${field} must be text.`;
|
|
249
|
+
case 'enum':
|
|
250
|
+
case 'token':
|
|
251
|
+
return `${field} must be one of ${quoted(type.values ?? [])}.`;
|
|
252
|
+
case 'member':
|
|
253
|
+
return `${field} must be a workspace member's user id.`;
|
|
254
|
+
case 'project':
|
|
255
|
+
return `${field} must be a project id.`;
|
|
256
|
+
case 'date':
|
|
257
|
+
return `${field} must be a date, written YYYY-MM-DD.`;
|
|
258
|
+
case 'number':
|
|
259
|
+
return `${field} must be a number.`;
|
|
260
|
+
case 'boolean':
|
|
261
|
+
return `${field} must be true or false.`;
|
|
262
|
+
case 'string[]':
|
|
263
|
+
return Array.isArray(value) && value.length > FIELD_LIMITS.listEntries
|
|
264
|
+
? `${field} may hold at most ${FIELD_LIMITS.listEntries} entries.`
|
|
265
|
+
: `${field} must be a list of short texts, each at most ${FIELD_LIMITS.stringChars.toLocaleString('en-GB')} characters.`;
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
/** A type in a few words, for a tool's description: every allowed value named. */
|
|
269
|
+
export function describeType(type) {
|
|
270
|
+
switch (type.kind) {
|
|
271
|
+
case 'string':
|
|
272
|
+
return 'short text';
|
|
273
|
+
case 'text':
|
|
274
|
+
return 'long text';
|
|
275
|
+
case 'enum':
|
|
276
|
+
return `one of ${quoted(type.values ?? [])}${labelWords(type)}`;
|
|
277
|
+
case 'token':
|
|
278
|
+
return `a colour, one of ${quoted(type.values ?? [])}`;
|
|
279
|
+
case 'member':
|
|
280
|
+
return "a workspace member's user id";
|
|
281
|
+
case 'project':
|
|
282
|
+
return 'a project id';
|
|
283
|
+
case 'date':
|
|
284
|
+
return 'a date, YYYY-MM-DD';
|
|
285
|
+
case 'number':
|
|
286
|
+
return 'a number';
|
|
287
|
+
case 'boolean':
|
|
288
|
+
return 'true or false';
|
|
289
|
+
case 'string[]':
|
|
290
|
+
return 'a list of short texts';
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
/** ", read as \"todo\" is To do, \"done\" is Done", for a choice with labels. */
|
|
294
|
+
const labelWords = (type) => {
|
|
295
|
+
const pairs = Object.entries(type.labels ?? {});
|
|
296
|
+
return pairs.length ? ` (${pairs.map(([value, label]) => `"${value}" reads as ${label}`).join(', ')})` : '';
|
|
297
|
+
};
|
|
298
|
+
export const quoted = (values) => values.map(value => `"${value}"`).join(', ');
|
package/src/grants.d.ts
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The host grants Brydio has a meaning for, and its refusal of any other
|
|
3
|
+
* (A5-F04-S03, A8-F04-S03).
|
|
4
|
+
*
|
|
5
|
+
* Copies of `HOST_CAPABILITIES` and `isHostCapability` in Brydio's
|
|
6
|
+
* `apps/api/src/apps/manifest/grants.ts`, and the `grant_unknown` sentence in
|
|
7
|
+
* `apps/api/src/apps/publishing/app-publish.service.ts`.
|
|
8
|
+
*/
|
|
9
|
+
/** The host grants with a name of their own. A connection's is `connection:<name>`. */
|
|
10
|
+
export declare const HOST_CAPABILITIES: readonly ["navigate", "message", "members", "projects"];
|
|
11
|
+
/** The older name for `HOST_CAPABILITIES`, kept for what already imports it. */
|
|
12
|
+
export declare const KNOWN_HOST_GRANTS: readonly string[];
|
|
13
|
+
/** True for a host grant Brydio knows how to honour: a named capability, or one connection. */
|
|
14
|
+
export declare const isHostCapability: (value: string) => boolean;
|
|
15
|
+
/** The server's refusal of the first host grant it does not know, or null. */
|
|
16
|
+
export declare function unknownHostGrant(manifest: unknown): {
|
|
17
|
+
code: 'grant_unknown';
|
|
18
|
+
message: string;
|
|
19
|
+
path: 'grants.host';
|
|
20
|
+
} | null;
|
package/src/grants.js
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The host grants Brydio has a meaning for, and its refusal of any other
|
|
3
|
+
* (A5-F04-S03, A8-F04-S03).
|
|
4
|
+
*
|
|
5
|
+
* Copies of `HOST_CAPABILITIES` and `isHostCapability` in Brydio's
|
|
6
|
+
* `apps/api/src/apps/manifest/grants.ts`, and the `grant_unknown` sentence in
|
|
7
|
+
* `apps/api/src/apps/publishing/app-publish.service.ts`.
|
|
8
|
+
*/
|
|
9
|
+
import { BUNDLE_MANIFEST } from "./bundle.js";
|
|
10
|
+
/** The host grants with a name of their own. A connection's is `connection:<name>`. */
|
|
11
|
+
export const HOST_CAPABILITIES = ['navigate', 'message', 'members', 'projects'];
|
|
12
|
+
/** The older name for `HOST_CAPABILITIES`, kept for what already imports it. */
|
|
13
|
+
export const KNOWN_HOST_GRANTS = HOST_CAPABILITIES;
|
|
14
|
+
const CONNECTION = /^connection:[a-z0-9][a-z0-9_-]{0,59}$/;
|
|
15
|
+
/** True for a host grant Brydio knows how to honour: a named capability, or one connection. */
|
|
16
|
+
export const isHostCapability = (value) => HOST_CAPABILITIES.includes(value) || CONNECTION.test(value);
|
|
17
|
+
/** The server's refusal of the first host grant it does not know, or null. */
|
|
18
|
+
export function unknownHostGrant(manifest) {
|
|
19
|
+
const grants = manifest?.grants;
|
|
20
|
+
const host = grants?.host;
|
|
21
|
+
const unknown = (Array.isArray(host) ? host : []).find(grant => typeof grant !== 'string' || !isHostCapability(grant));
|
|
22
|
+
if (unknown === undefined)
|
|
23
|
+
return null;
|
|
24
|
+
return {
|
|
25
|
+
code: 'grant_unknown',
|
|
26
|
+
message: `${BUNDLE_MANIFEST} asks for "${String(unknown)}", which Brydio does not grant. ` +
|
|
27
|
+
`An app may ask for ${HOST_CAPABILITIES.join(', ')} or connection:<name>.`,
|
|
28
|
+
path: 'grants.host',
|
|
29
|
+
};
|
|
30
|
+
}
|
package/src/index.d.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@brydio/manifest`: the shape of `.brydio/app.json`, its collections' field
|
|
3
|
+
* types, the tools Brydio generates from them, and the bundle rules. Each is
|
|
4
|
+
* a copy of the server's own, so an app that passes here passes there.
|
|
5
|
+
*/
|
|
6
|
+
export { APP_NAME_FORMAT, MANIFEST_LIMITS, SEMVER_FORMAT, baseManifestSchema } from './base.js';
|
|
7
|
+
export { BUNDLE_MANIFEST, BUNDLE_MAX_BYTES, BUNDLE_PATH_MAX_CHARS, bundleBytes, bundleHash, bundleProblem, codeOf, isBundlePath, isScriptPath, sizeOf, type BundleFiles, type BundleProblem, type BundleProblemCode, } from './bundle.js';
|
|
8
|
+
export { COLOUR_TOKENS, FIELD_LIMITS, RESERVED_FIELDS, describeType, isSearchable, isSortable, isStructured, parseFieldType, structuredFields, valueProblem, valueSchema, FieldTypeInvalid, type DocumentOf, type FieldKind, type FieldType, type FieldValue, type FieldsOf, } from './field-types.js';
|
|
9
|
+
export { appManifestSchema, collectionOf, collectionsOf, dataProblems, labelOf, manifestExtensionsSchema, storedExtensionsSchema, type AppManifestWithData, type CollectionSpec, type DataProblem, type DataProblemCode, type ManifestExtensions, } from './schema.js';
|
|
10
|
+
export { DOCUMENT_LIMITS } from './document-limits.js';
|
|
11
|
+
export { HOST_CAPABILITIES, KNOWN_HOST_GRANTS, isHostCapability, unknownHostGrant } from './grants.js';
|
|
12
|
+
export { diffSchemas, migrationProblems, migrationSchema, migrationStepSchema, migrationsSchema, publishedMigrationProblems, schemaOf, type Migration, type MigrationCode, type MigrationProblem, type MigrationStep, type SchemaChange, } from './migrations.js';
|
|
13
|
+
export { SECRET_MESSAGE, findSecrets, secretsInJson, type SecretFound } from './secrets.js';
|
|
14
|
+
export { compareVersions, sdkRefusal, type SdkSupport } from './sdk.js';
|
|
15
|
+
export { TOOL_WRITES, generatedToolsOf, toolNames, type GeneratedTool, type ToolVerb } from './tools.js';
|
|
16
|
+
export { validateManifest, validateManifestText, type ManifestProblem, type ManifestProblemCode, type ManifestValidation, } from './validate.js';
|
|
17
|
+
export { defineManifest, type ManifestShape, type ScreenNameOf, type WithDeclaredScreens } from './define.js';
|
package/src/index.js
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@brydio/manifest`: the shape of `.brydio/app.json`, its collections' field
|
|
3
|
+
* types, the tools Brydio generates from them, and the bundle rules. Each is
|
|
4
|
+
* a copy of the server's own, so an app that passes here passes there.
|
|
5
|
+
*/
|
|
6
|
+
export { APP_NAME_FORMAT, MANIFEST_LIMITS, SEMVER_FORMAT, baseManifestSchema } from "./base.js";
|
|
7
|
+
export { BUNDLE_MANIFEST, BUNDLE_MAX_BYTES, BUNDLE_PATH_MAX_CHARS, bundleBytes, bundleHash, bundleProblem, codeOf, isBundlePath, isScriptPath, sizeOf, } from "./bundle.js";
|
|
8
|
+
export { COLOUR_TOKENS, FIELD_LIMITS, RESERVED_FIELDS, describeType, isSearchable, isSortable, isStructured, parseFieldType, structuredFields, valueProblem, valueSchema, FieldTypeInvalid, } from "./field-types.js";
|
|
9
|
+
export { appManifestSchema, collectionOf, collectionsOf, dataProblems, labelOf, manifestExtensionsSchema, storedExtensionsSchema, } from "./schema.js";
|
|
10
|
+
export { DOCUMENT_LIMITS } from "./document-limits.js";
|
|
11
|
+
export { HOST_CAPABILITIES, KNOWN_HOST_GRANTS, isHostCapability, unknownHostGrant } from "./grants.js";
|
|
12
|
+
export { diffSchemas, migrationProblems, migrationSchema, migrationStepSchema, migrationsSchema, publishedMigrationProblems, schemaOf, } from "./migrations.js";
|
|
13
|
+
export { SECRET_MESSAGE, findSecrets, secretsInJson } from "./secrets.js";
|
|
14
|
+
export { compareVersions, sdkRefusal } from "./sdk.js";
|
|
15
|
+
export { TOOL_WRITES, generatedToolsOf, toolNames } from "./tools.js";
|
|
16
|
+
export { validateManifest, validateManifestText, } from "./validate.js";
|
|
17
|
+
export { defineManifest } from "./define.js";
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { type FieldType } from './field-types.js';
|
|
3
|
+
export declare const migrationStepSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
4
|
+
op: z.ZodLiteral<"add">;
|
|
5
|
+
collection: z.ZodString;
|
|
6
|
+
field: z.ZodString;
|
|
7
|
+
default: z.ZodOptional<z.ZodUnknown>;
|
|
8
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
9
|
+
op: z.ZodLiteral<"rename">;
|
|
10
|
+
collection: z.ZodString;
|
|
11
|
+
from: z.ZodString;
|
|
12
|
+
to: z.ZodString;
|
|
13
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
14
|
+
op: z.ZodLiteral<"drop">;
|
|
15
|
+
collection: z.ZodString;
|
|
16
|
+
field: z.ZodString;
|
|
17
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
18
|
+
op: z.ZodLiteral<"dropCollection">;
|
|
19
|
+
collection: z.ZodString;
|
|
20
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
21
|
+
op: z.ZodLiteral<"replace">;
|
|
22
|
+
collection: z.ZodString;
|
|
23
|
+
field: z.ZodString;
|
|
24
|
+
from: z.ZodString;
|
|
25
|
+
to: z.ZodString;
|
|
26
|
+
}, z.core.$strip>], "op">;
|
|
27
|
+
export declare const migrationSchema: z.ZodObject<{
|
|
28
|
+
version: z.ZodString;
|
|
29
|
+
steps: z.ZodArray<z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
30
|
+
op: z.ZodLiteral<"add">;
|
|
31
|
+
collection: z.ZodString;
|
|
32
|
+
field: z.ZodString;
|
|
33
|
+
default: z.ZodOptional<z.ZodUnknown>;
|
|
34
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
35
|
+
op: z.ZodLiteral<"rename">;
|
|
36
|
+
collection: z.ZodString;
|
|
37
|
+
from: z.ZodString;
|
|
38
|
+
to: z.ZodString;
|
|
39
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
40
|
+
op: z.ZodLiteral<"drop">;
|
|
41
|
+
collection: z.ZodString;
|
|
42
|
+
field: z.ZodString;
|
|
43
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
44
|
+
op: z.ZodLiteral<"dropCollection">;
|
|
45
|
+
collection: z.ZodString;
|
|
46
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
47
|
+
op: z.ZodLiteral<"replace">;
|
|
48
|
+
collection: z.ZodString;
|
|
49
|
+
field: z.ZodString;
|
|
50
|
+
from: z.ZodString;
|
|
51
|
+
to: z.ZodString;
|
|
52
|
+
}, z.core.$strip>], "op">>;
|
|
53
|
+
}, z.core.$strip>;
|
|
54
|
+
export declare const migrationsSchema: z.ZodArray<z.ZodObject<{
|
|
55
|
+
version: z.ZodString;
|
|
56
|
+
steps: z.ZodArray<z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
57
|
+
op: z.ZodLiteral<"add">;
|
|
58
|
+
collection: z.ZodString;
|
|
59
|
+
field: z.ZodString;
|
|
60
|
+
default: z.ZodOptional<z.ZodUnknown>;
|
|
61
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
62
|
+
op: z.ZodLiteral<"rename">;
|
|
63
|
+
collection: z.ZodString;
|
|
64
|
+
from: z.ZodString;
|
|
65
|
+
to: z.ZodString;
|
|
66
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
67
|
+
op: z.ZodLiteral<"drop">;
|
|
68
|
+
collection: z.ZodString;
|
|
69
|
+
field: z.ZodString;
|
|
70
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
71
|
+
op: z.ZodLiteral<"dropCollection">;
|
|
72
|
+
collection: z.ZodString;
|
|
73
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
74
|
+
op: z.ZodLiteral<"replace">;
|
|
75
|
+
collection: z.ZodString;
|
|
76
|
+
field: z.ZodString;
|
|
77
|
+
from: z.ZodString;
|
|
78
|
+
to: z.ZodString;
|
|
79
|
+
}, z.core.$strip>], "op">>;
|
|
80
|
+
}, z.core.$strip>>;
|
|
81
|
+
export type MigrationStep = z.infer<typeof migrationStepSchema>;
|
|
82
|
+
export type Migration = z.infer<typeof migrationSchema>;
|
|
83
|
+
type Schema = Map<string, Map<string, FieldType>>;
|
|
84
|
+
/** A version's collections and their field types, read tolerantly: a stored manifest was checked when published. */
|
|
85
|
+
export declare function schemaOf(manifest: unknown): Schema;
|
|
86
|
+
export type SchemaChange = {
|
|
87
|
+
kind: 'collection_added' | 'collection_removed';
|
|
88
|
+
collection: string;
|
|
89
|
+
} | {
|
|
90
|
+
kind: 'field_added' | 'field_removed';
|
|
91
|
+
collection: string;
|
|
92
|
+
field: string;
|
|
93
|
+
type: FieldType;
|
|
94
|
+
} | {
|
|
95
|
+
kind: 'field_type_changed';
|
|
96
|
+
collection: string;
|
|
97
|
+
field: string;
|
|
98
|
+
from: FieldType;
|
|
99
|
+
to: FieldType;
|
|
100
|
+
} | {
|
|
101
|
+
kind: 'field_now_required' | 'field_now_optional';
|
|
102
|
+
collection: string;
|
|
103
|
+
field: string;
|
|
104
|
+
} | {
|
|
105
|
+
kind: 'values_added' | 'values_removed';
|
|
106
|
+
collection: string;
|
|
107
|
+
field: string;
|
|
108
|
+
values: string[];
|
|
109
|
+
};
|
|
110
|
+
/**
|
|
111
|
+
* Every way the records one schema describes differ from another's, collection
|
|
112
|
+
* by collection and field by field, in a fixed order.
|
|
113
|
+
*
|
|
114
|
+
* About storage only: labels, search lists and everything outside `data` are
|
|
115
|
+
* Hodler's `diffManifests` to show, and none of them moves a record.
|
|
116
|
+
*/
|
|
117
|
+
export declare function diffSchemas(from: unknown, to: unknown): SchemaChange[];
|
|
118
|
+
export type MigrationCode = 'migration_step_invalid' | 'migration_step_pointless' | 'migration_default_missing' | 'migration_default_invalid' | 'migration_unexplained' | 'migration_type_changed' | 'migration_value_removed';
|
|
119
|
+
export interface MigrationProblem {
|
|
120
|
+
code: MigrationCode;
|
|
121
|
+
collection: string;
|
|
122
|
+
field?: string;
|
|
123
|
+
message: string;
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Whether `steps` take the records `from` describes to the records `to`
|
|
127
|
+
* describes: every difference explained by a step, every step making one.
|
|
128
|
+
*
|
|
129
|
+
* Worked out by running the steps over a copy of the old schema and
|
|
130
|
+
* comparing what comes out with the new one, so a rename followed by an add
|
|
131
|
+
* under the old name is judged as the two moves it is, not as "nothing
|
|
132
|
+
* changed". An empty list means the steps are right.
|
|
133
|
+
*/
|
|
134
|
+
export declare function migrationProblems(from: unknown, to: unknown, steps: readonly MigrationStep[]): MigrationProblem[];
|
|
135
|
+
/**
|
|
136
|
+
* What is wrong with a version's migration against the version published
|
|
137
|
+
* before it (A3-F07-S01): the check the publish route runs, so a version
|
|
138
|
+
* that could never be pinned never reaches a workspace. `previous` is null
|
|
139
|
+
* for an app's first version, which has nothing to migrate.
|
|
140
|
+
*/
|
|
141
|
+
export declare function publishedMigrationProblems(previous: unknown, next: unknown): MigrationProblem[];
|
|
142
|
+
export {};
|