@segmentstream-cloud/ontology 0.6.0 → 0.7.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.
Files changed (43) hide show
  1. package/README.md +14 -3
  2. package/dist/analyze.js +46 -13
  3. package/dist/connection.js +1 -1
  4. package/dist/entity.d.ts +6 -6
  5. package/dist/index.d.ts +1 -1
  6. package/dist/index.js +1 -1
  7. package/dist/manifest.d.ts +468 -10
  8. package/dist/manifest.js +46 -6
  9. package/dist/migrate/apply.d.ts +1 -1
  10. package/dist/migrate/apply.js +5 -4
  11. package/dist/migrate/model.d.ts +112 -2
  12. package/dist/migrate/plan.js +7 -6
  13. package/dist/migrate/storage.d.ts +6 -6
  14. package/dist/migrate/storage.js +25 -15
  15. package/dist/names.d.ts +7 -10
  16. package/dist/names.js +10 -13
  17. package/dist/permissions.d.ts +2 -2
  18. package/dist/permissions.js +4 -4
  19. package/dist/query.d.ts +6 -6
  20. package/dist/query.js +41 -30
  21. package/dist/runtime/cursor.js +1 -1
  22. package/dist/runtime/generated-actions.d.ts +4 -4
  23. package/dist/runtime/generated-actions.js +4 -7
  24. package/dist/runtime/history.js +1 -1
  25. package/dist/runtime/model.d.ts +2 -2
  26. package/dist/runtime/model.js +1 -1
  27. package/dist/runtime/principals.js +15 -12
  28. package/dist/runtime/reads.js +1 -1
  29. package/dist/runtime/records.js +1 -1
  30. package/dist/runtime/sql-runtime.js +1 -1
  31. package/dist/service/errors.d.ts +1 -0
  32. package/dist/service/errors.js +5 -1
  33. package/dist/service/handlers.js +2 -2
  34. package/dist/service/runtime-contract.d.ts +1 -1
  35. package/dist/service/schemas.d.ts +201 -26
  36. package/dist/service/schemas.js +30 -21
  37. package/dist/service/service.js +2 -2
  38. package/dist/user.d.ts +3 -3
  39. package/dist/user.js +7 -4
  40. package/dist/warehouse-backed.js +3 -3
  41. package/dist/wire.d.ts +5 -0
  42. package/dist/wire.js +21 -0
  43. package/package.json +1 -1
@@ -1,4 +1,4 @@
1
- import { Schema } from "effect";
1
+ import { Effect, Schema } from "effect";
2
2
  import type { ResolvedConnection, ResolvedEntity } from "./analyze.js";
3
3
  import type { Role } from "./role.js";
4
4
  import { type SignInOptions, type SignInPolicy } from "./sign-in.js";
@@ -9,7 +9,7 @@ import { type SignInOptions, type SignInPolicy } from "./sign-in.js";
9
9
  * `signIn` came later within version 2 and is optional too, as are the actions' `connections` and the
10
10
  * top-level `connections`, which are written only when an action calls a connection.
11
11
  */
12
- export declare const MANIFEST_VERSION = 3;
12
+ export declare const MANIFEST_VERSION = 4;
13
13
  /** The phrase used for a direction the author did not phrase. */
14
14
  export declare const DEFAULT_PHRASE = "has";
15
15
  export declare const SelectOptionManifest: Schema.Struct<{
@@ -88,7 +88,7 @@ export type ActionManifest = typeof ActionManifest.Type;
88
88
  /**
89
89
  * The permissions of an entity's reads and generated actions, so that readers need not rebuild the
90
90
  * convention. The built-in user has only `read`; `link` and `unlink` exist with a `rel.many`. The
91
- * generated `upsertBy<Property>` requires both `create` and `update`.
91
+ * generated `upsert_by_<property>` requires both `create` and `update`.
92
92
  */
93
93
  export declare const EntityPermissionsManifest: Schema.Struct<{
94
94
  readonly read: Schema.String;
@@ -220,12 +220,270 @@ export declare const SignInManifest: Schema.Struct<{
220
220
  readonly policy: Schema.String;
221
221
  }>;
222
222
  export type SignInManifest = typeof SignInManifest.Type;
223
- /**
224
- * The ontology catalog: the contract between the SDK, backend-next, the console and the CLI. Decoding
225
- * ignores unknown fields and accepts any `version`. The closed sets (property types, cardinalities,
226
- * `onDelete`) are validated because the migration planner must map every value to storage.
227
- */
228
- export declare const OntologyManifest: Schema.Struct<{
223
+ /** The JSON contract written by version 4; decoded SDK models remain camelCase. */
224
+ export declare const OntologyManifestWire: Schema.encodeKeys<Schema.Struct<{
225
+ readonly entities: Schema.$Array<Schema.encodeKeys<Schema.Struct<{
226
+ readonly properties: Schema.$Array<Schema.encodeKeys<Schema.Struct<{
227
+ readonly column: Schema.optionalKey<Schema.String>;
228
+ readonly scope: Schema.optionalKey<Schema.$Array<Schema.String>>;
229
+ readonly name: Schema.String;
230
+ readonly type: Schema.Literals<readonly ["text", "email", "url", "integer", "number", "boolean", "date", "timestamp", "select", "json"]>;
231
+ readonly label: Schema.String;
232
+ readonly description: Schema.optionalKey<Schema.String>;
233
+ readonly optional: Schema.Boolean;
234
+ readonly unique: Schema.Boolean;
235
+ readonly managed: Schema.Boolean;
236
+ readonly default: Schema.optionalKey<Schema.Codec<Schema.Json, Schema.Json, never, never>>;
237
+ readonly display: Schema.optionalKey<Schema.String>;
238
+ readonly renamedFrom: Schema.optionalKey<Schema.String>;
239
+ readonly min: Schema.optionalKey<Schema.Number>;
240
+ readonly max: Schema.optionalKey<Schema.Number>;
241
+ readonly maxLength: Schema.optionalKey<Schema.Number>;
242
+ readonly options: Schema.optionalKey<Schema.$Array<Schema.Struct<{
243
+ readonly value: Schema.String;
244
+ readonly label: Schema.String;
245
+ readonly description: Schema.String;
246
+ }>>>;
247
+ }>, {
248
+ readonly renamedFrom: "renamed_from";
249
+ readonly maxLength: "max_length";
250
+ }>>;
251
+ readonly relationships: Schema.$Array<Schema.encodeKeys<Schema.Struct<{
252
+ readonly on: Schema.optionalKey<Schema.encodeKeys<Schema.Struct<{
253
+ readonly property: Schema.String;
254
+ readonly targetProperty: Schema.String;
255
+ }>, {
256
+ readonly targetProperty: "target_property";
257
+ }>>;
258
+ readonly onDelete: Schema.optionalKey<Schema.decodeTo<Schema.Literals<readonly ["setNull", "cascade", "restrict"]>, Schema.Literals<readonly ["set_null", "cascade", "restrict"]>, never, never>>;
259
+ readonly name: Schema.String;
260
+ readonly to: Schema.String;
261
+ readonly inverse: Schema.String;
262
+ readonly cardinality: Schema.Literals<readonly ["one", "many"]>;
263
+ /** `rel.one` only. */
264
+ readonly optional: Schema.optionalKey<Schema.Boolean>;
265
+ /** `rel.many` only. */
266
+ readonly ordered: Schema.optionalKey<Schema.Boolean>;
267
+ /** Forward and inverse phrases; unphrased directions read "has". */
268
+ readonly phrase: Schema.Tuple<readonly [Schema.String, Schema.String]>;
269
+ readonly description: Schema.String;
270
+ }>, {
271
+ readonly onDelete: "on_delete";
272
+ }>>;
273
+ readonly source: Schema.optionalKey<Schema.Struct<{
274
+ readonly asset: Schema.String;
275
+ readonly key: Schema.$Array<Schema.String>;
276
+ readonly currency: Schema.optionalKey<Schema.String>;
277
+ }>>;
278
+ readonly measures: Schema.optionalKey<Schema.$Array<Schema.Struct<{
279
+ readonly name: Schema.String;
280
+ readonly label: Schema.String;
281
+ readonly description: Schema.String;
282
+ readonly unit: Schema.Literals<readonly ["number", "currency", "ratio", "percent"]>;
283
+ readonly format: Schema.Literals<readonly ["decimal", "currency", "percent"]>;
284
+ readonly expression: Schema.Codec<Schema.Json, Schema.Json, never, never>;
285
+ }>>>;
286
+ readonly id: Schema.String;
287
+ readonly name: Schema.String;
288
+ readonly label: Schema.String;
289
+ readonly plural: Schema.String;
290
+ readonly title: Schema.String;
291
+ readonly description: Schema.String;
292
+ readonly builtIn: Schema.Boolean;
293
+ readonly renamedFrom: Schema.optionalKey<Schema.String>;
294
+ readonly guide: Schema.optionalKey<Schema.String>;
295
+ readonly indexes: Schema.$Array<Schema.Struct<{
296
+ readonly properties: Schema.$Array<Schema.String>;
297
+ readonly unique: Schema.Boolean;
298
+ }>>;
299
+ readonly actions: Schema.$Array<Schema.Struct<{
300
+ readonly name: Schema.String;
301
+ readonly description: Schema.String;
302
+ /**
303
+ * The permission a principal needs to run the action, for example `lead:changeStatus`. Always
304
+ * written; absent from version 1 manifests, where it reads as `<entity>:<action name>`.
305
+ */
306
+ readonly permission: Schema.optionalKey<Schema.String>;
307
+ /** JSON Schema (draft 2020-12) of the input object. */
308
+ readonly input: Schema.$Record<Schema.String, Schema.Codec<Schema.Json, Schema.Json, never, never>>;
309
+ /**
310
+ * The keys of the workspace connections the action calls before its transaction. Written only
311
+ * when there are some; absent reads as none.
312
+ */
313
+ readonly connections: Schema.optionalKey<Schema.$Array<Schema.String>>;
314
+ }>>;
315
+ /** Always written; absent from version 1 manifests. */
316
+ readonly permissions: Schema.optionalKey<Schema.Struct<{
317
+ readonly read: Schema.String;
318
+ readonly create: Schema.optionalKey<Schema.String>;
319
+ readonly update: Schema.optionalKey<Schema.String>;
320
+ readonly delete: Schema.optionalKey<Schema.String>;
321
+ readonly link: Schema.optionalKey<Schema.String>;
322
+ readonly unlink: Schema.optionalKey<Schema.String>;
323
+ }>>;
324
+ }>, {
325
+ readonly builtIn: "built_in";
326
+ readonly renamedFrom: "renamed_from";
327
+ }>>;
328
+ readonly roles: Schema.$Array<Schema.encodeKeys<Schema.Struct<{
329
+ readonly name: Schema.String;
330
+ readonly description: Schema.String;
331
+ readonly builtIn: Schema.Boolean;
332
+ /**
333
+ * What the role may do, wildcards included; for the built-in `member`, what every active app user
334
+ * may do. Always written; absent from version 1 manifests.
335
+ */
336
+ readonly permissions: Schema.optionalKey<Schema.$Array<Schema.String>>;
337
+ }>, {
338
+ readonly builtIn: "built_in";
339
+ }>>;
340
+ readonly version: Schema.Int;
341
+ /** `sha256:` and the hex digest of the canonical JSON of the manifest without `hash`. */
342
+ readonly hash: Schema.String;
343
+ /** Always written; absent from manifests built before sign-in policies, which read as `invited`. */
344
+ readonly signIn: Schema.optionalKey<Schema.Struct<{
345
+ readonly policy: Schema.String;
346
+ }>>;
347
+ /** The connections the actions call. Written only when there are some; absent reads as none. */
348
+ readonly connections: Schema.optionalKey<Schema.$Array<Schema.Struct<{
349
+ readonly key: Schema.String;
350
+ readonly description: Schema.String;
351
+ }>>>;
352
+ }>, {
353
+ readonly signIn: "sign_in";
354
+ }>;
355
+ /** Historical readers accept either metadata spelling and any manifest version. */
356
+ export declare const OntologyManifest: Schema.Union<readonly [Schema.encodeKeys<Schema.Struct<{
357
+ readonly entities: Schema.$Array<Schema.encodeKeys<Schema.Struct<{
358
+ readonly properties: Schema.$Array<Schema.encodeKeys<Schema.Struct<{
359
+ readonly column: Schema.optionalKey<Schema.String>;
360
+ readonly scope: Schema.optionalKey<Schema.$Array<Schema.String>>;
361
+ readonly name: Schema.String;
362
+ readonly type: Schema.Literals<readonly ["text", "email", "url", "integer", "number", "boolean", "date", "timestamp", "select", "json"]>;
363
+ readonly label: Schema.String;
364
+ readonly description: Schema.optionalKey<Schema.String>;
365
+ readonly optional: Schema.Boolean;
366
+ readonly unique: Schema.Boolean;
367
+ readonly managed: Schema.Boolean;
368
+ readonly default: Schema.optionalKey<Schema.Codec<Schema.Json, Schema.Json, never, never>>;
369
+ readonly display: Schema.optionalKey<Schema.String>;
370
+ readonly renamedFrom: Schema.optionalKey<Schema.String>;
371
+ readonly min: Schema.optionalKey<Schema.Number>;
372
+ readonly max: Schema.optionalKey<Schema.Number>;
373
+ readonly maxLength: Schema.optionalKey<Schema.Number>;
374
+ readonly options: Schema.optionalKey<Schema.$Array<Schema.Struct<{
375
+ readonly value: Schema.String;
376
+ readonly label: Schema.String;
377
+ readonly description: Schema.String;
378
+ }>>>;
379
+ }>, {
380
+ readonly renamedFrom: "renamed_from";
381
+ readonly maxLength: "max_length";
382
+ }>>;
383
+ readonly relationships: Schema.$Array<Schema.encodeKeys<Schema.Struct<{
384
+ readonly on: Schema.optionalKey<Schema.encodeKeys<Schema.Struct<{
385
+ readonly property: Schema.String;
386
+ readonly targetProperty: Schema.String;
387
+ }>, {
388
+ readonly targetProperty: "target_property";
389
+ }>>;
390
+ readonly onDelete: Schema.optionalKey<Schema.decodeTo<Schema.Literals<readonly ["setNull", "cascade", "restrict"]>, Schema.Literals<readonly ["set_null", "cascade", "restrict"]>, never, never>>;
391
+ readonly name: Schema.String;
392
+ readonly to: Schema.String;
393
+ readonly inverse: Schema.String;
394
+ readonly cardinality: Schema.Literals<readonly ["one", "many"]>;
395
+ /** `rel.one` only. */
396
+ readonly optional: Schema.optionalKey<Schema.Boolean>;
397
+ /** `rel.many` only. */
398
+ readonly ordered: Schema.optionalKey<Schema.Boolean>;
399
+ /** Forward and inverse phrases; unphrased directions read "has". */
400
+ readonly phrase: Schema.Tuple<readonly [Schema.String, Schema.String]>;
401
+ readonly description: Schema.String;
402
+ }>, {
403
+ readonly onDelete: "on_delete";
404
+ }>>;
405
+ readonly source: Schema.optionalKey<Schema.Struct<{
406
+ readonly asset: Schema.String;
407
+ readonly key: Schema.$Array<Schema.String>;
408
+ readonly currency: Schema.optionalKey<Schema.String>;
409
+ }>>;
410
+ readonly measures: Schema.optionalKey<Schema.$Array<Schema.Struct<{
411
+ readonly name: Schema.String;
412
+ readonly label: Schema.String;
413
+ readonly description: Schema.String;
414
+ readonly unit: Schema.Literals<readonly ["number", "currency", "ratio", "percent"]>;
415
+ readonly format: Schema.Literals<readonly ["decimal", "currency", "percent"]>;
416
+ readonly expression: Schema.Codec<Schema.Json, Schema.Json, never, never>;
417
+ }>>>;
418
+ readonly id: Schema.String;
419
+ readonly name: Schema.String;
420
+ readonly label: Schema.String;
421
+ readonly plural: Schema.String;
422
+ readonly title: Schema.String;
423
+ readonly description: Schema.String;
424
+ readonly builtIn: Schema.Boolean;
425
+ readonly renamedFrom: Schema.optionalKey<Schema.String>;
426
+ readonly guide: Schema.optionalKey<Schema.String>;
427
+ readonly indexes: Schema.$Array<Schema.Struct<{
428
+ readonly properties: Schema.$Array<Schema.String>;
429
+ readonly unique: Schema.Boolean;
430
+ }>>;
431
+ readonly actions: Schema.$Array<Schema.Struct<{
432
+ readonly name: Schema.String;
433
+ readonly description: Schema.String;
434
+ /**
435
+ * The permission a principal needs to run the action, for example `lead:changeStatus`. Always
436
+ * written; absent from version 1 manifests, where it reads as `<entity>:<action name>`.
437
+ */
438
+ readonly permission: Schema.optionalKey<Schema.String>;
439
+ /** JSON Schema (draft 2020-12) of the input object. */
440
+ readonly input: Schema.$Record<Schema.String, Schema.Codec<Schema.Json, Schema.Json, never, never>>;
441
+ /**
442
+ * The keys of the workspace connections the action calls before its transaction. Written only
443
+ * when there are some; absent reads as none.
444
+ */
445
+ readonly connections: Schema.optionalKey<Schema.$Array<Schema.String>>;
446
+ }>>;
447
+ /** Always written; absent from version 1 manifests. */
448
+ readonly permissions: Schema.optionalKey<Schema.Struct<{
449
+ readonly read: Schema.String;
450
+ readonly create: Schema.optionalKey<Schema.String>;
451
+ readonly update: Schema.optionalKey<Schema.String>;
452
+ readonly delete: Schema.optionalKey<Schema.String>;
453
+ readonly link: Schema.optionalKey<Schema.String>;
454
+ readonly unlink: Schema.optionalKey<Schema.String>;
455
+ }>>;
456
+ }>, {
457
+ readonly builtIn: "built_in";
458
+ readonly renamedFrom: "renamed_from";
459
+ }>>;
460
+ readonly roles: Schema.$Array<Schema.encodeKeys<Schema.Struct<{
461
+ readonly name: Schema.String;
462
+ readonly description: Schema.String;
463
+ readonly builtIn: Schema.Boolean;
464
+ /**
465
+ * What the role may do, wildcards included; for the built-in `member`, what every active app user
466
+ * may do. Always written; absent from version 1 manifests.
467
+ */
468
+ readonly permissions: Schema.optionalKey<Schema.$Array<Schema.String>>;
469
+ }>, {
470
+ readonly builtIn: "built_in";
471
+ }>>;
472
+ readonly version: Schema.Int;
473
+ /** `sha256:` and the hex digest of the canonical JSON of the manifest without `hash`. */
474
+ readonly hash: Schema.String;
475
+ /** Always written; absent from manifests built before sign-in policies, which read as `invited`. */
476
+ readonly signIn: Schema.optionalKey<Schema.Struct<{
477
+ readonly policy: Schema.String;
478
+ }>>;
479
+ /** The connections the actions call. Written only when there are some; absent reads as none. */
480
+ readonly connections: Schema.optionalKey<Schema.$Array<Schema.Struct<{
481
+ readonly key: Schema.String;
482
+ readonly description: Schema.String;
483
+ }>>>;
484
+ }>, {
485
+ readonly signIn: "sign_in";
486
+ }>, Schema.Struct<{
229
487
  readonly version: Schema.Int;
230
488
  /** `sha256:` and the hex digest of the canonical JSON of the manifest without `hash`. */
231
489
  readonly hash: Schema.String;
@@ -342,8 +600,208 @@ export declare const OntologyManifest: Schema.Struct<{
342
600
  readonly key: Schema.String;
343
601
  readonly description: Schema.String;
344
602
  }>>>;
345
- }>;
603
+ }>]>;
346
604
  export type OntologyManifest = typeof OntologyManifest.Type;
605
+ /** Retain the complete document when decoding a persisted or published manifest. */
606
+ export declare const decodeManifestDocument: (value: unknown) => Effect.Effect<{
607
+ readonly entities: readonly {
608
+ readonly name: string;
609
+ readonly id: string;
610
+ readonly description: string;
611
+ readonly label: string;
612
+ readonly actions: readonly {
613
+ readonly name: string;
614
+ readonly description: string;
615
+ readonly input: {
616
+ readonly [x: string]: Schema.Json;
617
+ };
618
+ readonly permission?: string | undefined;
619
+ readonly connections?: readonly string[] | undefined;
620
+ }[];
621
+ readonly title: string;
622
+ readonly properties: readonly {
623
+ readonly optional: boolean;
624
+ readonly name: string;
625
+ readonly label: string;
626
+ readonly type: "number" | "boolean" | "text" | "integer" | "date" | "timestamp" | "select" | "email" | "url" | "json";
627
+ readonly unique: boolean;
628
+ readonly managed: boolean;
629
+ readonly column?: string | undefined;
630
+ readonly description?: string | undefined;
631
+ readonly default?: Schema.Json | undefined;
632
+ readonly scope?: readonly string[] | undefined;
633
+ readonly maxLength?: number | undefined;
634
+ readonly min?: number | undefined;
635
+ readonly max?: number | undefined;
636
+ readonly options?: readonly {
637
+ readonly value: string;
638
+ readonly label: string;
639
+ readonly description: string;
640
+ }[] | undefined;
641
+ readonly renamedFrom?: string | undefined;
642
+ readonly display?: string | undefined;
643
+ }[];
644
+ readonly relationships: readonly {
645
+ readonly name: string;
646
+ readonly description: string;
647
+ readonly phrase: readonly [string, string];
648
+ readonly cardinality: "one" | "many";
649
+ readonly to: string;
650
+ readonly inverse: string;
651
+ readonly optional?: boolean | undefined;
652
+ readonly on?: {
653
+ readonly property: string;
654
+ readonly targetProperty: string;
655
+ } | undefined;
656
+ readonly onDelete?: "setNull" | "cascade" | "restrict" | undefined;
657
+ readonly ordered?: boolean | undefined;
658
+ }[];
659
+ readonly indexes: readonly {
660
+ readonly properties: readonly string[];
661
+ readonly unique: boolean;
662
+ }[];
663
+ readonly plural: string;
664
+ readonly builtIn: boolean;
665
+ readonly source?: {
666
+ readonly key: readonly string[];
667
+ readonly asset: string;
668
+ readonly currency?: string | undefined;
669
+ } | undefined;
670
+ readonly measures?: readonly {
671
+ readonly name: string;
672
+ readonly label: string;
673
+ readonly description: string;
674
+ readonly unit: "number" | "currency" | "ratio" | "percent";
675
+ readonly format: "currency" | "percent" | "decimal";
676
+ readonly expression: Schema.Json;
677
+ }[] | undefined;
678
+ readonly renamedFrom?: string | undefined;
679
+ readonly guide?: string | undefined;
680
+ readonly permissions?: {
681
+ readonly read: string;
682
+ readonly create?: string | undefined;
683
+ readonly update?: string | undefined;
684
+ readonly delete?: string | undefined;
685
+ readonly link?: string | undefined;
686
+ readonly unlink?: string | undefined;
687
+ } | undefined;
688
+ }[];
689
+ readonly roles: readonly {
690
+ readonly name: string;
691
+ readonly description: string;
692
+ readonly builtIn: boolean;
693
+ readonly permissions?: readonly string[] | undefined;
694
+ }[];
695
+ readonly version: number;
696
+ readonly hash: string;
697
+ readonly connections?: readonly {
698
+ readonly key: string;
699
+ readonly description: string;
700
+ }[] | undefined;
701
+ readonly signIn?: {
702
+ readonly policy: string;
703
+ } | undefined;
704
+ } | {
705
+ readonly entities: readonly {
706
+ readonly name: string;
707
+ readonly id: string;
708
+ readonly description: string;
709
+ readonly label: string;
710
+ readonly actions: readonly {
711
+ readonly name: string;
712
+ readonly description: string;
713
+ readonly input: {
714
+ readonly [x: string]: Schema.Json;
715
+ };
716
+ readonly permission?: string | undefined;
717
+ readonly connections?: readonly string[] | undefined;
718
+ }[];
719
+ readonly title: string;
720
+ readonly properties: readonly {
721
+ readonly optional: boolean;
722
+ readonly name: string;
723
+ readonly label: string;
724
+ readonly type: "number" | "boolean" | "text" | "integer" | "date" | "timestamp" | "select" | "email" | "url" | "json";
725
+ readonly unique: boolean;
726
+ readonly managed: boolean;
727
+ readonly column?: string | undefined;
728
+ readonly description?: string | undefined;
729
+ readonly default?: Schema.Json | undefined;
730
+ readonly scope?: readonly string[] | undefined;
731
+ readonly maxLength?: number | undefined;
732
+ readonly min?: number | undefined;
733
+ readonly max?: number | undefined;
734
+ readonly options?: readonly {
735
+ readonly value: string;
736
+ readonly label: string;
737
+ readonly description: string;
738
+ }[] | undefined;
739
+ readonly renamedFrom?: string | undefined;
740
+ readonly display?: string | undefined;
741
+ }[];
742
+ readonly relationships: readonly {
743
+ readonly name: string;
744
+ readonly description: string;
745
+ readonly phrase: readonly [string, string];
746
+ readonly cardinality: "one" | "many";
747
+ readonly to: string;
748
+ readonly inverse: string;
749
+ readonly optional?: boolean | undefined;
750
+ readonly on?: {
751
+ readonly property: string;
752
+ readonly targetProperty: string;
753
+ } | undefined;
754
+ readonly onDelete?: "setNull" | "cascade" | "restrict" | undefined;
755
+ readonly ordered?: boolean | undefined;
756
+ }[];
757
+ readonly indexes: readonly {
758
+ readonly properties: readonly string[];
759
+ readonly unique: boolean;
760
+ }[];
761
+ readonly plural: string;
762
+ readonly builtIn: boolean;
763
+ readonly source?: {
764
+ readonly key: readonly string[];
765
+ readonly asset: string;
766
+ readonly currency?: string | undefined;
767
+ } | undefined;
768
+ readonly measures?: readonly {
769
+ readonly name: string;
770
+ readonly label: string;
771
+ readonly description: string;
772
+ readonly unit: "number" | "currency" | "ratio" | "percent";
773
+ readonly format: "currency" | "percent" | "decimal";
774
+ readonly expression: Schema.Json;
775
+ }[] | undefined;
776
+ readonly renamedFrom?: string | undefined;
777
+ readonly guide?: string | undefined;
778
+ readonly permissions?: {
779
+ readonly read: string;
780
+ readonly create?: string | undefined;
781
+ readonly update?: string | undefined;
782
+ readonly delete?: string | undefined;
783
+ readonly link?: string | undefined;
784
+ readonly unlink?: string | undefined;
785
+ } | undefined;
786
+ }[];
787
+ readonly roles: readonly {
788
+ readonly name: string;
789
+ readonly description: string;
790
+ readonly builtIn: boolean;
791
+ readonly permissions?: readonly string[] | undefined;
792
+ }[];
793
+ readonly version: number;
794
+ readonly hash: string;
795
+ readonly connections?: readonly {
796
+ readonly key: string;
797
+ readonly description: string;
798
+ }[] | undefined;
799
+ readonly signIn?: {
800
+ readonly policy: string;
801
+ } | undefined;
802
+ }, Schema.SchemaError, never>;
803
+ /** Encode only ontology-owned metadata. JSON values and JSON Schema keywords remain opaque. */
804
+ export declare function encodeManifest(manifest: OntologyManifest): Readonly<Record<string, unknown>>;
347
805
  /**
348
806
  * The sign-in policy a manifest declares. A manifest without one, or with a policy this reader does
349
807
  * not know, reads as `invited`: the closed choice, so that a misread never lets strangers in.
package/dist/manifest.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { measureExpression } from "./measure.js";
2
2
  import { createHash } from "node:crypto";
3
- import { Schema } from "effect";
3
+ import { Effect, Schema, SchemaTransformation } from "effect";
4
4
  import { humanize, MEMBER_ROLE } from "./names.js";
5
5
  import { PROPERTY_TYPES } from "./property.js";
6
6
  import { ON_DELETE } from "./relationship.js";
@@ -12,7 +12,7 @@ import { DEFAULT_SIGN_IN_POLICY, isSignInPolicy, } from "./sign-in.js";
12
12
  * `signIn` came later within version 2 and is optional too, as are the actions' `connections` and the
13
13
  * top-level `connections`, which are written only when an action calls a connection.
14
14
  */
15
- export const MANIFEST_VERSION = 3;
15
+ export const MANIFEST_VERSION = 4;
16
16
  /** The phrase used for a direction the author did not phrase. */
17
17
  export const DEFAULT_PHRASE = "has";
18
18
  export const SelectOptionManifest = Schema.Struct({
@@ -81,7 +81,7 @@ export const ActionManifest = Schema.Struct({
81
81
  /**
82
82
  * The permissions of an entity's reads and generated actions, so that readers need not rebuild the
83
83
  * convention. The built-in user has only `read`; `link` and `unlink` exist with a `rel.many`. The
84
- * generated `upsertBy<Property>` requires both `create` and `update`.
84
+ * generated `upsert_by_<property>` requires both `create` and `update`.
85
85
  */
86
86
  export const EntityPermissionsManifest = Schema.Struct({
87
87
  read: Schema.String,
@@ -149,7 +149,7 @@ export const SignInManifest = Schema.Struct({
149
149
  * ignores unknown fields and accepts any `version`. The closed sets (property types, cardinalities,
150
150
  * `onDelete`) are validated because the migration planner must map every value to storage.
151
151
  */
152
- export const OntologyManifest = Schema.Struct({
152
+ const OntologyManifestModel = Schema.Struct({
153
153
  version: Schema.Int,
154
154
  /** `sha256:` and the hex digest of the canonical JSON of the manifest without `hash`. */
155
155
  hash: Schema.String,
@@ -160,6 +160,46 @@ export const OntologyManifest = Schema.Struct({
160
160
  /** The connections the actions call. Written only when there are some; absent reads as none. */
161
161
  connections: Schema.optionalKey(Schema.Array(ConnectionManifest)),
162
162
  });
163
+ const PropertyManifestWire = PropertyManifest.pipe(Schema.encodeKeys({ renamedFrom: "renamed_from", maxLength: "max_length" }));
164
+ const OnDeleteWire = Schema.Literals(["set_null", "cascade", "restrict"]).pipe(Schema.decodeTo(Schema.Literals(ON_DELETE), SchemaTransformation.transform({
165
+ decode: (value) => (value === "set_null" ? "setNull" : value),
166
+ encode: (value) => (value === "setNull" ? "set_null" : value),
167
+ })));
168
+ const RelationshipManifestWire = Schema.Struct({
169
+ ...RelationshipManifest.fields,
170
+ on: Schema.optionalKey(Schema.Struct({ property: Schema.String, targetProperty: Schema.String }).pipe(Schema.encodeKeys({ targetProperty: "target_property" }))),
171
+ onDelete: Schema.optionalKey(OnDeleteWire),
172
+ }).pipe(Schema.encodeKeys({ onDelete: "on_delete" }));
173
+ const EntityManifestWire = Schema.Struct({
174
+ ...EntityManifest.fields,
175
+ properties: Schema.Array(PropertyManifestWire),
176
+ relationships: Schema.Array(RelationshipManifestWire),
177
+ }).pipe(Schema.encodeKeys({ builtIn: "built_in", renamedFrom: "renamed_from" }));
178
+ const RoleManifestWire = RoleManifest.pipe(Schema.encodeKeys({ builtIn: "built_in" }));
179
+ /** The JSON contract written by version 4; decoded SDK models remain camelCase. */
180
+ export const OntologyManifestWire = Schema.Struct({
181
+ ...OntologyManifestModel.fields,
182
+ entities: Schema.Array(EntityManifestWire),
183
+ roles: Schema.Array(RoleManifestWire),
184
+ }).pipe(Schema.encodeKeys({ signIn: "sign_in" }));
185
+ /** Historical readers accept either metadata spelling and any manifest version. */
186
+ export const OntologyManifest = Schema.Union([OntologyManifestWire, OntologyManifestModel]);
187
+ const originalDocuments = new WeakMap();
188
+ /** Retain the complete document when decoding a persisted or published manifest. */
189
+ export const decodeManifestDocument = (value) => Schema.decodeUnknownEffect(OntologyManifest)(value).pipe(Effect.map((manifest) => {
190
+ originalDocuments.set(manifest, value);
191
+ return manifest;
192
+ }));
193
+ /** Encode only ontology-owned metadata. JSON values and JSON Schema keywords remain opaque. */
194
+ export function encodeManifest(manifest) {
195
+ const original = originalDocuments.get(manifest);
196
+ if (original !== undefined) {
197
+ return original;
198
+ }
199
+ return manifest.version < MANIFEST_VERSION
200
+ ? manifest
201
+ : Schema.encodeSync(OntologyManifestWire)(manifest);
202
+ }
163
203
  /**
164
204
  * The sign-in policy a manifest declares. A manifest without one, or with a policy this reader does
165
205
  * not know, reads as `invited`: the closed choice, so that a misread never lets strangers in.
@@ -200,7 +240,7 @@ export function canonicalJson(value) {
200
240
  }
201
241
  /** The manifest as a file: canonical key order, two-space indentation and a final newline. */
202
242
  export function formatManifest(manifest) {
203
- return `${serialize(manifest, " ", 0)}\n`;
243
+ return `${serialize(encodeManifest(manifest), " ", 0)}\n`;
204
244
  }
205
245
  /** The hash of a manifest, computed over its canonical JSON without the `hash` field. */
206
246
  export function manifestHash(manifest) {
@@ -330,7 +370,7 @@ export function buildManifest(input) {
330
370
  }
331
371
  : {}),
332
372
  };
333
- return { ...body, hash: manifestHash(body) };
373
+ return { ...body, hash: manifestHash(encodeManifest({ ...body, hash: "" })) };
334
374
  }
335
375
  /** The keys of every connection the manifest's actions call, without duplicates. */
336
376
  export function actionConnectionKeys(manifest) {
@@ -1,7 +1,7 @@
1
1
  import { Duration, Effect } from "effect";
2
2
  import type { SqlClient } from "effect/unstable/sql";
3
3
  import type { SqlError } from "effect/unstable/sql/SqlError";
4
- import { OntologyManifest } from "../manifest.js";
4
+ import { type OntologyManifest } from "../manifest.js";
5
5
  import { CatalogInvalid, CatalogMismatch, MigrationFailed, MigrationRefused } from "./errors.js";
6
6
  import { type MigrationPlan } from "./model.js";
7
7
  import { type MigrationOptions } from "./plan.js";
@@ -1,5 +1,5 @@
1
1
  import { Duration, Effect, Schema } from "effect";
2
- import { canonicalJson, OntologyManifest } from "../manifest.js";
2
+ import { canonicalJson, decodeManifestDocument, encodeManifest, manifestHash, } from "../manifest.js";
3
3
  import { CatalogInvalid, CatalogMismatch, MigrationFailed, MigrationRefused } from "./errors.js";
4
4
  import { isNoopMigration } from "./model.js";
5
5
  import { planMigration } from "./plan.js";
@@ -11,7 +11,7 @@ const DEFAULT_STATEMENT_TIMEOUT = "5 minutes";
11
11
  const DEFAULT_LOCK_TIMEOUT = "5 seconds";
12
12
  const Presence = Schema.Struct({ present: Schema.Boolean });
13
13
  const CatalogRow = Schema.Struct({ hash: Schema.String, manifest: Schema.String });
14
- const decodeManifest = Schema.decodeUnknownEffect(OntologyManifest);
14
+ const decodeManifest = decodeManifestDocument;
15
15
  const readCatalogRow = (sql, options) => Effect.gen(function* () {
16
16
  const presence = yield* sql
17
17
  .unsafe(`SELECT to_regclass($1) IS NOT NULL AS present`, [CATALOG])
@@ -47,7 +47,8 @@ export const readCatalog = (input) => Effect.gen(function* () {
47
47
  message: "The _ontology manifest does not match the contract.",
48
48
  cause,
49
49
  })));
50
- if (manifest.hash !== row.hash) {
50
+ if (manifest.hash !== row.hash ||
51
+ manifestHash(parsed) !== row.hash) {
51
52
  return yield* new CatalogInvalid({
52
53
  message: "The _ontology hash does not match the manifest it records.",
53
54
  cause: { recordedHash: row.hash, manifestHash: manifest.hash },
@@ -121,7 +122,7 @@ export const applyMigration = (plan, options) => Effect.gen(function* () {
121
122
  "hash" = excluded."hash",
122
123
  "manifest" = excluded."manifest",
123
124
  "applied_at" = now()
124
- `, [plan.target.version, plan.target.hash, canonicalJson(plan.target)]);
125
+ `, [plan.target.version, plan.target.hash, canonicalJson(encodeManifest(plan.target))]);
125
126
  }));
126
127
  return { hash: plan.target.hash, appliedSteps: plan.steps.length, changed: true };
127
128
  }).pipe(Effect.withSpan("ontology.migrate.apply", {