@brydio/manifest 0.1.0-alpha.4 → 0.1.0-alpha.40

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.
@@ -65,10 +65,12 @@ const STRUCTURED = new Set([
65
65
  'number',
66
66
  'boolean',
67
67
  'token',
68
+ // Another of the app's records, by id (DW06): an id, never words, so a list can name it.
69
+ 'ref',
68
70
  ]);
69
71
  /** Only the words in these may one day be searched (A3-F01-S02). */
70
72
  const SEARCHABLE = new Set(['string', 'text', 'string[]']);
71
- const SCALARS = new Set(['string', 'text', 'member', 'project', 'date', 'number', 'boolean', 'token']);
73
+ const SCALARS = new Set(['string', 'text', 'member', 'project', 'date', 'number', 'boolean', 'token', 'canvas', 'ref']);
72
74
  /** A field type the manifest wrote that Brydio does not have, or a bad list. */
73
75
  export class FieldTypeInvalid extends Error {
74
76
  code;
@@ -78,6 +80,10 @@ export class FieldTypeInvalid extends Error {
78
80
  this.name = 'FieldTypeInvalid';
79
81
  }
80
82
  }
83
+ /** The longest record id a `ref` field holds. */
84
+ export const REF_CHARS = 128;
85
+ /** What a record id looks like: letters, digits, `_` and `-`; never words. */
86
+ export const REF_FORMAT = /^[A-Za-z0-9_-]+$/;
81
87
  /**
82
88
  * One field's manifest spelling, as a type.
83
89
  *
@@ -99,18 +105,24 @@ export function parseFieldType(raw) {
99
105
  return { kind: 'string[]', optional };
100
106
  if (name === 'token')
101
107
  return { kind: 'token', optional, values: COLOUR_TOKENS };
108
+ // A drawing is always co-edited and never required: it starts empty (WB01).
109
+ if (name === 'canvas')
110
+ return { kind: 'canvas', optional: true, coedit: true };
102
111
  if (SCALARS.has(name))
103
112
  return { kind: name, optional };
104
113
  throw new FieldTypeInvalid('data_field_type_unknown', `"${raw}" is not a field type.`);
105
114
  }
106
- const FIELD_KEYS = new Set(['type', 'optional', 'default', 'labels']);
115
+ const FIELD_KEYS = new Set(['type', 'optional', 'default', 'labels', 'coedit']);
107
116
  /** How long a choice value's label may be. */
108
117
  export const LABEL_CHARS = 60;
109
118
  /** The object form: the short form under `type`, and what it may add. */
110
119
  function parseFieldObject(raw) {
111
120
  const unknown = Object.keys(raw).find(key => !FIELD_KEYS.has(key));
112
121
  if (unknown) {
113
- throw new FieldTypeInvalid('data_field_key_unknown', `"${unknown}" is not something a field may say; use type, optional, default and labels.`);
122
+ throw new FieldTypeInvalid('data_field_key_unknown', `"${unknown}" is not something a field may say; use type, optional, default, labels and coedit.`);
123
+ }
124
+ if (raw.coedit !== undefined && typeof raw.coedit !== 'boolean') {
125
+ throw new FieldTypeInvalid('data_field_key_unknown', '"coedit" is true or false.');
114
126
  }
115
127
  if (typeof raw.type !== 'string' && !Array.isArray(raw.type)) {
116
128
  throw new FieldTypeInvalid('data_field_type_unknown', 'A field written as an object names its type under "type".');
@@ -119,7 +131,12 @@ function parseFieldObject(raw) {
119
131
  throw new FieldTypeInvalid('data_field_key_unknown', '"optional" is true or false.');
120
132
  }
121
133
  const inner = parseFieldType(raw.type);
122
- const type = { ...inner, optional: inner.optional || raw.optional === true, ...labelsOf(inner, raw) };
134
+ const type = {
135
+ ...inner,
136
+ optional: inner.optional || raw.optional === true,
137
+ ...labelsOf(inner, raw),
138
+ ...coeditOf(inner, raw),
139
+ };
123
140
  if (!('default' in raw))
124
141
  return type;
125
142
  const value = raw.default;
@@ -157,6 +174,27 @@ function labelsOf(type, raw) {
157
174
  }
158
175
  return { labels: { ...labels } };
159
176
  }
177
+ /** `coedit`, on long text only: nothing else is typed into by several people at once. */
178
+ function coeditOf(type, raw) {
179
+ if (raw.coedit !== true)
180
+ return {};
181
+ if (type.kind !== 'text' && type.kind !== 'canvas') {
182
+ throw new FieldTypeInvalid('data_coedit_not_text', `Only a text or canvas field may be co-edited, not a ${type.kind}.`);
183
+ }
184
+ return { coedit: true };
185
+ }
186
+ /** True for a field several people write at once, through the co-editing layer. */
187
+ export const isCoedited = (type) => type.kind === 'canvas' || (type.kind === 'text' && type.coedit === true);
188
+ /**
189
+ * What a drawing's field holds in the record (WB01): a summary the host writes
190
+ * from the live board, so a list, search and the assistant can say what is on
191
+ * it. The drawing itself lives only in the co-edited document.
192
+ */
193
+ export const CANVAS_SUMMARY_CHARS = 10_000;
194
+ /** A collection's co-edited fields, by name. */
195
+ export const coeditedFields = (fields) => Object.entries(fields)
196
+ .filter(([, type]) => isCoedited(type))
197
+ .map(([field]) => field);
160
198
  /** How a choice's value reads to a person: its label, or the value itself. */
161
199
  export const labelOfValue = (type, value) => type.labels?.[value] ?? value;
162
200
  function parseEnumeration(values) {
@@ -177,9 +215,9 @@ function parseEnumeration(values) {
177
215
  return { kind: 'enum', optional: false, values: values };
178
216
  }
179
217
  /** True for a type whose value lands in the plain `fields` column. */
180
- export const isStructured = (type) => STRUCTURED.has(type.kind);
218
+ export const isStructured = (type) => STRUCTURED.has(type.kind) || type.plain === true;
181
219
  /** True for a type a list can be ordered by: structured, and one value. */
182
- export const isSortable = (type) => STRUCTURED.has(type.kind);
220
+ export const isSortable = (type) => STRUCTURED.has(type.kind) || (type.plain === true && type.kind !== 'string[]');
183
221
  export const isSearchable = (type) => SEARCHABLE.has(type.kind);
184
222
  /**
185
223
  * The names of a schema's structured fields: the ones kept in plain beside the
@@ -227,7 +265,11 @@ export function valueSchema(type) {
227
265
  case 'boolean':
228
266
  return z.boolean();
229
267
  case 'string[]':
230
- return z.array(z.string().max(FIELD_LIMITS.stringChars)).max(FIELD_LIMITS.listEntries);
268
+ return z.array(z.string().max(FIELD_LIMITS.stringChars)).max(type.maxEntries ?? FIELD_LIMITS.listEntries);
269
+ case 'canvas':
270
+ return z.object({ elements: z.number().int().min(0), text: z.string().max(CANVAS_SUMMARY_CHARS) }).strict();
271
+ case 'ref':
272
+ return z.string().min(1).max(REF_CHARS).regex(REF_FORMAT);
231
273
  }
232
274
  }
233
275
  /**
@@ -260,9 +302,13 @@ export function valueProblem(field, type, value) {
260
302
  case 'boolean':
261
303
  return `${field} must be true or false.`;
262
304
  case 'string[]':
263
- return Array.isArray(value) && value.length > FIELD_LIMITS.listEntries
264
- ? `${field} may hold at most ${FIELD_LIMITS.listEntries} entries.`
305
+ return Array.isArray(value) && value.length > (type.maxEntries ?? FIELD_LIMITS.listEntries)
306
+ ? `${field} may hold at most ${type.maxEntries ?? FIELD_LIMITS.listEntries} entries.`
265
307
  : `${field} must be a list of short texts, each at most ${FIELD_LIMITS.stringChars.toLocaleString('en-GB')} characters.`;
308
+ case 'canvas':
309
+ return `${field} is a drawing: it is changed on its board, never written.`;
310
+ case 'ref':
311
+ return `${field} must be the id of one of this app's records.`;
266
312
  }
267
313
  }
268
314
  /** A type in a few words, for a tool's description: every allowed value named. */
@@ -288,6 +334,10 @@ export function describeType(type) {
288
334
  return 'true or false';
289
335
  case 'string[]':
290
336
  return 'a list of short texts';
337
+ case 'canvas':
338
+ return 'a drawing, read only: how many shapes it has and the words on it';
339
+ case 'ref':
340
+ return "the id of another of this app's records";
291
341
  }
292
342
  }
293
343
  /** ", read as \"todo\" is To do, \"done\" is Done", for a choice with labels. */
package/src/grants.d.ts CHANGED
@@ -6,8 +6,16 @@
6
6
  * `apps/api/src/apps/manifest/grants.ts`, and the `grant_unknown` sentence in
7
7
  * `apps/api/src/apps/publishing/app-publish.service.ts`.
8
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"];
9
+ /**
10
+ * The host grants with a name of their own. A connection's is `connection:<name>`.
11
+ *
12
+ * `chats` and `chat` differ: `chats` is the typed Brydio API's reach into the
13
+ * person's assistant conversations; `chat` is a handler's `chat.post`, the
14
+ * app posting one of its cards into a workspace room (FO03). `directory` is
15
+ * a handler's `directory.*` and a screen's `useProfile` (FO02); `webhooks`
16
+ * is a handler's `webhooks.send` (FO07).
17
+ */
18
+ export declare const HOST_CAPABILITIES: readonly ["navigate", "message", "members", "projects", "files", "chats", "model", "secrets", "notify", "approvals", "chat", "directory", "webhooks"];
11
19
  /** The older name for `HOST_CAPABILITIES`, kept for what already imports it. */
12
20
  export declare const KNOWN_HOST_GRANTS: readonly string[];
13
21
  /** True for a host grant Brydio knows how to honour: a named capability, or one connection. */
package/src/grants.js CHANGED
@@ -7,8 +7,30 @@
7
7
  * `apps/api/src/apps/publishing/app-publish.service.ts`.
8
8
  */
9
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'];
10
+ /**
11
+ * The host grants with a name of their own. A connection's is `connection:<name>`.
12
+ *
13
+ * `chats` and `chat` differ: `chats` is the typed Brydio API's reach into the
14
+ * person's assistant conversations; `chat` is a handler's `chat.post`, the
15
+ * app posting one of its cards into a workspace room (FO03). `directory` is
16
+ * a handler's `directory.*` and a screen's `useProfile` (FO02); `webhooks`
17
+ * is a handler's `webhooks.send` (FO07).
18
+ */
19
+ export const HOST_CAPABILITIES = [
20
+ 'navigate',
21
+ 'message',
22
+ 'members',
23
+ 'projects',
24
+ 'files',
25
+ 'chats',
26
+ 'model',
27
+ 'secrets',
28
+ 'notify',
29
+ 'approvals',
30
+ 'chat',
31
+ 'directory',
32
+ 'webhooks',
33
+ ];
12
34
  /** The older name for `HOST_CAPABILITIES`, kept for what already imports it. */
13
35
  export const KNOWN_HOST_GRANTS = HOST_CAPABILITIES;
14
36
  const CONNECTION = /^connection:[a-z0-9][a-z0-9_-]{0,59}$/;
package/src/index.d.ts CHANGED
@@ -3,14 +3,19 @@
3
3
  * types, the tools Brydio generates from them, and the bundle rules. Each is
4
4
  * a copy of the server's own, so an app that passes here passes there.
5
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';
6
+ export { APP_NAME_FORMAT, MANIFEST_LIMITS, SEMVER_FORMAT, baseManifestSchema, brandImagesSchema } from './base.js';
7
+ export { BRAND_FIELDS, BRAND_LIMITS, brandFilePath, brandPathsOf, brandProblems, brandShapeOf, isBrandPath, pngVerdict, svgColours, toolsProblem, type BrandField, type RequirementCode, type RequirementFiles, type RequirementProblem, } from './requirements.js';
8
+ export { BUNDLE_MANIFEST, BUNDLE_MAX_BYTES, BUNDLE_PATH_MAX_CHARS, bundleBytes, bundleHash, brandOf, bundleProblem, codeOf, isBundlePath, isScriptPath, sizeOf, type BundleFiles, type BundleProblem, type BundleProblemCode, } from './bundle.js';
9
+ export { COLOUR_TOKENS, coeditedFields, FIELD_LIMITS, RESERVED_FIELDS, describeType, isCoedited, isSearchable, isSortable, isStructured, parseFieldType, structuredFields, valueProblem, valueSchema, FieldTypeInvalid, type DocumentOf, type FieldKind, type FieldType, type FieldValue, type FieldsOf, } from './field-types.js';
10
+ export { appManifestSchema, appToolPrefix, collectionOf, crossAppTool, collectionsOf, dataProblems, effectivePlacementKey, HOME_CARD_SIZES, labelOf, manifestExtensionsSchema, newAppManifestSchema, SIZED_PLACEMENT_KINDS, MAX_APP_SECRETS, MAX_SECRET_CHARS, PLACEMENT_KEY, SECRET_NAME, storedExtensionsSchema, type AppManifestWithData, type CollectionSpec, type DataProblem, type DataProblemCode, type DataProblemOptions, type HomeCardSize, type ManifestExtensions, type SecretSpec, } from './schema.js';
11
+ export { OPEN_LIMITS, OPEN_TYPES, choicesProblem, definitionOf, definitionTypeProblem, describeField, fieldTypeOf, hasChoices, isPlainOpenType, keyFromName, keyProblem, missingRequired, moveValue, openSchemaProblems, openSpec, openValueProblem, type Moved, type OpenField, type OpenFieldChange, type OpenFieldValue, type OpenProblem, type OpenProblemCode, type OpenSchemaFlag, type OpenType, } from './open-schema.js';
12
+ export { CONFIRM_LIMITS, confirmEmailProblems, confirmEmailSpec, type ConfirmEmailDeclared, type ConfirmEmailSpec, type ConfirmProblemCode, } from './confirm-email.js';
13
+ export { ANONYMOUS_LIMITS, ANONYMOUS_WRITER, anonymousProblems, anonymousSpec, type AnonymousDeclared, type AnonymousProblemCode, type AnonymousSpec, } from './anonymous.js';
14
+ export { editorsOf, editorsProblems, READERS_LIMIT, readersOf, readersProblems, type EditorsProblemCode, type ReadersProblemCode } from './readers.js';
10
15
  export { DOCUMENT_LIMITS } from './document-limits.js';
11
16
  export { HOST_CAPABILITIES, KNOWN_HOST_GRANTS, isHostCapability, unknownHostGrant } from './grants.js';
12
17
  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';
18
+ export { DEVELOPER_KEY_MESSAGE, SECRET_MESSAGE, developerKeyIn, findSecrets, holdsDeveloperKey, secretsInJson, type SecretFound, } from './secrets.js';
14
19
  export { compareVersions, sdkRefusal, type SdkSupport } from './sdk.js';
15
20
  export { TOOL_WRITES, generatedToolsOf, toolNames, type GeneratedTool, type ToolVerb } from './tools.js';
16
21
  export { validateManifest, validateManifestText, type ManifestProblem, type ManifestProblemCode, type ManifestValidation, } from './validate.js';
package/src/index.js CHANGED
@@ -3,14 +3,19 @@
3
3
  * types, the tools Brydio generates from them, and the bundle rules. Each is
4
4
  * a copy of the server's own, so an app that passes here passes there.
5
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";
6
+ export { APP_NAME_FORMAT, MANIFEST_LIMITS, SEMVER_FORMAT, baseManifestSchema, brandImagesSchema } from "./base.js";
7
+ export { BRAND_FIELDS, BRAND_LIMITS, brandFilePath, brandPathsOf, brandProblems, brandShapeOf, isBrandPath, pngVerdict, svgColours, toolsProblem, } from "./requirements.js";
8
+ export { BUNDLE_MANIFEST, BUNDLE_MAX_BYTES, BUNDLE_PATH_MAX_CHARS, bundleBytes, bundleHash, brandOf, bundleProblem, codeOf, isBundlePath, isScriptPath, sizeOf, } from "./bundle.js";
9
+ export { COLOUR_TOKENS, coeditedFields, FIELD_LIMITS, RESERVED_FIELDS, describeType, isCoedited, isSearchable, isSortable, isStructured, parseFieldType, structuredFields, valueProblem, valueSchema, FieldTypeInvalid, } from "./field-types.js";
10
+ export { appManifestSchema, appToolPrefix, collectionOf, crossAppTool, collectionsOf, dataProblems, effectivePlacementKey, HOME_CARD_SIZES, labelOf, manifestExtensionsSchema, newAppManifestSchema, SIZED_PLACEMENT_KINDS, MAX_APP_SECRETS, MAX_SECRET_CHARS, PLACEMENT_KEY, SECRET_NAME, storedExtensionsSchema, } from "./schema.js";
11
+ export { OPEN_LIMITS, OPEN_TYPES, choicesProblem, definitionOf, definitionTypeProblem, describeField, fieldTypeOf, hasChoices, isPlainOpenType, keyFromName, keyProblem, missingRequired, moveValue, openSchemaProblems, openSpec, openValueProblem, } from "./open-schema.js";
12
+ export { CONFIRM_LIMITS, confirmEmailProblems, confirmEmailSpec, } from "./confirm-email.js";
13
+ export { ANONYMOUS_LIMITS, ANONYMOUS_WRITER, anonymousProblems, anonymousSpec, } from "./anonymous.js";
14
+ export { editorsOf, editorsProblems, READERS_LIMIT, readersOf, readersProblems } from "./readers.js";
10
15
  export { DOCUMENT_LIMITS } from "./document-limits.js";
11
16
  export { HOST_CAPABILITIES, KNOWN_HOST_GRANTS, isHostCapability, unknownHostGrant } from "./grants.js";
12
17
  export { diffSchemas, migrationProblems, migrationSchema, migrationStepSchema, migrationsSchema, publishedMigrationProblems, schemaOf, } from "./migrations.js";
13
- export { SECRET_MESSAGE, findSecrets, secretsInJson } from "./secrets.js";
18
+ export { DEVELOPER_KEY_MESSAGE, SECRET_MESSAGE, developerKeyIn, findSecrets, holdsDeveloperKey, secretsInJson, } from "./secrets.js";
14
19
  export { compareVersions, sdkRefusal } from "./sdk.js";
15
20
  export { TOOL_WRITES, generatedToolsOf, toolNames } from "./tools.js";
16
21
  export { validateManifest, validateManifestText, } from "./validate.js";
@@ -115,7 +115,7 @@ export type SchemaChange = {
115
115
  * Hodler's `diffManifests` to show, and none of them moves a record.
116
116
  */
117
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';
118
+ export type MigrationCode = 'migration_step_invalid' | 'migration_step_pointless' | 'migration_default_missing' | 'migration_default_invalid' | 'migration_unexplained' | 'migration_type_changed' | 'migration_value_removed' | 'migration_anonymous_changed';
119
119
  export interface MigrationProblem {
120
120
  code: MigrationCode;
121
121
  collection: string;
package/src/migrations.js CHANGED
@@ -181,6 +181,35 @@ export function migrationProblems(from, to, steps) {
181
181
  break;
182
182
  }
183
183
  }
184
+ problems.push(...anonymityChanges(from, to));
185
+ return problems;
186
+ }
187
+ /**
188
+ * A kept collection that becomes anonymous, stops being, or moves its group
189
+ * (P13): no step can say what its records become. Records kept before kept
190
+ * their writers and times, and records kept since were read only by group.
191
+ */
192
+ function anonymityChanges(from, to) {
193
+ const before = record(record(from).data);
194
+ const after = record(record(to).data);
195
+ const problems = [];
196
+ for (const [name, declared] of Object.entries(after)) {
197
+ if (!(name in before))
198
+ continue;
199
+ const was = record(record(before[name]).anonymous).group;
200
+ const now = record(record(declared).anonymous).group;
201
+ if (was === now)
202
+ continue;
203
+ problems.push({
204
+ code: 'migration_anonymous_changed',
205
+ collection: name,
206
+ message: was === undefined
207
+ ? `${name} already keeps records with their writers, so it can't become anonymous; keep anonymous answers in a new collection.`
208
+ : now === undefined
209
+ ? `${name} keeps anonymous answers, so it stays anonymous.`
210
+ : `${name}'s anonymous answers stay grouped by ${String(was)}.`,
211
+ });
212
+ }
184
213
  return problems;
185
214
  }
186
215
  /** The old schema with the steps applied, and every step that could not apply or changed nothing. */
@@ -0,0 +1,128 @@
1
+ import { type FieldType } from './field-types.js';
2
+ import type { CollectionSpec } from './schema.js';
3
+ /**
4
+ * Open-schema collections (P5).
5
+ *
6
+ * A collection flagged `openSchema: { fields: "columns", table?: "table" }`
7
+ * keeps the fields its manifest declares, and also the fields its companion
8
+ * collection's records define, per table. This file is the manifest half:
9
+ * which types a definition may name, what the companion must declare, how a
10
+ * definition reads as a `FieldType` the store already knows, and how a value
11
+ * moves when its field changes type. The store half is
12
+ * `apps/data/open-schema.ts`.
13
+ */
14
+ /** The types a field definition may name. */
15
+ export declare const OPEN_TYPES: readonly ["text", "long_text", "number", "currency", "percent", "rating", "date", "datetime", "checkbox", "select", "multi_select", "person", "link", "url", "email", "phone", "attachment"];
16
+ export type OpenType = (typeof OPEN_TYPES)[number];
17
+ export declare const OPEN_LIMITS: {
18
+ /** Live rows one table may hold (DB-Q2, owner 30 Sep): measured at this size. */
19
+ readonly rowsPerTable: 50000;
20
+ /** Live field definitions one table may hold. */
21
+ readonly fieldsPerTable: 200;
22
+ /** The highest a rating may be. */
23
+ readonly ratingMax: 10;
24
+ };
25
+ /** The flag as the manifest writes it. */
26
+ export interface OpenSchemaFlag {
27
+ /** The companion collection whose records define the fields. */
28
+ fields: string;
29
+ /** A `string` field on both collections naming a row's or a definition's table. */
30
+ table?: string;
31
+ }
32
+ /** One live field, read from a companion record. */
33
+ export interface OpenField {
34
+ /** The companion record's id. */
35
+ id: string;
36
+ /** Stable across renames: what rows, tools, filters and sorts use. */
37
+ key: string;
38
+ name: string;
39
+ type: OpenType;
40
+ /** Null for a collection with no `table`. */
41
+ table: string | null;
42
+ choices: string[];
43
+ required: boolean;
44
+ currency?: string;
45
+ precision?: number;
46
+ linkTo?: string;
47
+ description?: string;
48
+ }
49
+ export type OpenProblemCode = 'data_open_fields_unknown' | 'data_open_fields_shape' | 'data_open_table_unknown';
50
+ export interface OpenProblem {
51
+ code: OpenProblemCode;
52
+ field?: string;
53
+ message: string;
54
+ }
55
+ /**
56
+ * Everything wrong with one collection's `openSchema`, given every
57
+ * collection's raw schema. Empty when it is right.
58
+ */
59
+ export declare function openSchemaProblems(collection: string, flag: OpenSchemaFlag, data: Readonly<Record<string, {
60
+ schema: Record<string, unknown>;
61
+ openSchema?: OpenSchemaFlag;
62
+ }>>): OpenProblem[];
63
+ /** A definition's type as the store's own field type: what `checked` and the list path read. */
64
+ export declare function fieldTypeOf(field: OpenField): FieldType;
65
+ /** True for a type whose values a definition must list. */
66
+ export declare const hasChoices: (type: OpenType) => boolean;
67
+ /** A definition in a few words, for an answer or a tool: `Amount (deal_size): currency, USD`. */
68
+ export declare function describeField(field: OpenField): string;
69
+ /**
70
+ * A key made from a name: `"Deal size"` → `deal_size`, suffixed when taken.
71
+ * Always a field name the tools can use, never a reserved one.
72
+ */
73
+ export declare function keyFromName(name: string, taken: ReadonlySet<string>): string;
74
+ /** Why a key cannot be a field's key here, or null. */
75
+ export declare function keyProblem(key: string, fixed: ReadonlySet<string>, taken: ReadonlySet<string>): string | null;
76
+ /** The choices a definition lists, checked: some, short, distinct. */
77
+ export declare function choicesProblem(type: OpenType, choices: readonly string[]): string | null;
78
+ /**
79
+ * What the store's own field type cannot say about an open value: a web
80
+ * address, an email, a rating's range, a date with no time, the choices of a
81
+ * multi-select. Null when it is fine; `checked` then checks the rest.
82
+ */
83
+ export declare function openValueProblem(field: OpenField, value: unknown): string | null;
84
+ /** True when a create leaves a required field out. */
85
+ export declare const missingRequired: (field: OpenField, value: unknown) => boolean;
86
+ /** What happened to one value when its field changed. */
87
+ export type Moved = {
88
+ outcome: 'kept' | 'converted';
89
+ value: unknown;
90
+ } | {
91
+ outcome: 'cleared';
92
+ };
93
+ /**
94
+ * One value moved from a field's old definition to its new one (P5 retype):
95
+ * kept when it is still right, converted when it can be read as the new type,
96
+ * else cleared. Never invents a choice.
97
+ */
98
+ export declare function moveValue(value: unknown, from: OpenField, to: OpenField): Moved;
99
+ /**
100
+ * What a field definition's change did to its table's rows: counts, never
101
+ * values. `rows` is how many held a value for the field. A definition's
102
+ * `update` or `delete` answer carries it as `fieldChange` when values moved.
103
+ */
104
+ export interface OpenFieldChange {
105
+ field: string;
106
+ op: 'retype' | 'choices' | 'delete';
107
+ rows: number;
108
+ kept: number;
109
+ converted: number;
110
+ cleared: number;
111
+ }
112
+ /** One value an open field may hold, as a row hands it out. */
113
+ export type OpenFieldValue = string | number | boolean | string[] | null;
114
+ /** Open types whose values are kept in the plain column: filtered, and all but the two lists sorted. */
115
+ export declare const isPlainOpenType: (type: OpenType) => boolean;
116
+ /**
117
+ * One companion record as a definition, or null when it cannot be one.
118
+ * `table` is the field naming its table, if the collection has tables.
119
+ */
120
+ export declare function definitionOf(id: string, body: Record<string, unknown>, table: string | null): OpenField | null;
121
+ /**
122
+ * The collection's spec with one table's live fields folded in: what a row
123
+ * is checked against and what a list filters and sorts on. A fixed field
124
+ * always wins over a definition of the same key.
125
+ */
126
+ export declare function openSpec(base: CollectionSpec, fields: readonly OpenField[]): CollectionSpec;
127
+ /** Why a definition's type and choices don't go together, or null. */
128
+ export declare function definitionTypeProblem(body: Record<string, unknown>): string | null;