@segmentstream-cloud/ontology 0.9.0 → 0.10.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 (52) hide show
  1. package/dist/analyze.js +16 -1
  2. package/dist/blob.d.ts +39 -0
  3. package/dist/blob.js +49 -0
  4. package/dist/entity.d.ts +5 -3
  5. package/dist/index.d.ts +4 -1
  6. package/dist/index.js +2 -0
  7. package/dist/manifest.d.ts +23 -7
  8. package/dist/manifest.js +9 -1
  9. package/dist/migrate/model.d.ts +7 -2
  10. package/dist/migrate/plan.js +26 -2
  11. package/dist/migrate/storage.d.ts +2 -0
  12. package/dist/migrate/storage.js +13 -0
  13. package/dist/property.d.ts +10 -1
  14. package/dist/property.js +12 -1
  15. package/dist/query.js +6 -1
  16. package/dist/runtime/generated-actions.js +7 -3
  17. package/dist/runtime/history.d.ts +3 -0
  18. package/dist/runtime/pipeline.d.ts +5 -1
  19. package/dist/runtime/pipeline.js +87 -3
  20. package/dist/runtime/records.js +9 -0
  21. package/dist/runtime/service-binding.d.ts +2 -1
  22. package/dist/runtime/service-binding.js +13 -2
  23. package/dist/runtime/writes.js +17 -3
  24. package/dist/service/api.js +15 -0
  25. package/dist/service/config.d.ts +3 -0
  26. package/dist/service/config.js +18 -0
  27. package/dist/service/handlers.js +40 -1
  28. package/dist/service/runtime-contract.d.ts +9 -0
  29. package/dist/service/schemas.d.ts +10 -2
  30. package/dist/service/schemas.js +18 -2
  31. package/dist/service/service.js +18 -3
  32. package/dist/storage/api.d.ts +4 -0
  33. package/dist/storage/api.js +39 -0
  34. package/dist/storage/authorization.d.ts +8 -0
  35. package/dist/storage/authorization.js +30 -0
  36. package/dist/storage/maintenance-auth.d.ts +20 -0
  37. package/dist/storage/maintenance-auth.js +24 -0
  38. package/dist/storage/maintenance.d.ts +7 -0
  39. package/dist/storage/maintenance.js +50 -0
  40. package/dist/storage/neon.d.ts +3 -0
  41. package/dist/storage/neon.js +212 -0
  42. package/dist/storage/operator.d.ts +10 -0
  43. package/dist/storage/operator.js +30 -0
  44. package/dist/storage/provider.d.ts +80 -0
  45. package/dist/storage/provider.js +20 -0
  46. package/dist/storage/sessions.d.ts +87 -0
  47. package/dist/storage/sessions.js +341 -0
  48. package/dist/storage/tables.d.ts +2 -0
  49. package/dist/storage/tables.js +55 -0
  50. package/dist/storage/writes.d.ts +7 -0
  51. package/dist/storage/writes.js +88 -0
  52. package/package.json +3 -1
package/dist/analyze.js CHANGED
@@ -1,5 +1,7 @@
1
1
  import { checkWarehouseSource } from "./warehouse-backed.js";
2
2
  import { Schema } from "effect";
3
+ import { BLOB_UPLOAD_ANNOTATION } from "./blob.js";
4
+ import { BLOB_CONTENT_TYPES, MAX_BLOB_BYTES } from "./blob.js";
3
5
  import { isAction } from "./action.js";
4
6
  import { CONNECTION_KEY, isConnection } from "./connection.js";
5
7
  import { isEntity } from "./entity.js";
@@ -340,6 +342,19 @@ function resolveProperties(candidate, scope) {
340
342
  return properties;
341
343
  }
342
344
  function checkProperty(path, schema, definition, report) {
345
+ if (definition.type === "blob") {
346
+ if (definition.unique || "default" in definition) {
347
+ report("invalid-property", path, "Blob properties cannot be unique or have a default.");
348
+ }
349
+ if (!definition.accept?.length ||
350
+ definition.accept.some((type) => !BLOB_CONTENT_TYPES.includes(type)) ||
351
+ new Set(definition.accept).size !== definition.accept.length ||
352
+ !Number.isInteger(definition.maxBytes) ||
353
+ definition.maxBytes < 1 ||
354
+ definition.maxBytes > MAX_BLOB_BYTES) {
355
+ report("invalid-constraint", path, "Use supported content types and a limit from 1 byte to 25 MiB.");
356
+ }
357
+ }
343
358
  if (definition.type === "select") {
344
359
  checkSelectOptions(path, definition, report);
345
360
  }
@@ -563,7 +578,7 @@ function resolveInput(path, input, scope) {
563
578
  }
564
579
  try {
565
580
  const document = Schema.toJsonSchemaDocument(input, {
566
- includeAnnotationKey: (key) => key === REFERENCE_ANNOTATION,
581
+ includeAnnotationKey: (key) => key === REFERENCE_ANNOTATION || key === BLOB_UPLOAD_ANNOTATION,
567
582
  onExcessProperty: "error",
568
583
  });
569
584
  const schema = document.schema;
package/dist/blob.d.ts ADDED
@@ -0,0 +1,39 @@
1
+ import { Schema } from "effect";
2
+ /** Metadata of an immutable file revision. Provider keys and grants never appear in records. */
3
+ export declare const BlobValue: Schema.Struct<{
4
+ readonly id: Schema.String;
5
+ readonly filename: Schema.String;
6
+ readonly contentType: Schema.String;
7
+ readonly size: Schema.Int;
8
+ readonly createdAt: Schema.String;
9
+ }>;
10
+ export type BlobValue = typeof BlobValue.Type;
11
+ /** An opaque completed-upload ticket. Only the action runtime may resolve and consume it. */
12
+ declare function uploadCodec(entity?: string, property?: string): Schema.decodeTo<Schema.Struct<{
13
+ readonly uploadId: Schema.String;
14
+ }>, Schema.Struct<{
15
+ readonly upload_id: Schema.String;
16
+ }>, never, never>;
17
+ export declare const BlobUpload: Schema.Struct<{
18
+ readonly uploadId: Schema.String;
19
+ }>;
20
+ export type BlobUpload = typeof BlobUpload.Type;
21
+ export declare const BLOB_UPLOAD_ANNOTATION = "x-ontology-upload";
22
+ export declare const MAX_BLOB_BYTES: number;
23
+ export declare const BLOB_CONTENT_TYPES: readonly ["application/pdf", "image/jpeg", "image/png", "text/plain"];
24
+ type SchemaValue<S> = S extends {
25
+ readonly Type: infer T;
26
+ } ? T : S extends Schema.optionalKey<infer Inner> ? Inner["Type"] : never;
27
+ type BlobPropertyKey<P> = {
28
+ [K in keyof P]: NonNullable<SchemaValue<P[K]>> extends BlobValue ? K : never;
29
+ }[keyof P] & string;
30
+ /** Internal wire codec for session tickets and generated action inputs. */
31
+ export declare function uploadInput(entity?: string, property?: string): ReturnType<typeof uploadCodec>;
32
+ /** Declares which blob field an authored action's upload input may write. */
33
+ export declare const uploads: {
34
+ for<const P extends Readonly<Record<string, unknown>>>(entity: {
35
+ readonly name: string;
36
+ readonly properties: P;
37
+ }, property: BlobPropertyKey<P>): typeof BlobUpload;
38
+ };
39
+ export {};
package/dist/blob.js ADDED
@@ -0,0 +1,49 @@
1
+ import { Schema, SchemaTransformation } from "effect";
2
+ import { nameToWire } from "./authoring-names.js";
3
+ /** Metadata of an immutable file revision. Provider keys and grants never appear in records. */
4
+ export const BlobValue = Schema.Struct({
5
+ id: Schema.String.check(Schema.isUUID()),
6
+ filename: Schema.String,
7
+ contentType: Schema.String,
8
+ size: Schema.Int.check(Schema.isGreaterThanOrEqualTo(0)),
9
+ createdAt: Schema.String,
10
+ });
11
+ /** An opaque completed-upload ticket. Only the action runtime may resolve and consume it. */
12
+ function uploadCodec(entity, property) {
13
+ const id = Schema.String.check(Schema.isUUID());
14
+ return Schema.Struct({ upload_id: id })
15
+ .annotate({
16
+ ...(entity && property
17
+ ? {
18
+ description: `A completed upload for ${entity}.${property}.`,
19
+ "x-ontology-upload": { entity, property },
20
+ }
21
+ : {}),
22
+ })
23
+ .pipe(Schema.decodeTo(Schema.Struct({ uploadId: id }), SchemaTransformation.transform({
24
+ decode: (value) => ({ uploadId: value.upload_id }),
25
+ encode: (value) => ({ upload_id: value.uploadId }),
26
+ })));
27
+ }
28
+ export const BlobUpload = Schema.Struct({ uploadId: Schema.String.check(Schema.isUUID()) });
29
+ export const BLOB_UPLOAD_ANNOTATION = "x-ontology-upload";
30
+ export const MAX_BLOB_BYTES = 25 * 1024 * 1024;
31
+ export const BLOB_CONTENT_TYPES = [
32
+ "application/pdf",
33
+ "image/jpeg",
34
+ "image/png",
35
+ "text/plain",
36
+ ];
37
+ /** Internal wire codec for session tickets and generated action inputs. */
38
+ export function uploadInput(entity, property) {
39
+ return uploadCodec(entity, property);
40
+ }
41
+ /** Declares which blob field an authored action's upload input may write. */
42
+ export const uploads = {
43
+ for(entity, property) {
44
+ return BlobUpload.annotate({
45
+ description: `A completed upload for ${entity.name}.${property}.`,
46
+ "x-ontology-upload": { entity: nameToWire(entity.name), property: nameToWire(property) },
47
+ });
48
+ },
49
+ };
package/dist/entity.d.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import type { BlobUpload, BlobValue } from "./blob.js";
1
2
  import { Effect, type Schema } from "effect";
2
3
  import type { Measures } from "./measure.js";
3
4
  import type { WarehouseSource } from "./source.js";
@@ -39,6 +40,7 @@ type IsOptional<S> = S extends {
39
40
  type ValueOf<S> = S extends {
40
41
  readonly Type: infer T;
41
42
  } ? IsOptional<S> extends true ? T | null : T : never;
43
+ type WriteValueOf<S> = NonNullable<ValueOf<S>> extends BlobValue ? BlobUpload | (null extends ValueOf<S> ? null : never) : ValueOf<S>;
42
44
  type RequiredOnCreate<S> = IsOptional<S> extends true ? false : S extends {
43
45
  readonly "~ontology.defaulted": true;
44
46
  } ? false : true;
@@ -68,9 +70,9 @@ export type EntityRecord<P, R> = Simplify<SystemFields & {
68
70
  * relationships take ids by relationship name (`{ lead: lead.id }`), `rel.many` a list of ids.
69
71
  */
70
72
  export type CreateValues<P, R> = Simplify<{
71
- readonly [K in RequiredPropertyKeys<P>]: ValueOf<P[K]>;
73
+ readonly [K in RequiredPropertyKeys<P>]: WriteValueOf<P[K]>;
72
74
  } & {
73
- readonly [K in Exclude<keyof P, RequiredPropertyKeys<P>>]?: ValueOf<P[K]>;
75
+ readonly [K in Exclude<keyof P, RequiredPropertyKeys<P>>]?: WriteValueOf<P[K]>;
74
76
  } & {
75
77
  readonly [K in RequiredOneKeys<R>]: string;
76
78
  } & {
@@ -80,7 +82,7 @@ export type CreateValues<P, R> = Simplify<{
80
82
  }>;
81
83
  /** Values for `update`. Handlers may write `managed` properties; the generated `update` may not. */
82
84
  export type UpdateValues<P, R> = Simplify<{
83
- readonly [K in keyof P]?: ValueOf<P[K]>;
85
+ readonly [K in keyof P]?: WriteValueOf<P[K]>;
84
86
  } & {
85
87
  readonly [K in OneKeys<R>]?: R[K] extends OneRelationship<true> ? string | null : string;
86
88
  } & {
package/dist/index.d.ts CHANGED
@@ -1,3 +1,4 @@
1
+ export { BlobValue, BlobUpload, uploads, BLOB_CONTENT_TYPES, MAX_BLOB_BYTES } from "./blob.js";
1
2
  export { warehouse, compatibleWarehouseColumn, warehouseColumnType, validateWarehouseSource, } from "./source.js";
2
3
  export type { WarehouseSource } from "./source.js";
3
4
  export { m } from "./measure.js";
@@ -21,7 +22,7 @@ export type { Ontology } from "./ontology.js";
21
22
  export { DEFAULT_MEMBER_PERMISSIONS, generatedActionPermissions, grants, isAllowed, parsePermission, permissionsOf, READ_VERB, rolesGranting, WILDCARD, } from "./permissions.js";
22
23
  export type { ParsedPermission, PermissionHolder, RolePermissions } from "./permissions.js";
23
24
  export { p, PROPERTY_TYPES, propertyDefinition } from "./property.js";
24
- export type { AnyProperty, NumberOptions, Property, PropertyDefinition, PropertyOf, PropertyOptions, PropertyType, ReferenceOptions, SelectOption, SelectOptionInput, TextOptions, } from "./property.js";
25
+ export type { AnyProperty, NumberOptions, Property, PropertyDefinition, PropertyOf, PropertyOptions, BlobOptions, PropertyType, ReferenceOptions, SelectOption, SelectOptionInput, TextOptions, } from "./property.js";
25
26
  export { ON_DELETE, rel } from "./relationship.js";
26
27
  export type { AnyRelationship, EntityTarget, LazyEntity, ManyOptions, ManyRelationship, OnDelete, OneOptions, OneRelationship, Phrase, } from "./relationship.js";
27
28
  export { renderCatalog } from "./render.js";
@@ -59,3 +60,5 @@ export { createOntologyService, ONTOLOGY_HASH_HEADER, REQUEST_ID_HEADER, } from
59
60
  export type { OntologyService } from "./service/service.js";
60
61
  export { applyMigration, CatalogInvalid, CatalogMismatch, isNoopMigration, migrate, MIGRATION_REFUSAL_CODES, MIGRATION_STEP_CLASSIFICATIONS, MIGRATION_STEP_KINDS, MigrationFailed, MigrationPlan, MigrationRefusal, MigrationRefused, MigrationStep, OntologyStorage, planMigration, readCatalog, } from "./migrate/index.js";
61
62
  export type { ApplyMigrationError, ApplyMigrationOptions, CatalogInput, MigrateInput, MigrationOptions, MigrationRefusalCode, MigrationResult, MigrationStepClassification, MigrationStepKind, PlanMigrationInput, } from "./migrate/index.js";
63
+ export type { BlobStorageBinding } from "./storage/provider.js";
64
+ export { runBlobStorageMaintenance } from "./storage/operator.js";
package/dist/index.js CHANGED
@@ -1,3 +1,4 @@
1
+ export { BlobValue, BlobUpload, uploads, BLOB_CONTENT_TYPES, MAX_BLOB_BYTES } from "./blob.js";
1
2
  export { warehouse, compatibleWarehouseColumn, warehouseColumnType, validateWarehouseSource, } from "./source.js";
2
3
  export { m } from "./measure.js";
3
4
  export { action, isAction } from "./action.js";
@@ -35,3 +36,4 @@ export { NotInvited, OntologyBackend } from "./service/runtime-contract.js";
35
36
  export { createOntologyService, ONTOLOGY_HASH_HEADER, REQUEST_ID_HEADER, } from "./service/service.js";
36
37
  // Migrations: the planner, the applier and the physical mapping they share with the runtime.
37
38
  export { applyMigration, CatalogInvalid, CatalogMismatch, isNoopMigration, migrate, MIGRATION_REFUSAL_CODES, MIGRATION_STEP_CLASSIFICATIONS, MIGRATION_STEP_KINDS, MigrationFailed, MigrationPlan, MigrationRefusal, MigrationRefused, MigrationStep, OntologyStorage, planMigration, readCatalog, } from "./migrate/index.js";
39
+ export { runBlobStorageMaintenance } from "./storage/operator.js";
@@ -21,7 +21,7 @@ export declare const PropertyManifest: Schema.Struct<{
21
21
  readonly column: Schema.optionalKey<Schema.String>;
22
22
  readonly scope: Schema.optionalKey<Schema.$Array<Schema.String>>;
23
23
  readonly name: Schema.String;
24
- readonly type: Schema.Literals<readonly ["text", "email", "url", "integer", "number", "boolean", "date", "timestamp", "select", "json"]>;
24
+ readonly type: Schema.Literals<readonly ["text", "email", "url", "integer", "number", "boolean", "date", "timestamp", "select", "json", "blob"]>;
25
25
  readonly label: Schema.String;
26
26
  readonly description: Schema.optionalKey<Schema.String>;
27
27
  readonly optional: Schema.Boolean;
@@ -33,6 +33,8 @@ export declare const PropertyManifest: Schema.Struct<{
33
33
  readonly min: Schema.optionalKey<Schema.Number>;
34
34
  readonly max: Schema.optionalKey<Schema.Number>;
35
35
  readonly maxLength: Schema.optionalKey<Schema.Number>;
36
+ readonly accept: Schema.optionalKey<Schema.$Array<Schema.String>>;
37
+ readonly maxBytes: Schema.optionalKey<Schema.Number>;
36
38
  readonly options: Schema.optionalKey<Schema.$Array<Schema.Struct<{
37
39
  readonly value: Schema.String;
38
40
  readonly label: Schema.String;
@@ -126,7 +128,7 @@ export declare const EntityManifest: Schema.Struct<{
126
128
  readonly column: Schema.optionalKey<Schema.String>;
127
129
  readonly scope: Schema.optionalKey<Schema.$Array<Schema.String>>;
128
130
  readonly name: Schema.String;
129
- readonly type: Schema.Literals<readonly ["text", "email", "url", "integer", "number", "boolean", "date", "timestamp", "select", "json"]>;
131
+ readonly type: Schema.Literals<readonly ["text", "email", "url", "integer", "number", "boolean", "date", "timestamp", "select", "json", "blob"]>;
130
132
  readonly label: Schema.String;
131
133
  readonly description: Schema.optionalKey<Schema.String>;
132
134
  readonly optional: Schema.Boolean;
@@ -138,6 +140,8 @@ export declare const EntityManifest: Schema.Struct<{
138
140
  readonly min: Schema.optionalKey<Schema.Number>;
139
141
  readonly max: Schema.optionalKey<Schema.Number>;
140
142
  readonly maxLength: Schema.optionalKey<Schema.Number>;
143
+ readonly accept: Schema.optionalKey<Schema.$Array<Schema.String>>;
144
+ readonly maxBytes: Schema.optionalKey<Schema.Number>;
141
145
  readonly options: Schema.optionalKey<Schema.$Array<Schema.Struct<{
142
146
  readonly value: Schema.String;
143
147
  readonly label: Schema.String;
@@ -227,7 +231,7 @@ export declare const OntologyManifestWire: Schema.encodeKeys<Schema.Struct<{
227
231
  readonly column: Schema.optionalKey<Schema.String>;
228
232
  readonly scope: Schema.optionalKey<Schema.$Array<Schema.String>>;
229
233
  readonly name: Schema.String;
230
- readonly type: Schema.Literals<readonly ["text", "email", "url", "integer", "number", "boolean", "date", "timestamp", "select", "json"]>;
234
+ readonly type: Schema.Literals<readonly ["text", "email", "url", "integer", "number", "boolean", "date", "timestamp", "select", "json", "blob"]>;
231
235
  readonly label: Schema.String;
232
236
  readonly description: Schema.optionalKey<Schema.String>;
233
237
  readonly optional: Schema.Boolean;
@@ -239,6 +243,8 @@ export declare const OntologyManifestWire: Schema.encodeKeys<Schema.Struct<{
239
243
  readonly min: Schema.optionalKey<Schema.Number>;
240
244
  readonly max: Schema.optionalKey<Schema.Number>;
241
245
  readonly maxLength: Schema.optionalKey<Schema.Number>;
246
+ readonly accept: Schema.optionalKey<Schema.$Array<Schema.String>>;
247
+ readonly maxBytes: Schema.optionalKey<Schema.Number>;
242
248
  readonly options: Schema.optionalKey<Schema.$Array<Schema.Struct<{
243
249
  readonly value: Schema.String;
244
250
  readonly label: Schema.String;
@@ -247,6 +253,7 @@ export declare const OntologyManifestWire: Schema.encodeKeys<Schema.Struct<{
247
253
  }>, {
248
254
  readonly renamedFrom: "renamed_from";
249
255
  readonly maxLength: "max_length";
256
+ readonly maxBytes: "max_bytes";
250
257
  }>>;
251
258
  readonly relationships: Schema.$Array<Schema.encodeKeys<Schema.Struct<{
252
259
  readonly on: Schema.optionalKey<Schema.encodeKeys<Schema.Struct<{
@@ -359,7 +366,7 @@ export declare const OntologyManifest: Schema.Union<readonly [Schema.encodeKeys<
359
366
  readonly column: Schema.optionalKey<Schema.String>;
360
367
  readonly scope: Schema.optionalKey<Schema.$Array<Schema.String>>;
361
368
  readonly name: Schema.String;
362
- readonly type: Schema.Literals<readonly ["text", "email", "url", "integer", "number", "boolean", "date", "timestamp", "select", "json"]>;
369
+ readonly type: Schema.Literals<readonly ["text", "email", "url", "integer", "number", "boolean", "date", "timestamp", "select", "json", "blob"]>;
363
370
  readonly label: Schema.String;
364
371
  readonly description: Schema.optionalKey<Schema.String>;
365
372
  readonly optional: Schema.Boolean;
@@ -371,6 +378,8 @@ export declare const OntologyManifest: Schema.Union<readonly [Schema.encodeKeys<
371
378
  readonly min: Schema.optionalKey<Schema.Number>;
372
379
  readonly max: Schema.optionalKey<Schema.Number>;
373
380
  readonly maxLength: Schema.optionalKey<Schema.Number>;
381
+ readonly accept: Schema.optionalKey<Schema.$Array<Schema.String>>;
382
+ readonly maxBytes: Schema.optionalKey<Schema.Number>;
374
383
  readonly options: Schema.optionalKey<Schema.$Array<Schema.Struct<{
375
384
  readonly value: Schema.String;
376
385
  readonly label: Schema.String;
@@ -379,6 +388,7 @@ export declare const OntologyManifest: Schema.Union<readonly [Schema.encodeKeys<
379
388
  }>, {
380
389
  readonly renamedFrom: "renamed_from";
381
390
  readonly maxLength: "max_length";
391
+ readonly maxBytes: "max_bytes";
382
392
  }>>;
383
393
  readonly relationships: Schema.$Array<Schema.encodeKeys<Schema.Struct<{
384
394
  readonly on: Schema.optionalKey<Schema.encodeKeys<Schema.Struct<{
@@ -514,7 +524,7 @@ export declare const OntologyManifest: Schema.Union<readonly [Schema.encodeKeys<
514
524
  readonly column: Schema.optionalKey<Schema.String>;
515
525
  readonly scope: Schema.optionalKey<Schema.$Array<Schema.String>>;
516
526
  readonly name: Schema.String;
517
- readonly type: Schema.Literals<readonly ["text", "email", "url", "integer", "number", "boolean", "date", "timestamp", "select", "json"]>;
527
+ readonly type: Schema.Literals<readonly ["text", "email", "url", "integer", "number", "boolean", "date", "timestamp", "select", "json", "blob"]>;
518
528
  readonly label: Schema.String;
519
529
  readonly description: Schema.optionalKey<Schema.String>;
520
530
  readonly optional: Schema.Boolean;
@@ -526,6 +536,8 @@ export declare const OntologyManifest: Schema.Union<readonly [Schema.encodeKeys<
526
536
  readonly min: Schema.optionalKey<Schema.Number>;
527
537
  readonly max: Schema.optionalKey<Schema.Number>;
528
538
  readonly maxLength: Schema.optionalKey<Schema.Number>;
539
+ readonly accept: Schema.optionalKey<Schema.$Array<Schema.String>>;
540
+ readonly maxBytes: Schema.optionalKey<Schema.Number>;
529
541
  readonly options: Schema.optionalKey<Schema.$Array<Schema.Struct<{
530
542
  readonly value: Schema.String;
531
543
  readonly label: Schema.String;
@@ -623,7 +635,7 @@ export declare const decodeManifestDocument: (value: unknown) => Effect.Effect<{
623
635
  readonly optional: boolean;
624
636
  readonly name: string;
625
637
  readonly label: string;
626
- readonly type: "number" | "boolean" | "text" | "integer" | "date" | "timestamp" | "select" | "email" | "url" | "json";
638
+ readonly type: "number" | "boolean" | "text" | "integer" | "date" | "timestamp" | "select" | "email" | "url" | "json" | "blob";
627
639
  readonly unique: boolean;
628
640
  readonly managed: boolean;
629
641
  readonly column?: string | undefined;
@@ -640,6 +652,8 @@ export declare const decodeManifestDocument: (value: unknown) => Effect.Effect<{
640
652
  }[] | undefined;
641
653
  readonly renamedFrom?: string | undefined;
642
654
  readonly display?: string | undefined;
655
+ readonly accept?: readonly string[] | undefined;
656
+ readonly maxBytes?: number | undefined;
643
657
  }[];
644
658
  readonly relationships: readonly {
645
659
  readonly name: string;
@@ -721,7 +735,7 @@ export declare const decodeManifestDocument: (value: unknown) => Effect.Effect<{
721
735
  readonly optional: boolean;
722
736
  readonly name: string;
723
737
  readonly label: string;
724
- readonly type: "number" | "boolean" | "text" | "integer" | "date" | "timestamp" | "select" | "email" | "url" | "json";
738
+ readonly type: "number" | "boolean" | "text" | "integer" | "date" | "timestamp" | "select" | "email" | "url" | "json" | "blob";
725
739
  readonly unique: boolean;
726
740
  readonly managed: boolean;
727
741
  readonly column?: string | undefined;
@@ -738,6 +752,8 @@ export declare const decodeManifestDocument: (value: unknown) => Effect.Effect<{
738
752
  }[] | undefined;
739
753
  readonly renamedFrom?: string | undefined;
740
754
  readonly display?: string | undefined;
755
+ readonly accept?: readonly string[] | undefined;
756
+ readonly maxBytes?: number | undefined;
741
757
  }[];
742
758
  readonly relationships: readonly {
743
759
  readonly name: string;
package/dist/manifest.js CHANGED
@@ -36,6 +36,8 @@ export const PropertyManifest = Schema.Struct({
36
36
  min: Schema.optionalKey(Schema.Number),
37
37
  max: Schema.optionalKey(Schema.Number),
38
38
  maxLength: Schema.optionalKey(Schema.Number),
39
+ accept: Schema.optionalKey(Schema.Array(Schema.String)),
40
+ maxBytes: Schema.optionalKey(Schema.Number),
39
41
  options: Schema.optionalKey(Schema.Array(SelectOptionManifest)),
40
42
  });
41
43
  /**
@@ -160,7 +162,11 @@ const OntologyManifestModel = Schema.Struct({
160
162
  /** The connections the actions call. Written only when there are some; absent reads as none. */
161
163
  connections: Schema.optionalKey(Schema.Array(ConnectionManifest)),
162
164
  });
163
- const PropertyManifestWire = PropertyManifest.pipe(Schema.encodeKeys({ renamedFrom: "renamed_from", maxLength: "max_length" }));
165
+ const PropertyManifestWire = PropertyManifest.pipe(Schema.encodeKeys({
166
+ renamedFrom: "renamed_from",
167
+ maxLength: "max_length",
168
+ maxBytes: "max_bytes",
169
+ }));
164
170
  const OnDeleteWire = Schema.Literals(["set_null", "cascade", "restrict"]).pipe(Schema.decodeTo(Schema.Literals(ON_DELETE), SchemaTransformation.transform({
165
171
  decode: (value) => (value === "set_null" ? "setNull" : value),
166
172
  encode: (value) => (value === "setNull" ? "set_null" : value),
@@ -265,6 +271,8 @@ function propertyManifest({ name, definition }) {
265
271
  ...(definition.min !== undefined ? { min: definition.min } : {}),
266
272
  ...(definition.max !== undefined ? { max: definition.max } : {}),
267
273
  ...(definition.maxLength !== undefined ? { maxLength: definition.maxLength } : {}),
274
+ ...(definition.accept !== undefined ? { accept: definition.accept } : {}),
275
+ ...(definition.maxBytes !== undefined ? { maxBytes: definition.maxBytes } : {}),
268
276
  ...(definition.options !== undefined
269
277
  ? {
270
278
  options: definition.options.map((option) => ({
@@ -48,7 +48,7 @@ export declare const MigrationPlan: Schema.Struct<{
48
48
  readonly column: Schema.optionalKey<Schema.String>;
49
49
  readonly scope: Schema.optionalKey<Schema.$Array<Schema.String>>;
50
50
  readonly name: Schema.String;
51
- readonly type: Schema.Literals<readonly ["text", "email", "url", "integer", "number", "boolean", "date", "timestamp", "select", "json"]>;
51
+ readonly type: Schema.Literals<readonly ["text", "email", "url", "integer", "number", "boolean", "date", "timestamp", "select", "json", "blob"]>;
52
52
  readonly label: Schema.String;
53
53
  readonly description: Schema.optionalKey<Schema.String>;
54
54
  readonly optional: Schema.Boolean;
@@ -60,6 +60,8 @@ export declare const MigrationPlan: Schema.Struct<{
60
60
  readonly min: Schema.optionalKey<Schema.Number>;
61
61
  readonly max: Schema.optionalKey<Schema.Number>;
62
62
  readonly maxLength: Schema.optionalKey<Schema.Number>;
63
+ readonly accept: Schema.optionalKey<Schema.$Array<Schema.String>>;
64
+ readonly maxBytes: Schema.optionalKey<Schema.Number>;
63
65
  readonly options: Schema.optionalKey<Schema.$Array<Schema.Struct<{
64
66
  readonly value: Schema.String;
65
67
  readonly label: Schema.String;
@@ -68,6 +70,7 @@ export declare const MigrationPlan: Schema.Struct<{
68
70
  }>, {
69
71
  readonly renamedFrom: "renamed_from";
70
72
  readonly maxLength: "max_length";
73
+ readonly maxBytes: "max_bytes";
71
74
  }>>;
72
75
  readonly relationships: Schema.$Array<Schema.encodeKeys<Schema.Struct<{
73
76
  readonly on: Schema.optionalKey<Schema.encodeKeys<Schema.Struct<{
@@ -182,7 +185,7 @@ export declare const MigrationPlan: Schema.Struct<{
182
185
  readonly column: Schema.optionalKey<Schema.String>;
183
186
  readonly scope: Schema.optionalKey<Schema.$Array<Schema.String>>;
184
187
  readonly name: Schema.String;
185
- readonly type: Schema.Literals<readonly ["text", "email", "url", "integer", "number", "boolean", "date", "timestamp", "select", "json"]>;
188
+ readonly type: Schema.Literals<readonly ["text", "email", "url", "integer", "number", "boolean", "date", "timestamp", "select", "json", "blob"]>;
186
189
  readonly label: Schema.String;
187
190
  readonly description: Schema.optionalKey<Schema.String>;
188
191
  readonly optional: Schema.Boolean;
@@ -194,6 +197,8 @@ export declare const MigrationPlan: Schema.Struct<{
194
197
  readonly min: Schema.optionalKey<Schema.Number>;
195
198
  readonly max: Schema.optionalKey<Schema.Number>;
196
199
  readonly maxLength: Schema.optionalKey<Schema.Number>;
200
+ readonly accept: Schema.optionalKey<Schema.$Array<Schema.String>>;
201
+ readonly maxBytes: Schema.optionalKey<Schema.Number>;
197
202
  readonly options: Schema.optionalKey<Schema.$Array<Schema.Struct<{
198
203
  readonly value: Schema.String;
199
204
  readonly label: Schema.String;
@@ -1,5 +1,6 @@
1
+ import { BLOB_TABLES_DDL } from "../storage/tables.js";
1
2
  import { SNAKE_CASE_NAME, isSystemProperty, toSnakeCase } from "../names.js";
2
- import { CATALOG_TABLE_DDL, MAX_IDENTIFIER_LENGTH, USER_TABLE, addConstraintStatement, columnDefinition, constraintDefinition, createEnumStatement, createIndexStatement, createTableStatement, defaultExpression, entityStorage, enumTypeName, fieldColumnName, joinTable, propertyCheck, propertyColumn, propertyEnum, quoteIdentifier, quoteLiteral, relationshipColumn, RUNTIME_TABLES_DDL, relationshipForeignKey, SYSTEM_COLUMNS, tableName, } from "./storage.js";
3
+ import { CATALOG_TABLE_DDL, MAX_IDENTIFIER_LENGTH, USER_TABLE, addConstraintStatement, columnDefinition, constraintDefinition, createEnumStatement, createIndexStatement, createTableStatement, defaultExpression, entityStorage, enumTypeName, fieldColumnName, joinTable, propertyCheck, blobForeignKey, propertyColumn, propertyEnum, quoteIdentifier, quoteLiteral, relationshipColumn, RUNTIME_TABLES_DDL, relationshipForeignKey, SYSTEM_COLUMNS, tableName, } from "./storage.js";
3
4
  const GLOBAL_KINDS = new Set(["table", "type", "index"]);
4
5
  function step(input) {
5
6
  return {
@@ -72,6 +73,15 @@ export function planMigration(input) {
72
73
  sql: RUNTIME_TABLES_DDL,
73
74
  }));
74
75
  }
76
+ if (storedTargets.some((entity) => entity.properties.some((property) => property.type === "blob")) &&
77
+ !current?.entities.some((entity) => entity.properties.some((property) => property.type === "blob"))) {
78
+ draft.runtimeTables.push(step({
79
+ kind: "createCatalog",
80
+ classification: "additive",
81
+ description: "Create the ontology's file revision and upload tables.",
82
+ sql: BLOB_TABLES_DDL,
83
+ }));
84
+ }
75
85
  planDrops({ pairs, removedEntities, addedEntities, allowDrops, draft });
76
86
  const renamed = planRenames({ pairs, draft });
77
87
  const appliedNames = new Set(storageObjects(currentEntities)
@@ -573,6 +583,12 @@ function logicalRenames(pairs, refusals) {
573
583
  path: `${pair.target.name}.properties.${to}`,
574
584
  description: `Rename property ${pair.target.name}.${from} to ${to}.`,
575
585
  apply: (model) => renameProperty(model, pair.target.id, from, to),
586
+ ...(pair.current.properties.some((property) => property.name === from && property.type === "blob")
587
+ ? {
588
+ metadataSql: ["_blobs", "_blob_uploads", "_blob_references"].map((table) => `UPDATE ${quoteIdentifier(table)} SET property_id = ${quoteLiteral(to)} ` +
589
+ `WHERE entity_id = ${quoteLiteral(pair.target.id)} AND property_id = ${quoteLiteral(from)}`),
590
+ }
591
+ : {}),
576
592
  });
577
593
  }
578
594
  }
@@ -595,7 +611,7 @@ function planRenames(input) {
595
611
  kind: rename.kind,
596
612
  classification: "rename",
597
613
  description: rename.description,
598
- sql: statements,
614
+ sql: [...statements, ...(rename.metadataSql ?? [])],
599
615
  }));
600
616
  model = next;
601
617
  }
@@ -893,6 +909,14 @@ function planEntityChanges(input) {
893
909
  }
894
910
  }
895
911
  for (const property of pair.addedProperties) {
912
+ if (property.type === "blob") {
913
+ draft.foreignKeys.push(step({
914
+ kind: "addProperty",
915
+ classification: "additive",
916
+ description: `Reference the file revision for ${entity}.${property.name}.`,
917
+ sql: [addConstraintStatement(table, blobForeignKey(entity, property))],
918
+ }));
919
+ }
896
920
  const enumStorage = propertyEnum(entity, property);
897
921
  const check = propertyCheck(entity, property);
898
922
  const column = propertyColumn(entity, property);
@@ -30,6 +30,7 @@ export declare const COLUMN_TYPES: {
30
30
  readonly date: "date";
31
31
  readonly timestamp: "timestamptz";
32
32
  readonly json: "jsonb";
33
+ readonly blob: "uuid";
33
34
  };
34
35
  export interface ColumnStorage {
35
36
  readonly name: string;
@@ -130,6 +131,7 @@ export declare function checkExpression(property: PropertyManifest): string | un
130
131
  export declare function propertyColumn(entity: string, property: PropertyManifest): ColumnStorage;
131
132
  export declare function propertyCheck(entity: string, property: PropertyManifest): ConstraintStorage | undefined;
132
133
  export declare function propertyEnum(entity: string, property: PropertyManifest): EnumStorage | undefined;
134
+ export declare function blobForeignKey(entity: string, property: PropertyManifest): ConstraintStorage;
133
135
  /** The key column of a `rel.one`. */
134
136
  export declare function relationshipColumn(relationship: RelationshipManifest): ColumnStorage;
135
137
  /** The foreign key of a `rel.one`, with its `onDelete` action. */
@@ -29,6 +29,7 @@ export const COLUMN_TYPES = {
29
29
  date: "date",
30
30
  timestamp: "timestamptz",
31
31
  json: "jsonb",
32
+ blob: "uuid",
32
33
  };
33
34
  const ON_DELETE_ACTIONS = {
34
35
  setNull: "SET NULL",
@@ -198,6 +199,15 @@ function foreignKeyDefinition(input) {
198
199
  `REFERENCES ${quoteIdentifier(input.references)} (${quoteIdentifier(SYSTEM_COLUMNS.id)})`;
199
200
  return input.onDelete === undefined ? reference : `${reference} ON DELETE ${input.onDelete}`;
200
201
  }
202
+ export function blobForeignKey(entity, property) {
203
+ const column = columnName(property.name);
204
+ return {
205
+ name: foreignKeyName(entity, column),
206
+ kind: "foreignKey",
207
+ definition: foreignKeyDefinition({ column, references: "_blobs" }),
208
+ references: "_blobs",
209
+ };
210
+ }
201
211
  function userReference(table, column) {
202
212
  return {
203
213
  name: foreignKeyName(table, column),
@@ -389,6 +399,9 @@ export function entityStorage(entity) {
389
399
  userReference(table, SYSTEM_COLUMNS.created_by),
390
400
  userReference(table, SYSTEM_COLUMNS.updated_by),
391
401
  ...checks,
402
+ ...entity.properties
403
+ .filter((property) => property.type === "blob")
404
+ .map((property) => blobForeignKey(entity.name, property)),
392
405
  ...ones.map((relationship) => relationshipForeignKey(entity.name, relationship)),
393
406
  ],
394
407
  indexes: entityIndexes(entity),
@@ -1,6 +1,7 @@
1
1
  import { Schema } from "effect";
2
+ import { BlobValue, type BLOB_CONTENT_TYPES } from "./blob.js";
2
3
  /** The closed set of storage types. Each has a known column, JSON Schema, filters and rendering. */
3
- export declare const PROPERTY_TYPES: readonly ["text", "email", "url", "integer", "number", "boolean", "date", "timestamp", "select", "json"];
4
+ export declare const PROPERTY_TYPES: readonly ["text", "email", "url", "integer", "number", "boolean", "date", "timestamp", "select", "json", "blob"];
4
5
  export type PropertyType = (typeof PROPERTY_TYPES)[number];
5
6
  /** Schema annotation that carries a property's storage metadata. */
6
7
  export declare const PROPERTY_ANNOTATION = "segmentstream/ontology/property";
@@ -40,6 +41,8 @@ export interface PropertyDefinition {
40
41
  readonly max?: number;
41
42
  readonly maxLength?: number;
42
43
  readonly options?: ReadonlyArray<SelectOption>;
44
+ readonly accept?: ReadonlyArray<string>;
45
+ readonly maxBytes?: number;
43
46
  }
44
47
  /**
45
48
  * A property: an Effect Schema of its value, usable in action inputs, with its storage metadata in
@@ -127,6 +130,11 @@ declare function ref<const O extends ReferenceOptions>(target: string | {
127
130
  readonly name: string;
128
131
  }, options?: O): ReferenceOf<O>;
129
132
  /** Property constructors. */
133
+ export interface BlobOptions extends Omit<PropertyOptions<BlobValue>, "default" | "unique"> {
134
+ readonly accept: ReadonlyArray<(typeof BLOB_CONTENT_TYPES)[number]>;
135
+ readonly maxBytes?: number;
136
+ }
137
+ declare function blob<const O extends BlobOptions>(options: O): PropertyOf<BlobValue, O>;
130
138
  export declare const p: {
131
139
  readonly text: typeof text;
132
140
  readonly email: typeof email;
@@ -138,6 +146,7 @@ export declare const p: {
138
146
  readonly timestamp: typeof timestamp;
139
147
  readonly select: typeof select;
140
148
  readonly json: typeof json;
149
+ readonly blob: typeof blob;
141
150
  readonly ref: typeof ref;
142
151
  };
143
152
  /** The storage metadata of a schema built with a `p.*` constructor, or `undefined` for any other value. */
package/dist/property.js CHANGED
@@ -1,5 +1,6 @@
1
1
  import { Schema } from "effect";
2
2
  import { humanize } from "./names.js";
3
+ import { BlobValue, MAX_BLOB_BYTES } from "./blob.js";
3
4
  import { nameToWire } from "./authoring-names.js";
4
5
  /** The closed set of storage types. Each has a known column, JSON Schema, filters and rendering. */
5
6
  export const PROPERTY_TYPES = [
@@ -13,6 +14,7 @@ export const PROPERTY_TYPES = [
13
14
  "timestamp",
14
15
  "select",
15
16
  "json",
17
+ "blob",
16
18
  ];
17
19
  /** Schema annotation that carries a property's storage metadata. */
18
20
  export const PROPERTY_ANNOTATION = "segmentstream/ontology/property";
@@ -47,6 +49,8 @@ function construct({ type, base, options, checks = [], format, selectOptions, })
47
49
  ...(options.min !== undefined ? { min: options.min } : {}),
48
50
  ...(options.max !== undefined ? { max: options.max } : {}),
49
51
  ...(options.maxLength !== undefined ? { maxLength: options.maxLength } : {}),
52
+ ...(options.accept !== undefined ? { accept: options.accept } : {}),
53
+ ...(options.maxBytes !== undefined ? { maxBytes: options.maxBytes } : {}),
50
54
  ...(selectOptions !== undefined ? { options: selectOptions } : {}),
51
55
  };
52
56
  // Annotations go on the base node, before the checks, so that the JSON Schema keeps them at the top
@@ -176,7 +180,13 @@ function ref(target, options) {
176
180
  }).check(Schema.isPattern(UUID));
177
181
  return (settings.optional === true ? Schema.optionalKey(schema) : schema);
178
182
  }
179
- /** Property constructors. */
183
+ function blob(options) {
184
+ return construct({
185
+ type: "blob",
186
+ base: BlobValue,
187
+ options: { ...options, maxBytes: options.maxBytes ?? MAX_BLOB_BYTES },
188
+ });
189
+ }
180
190
  export const p = {
181
191
  text,
182
192
  email,
@@ -188,6 +198,7 @@ export const p = {
188
198
  timestamp,
189
199
  select,
190
200
  json,
201
+ blob,
191
202
  ref,
192
203
  };
193
204
  function findAnnotation(ast, key) {
package/dist/query.js CHANGED
@@ -1,5 +1,6 @@
1
1
  import { Schema } from "effect";
2
2
  import { wireStruct } from "./wire.js";
3
+ import { BlobValue } from "./blob.js";
3
4
  /**
4
5
  * The query language of `find` and of the HTTP API's list and query endpoints.
5
6
  *
@@ -194,12 +195,13 @@ export function fieldValueSchema(field) {
194
195
  case "json":
195
196
  return JsonValue;
196
197
  case "uuid":
198
+ case "blob":
197
199
  return UuidValue;
198
200
  }
199
201
  }
200
202
  /** Fields a query can sort on: every field but `json` properties. */
201
203
  export function isOrderable(field) {
202
- return field.storage !== "json";
204
+ return field.storage !== "json" && field.storage !== "blob";
203
205
  }
204
206
  const FILTER_IDENTIFIERS = {
205
207
  text: "TextFilter",
@@ -212,6 +214,7 @@ const FILTER_IDENTIFIERS = {
212
214
  timestamp: "TimestampFilter",
213
215
  json: "JsonFilter",
214
216
  uuid: "IdFilter",
217
+ blob: "IdFilter",
215
218
  };
216
219
  const FILTER_DESCRIPTIONS = {
217
220
  TextFilter: "contains and startsWith ignore case.",
@@ -413,6 +416,8 @@ function recordValueSchema(field) {
413
416
  return Schema.Literals(field.options ?? []);
414
417
  case "json":
415
418
  return Schema.Json;
419
+ case "blob":
420
+ return BlobValue;
416
421
  default:
417
422
  return Schema.String;
418
423
  }