@brydio/manifest 0.1.0-alpha.35 → 0.1.0-alpha.36
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/package.json +2 -2
- package/src/anonymous.d.ts +42 -0
- package/src/anonymous.js +86 -0
- package/src/index.d.ts +1 -0
- package/src/index.js +1 -0
- package/src/migrations.d.ts +1 -1
- package/src/migrations.js +29 -0
- package/src/schema.d.ts +24 -1
- package/src/schema.js +15 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@brydio/manifest",
|
|
3
|
-
"version": "0.1.0-alpha.
|
|
3
|
+
"version": "0.1.0-alpha.36",
|
|
4
4
|
"description": "The shape of a Brydio app's manifest, its collections' schemas, and the checks that say what is wrong with one.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
"url": "https://github.com/nakel-ola/brydio-sdk",
|
|
11
11
|
"directory": "packages/manifest"
|
|
12
12
|
},
|
|
13
|
-
"gitHead": "
|
|
13
|
+
"gitHead": "2d8e769d02c74fe201ce734f6d77def59ea99d53",
|
|
14
14
|
"exports": {
|
|
15
15
|
".": {
|
|
16
16
|
"types": "./src/index.d.ts",
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
/**
|
|
3
|
+
* An anonymous collection (P13): answers nobody can tie back to the person
|
|
4
|
+
* who gave them. Mirrors Brydio's `apps/api/src/apps/manifest/anonymous.ts`;
|
|
5
|
+
* the two change together.
|
|
6
|
+
*
|
|
7
|
+
* An app only declares it: which structured field groups the answers (a
|
|
8
|
+
* form, a survey round: a number, a choice, a date), and how many answers a
|
|
9
|
+
* group must hold before any of them is read. Brydio does the rest in the store, never the app: no writer kept
|
|
10
|
+
* on the record, times cut to the day, no live change told, no update, a
|
|
11
|
+
* create naming its own writer refused, and reads only of a whole group of
|
|
12
|
+
* at least `minimum` answers.
|
|
13
|
+
*/
|
|
14
|
+
export declare const ANONYMOUS_LIMITS: {
|
|
15
|
+
/** The fewest answers a group, or a narrowed read of one, may be read at. */
|
|
16
|
+
readonly minimum: 5;
|
|
17
|
+
/** The most an app may ask for. */
|
|
18
|
+
readonly maximum: 1000;
|
|
19
|
+
};
|
|
20
|
+
/** What the store writes as `created_by` on every anonymous record. */
|
|
21
|
+
export declare const ANONYMOUS_WRITER = "anonymous";
|
|
22
|
+
export declare const anonymousSchema: z.ZodObject<{
|
|
23
|
+
group: z.ZodString;
|
|
24
|
+
minimum: z.ZodOptional<z.ZodNumber>;
|
|
25
|
+
}, z.core.$strict>;
|
|
26
|
+
export type AnonymousDeclared = z.infer<typeof anonymousSchema>;
|
|
27
|
+
/** As the host reads it, on `CollectionSpec.anonymous`. */
|
|
28
|
+
export interface AnonymousSpec {
|
|
29
|
+
group: string;
|
|
30
|
+
minimum: number;
|
|
31
|
+
}
|
|
32
|
+
export type AnonymousProblemCode = 'data_anonymous_group' | 'data_anonymous_member' | 'data_anonymous_minimum' | 'data_anonymous_coedit';
|
|
33
|
+
export declare function anonymousProblems(collection: string, declared: {
|
|
34
|
+
schema: Record<string, unknown>;
|
|
35
|
+
anonymous?: AnonymousDeclared;
|
|
36
|
+
}): {
|
|
37
|
+
code: AnonymousProblemCode;
|
|
38
|
+
collection: string;
|
|
39
|
+
field?: string;
|
|
40
|
+
message: string;
|
|
41
|
+
}[];
|
|
42
|
+
export declare function anonymousSpec(declared: AnonymousDeclared): AnonymousSpec;
|
package/src/anonymous.js
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { FIELD_LIMITS, isStructured, parseFieldType, } from "./field-types.js";
|
|
3
|
+
/**
|
|
4
|
+
* An anonymous collection (P13): answers nobody can tie back to the person
|
|
5
|
+
* who gave them. Mirrors Brydio's `apps/api/src/apps/manifest/anonymous.ts`;
|
|
6
|
+
* the two change together.
|
|
7
|
+
*
|
|
8
|
+
* An app only declares it: which structured field groups the answers (a
|
|
9
|
+
* form, a survey round: a number, a choice, a date), and how many answers a
|
|
10
|
+
* group must hold before any of them is read. Brydio does the rest in the store, never the app: no writer kept
|
|
11
|
+
* on the record, times cut to the day, no live change told, no update, a
|
|
12
|
+
* create naming its own writer refused, and reads only of a whole group of
|
|
13
|
+
* at least `minimum` answers.
|
|
14
|
+
*/
|
|
15
|
+
export const ANONYMOUS_LIMITS = {
|
|
16
|
+
/** The fewest answers a group, or a narrowed read of one, may be read at. */
|
|
17
|
+
minimum: 5,
|
|
18
|
+
/** The most an app may ask for. */
|
|
19
|
+
maximum: 1000,
|
|
20
|
+
};
|
|
21
|
+
/** What the store writes as `created_by` on every anonymous record. */
|
|
22
|
+
export const ANONYMOUS_WRITER = 'anonymous';
|
|
23
|
+
export const anonymousSchema = z
|
|
24
|
+
.object({
|
|
25
|
+
/** A structured field of the same collection, never a member: which group an answer belongs to. */
|
|
26
|
+
group: z.string().min(1).max(FIELD_LIMITS.nameChars),
|
|
27
|
+
/** At least 5, the default. */
|
|
28
|
+
minimum: z.number().int().max(ANONYMOUS_LIMITS.maximum).optional(),
|
|
29
|
+
})
|
|
30
|
+
.strict();
|
|
31
|
+
export function anonymousProblems(collection, declared) {
|
|
32
|
+
const anonymous = declared.anonymous;
|
|
33
|
+
if (!anonymous)
|
|
34
|
+
return [];
|
|
35
|
+
const problems = [];
|
|
36
|
+
const types = new Map();
|
|
37
|
+
for (const [field, raw] of Object.entries(declared.schema)) {
|
|
38
|
+
try {
|
|
39
|
+
types.set(field, parseFieldType(raw));
|
|
40
|
+
}
|
|
41
|
+
catch {
|
|
42
|
+
// Refused as a field type already.
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
// Kept in plain, so a read can name its group; words never are.
|
|
46
|
+
const group = types.get(anonymous.group);
|
|
47
|
+
if (!group || !isStructured(group) || group.kind === 'member') {
|
|
48
|
+
problems.push({
|
|
49
|
+
code: 'data_anonymous_group',
|
|
50
|
+
collection,
|
|
51
|
+
field: anonymous.group,
|
|
52
|
+
message: `${collection}.anonymous.group must name one of its structured fields (a number, choice, date, boolean, token or project); ${anonymous.group} is not.`,
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
if (anonymous.minimum !== undefined && anonymous.minimum < ANONYMOUS_LIMITS.minimum) {
|
|
56
|
+
problems.push({
|
|
57
|
+
code: 'data_anonymous_minimum',
|
|
58
|
+
collection,
|
|
59
|
+
message: `${collection}.anonymous.minimum is at least ${ANONYMOUS_LIMITS.minimum}: fewer answers than that can be told apart.`,
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
for (const [field, type] of types) {
|
|
63
|
+
// A member field names a person on the record, which is what this is for not doing.
|
|
64
|
+
if (type.kind === 'member') {
|
|
65
|
+
problems.push({
|
|
66
|
+
code: 'data_anonymous_member',
|
|
67
|
+
collection,
|
|
68
|
+
field,
|
|
69
|
+
message: `${collection} is anonymous, so it can't name a member: ${field} would.`,
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
// Writing together shows who is typing, and changes a record after it is given.
|
|
73
|
+
if (type.coedit) {
|
|
74
|
+
problems.push({
|
|
75
|
+
code: 'data_anonymous_coedit',
|
|
76
|
+
collection,
|
|
77
|
+
field,
|
|
78
|
+
message: `${collection} is anonymous, so its answers can't be written together: ${field} would be.`,
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
return problems;
|
|
83
|
+
}
|
|
84
|
+
export function anonymousSpec(declared) {
|
|
85
|
+
return { group: declared.group, minimum: declared.minimum ?? ANONYMOUS_LIMITS.minimum };
|
|
86
|
+
}
|
package/src/index.d.ts
CHANGED
|
@@ -10,6 +10,7 @@ export { COLOUR_TOKENS, coeditedFields, FIELD_LIMITS, RESERVED_FIELDS, describeT
|
|
|
10
10
|
export { appManifestSchema, collectionOf, 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
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
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';
|
|
13
14
|
export { DOCUMENT_LIMITS } from './document-limits.js';
|
|
14
15
|
export { HOST_CAPABILITIES, KNOWN_HOST_GRANTS, isHostCapability, unknownHostGrant } from './grants.js';
|
|
15
16
|
export { diffSchemas, migrationProblems, migrationSchema, migrationStepSchema, migrationsSchema, publishedMigrationProblems, schemaOf, type Migration, type MigrationCode, type MigrationProblem, type MigrationStep, type SchemaChange, } from './migrations.js';
|
package/src/index.js
CHANGED
|
@@ -10,6 +10,7 @@ export { COLOUR_TOKENS, coeditedFields, FIELD_LIMITS, RESERVED_FIELDS, describeT
|
|
|
10
10
|
export { appManifestSchema, collectionOf, 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
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
12
|
export { CONFIRM_LIMITS, confirmEmailProblems, confirmEmailSpec, } from "./confirm-email.js";
|
|
13
|
+
export { ANONYMOUS_LIMITS, ANONYMOUS_WRITER, anonymousProblems, anonymousSpec, } from "./anonymous.js";
|
|
13
14
|
export { DOCUMENT_LIMITS } from "./document-limits.js";
|
|
14
15
|
export { HOST_CAPABILITIES, KNOWN_HOST_GRANTS, isHostCapability, unknownHostGrant } from "./grants.js";
|
|
15
16
|
export { diffSchemas, migrationProblems, migrationSchema, migrationStepSchema, migrationsSchema, publishedMigrationProblems, schemaOf, } from "./migrations.js";
|
package/src/migrations.d.ts
CHANGED
|
@@ -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. */
|
package/src/schema.d.ts
CHANGED
|
@@ -2,6 +2,7 @@ import { z } from 'zod';
|
|
|
2
2
|
import { FieldTypeInvalid, type FieldType } from './field-types.js';
|
|
3
3
|
import { type OpenProblemCode } from './open-schema.js';
|
|
4
4
|
import { type ConfirmProblemCode } from './confirm-email.js';
|
|
5
|
+
import { type AnonymousProblemCode, type AnonymousSpec } from './anonymous.js';
|
|
5
6
|
export { COLOUR_TOKENS, FIELD_LIMITS, parseFieldType, RESERVED_FIELDS, structuredFields, type FieldKind, type FieldType, } from './field-types.js';
|
|
6
7
|
/**
|
|
7
8
|
* What an app adds to `.brydio/app.json` to be more than a bundle of servers
|
|
@@ -157,6 +158,10 @@ declare const extensionShape: {
|
|
|
157
158
|
message: z.ZodOptional<z.ZodString>;
|
|
158
159
|
link: z.ZodOptional<z.ZodBoolean>;
|
|
159
160
|
}, z.core.$strict>>;
|
|
161
|
+
anonymous: z.ZodOptional<z.ZodObject<{
|
|
162
|
+
group: z.ZodString;
|
|
163
|
+
minimum: z.ZodOptional<z.ZodNumber>;
|
|
164
|
+
}, z.core.$strict>>;
|
|
160
165
|
}, z.core.$strip>>>;
|
|
161
166
|
tools: z.ZodOptional<z.ZodObject<{
|
|
162
167
|
generated: z.ZodOptional<z.ZodBoolean>;
|
|
@@ -235,7 +240,7 @@ export interface DataProblem {
|
|
|
235
240
|
field?: string;
|
|
236
241
|
message: string;
|
|
237
242
|
}
|
|
238
|
-
export type DataProblemCode = FieldTypeInvalid['code'] | OpenProblemCode | ConfirmProblemCode | 'data_too_many_collections' | 'data_too_many_fields' | 'data_collection_name_format' | 'data_field_name_format' | 'data_field_reserved' | 'data_label_format' | 'data_label_taken' | 'data_project_field_twice' | 'data_search_unknown_field' | 'data_search_not_text' | 'grant_collection_missing' | 'grant_tool_missing' | 'custom_name_taken' | 'custom_collection_unknown' | 'custom_input_invalid' | 'placement_key_taken' | 'placement_screen_unknown' | 'placement_home_sizes' | 'placement_sizes_not_home' | 'placement_widget_sizes' | 'placement_tab_retired' | 'placement_public_shape' | 'placement_public_nothing' | 'placement_children_too_deep' | 'placement_create_tool_unknown' | 'placement_create_not_write' | 'secret_name_taken' | 'grant_secrets_missing';
|
|
243
|
+
export type DataProblemCode = FieldTypeInvalid['code'] | OpenProblemCode | ConfirmProblemCode | AnonymousProblemCode | 'data_too_many_collections' | 'data_too_many_fields' | 'data_collection_name_format' | 'data_field_name_format' | 'data_field_reserved' | 'data_label_format' | 'data_label_taken' | 'data_project_field_twice' | 'data_search_unknown_field' | 'data_search_not_text' | 'grant_collection_missing' | 'grant_tool_missing' | 'custom_name_taken' | 'custom_collection_unknown' | 'custom_input_invalid' | 'placement_key_taken' | 'placement_screen_unknown' | 'placement_home_sizes' | 'placement_sizes_not_home' | 'placement_widget_sizes' | 'placement_tab_retired' | 'placement_public_shape' | 'placement_public_nothing' | 'placement_children_too_deep' | 'placement_create_tool_unknown' | 'placement_create_not_write' | 'secret_name_taken' | 'grant_secrets_missing';
|
|
239
244
|
type Additions = z.infer<z.ZodObject<typeof extensionShape>>;
|
|
240
245
|
/**
|
|
241
246
|
* Everything wrong with an app's additions that a type cannot say.
|
|
@@ -318,6 +323,10 @@ export declare const manifestExtensionsSchema: z.ZodObject<{
|
|
|
318
323
|
message: z.ZodOptional<z.ZodString>;
|
|
319
324
|
link: z.ZodOptional<z.ZodBoolean>;
|
|
320
325
|
}, z.core.$strict>>;
|
|
326
|
+
anonymous: z.ZodOptional<z.ZodObject<{
|
|
327
|
+
group: z.ZodString;
|
|
328
|
+
minimum: z.ZodOptional<z.ZodNumber>;
|
|
329
|
+
}, z.core.$strict>>;
|
|
321
330
|
}, z.core.$strip>>>;
|
|
322
331
|
tools: z.ZodOptional<z.ZodObject<{
|
|
323
332
|
generated: z.ZodOptional<z.ZodBoolean>;
|
|
@@ -449,6 +458,10 @@ export declare const storedExtensionsSchema: z.ZodObject<{
|
|
|
449
458
|
message: z.ZodOptional<z.ZodString>;
|
|
450
459
|
link: z.ZodOptional<z.ZodBoolean>;
|
|
451
460
|
}, z.core.$strict>>;
|
|
461
|
+
anonymous: z.ZodOptional<z.ZodObject<{
|
|
462
|
+
group: z.ZodString;
|
|
463
|
+
minimum: z.ZodOptional<z.ZodNumber>;
|
|
464
|
+
}, z.core.$strict>>;
|
|
452
465
|
}, z.core.$strip>>>;
|
|
453
466
|
tools: z.ZodOptional<z.ZodObject<{
|
|
454
467
|
generated: z.ZodOptional<z.ZodBoolean>;
|
|
@@ -615,6 +628,10 @@ export declare const appManifestSchema: z.ZodObject<{
|
|
|
615
628
|
message: z.ZodOptional<z.ZodString>;
|
|
616
629
|
link: z.ZodOptional<z.ZodBoolean>;
|
|
617
630
|
}, z.core.$strict>>;
|
|
631
|
+
anonymous: z.ZodOptional<z.ZodObject<{
|
|
632
|
+
group: z.ZodString;
|
|
633
|
+
minimum: z.ZodOptional<z.ZodNumber>;
|
|
634
|
+
}, z.core.$strict>>;
|
|
618
635
|
}, z.core.$strip>>>;
|
|
619
636
|
tools: z.ZodOptional<z.ZodObject<{
|
|
620
637
|
generated: z.ZodOptional<z.ZodBoolean>;
|
|
@@ -785,6 +802,10 @@ export declare const newAppManifestSchema: z.ZodObject<{
|
|
|
785
802
|
message: z.ZodOptional<z.ZodString>;
|
|
786
803
|
link: z.ZodOptional<z.ZodBoolean>;
|
|
787
804
|
}, z.core.$strict>>;
|
|
805
|
+
anonymous: z.ZodOptional<z.ZodObject<{
|
|
806
|
+
group: z.ZodString;
|
|
807
|
+
minimum: z.ZodOptional<z.ZodNumber>;
|
|
808
|
+
}, z.core.$strict>>;
|
|
788
809
|
}, z.core.$strip>>>;
|
|
789
810
|
tools: z.ZodOptional<z.ZodObject<{
|
|
790
811
|
generated: z.ZodOptional<z.ZodBoolean>;
|
|
@@ -875,6 +896,8 @@ export interface CollectionSpec {
|
|
|
875
896
|
};
|
|
876
897
|
/** On a companion: the open collection whose fields its records define (P5). */
|
|
877
898
|
definesFieldsOf?: string;
|
|
899
|
+
/** Answers kept with no writer, read only a whole group of at least `minimum` at a time (P13). */
|
|
900
|
+
anonymous?: AnonymousSpec;
|
|
878
901
|
}
|
|
879
902
|
/**
|
|
880
903
|
* The singular a collection's tools are named with: the manifest's, else the
|
package/src/schema.js
CHANGED
|
@@ -4,6 +4,7 @@ import { COLLECTION_NAME, FIELD_LIMITS, FIELD_NAME, FieldTypeInvalid, isSearchab
|
|
|
4
4
|
import { migrationsSchema } from "./migrations.js";
|
|
5
5
|
import { openSchemaProblems } from "./open-schema.js";
|
|
6
6
|
import { confirmEmailProblems, confirmEmailSchema } from "./confirm-email.js";
|
|
7
|
+
import { anonymousProblems, anonymousSchema, anonymousSpec } from "./anonymous.js";
|
|
7
8
|
export { COLOUR_TOKENS, FIELD_LIMITS, parseFieldType, RESERVED_FIELDS, structuredFields, } from "./field-types.js";
|
|
8
9
|
/**
|
|
9
10
|
* What an app adds to `.brydio/app.json` to be more than a bundle of servers
|
|
@@ -138,6 +139,15 @@ const collectionSchema = z.object({
|
|
|
138
139
|
* Needs `publicSubmit`. Brydio decides when, how often and from whom.
|
|
139
140
|
*/
|
|
140
141
|
confirmEmail: confirmEmailSchema.optional(),
|
|
142
|
+
/**
|
|
143
|
+
* Answers nobody can tie to who gave them (P13): `group` names a
|
|
144
|
+
* structured field (a number, choice, date, boolean, token or project)
|
|
145
|
+
* the answers are read by, a whole group at a time and only once it holds
|
|
146
|
+
* `minimum` answers (at least 5, the default). Brydio keeps no writer,
|
|
147
|
+
* cuts times to the day, tells no live change, and refuses an update, a
|
|
148
|
+
* batch, a watch and a create naming its own writer.
|
|
149
|
+
*/
|
|
150
|
+
anonymous: anonymousSchema.optional(),
|
|
141
151
|
});
|
|
142
152
|
const screenSchema = z.object({
|
|
143
153
|
/**
|
|
@@ -371,6 +381,10 @@ export function dataProblems(additions, options = {}) {
|
|
|
371
381
|
for (const [collection, declared] of collections) {
|
|
372
382
|
problems.push(...confirmEmailProblems(collection, declared));
|
|
373
383
|
}
|
|
384
|
+
// An anonymous collection groups its answers by one of its structured fields, names nobody (P13).
|
|
385
|
+
for (const [collection, declared] of collections) {
|
|
386
|
+
problems.push(...anonymousProblems(collection, declared));
|
|
387
|
+
}
|
|
374
388
|
// What the app keeps must be what it asks to keep (A3-F06-S01): a
|
|
375
389
|
// collection the grants leave out would be data a workspace never agreed to
|
|
376
390
|
// hold, found only when the first write is refused.
|
|
@@ -681,6 +695,7 @@ export function collectionsOf(manifest) {
|
|
|
681
695
|
? { openSchema: { fields: declared.openSchema.fields, table: declared.openSchema.table ?? null } }
|
|
682
696
|
: {}),
|
|
683
697
|
...(owner ? { definesFieldsOf: owner.name } : {}),
|
|
698
|
+
...(declared.anonymous ? { anonymous: anonymousSpec(declared.anonymous) } : {}),
|
|
684
699
|
};
|
|
685
700
|
});
|
|
686
701
|
}
|