@brydio/manifest 0.1.0-alpha.37 → 0.1.0-alpha.38

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@brydio/manifest",
3
- "version": "0.1.0-alpha.37",
3
+ "version": "0.1.0-alpha.38",
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": "c0364642e34a602b75477af366aa61582e5598f7",
13
+ "gitHead": "4b6c80dcb849195171699f16022aab1f55d6e3b6",
14
14
  "exports": {
15
15
  ".": {
16
16
  "types": "./src/index.d.ts",
package/src/index.d.ts CHANGED
@@ -11,6 +11,7 @@ export { appManifestSchema, appToolPrefix, collectionOf, crossAppTool, collectio
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
13
  export { ANONYMOUS_LIMITS, ANONYMOUS_WRITER, anonymousProblems, anonymousSpec, type AnonymousDeclared, type AnonymousProblemCode, type AnonymousSpec, } from './anonymous.js';
14
+ export { READERS_LIMIT, readersOf, readersProblems, type ReadersProblemCode } from './readers.js';
14
15
  export { DOCUMENT_LIMITS } from './document-limits.js';
15
16
  export { HOST_CAPABILITIES, KNOWN_HOST_GRANTS, isHostCapability, unknownHostGrant } from './grants.js';
16
17
  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
@@ -11,6 +11,7 @@ export { appManifestSchema, appToolPrefix, collectionOf, crossAppTool, collectio
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
13
  export { ANONYMOUS_LIMITS, ANONYMOUS_WRITER, anonymousProblems, anonymousSpec, } from "./anonymous.js";
14
+ export { READERS_LIMIT, readersOf, readersProblems } from "./readers.js";
14
15
  export { DOCUMENT_LIMITS } from "./document-limits.js";
15
16
  export { HOST_CAPABILITIES, KNOWN_HOST_GRANTS, isHostCapability, unknownHostGrant } from "./grants.js";
16
17
  export { diffSchemas, migrationProblems, migrationSchema, migrationStepSchema, migrationsSchema, publishedMigrationProblems, schemaOf, } from "./migrations.js";
@@ -0,0 +1,31 @@
1
+ import { z } from 'zod';
2
+ /**
3
+ * Record readers (P16): a record only the people it
4
+ * names may read.
5
+ *
6
+ * An app names one of the collection's own `string[]` fields, which holds
7
+ * user ids. When a record's list is empty, everyone who can read the
8
+ * instance reads it, as before; when it names anyone, nobody else finds it —
9
+ * not on a screen, not through a tool, not through the assistant, not on the
10
+ * co-edit socket. There is no admin past it: an app that wants its admins
11
+ * in puts them in the list. Brydio keeps the list in plain beside the record
12
+ * (`app_document.readers`), so every read can be narrowed before anything
13
+ * is decrypted. Mirrors Brydio's `apps/api/src/apps/manifest/readers.ts`;
14
+ * the two change together.
15
+ */
16
+ export declare const readersSchema: z.ZodString;
17
+ export type ReadersProblemCode = 'data_readers_field' | 'data_readers_anonymous';
18
+ /** The most people one record's list may name. */
19
+ export declare const READERS_LIMIT = 1000;
20
+ export declare function readersProblems(collection: string, declared: {
21
+ schema: Record<string, unknown>;
22
+ readers?: string;
23
+ anonymous?: unknown;
24
+ }): {
25
+ code: ReadersProblemCode;
26
+ collection: string;
27
+ field?: string;
28
+ message: string;
29
+ }[];
30
+ /** The plain list a record is kept with: its readers, or null for everyone who can read the instance. */
31
+ export declare function readersOf(field: string | undefined, body: Record<string, unknown>): string[] | null;
package/src/readers.js ADDED
@@ -0,0 +1,59 @@
1
+ import { z } from 'zod';
2
+ import { FIELD_LIMITS, parseFieldType } from "./field-types.js";
3
+ /**
4
+ * Record readers (P16): a record only the people it
5
+ * names may read.
6
+ *
7
+ * An app names one of the collection's own `string[]` fields, which holds
8
+ * user ids. When a record's list is empty, everyone who can read the
9
+ * instance reads it, as before; when it names anyone, nobody else finds it —
10
+ * not on a screen, not through a tool, not through the assistant, not on the
11
+ * co-edit socket. There is no admin past it: an app that wants its admins
12
+ * in puts them in the list. Brydio keeps the list in plain beside the record
13
+ * (`app_document.readers`), so every read can be narrowed before anything
14
+ * is decrypted. Mirrors Brydio's `apps/api/src/apps/manifest/readers.ts`;
15
+ * the two change together.
16
+ */
17
+ export const readersSchema = z.string().min(1).max(FIELD_LIMITS.nameChars);
18
+ /** The most people one record's list may name. */
19
+ export const READERS_LIMIT = 1_000;
20
+ export function readersProblems(collection, declared) {
21
+ if (!declared.readers)
22
+ return [];
23
+ const problems = [];
24
+ const raw = declared.schema[declared.readers];
25
+ let listed = false;
26
+ try {
27
+ listed = raw !== undefined && parseFieldType(raw).kind === 'string[]';
28
+ }
29
+ catch {
30
+ // Refused as a field type already.
31
+ }
32
+ if (!listed) {
33
+ problems.push({
34
+ code: 'data_readers_field',
35
+ collection,
36
+ field: declared.readers,
37
+ message: `"${collection}" names "${declared.readers}" as its readers, which must be one of its own string[] fields holding user ids.`,
38
+ });
39
+ }
40
+ // An anonymous answer names nobody; a list of who may read it would.
41
+ if (declared.anonymous) {
42
+ problems.push({
43
+ code: 'data_readers_anonymous',
44
+ collection,
45
+ message: `"${collection}" can't be anonymous and name its readers.`,
46
+ });
47
+ }
48
+ return problems;
49
+ }
50
+ /** The plain list a record is kept with: its readers, or null for everyone who can read the instance. */
51
+ export function readersOf(field, body) {
52
+ if (!field)
53
+ return null;
54
+ const value = body[field];
55
+ if (!Array.isArray(value))
56
+ return null;
57
+ const ids = [...new Set(value.filter((one) => typeof one === 'string' && one.length > 0))];
58
+ return ids.length ? ids.sort() : null;
59
+ }
package/src/schema.d.ts CHANGED
@@ -3,6 +3,7 @@ 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
5
  import { type AnonymousProblemCode, type AnonymousSpec } from './anonymous.js';
6
+ import { type ReadersProblemCode } from './readers.js';
6
7
  export { COLOUR_TOKENS, FIELD_LIMITS, parseFieldType, RESERVED_FIELDS, structuredFields, type FieldKind, type FieldType, } from './field-types.js';
7
8
  /**
8
9
  * What an app adds to `.brydio/app.json` to be more than a bundle of servers
@@ -163,6 +164,7 @@ declare const extensionShape: {
163
164
  group: z.ZodString;
164
165
  minimum: z.ZodOptional<z.ZodNumber>;
165
166
  }, z.core.$strict>>;
167
+ readers: z.ZodOptional<z.ZodString>;
166
168
  }, z.core.$strip>>>;
167
169
  tools: z.ZodOptional<z.ZodObject<{
168
170
  generated: z.ZodOptional<z.ZodBoolean>;
@@ -241,7 +243,7 @@ export interface DataProblem {
241
243
  field?: string;
242
244
  message: string;
243
245
  }
244
- 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_chat_card_shape' | 'grant_tool_cross_app_self' | 'placement_children_too_deep' | 'placement_create_tool_unknown' | 'placement_create_not_write' | 'secret_name_taken' | 'grant_secrets_missing';
246
+ export type DataProblemCode = FieldTypeInvalid['code'] | OpenProblemCode | ConfirmProblemCode | AnonymousProblemCode | ReadersProblemCode | '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_chat_card_shape' | 'grant_tool_cross_app_self' | 'placement_children_too_deep' | 'placement_create_tool_unknown' | 'placement_create_not_write' | 'secret_name_taken' | 'grant_secrets_missing';
245
247
  type Additions = z.infer<z.ZodObject<typeof extensionShape>>;
246
248
  /**
247
249
  * Everything wrong with an app's additions that a type cannot say.
@@ -340,6 +342,7 @@ export declare const manifestExtensionsSchema: z.ZodObject<{
340
342
  group: z.ZodString;
341
343
  minimum: z.ZodOptional<z.ZodNumber>;
342
344
  }, z.core.$strict>>;
345
+ readers: z.ZodOptional<z.ZodString>;
343
346
  }, z.core.$strip>>>;
344
347
  tools: z.ZodOptional<z.ZodObject<{
345
348
  generated: z.ZodOptional<z.ZodBoolean>;
@@ -476,6 +479,7 @@ export declare const storedExtensionsSchema: z.ZodObject<{
476
479
  group: z.ZodString;
477
480
  minimum: z.ZodOptional<z.ZodNumber>;
478
481
  }, z.core.$strict>>;
482
+ readers: z.ZodOptional<z.ZodString>;
479
483
  }, z.core.$strip>>>;
480
484
  tools: z.ZodOptional<z.ZodObject<{
481
485
  generated: z.ZodOptional<z.ZodBoolean>;
@@ -647,6 +651,7 @@ export declare const appManifestSchema: z.ZodObject<{
647
651
  group: z.ZodString;
648
652
  minimum: z.ZodOptional<z.ZodNumber>;
649
653
  }, z.core.$strict>>;
654
+ readers: z.ZodOptional<z.ZodString>;
650
655
  }, z.core.$strip>>>;
651
656
  tools: z.ZodOptional<z.ZodObject<{
652
657
  generated: z.ZodOptional<z.ZodBoolean>;
@@ -822,6 +827,7 @@ export declare const newAppManifestSchema: z.ZodObject<{
822
827
  group: z.ZodString;
823
828
  minimum: z.ZodOptional<z.ZodNumber>;
824
829
  }, z.core.$strict>>;
830
+ readers: z.ZodOptional<z.ZodString>;
825
831
  }, z.core.$strip>>>;
826
832
  tools: z.ZodOptional<z.ZodObject<{
827
833
  generated: z.ZodOptional<z.ZodBoolean>;
@@ -914,6 +920,8 @@ export interface CollectionSpec {
914
920
  definesFieldsOf?: string;
915
921
  /** Answers kept with no writer, read only a whole group of at least `minimum` at a time (P13). */
916
922
  anonymous?: AnonymousSpec;
923
+ /** The string[] field naming who alone may read a record, when it names anyone (P16). */
924
+ readers?: string;
917
925
  }
918
926
  /**
919
927
  * The singular a collection's tools are named with: the manifest's, else the
package/src/schema.js CHANGED
@@ -5,6 +5,7 @@ import { migrationsSchema } from "./migrations.js";
5
5
  import { openSchemaProblems } from "./open-schema.js";
6
6
  import { confirmEmailProblems, confirmEmailSchema } from "./confirm-email.js";
7
7
  import { anonymousProblems, anonymousSchema, anonymousSpec } from "./anonymous.js";
8
+ import { readersProblems, readersSchema } from "./readers.js";
8
9
  export { COLOUR_TOKENS, FIELD_LIMITS, parseFieldType, RESERVED_FIELDS, structuredFields, } from "./field-types.js";
9
10
  /**
10
11
  * What an app adds to `.brydio/app.json` to be more than a bundle of servers
@@ -152,6 +153,8 @@ const collectionSchema = z.object({
152
153
  * batch, a watch and a create naming its own writer.
153
154
  */
154
155
  anonymous: anonymousSchema.optional(),
156
+ /** One of its string[] fields: the user ids who alone may read a record, when it names any (P16). */
157
+ readers: readersSchema.optional(),
155
158
  });
156
159
  const screenSchema = z.object({
157
160
  /**
@@ -389,6 +392,10 @@ export function dataProblems(additions, options = {}) {
389
392
  for (const [collection, declared] of collections) {
390
393
  problems.push(...anonymousProblems(collection, declared));
391
394
  }
395
+ // A collection whose records name their readers names a list of user ids to keep them in (P16).
396
+ for (const [collection, declared] of collections) {
397
+ problems.push(...readersProblems(collection, declared));
398
+ }
392
399
  // What the app keeps must be what it asks to keep (A3-F06-S01): a
393
400
  // collection the grants leave out would be data a workspace never agreed to
394
401
  // hold, found only when the first write is refused.
@@ -738,6 +745,7 @@ export function collectionsOf(manifest) {
738
745
  : {}),
739
746
  ...(owner ? { definesFieldsOf: owner.name } : {}),
740
747
  ...(declared.anonymous ? { anonymous: anonymousSpec(declared.anonymous) } : {}),
748
+ ...(declared.readers ? { readers: declared.readers } : {}),
741
749
  };
742
750
  });
743
751
  }