@segmentstream-cloud/ontology 0.6.0 → 0.8.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.
- package/README.md +14 -2
- package/dist/analyze.js +58 -17
- package/dist/authoring-input.d.ts +3 -0
- package/dist/authoring-input.js +107 -0
- package/dist/authoring-names.d.ts +4 -0
- package/dist/authoring-names.js +13 -0
- package/dist/authoring-records.d.ts +16 -0
- package/dist/authoring-records.js +144 -0
- package/dist/authoring.d.ts +12 -0
- package/dist/authoring.js +237 -0
- package/dist/connection.js +1 -1
- package/dist/entity.d.ts +1 -2
- package/dist/entity.js +31 -8
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/manifest.d.ts +468 -10
- package/dist/manifest.js +46 -6
- package/dist/migrate/apply.d.ts +1 -1
- package/dist/migrate/apply.js +5 -4
- package/dist/migrate/model.d.ts +112 -2
- package/dist/migrate/plan.js +11 -9
- package/dist/migrate/storage.d.ts +6 -6
- package/dist/migrate/storage.js +25 -15
- package/dist/names.d.ts +11 -11
- package/dist/names.js +36 -13
- package/dist/permissions.d.ts +2 -2
- package/dist/permissions.js +8 -8
- package/dist/property.js +2 -1
- package/dist/query.d.ts +6 -6
- package/dist/query.js +41 -30
- package/dist/runtime/cursor.js +1 -1
- package/dist/runtime/generated-actions.d.ts +4 -4
- package/dist/runtime/generated-actions.js +5 -9
- package/dist/runtime/history.js +1 -1
- package/dist/runtime/model.d.ts +2 -2
- package/dist/runtime/model.js +1 -1
- package/dist/runtime/principals.js +16 -13
- package/dist/runtime/reads.js +1 -1
- package/dist/runtime/records.js +1 -1
- package/dist/runtime/sql-runtime.js +1 -1
- package/dist/service/config.d.ts +1 -1
- package/dist/service/errors.d.ts +1 -0
- package/dist/service/errors.js +5 -1
- package/dist/service/handlers.js +27 -18
- package/dist/service/runtime-contract.d.ts +1 -1
- package/dist/service/schemas.d.ts +201 -26
- package/dist/service/schemas.js +30 -21
- package/dist/service/service.js +11 -4
- package/dist/user.js +3 -0
- package/dist/warehouse-backed.js +3 -3
- package/dist/wire.d.ts +5 -0
- package/dist/wire.js +21 -0
- package/package.json +1 -1
package/dist/migrate/model.d.ts
CHANGED
|
@@ -42,7 +42,117 @@ export type MigrationRefusal = typeof MigrationRefusal.Type;
|
|
|
42
42
|
export declare const MigrationPlan: Schema.Struct<{
|
|
43
43
|
/** The hash of the applied manifest the plan starts from; absent for a fresh database. */
|
|
44
44
|
readonly currentHash: Schema.optionalKey<Schema.String>;
|
|
45
|
-
readonly target: Schema.Struct<{
|
|
45
|
+
readonly target: Schema.Union<readonly [Schema.encodeKeys<Schema.Struct<{
|
|
46
|
+
readonly entities: Schema.$Array<Schema.encodeKeys<Schema.Struct<{
|
|
47
|
+
readonly properties: Schema.$Array<Schema.encodeKeys<Schema.Struct<{
|
|
48
|
+
readonly column: Schema.optionalKey<Schema.String>;
|
|
49
|
+
readonly scope: Schema.optionalKey<Schema.$Array<Schema.String>>;
|
|
50
|
+
readonly name: Schema.String;
|
|
51
|
+
readonly type: Schema.Literals<readonly ["text", "email", "url", "integer", "number", "boolean", "date", "timestamp", "select", "json"]>;
|
|
52
|
+
readonly label: Schema.String;
|
|
53
|
+
readonly description: Schema.optionalKey<Schema.String>;
|
|
54
|
+
readonly optional: Schema.Boolean;
|
|
55
|
+
readonly unique: Schema.Boolean;
|
|
56
|
+
readonly managed: Schema.Boolean;
|
|
57
|
+
readonly default: Schema.optionalKey<Schema.Codec<Schema.Json, Schema.Json, never, never>>;
|
|
58
|
+
readonly display: Schema.optionalKey<Schema.String>;
|
|
59
|
+
readonly renamedFrom: Schema.optionalKey<Schema.String>;
|
|
60
|
+
readonly min: Schema.optionalKey<Schema.Number>;
|
|
61
|
+
readonly max: Schema.optionalKey<Schema.Number>;
|
|
62
|
+
readonly maxLength: Schema.optionalKey<Schema.Number>;
|
|
63
|
+
readonly options: Schema.optionalKey<Schema.$Array<Schema.Struct<{
|
|
64
|
+
readonly value: Schema.String;
|
|
65
|
+
readonly label: Schema.String;
|
|
66
|
+
readonly description: Schema.String;
|
|
67
|
+
}>>>;
|
|
68
|
+
}>, {
|
|
69
|
+
readonly renamedFrom: "renamed_from";
|
|
70
|
+
readonly maxLength: "max_length";
|
|
71
|
+
}>>;
|
|
72
|
+
readonly relationships: Schema.$Array<Schema.encodeKeys<Schema.Struct<{
|
|
73
|
+
readonly on: Schema.optionalKey<Schema.encodeKeys<Schema.Struct<{
|
|
74
|
+
readonly property: Schema.String;
|
|
75
|
+
readonly targetProperty: Schema.String;
|
|
76
|
+
}>, {
|
|
77
|
+
readonly targetProperty: "target_property";
|
|
78
|
+
}>>;
|
|
79
|
+
readonly onDelete: Schema.optionalKey<Schema.decodeTo<Schema.Literals<readonly ["setNull", "cascade", "restrict"]>, Schema.Literals<readonly ["set_null", "cascade", "restrict"]>, never, never>>;
|
|
80
|
+
readonly name: Schema.String;
|
|
81
|
+
readonly to: Schema.String;
|
|
82
|
+
readonly inverse: Schema.String;
|
|
83
|
+
readonly cardinality: Schema.Literals<readonly ["one", "many"]>;
|
|
84
|
+
readonly optional: Schema.optionalKey<Schema.Boolean>;
|
|
85
|
+
readonly ordered: Schema.optionalKey<Schema.Boolean>;
|
|
86
|
+
readonly phrase: Schema.Tuple<readonly [Schema.String, Schema.String]>;
|
|
87
|
+
readonly description: Schema.String;
|
|
88
|
+
}>, {
|
|
89
|
+
readonly onDelete: "on_delete";
|
|
90
|
+
}>>;
|
|
91
|
+
readonly source: Schema.optionalKey<Schema.Struct<{
|
|
92
|
+
readonly asset: Schema.String;
|
|
93
|
+
readonly key: Schema.$Array<Schema.String>;
|
|
94
|
+
readonly currency: Schema.optionalKey<Schema.String>;
|
|
95
|
+
}>>;
|
|
96
|
+
readonly measures: Schema.optionalKey<Schema.$Array<Schema.Struct<{
|
|
97
|
+
readonly name: Schema.String;
|
|
98
|
+
readonly label: Schema.String;
|
|
99
|
+
readonly description: Schema.String;
|
|
100
|
+
readonly unit: Schema.Literals<readonly ["number", "currency", "ratio", "percent"]>;
|
|
101
|
+
readonly format: Schema.Literals<readonly ["decimal", "currency", "percent"]>;
|
|
102
|
+
readonly expression: Schema.Codec<Schema.Json, Schema.Json, never, never>;
|
|
103
|
+
}>>>;
|
|
104
|
+
readonly id: Schema.String;
|
|
105
|
+
readonly name: Schema.String;
|
|
106
|
+
readonly label: Schema.String;
|
|
107
|
+
readonly plural: Schema.String;
|
|
108
|
+
readonly title: Schema.String;
|
|
109
|
+
readonly description: Schema.String;
|
|
110
|
+
readonly builtIn: Schema.Boolean;
|
|
111
|
+
readonly renamedFrom: Schema.optionalKey<Schema.String>;
|
|
112
|
+
readonly guide: Schema.optionalKey<Schema.String>;
|
|
113
|
+
readonly indexes: Schema.$Array<Schema.Struct<{
|
|
114
|
+
readonly properties: Schema.$Array<Schema.String>;
|
|
115
|
+
readonly unique: Schema.Boolean;
|
|
116
|
+
}>>;
|
|
117
|
+
readonly actions: Schema.$Array<Schema.Struct<{
|
|
118
|
+
readonly name: Schema.String;
|
|
119
|
+
readonly description: Schema.String;
|
|
120
|
+
readonly permission: Schema.optionalKey<Schema.String>;
|
|
121
|
+
readonly input: Schema.$Record<Schema.String, Schema.Codec<Schema.Json, Schema.Json, never, never>>;
|
|
122
|
+
readonly connections: Schema.optionalKey<Schema.$Array<Schema.String>>;
|
|
123
|
+
}>>;
|
|
124
|
+
readonly permissions: Schema.optionalKey<Schema.Struct<{
|
|
125
|
+
readonly read: Schema.String;
|
|
126
|
+
readonly create: Schema.optionalKey<Schema.String>;
|
|
127
|
+
readonly update: Schema.optionalKey<Schema.String>;
|
|
128
|
+
readonly delete: Schema.optionalKey<Schema.String>;
|
|
129
|
+
readonly link: Schema.optionalKey<Schema.String>;
|
|
130
|
+
readonly unlink: Schema.optionalKey<Schema.String>;
|
|
131
|
+
}>>;
|
|
132
|
+
}>, {
|
|
133
|
+
readonly builtIn: "built_in";
|
|
134
|
+
readonly renamedFrom: "renamed_from";
|
|
135
|
+
}>>;
|
|
136
|
+
readonly roles: Schema.$Array<Schema.encodeKeys<Schema.Struct<{
|
|
137
|
+
readonly name: Schema.String;
|
|
138
|
+
readonly description: Schema.String;
|
|
139
|
+
readonly builtIn: Schema.Boolean;
|
|
140
|
+
readonly permissions: Schema.optionalKey<Schema.$Array<Schema.String>>;
|
|
141
|
+
}>, {
|
|
142
|
+
readonly builtIn: "built_in";
|
|
143
|
+
}>>;
|
|
144
|
+
readonly version: Schema.Int;
|
|
145
|
+
readonly hash: Schema.String;
|
|
146
|
+
readonly signIn: Schema.optionalKey<Schema.Struct<{
|
|
147
|
+
readonly policy: Schema.String;
|
|
148
|
+
}>>;
|
|
149
|
+
readonly connections: Schema.optionalKey<Schema.$Array<Schema.Struct<{
|
|
150
|
+
readonly key: Schema.String;
|
|
151
|
+
readonly description: Schema.String;
|
|
152
|
+
}>>>;
|
|
153
|
+
}>, {
|
|
154
|
+
readonly signIn: "sign_in";
|
|
155
|
+
}>, Schema.Struct<{
|
|
46
156
|
readonly version: Schema.Int;
|
|
47
157
|
readonly hash: Schema.String;
|
|
48
158
|
readonly entities: Schema.$Array<Schema.Struct<{
|
|
@@ -138,7 +248,7 @@ export declare const MigrationPlan: Schema.Struct<{
|
|
|
138
248
|
readonly key: Schema.String;
|
|
139
249
|
readonly description: Schema.String;
|
|
140
250
|
}>>>;
|
|
141
|
-
}>;
|
|
251
|
+
}>]>;
|
|
142
252
|
/** Non-transactional steps first, then the steps of the one transaction, in execution order. */
|
|
143
253
|
readonly steps: Schema.$Array<Schema.Struct<{
|
|
144
254
|
readonly kind: Schema.Literals<readonly ["createCatalog", "createEntity", "renameEntity", "dropEntities", "addProperty", "renameProperty", "dropProperty", "changeOptional", "changeDefault", "changeCheck", "addSelectOption", "rebuildSelect", "addRelationship", "dropRelationship", "changeOnDelete", "addForeignKey", "addIndex", "dropIndex"]>;
|
package/dist/migrate/plan.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { SNAKE_CASE_NAME, isSystemProperty, toSnakeCase } from "../names.js";
|
|
2
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
3
|
const GLOBAL_KINDS = new Set(["table", "type", "index"]);
|
|
4
4
|
function step(input) {
|
|
@@ -158,10 +158,11 @@ function validateEntity(entity, entityNames, invalid) {
|
|
|
158
158
|
for (const property of entity.properties) {
|
|
159
159
|
const path = `${entity.name}.properties.${property.name}`;
|
|
160
160
|
const values = (property.options ?? []).map((option) => option.value);
|
|
161
|
-
if (!
|
|
162
|
-
invalid(path, "property names are
|
|
161
|
+
if (!SNAKE_CASE_NAME.test(property.name) || isSystemProperty(property.name)) {
|
|
162
|
+
invalid(path, "property names are lowercase snake_case and cannot be system properties.");
|
|
163
163
|
}
|
|
164
|
-
if (property.type === "select" &&
|
|
164
|
+
if (property.type === "select" &&
|
|
165
|
+
(values.length === 0 || values.some((value) => !SNAKE_CASE_NAME.test(value)))) {
|
|
165
166
|
invalid(path, "a select property needs at least one non-empty option.");
|
|
166
167
|
}
|
|
167
168
|
if (new Set(values).size !== values.length) {
|
|
@@ -173,8 +174,8 @@ function validateEntity(entity, entityNames, invalid) {
|
|
|
173
174
|
}
|
|
174
175
|
for (const relationship of entity.relationships) {
|
|
175
176
|
const path = `${entity.name}.relationships.${relationship.name}`;
|
|
176
|
-
if (!
|
|
177
|
-
invalid(path, "relationship names are
|
|
177
|
+
if (!SNAKE_CASE_NAME.test(relationship.name) || isSystemProperty(relationship.name)) {
|
|
178
|
+
invalid(path, "relationship names are lowercase snake_case and cannot be system properties.");
|
|
178
179
|
}
|
|
179
180
|
if (!entityNames.has(relationship.to)) {
|
|
180
181
|
invalid(path, `it points at ${relationship.to}, which is not an entity.`);
|
|
@@ -295,9 +296,10 @@ function matchProperties(current, target) {
|
|
|
295
296
|
const addedProperties = [];
|
|
296
297
|
for (const property of target.properties) {
|
|
297
298
|
const sameName = byName.get(property.name);
|
|
298
|
-
const
|
|
299
|
-
?
|
|
300
|
-
:
|
|
299
|
+
const source = property.renamedFrom === undefined
|
|
300
|
+
? undefined
|
|
301
|
+
: (byName.get(property.renamedFrom) ?? byName.get(toSnakeCase(property.renamedFrom)));
|
|
302
|
+
const previous = source !== undefined && !claimed.has(source.name) ? source : undefined;
|
|
301
303
|
const match = sameName ?? previous;
|
|
302
304
|
if (match === undefined) {
|
|
303
305
|
addedProperties.push(property);
|
|
@@ -12,10 +12,10 @@ export declare const USER_TABLE = "user";
|
|
|
12
12
|
/** System properties and their columns, present on every entity table. */
|
|
13
13
|
export declare const SYSTEM_COLUMNS: {
|
|
14
14
|
readonly id: "id";
|
|
15
|
-
readonly
|
|
16
|
-
readonly
|
|
17
|
-
readonly
|
|
18
|
-
readonly
|
|
15
|
+
readonly created_at: "created_at";
|
|
16
|
+
readonly updated_at: "updated_at";
|
|
17
|
+
readonly created_by: "created_by";
|
|
18
|
+
readonly updated_by: "updated_by";
|
|
19
19
|
};
|
|
20
20
|
/** The kinds of change a history row records. */
|
|
21
21
|
export declare const HISTORY_KINDS: readonly ["create", "update", "delete", "link", "unlink"];
|
|
@@ -85,7 +85,7 @@ export interface EntityStorage {
|
|
|
85
85
|
export declare function quoteIdentifier(name: string): string;
|
|
86
86
|
/** `'value'`, with embedded quotes doubled (standard_conforming_strings is on). */
|
|
87
87
|
export declare function quoteLiteral(value: string): string;
|
|
88
|
-
/** `fitScore` → `fit_score`.
|
|
88
|
+
/** `fitScore` → `fit_score`. New property names already use snake_case; conversion remains for historical manifests. */
|
|
89
89
|
export declare function snakeCase(name: string): string;
|
|
90
90
|
/** An entity's table is named after the entity: `lead`, `user`. */
|
|
91
91
|
export declare function tableName(entity: string): string;
|
|
@@ -119,7 +119,7 @@ export declare function primaryKeyName(table: string): string;
|
|
|
119
119
|
/** An index over columns: `lead` + `status, created_at` → `lead_status_created_at_idx`. */
|
|
120
120
|
export declare function indexName(table: string, columns: ReadonlyArray<string>): string;
|
|
121
121
|
/**
|
|
122
|
-
* The column of an index field: a system property (`
|
|
122
|
+
* The column of an index field: a system property (`created_at` → `created_at`), a property, or a
|
|
123
123
|
* `rel.one` (`owner` → `owner_id`). Returns undefined for anything else.
|
|
124
124
|
*/
|
|
125
125
|
export declare function fieldColumnName(entity: EntityManifest, field: string): string | undefined;
|
package/dist/migrate/storage.js
CHANGED
|
@@ -11,10 +11,10 @@ export const USER_TABLE = "user";
|
|
|
11
11
|
/** System properties and their columns, present on every entity table. */
|
|
12
12
|
export const SYSTEM_COLUMNS = {
|
|
13
13
|
id: "id",
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
14
|
+
created_at: "created_at",
|
|
15
|
+
updated_at: "updated_at",
|
|
16
|
+
created_by: "created_by",
|
|
17
|
+
updated_by: "updated_by",
|
|
18
18
|
};
|
|
19
19
|
/** The kinds of change a history row records. */
|
|
20
20
|
export const HISTORY_KINDS = ["create", "update", "delete", "link", "unlink"];
|
|
@@ -43,7 +43,7 @@ export function quoteIdentifier(name) {
|
|
|
43
43
|
export function quoteLiteral(value) {
|
|
44
44
|
return `'${value.replaceAll("'", "''")}'`;
|
|
45
45
|
}
|
|
46
|
-
/** `fitScore` → `fit_score`.
|
|
46
|
+
/** `fitScore` → `fit_score`. New property names already use snake_case; conversion remains for historical manifests. */
|
|
47
47
|
export function snakeCase(name) {
|
|
48
48
|
return name.replace(/[A-Z]/g, (letter) => `_${letter.toLowerCase()}`);
|
|
49
49
|
}
|
|
@@ -100,10 +100,20 @@ export function indexName(table, columns) {
|
|
|
100
100
|
return `${table}_${columns.join("_")}_idx`;
|
|
101
101
|
}
|
|
102
102
|
/**
|
|
103
|
-
* The column of an index field: a system property (`
|
|
103
|
+
* The column of an index field: a system property (`created_at` → `created_at`), a property, or a
|
|
104
104
|
* `rel.one` (`owner` → `owner_id`). Returns undefined for anything else.
|
|
105
105
|
*/
|
|
106
106
|
export function fieldColumnName(entity, field) {
|
|
107
|
+
// Applied manifests may still name system fields in camelCase.
|
|
108
|
+
const historical = {
|
|
109
|
+
createdAt: "created_at",
|
|
110
|
+
updatedAt: "updated_at",
|
|
111
|
+
createdBy: "created_by",
|
|
112
|
+
updatedBy: "updated_by",
|
|
113
|
+
};
|
|
114
|
+
if (Object.hasOwn(historical, field)) {
|
|
115
|
+
return historical[field];
|
|
116
|
+
}
|
|
107
117
|
if (Object.hasOwn(SYSTEM_COLUMNS, field)) {
|
|
108
118
|
return SYSTEM_COLUMNS[field];
|
|
109
119
|
}
|
|
@@ -228,8 +238,8 @@ export function joinTable(entity, relationship) {
|
|
|
228
238
|
columns: [
|
|
229
239
|
{ name: keys.source, type: "uuid", notNull: true },
|
|
230
240
|
{ name: keys.target, type: "uuid", notNull: true },
|
|
231
|
-
{ name: SYSTEM_COLUMNS.
|
|
232
|
-
{ name: SYSTEM_COLUMNS.
|
|
241
|
+
{ name: SYSTEM_COLUMNS.created_at, type: "timestamptz", notNull: true, default: "now()" },
|
|
242
|
+
{ name: SYSTEM_COLUMNS.created_by, type: "uuid", notNull: false },
|
|
233
243
|
],
|
|
234
244
|
primaryKey: { name: primaryKeyName(name), columns: [keys.source, keys.target] },
|
|
235
245
|
constraints: [
|
|
@@ -253,7 +263,7 @@ export function joinTable(entity, relationship) {
|
|
|
253
263
|
}),
|
|
254
264
|
references: tableName(relationship.to),
|
|
255
265
|
},
|
|
256
|
-
userReference(name, SYSTEM_COLUMNS.
|
|
266
|
+
userReference(name, SYSTEM_COLUMNS.created_by),
|
|
257
267
|
],
|
|
258
268
|
indexes: [
|
|
259
269
|
{
|
|
@@ -311,10 +321,10 @@ export function historyTable(entity) {
|
|
|
311
321
|
function systemColumns() {
|
|
312
322
|
return [
|
|
313
323
|
{ name: SYSTEM_COLUMNS.id, type: "uuid", notNull: true, default: "gen_random_uuid()" },
|
|
314
|
-
{ name: SYSTEM_COLUMNS.
|
|
315
|
-
{ name: SYSTEM_COLUMNS.
|
|
316
|
-
{ name: SYSTEM_COLUMNS.
|
|
317
|
-
{ name: SYSTEM_COLUMNS.
|
|
324
|
+
{ name: SYSTEM_COLUMNS.created_at, type: "timestamptz", notNull: true, default: "now()" },
|
|
325
|
+
{ name: SYSTEM_COLUMNS.updated_at, type: "timestamptz", notNull: true, default: "now()" },
|
|
326
|
+
{ name: SYSTEM_COLUMNS.created_by, type: "uuid", notNull: false },
|
|
327
|
+
{ name: SYSTEM_COLUMNS.updated_by, type: "uuid", notNull: false },
|
|
318
328
|
];
|
|
319
329
|
}
|
|
320
330
|
/**
|
|
@@ -376,8 +386,8 @@ export function entityStorage(entity) {
|
|
|
376
386
|
],
|
|
377
387
|
primaryKey: { name: primaryKeyName(table), columns: [SYSTEM_COLUMNS.id] },
|
|
378
388
|
constraints: [
|
|
379
|
-
userReference(table, SYSTEM_COLUMNS.
|
|
380
|
-
userReference(table, SYSTEM_COLUMNS.
|
|
389
|
+
userReference(table, SYSTEM_COLUMNS.created_by),
|
|
390
|
+
userReference(table, SYSTEM_COLUMNS.updated_by),
|
|
381
391
|
...checks,
|
|
382
392
|
...ones.map((relationship) => relationshipForeignKey(entity.name, relationship)),
|
|
383
393
|
],
|
package/dist/names.d.ts
CHANGED
|
@@ -1,19 +1,19 @@
|
|
|
1
|
-
/**
|
|
1
|
+
/** Ontology names: lowercase snake_case. */
|
|
2
2
|
export declare const SNAKE_CASE_NAME: RegExp;
|
|
3
|
-
/**
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
*/
|
|
7
|
-
export declare const
|
|
3
|
+
/** Convert each character once, including acronym boundaries such as HTTPClient. */
|
|
4
|
+
export declare function toSnakeCase(name: string): string;
|
|
5
|
+
export declare function toCamelCase(name: string): string;
|
|
6
|
+
/** Historical property names accepted only as explicit rename sources. */
|
|
7
|
+
export declare const LEGACY_MEMBER_NAME: RegExp;
|
|
8
8
|
/** Stable entity identities, for example `ent_01J9Q4M8ZK`. */
|
|
9
9
|
export declare const ENTITY_ID: RegExp;
|
|
10
10
|
/** Properties every entity has. They are never declared and cannot be redeclared. */
|
|
11
|
-
export declare const SYSTEM_PROPERTIES: readonly ["id", "
|
|
12
|
-
export type SystemProperty =
|
|
11
|
+
export declare const SYSTEM_PROPERTIES: readonly ["id", "created_at", "updated_at", "created_by", "updated_by"];
|
|
12
|
+
export type SystemProperty = "id" | "createdAt" | "updatedAt" | "createdBy" | "updatedBy";
|
|
13
13
|
/** Actions the platform generates for every entity; authored actions cannot use these names. */
|
|
14
14
|
export declare const GENERATED_ACTIONS: readonly ["create", "update", "delete", "link", "unlink"];
|
|
15
|
-
/** Prefix of the generated `
|
|
16
|
-
export declare const GENERATED_UPSERT_PREFIX = "
|
|
15
|
+
/** Prefix of the generated `upsert_by_<property>` actions. */
|
|
16
|
+
export declare const GENERATED_UPSERT_PREFIX = "upsert_by_";
|
|
17
17
|
/** The built-in role every active app user holds. */
|
|
18
18
|
export declare const MEMBER_ROLE = "member";
|
|
19
19
|
/** Suffix reserved for the history table each entity gets. */
|
|
@@ -25,5 +25,5 @@ export declare const RESERVED_ENTITY_NAMES: readonly ["graph", "status", "descri
|
|
|
25
25
|
/** `/api/ontology/<entity>/{id}/history`, which a relationship's traversal would shadow. */
|
|
26
26
|
export declare const RESERVED_RELATIONSHIP_NAMES: readonly ["history"];
|
|
27
27
|
export declare function isSystemProperty(name: string): boolean;
|
|
28
|
-
/** `
|
|
28
|
+
/** `created_at` → `Created at`, `pilot_request` → `Pilot request`. */
|
|
29
29
|
export declare function humanize(name: string): string;
|
package/dist/names.js
CHANGED
|
@@ -1,24 +1,47 @@
|
|
|
1
|
-
/**
|
|
1
|
+
/** Ontology names: lowercase snake_case. */
|
|
2
2
|
export const SNAKE_CASE_NAME = /^[a-z][a-z0-9]*(?:_[a-z0-9]+)*$/;
|
|
3
|
-
/**
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
3
|
+
/** TypeScript names are converted only at the ontology's serialization boundary. */
|
|
4
|
+
function isUppercase(letter) {
|
|
5
|
+
return letter !== undefined && letter >= "A" && letter <= "Z";
|
|
6
|
+
}
|
|
7
|
+
function isLowercase(letter) {
|
|
8
|
+
return letter !== undefined && letter >= "a" && letter <= "z";
|
|
9
|
+
}
|
|
10
|
+
/** Convert each character once, including acronym boundaries such as HTTPClient. */
|
|
11
|
+
export function toSnakeCase(name) {
|
|
12
|
+
const characters = [];
|
|
13
|
+
for (let index = 0; index < name.length; index++) {
|
|
14
|
+
const current = name[index];
|
|
15
|
+
const previous = name[index - 1];
|
|
16
|
+
const next = name[index + 1];
|
|
17
|
+
const followsWord = isLowercase(previous) || (previous !== undefined && previous >= "0" && previous <= "9");
|
|
18
|
+
const endsAcronym = isUppercase(previous) && isLowercase(next);
|
|
19
|
+
if (isUppercase(current) && (followsWord || endsAcronym)) {
|
|
20
|
+
characters.push("_");
|
|
21
|
+
}
|
|
22
|
+
characters.push(current);
|
|
23
|
+
}
|
|
24
|
+
return characters.join("").toLowerCase();
|
|
25
|
+
}
|
|
26
|
+
export function toCamelCase(name) {
|
|
27
|
+
return name.replace(/_([a-z0-9])/g, (_, letter) => letter.toUpperCase());
|
|
28
|
+
}
|
|
29
|
+
/** Historical property names accepted only as explicit rename sources. */
|
|
30
|
+
export const LEGACY_MEMBER_NAME = /^(?:[a-z][a-zA-Z0-9]*|[a-z][a-z0-9]*(?:_[a-z0-9]+)*)$/;
|
|
8
31
|
/** Stable entity identities, for example `ent_01J9Q4M8ZK`. */
|
|
9
32
|
export const ENTITY_ID = /^ent_[A-Za-z0-9_]+$/;
|
|
10
33
|
/** Properties every entity has. They are never declared and cannot be redeclared. */
|
|
11
34
|
export const SYSTEM_PROPERTIES = [
|
|
12
35
|
"id",
|
|
13
|
-
"
|
|
14
|
-
"
|
|
15
|
-
"
|
|
16
|
-
"
|
|
36
|
+
"created_at",
|
|
37
|
+
"updated_at",
|
|
38
|
+
"created_by",
|
|
39
|
+
"updated_by",
|
|
17
40
|
];
|
|
18
41
|
/** Actions the platform generates for every entity; authored actions cannot use these names. */
|
|
19
42
|
export const GENERATED_ACTIONS = ["create", "update", "delete", "link", "unlink"];
|
|
20
|
-
/** Prefix of the generated `
|
|
21
|
-
export const GENERATED_UPSERT_PREFIX = "
|
|
43
|
+
/** Prefix of the generated `upsert_by_<property>` actions. */
|
|
44
|
+
export const GENERATED_UPSERT_PREFIX = "upsert_by_";
|
|
22
45
|
/** The built-in role every active app user holds. */
|
|
23
46
|
export const MEMBER_ROLE = "member";
|
|
24
47
|
/** Suffix reserved for the history table each entity gets. */
|
|
@@ -39,7 +62,7 @@ export const RESERVED_RELATIONSHIP_NAMES = ["history"];
|
|
|
39
62
|
export function isSystemProperty(name) {
|
|
40
63
|
return SYSTEM_PROPERTIES.includes(name);
|
|
41
64
|
}
|
|
42
|
-
/** `
|
|
65
|
+
/** `created_at` → `Created at`, `pilot_request` → `Pilot request`. */
|
|
43
66
|
export function humanize(name) {
|
|
44
67
|
const words = name
|
|
45
68
|
.replace(/([a-z0-9])([A-Z])/g, "$1 $2")
|
package/dist/permissions.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Permissions are `<entity>:<verb>` strings. The verbs are `read`, the generated `create`, `update`,
|
|
3
|
-
* `delete`, `link` and `unlink`, and the
|
|
3
|
+
* `delete`, `link` and `unlink`, and the snake_case name of an authored action. A role may use `*` for
|
|
4
4
|
* the entity (`*:read`), the verb (`lead:*`) or both (`*:*`); what an action or a read requires never
|
|
5
5
|
* holds a wildcard.
|
|
6
6
|
*/
|
|
@@ -34,7 +34,7 @@ export declare function isAllowed(required: string, principal: PermissionHolder)
|
|
|
34
34
|
/** The first of the required permissions the principal lacks, or `undefined` when it holds all. */
|
|
35
35
|
export declare function missingPermission(required: ReadonlyArray<string>, principal: PermissionHolder): string | undefined;
|
|
36
36
|
/**
|
|
37
|
-
* What a generated action requires: `<entity>:<action>`, except `
|
|
37
|
+
* What a generated action requires: `<entity>:<action>`, except `upsert_by_<property>`, which may
|
|
38
38
|
* create or update and so requires both `<entity>:create` and `<entity>:update`.
|
|
39
39
|
*/
|
|
40
40
|
export declare function generatedActionPermissions(entity: string, action: string): ReadonlyArray<string>;
|
package/dist/permissions.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { GENERATED_UPSERT_PREFIX } from "./names.js";
|
|
1
|
+
import { GENERATED_UPSERT_PREFIX, toSnakeCase } from "./names.js";
|
|
2
2
|
/**
|
|
3
3
|
* Permissions are `<entity>:<verb>` strings. The verbs are `read`, the generated `create`, `update`,
|
|
4
|
-
* `delete`, `link` and `unlink`, and the
|
|
4
|
+
* `delete`, `link` and `unlink`, and the snake_case name of an authored action. A role may use `*` for
|
|
5
5
|
* the entity (`*:read`), the verb (`lead:*`) or both (`*:*`); what an action or a read requires never
|
|
6
6
|
* holds a wildcard.
|
|
7
7
|
*/
|
|
@@ -11,8 +11,8 @@ export const WILDCARD = "*";
|
|
|
11
11
|
export const READ_VERB = "read";
|
|
12
12
|
/** What the built-in `member` role holds when `defineOntology` does not say: reading everything. */
|
|
13
13
|
export const DEFAULT_MEMBER_PERMISSIONS = [`${WILDCARD}:${READ_VERB}`];
|
|
14
|
-
/** `<entity>:<verb>` with lowercase snake_case entities and
|
|
15
|
-
const PERMISSION = /^(\*|[a-z][a-
|
|
14
|
+
/** `<entity>:<verb>` with lowercase snake_case entities and lowercase snake_case verbs, or `*` for either. */
|
|
15
|
+
const PERMISSION = /^(\*|[a-z][a-zA-Z0-9]*(?:_[a-z0-9]+)*):(\*|[a-z][a-zA-Z0-9]*(?:_[a-z0-9]+)*)$/;
|
|
16
16
|
/** The entity and verb of a permission, or `undefined` when the value is not one. */
|
|
17
17
|
export function parsePermission(value) {
|
|
18
18
|
if (typeof value !== "string") {
|
|
@@ -32,8 +32,8 @@ export function grants(granted, required) {
|
|
|
32
32
|
if (held === undefined || wanted === undefined) {
|
|
33
33
|
return false;
|
|
34
34
|
}
|
|
35
|
-
return ((held.entity === WILDCARD || held.entity === wanted.entity) &&
|
|
36
|
-
(held.verb === WILDCARD || held.verb === wanted.verb));
|
|
35
|
+
return ((held.entity === WILDCARD || toSnakeCase(held.entity) === toSnakeCase(wanted.entity)) &&
|
|
36
|
+
(held.verb === WILDCARD || toSnakeCase(held.verb) === toSnakeCase(wanted.verb)));
|
|
37
37
|
}
|
|
38
38
|
/**
|
|
39
39
|
* Whether the principal may do what `required` names: one of its permissions equals it, or covers it
|
|
@@ -47,11 +47,11 @@ export function missingPermission(required, principal) {
|
|
|
47
47
|
return required.find((permission) => !isAllowed(permission, principal));
|
|
48
48
|
}
|
|
49
49
|
/**
|
|
50
|
-
* What a generated action requires: `<entity>:<action>`, except `
|
|
50
|
+
* What a generated action requires: `<entity>:<action>`, except `upsert_by_<property>`, which may
|
|
51
51
|
* create or update and so requires both `<entity>:create` and `<entity>:update`.
|
|
52
52
|
*/
|
|
53
53
|
export function generatedActionPermissions(entity, action) {
|
|
54
|
-
return action.startsWith(GENERATED_UPSERT_PREFIX)
|
|
54
|
+
return toSnakeCase(action).startsWith(GENERATED_UPSERT_PREFIX)
|
|
55
55
|
? [permissionOf(entity, "create"), permissionOf(entity, "update")]
|
|
56
56
|
: [permissionOf(entity, action)];
|
|
57
57
|
}
|
package/dist/property.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Schema } from "effect";
|
|
2
2
|
import { humanize } from "./names.js";
|
|
3
|
+
import { nameToWire } from "./authoring-names.js";
|
|
3
4
|
/** The closed set of storage types. Each has a known column, JSON Schema, filters and rendering. */
|
|
4
5
|
export const PROPERTY_TYPES = [
|
|
5
6
|
"text",
|
|
@@ -171,7 +172,7 @@ function ref(target, options) {
|
|
|
171
172
|
...(settings.label !== undefined ? { title: settings.label } : {}),
|
|
172
173
|
description: settings.description ?? `The id of a ${entityName} record.`,
|
|
173
174
|
format: "uuid",
|
|
174
|
-
[REFERENCE_ANNOTATION]: entityName,
|
|
175
|
+
[REFERENCE_ANNOTATION]: nameToWire(entityName),
|
|
175
176
|
}).check(Schema.isPattern(UUID));
|
|
176
177
|
return (settings.optional === true ? Schema.optionalKey(schema) : schema);
|
|
177
178
|
}
|
package/dist/query.d.ts
CHANGED
|
@@ -2,7 +2,7 @@ import { Schema } from "effect";
|
|
|
2
2
|
import type { JsonSchemaObject } from "./analyze.js";
|
|
3
3
|
import type { EntityManifest, OntologyManifest } from "./manifest.js";
|
|
4
4
|
import type { PropertyType } from "./property.js";
|
|
5
|
-
/** How a record field is stored: a property type, or a UUID for ids and `<relationship>
|
|
5
|
+
/** How a record field is stored: a property type, or a UUID for ids and `<relationship>_id` keys. */
|
|
6
6
|
export type StorageType = PropertyType | "uuid";
|
|
7
7
|
/**
|
|
8
8
|
* The query language of `find` and of the HTTP API's list and query endpoints.
|
|
@@ -15,7 +15,7 @@ export type StorageType = PropertyType | "uuid";
|
|
|
15
15
|
* "tags": { "some": { "name": { "eq": "priority" } } },
|
|
16
16
|
* "or": [{ "fitScore": { "gte": 80 } }, { "source": "pilot_request" }]
|
|
17
17
|
* },
|
|
18
|
-
* "orderBy": [{ "field": "
|
|
18
|
+
* "orderBy": [{ "field": "created_at", "direction": "desc" }],
|
|
19
19
|
* "include": { "owner": true },
|
|
20
20
|
* "limit": 50
|
|
21
21
|
* }
|
|
@@ -114,7 +114,7 @@ export declare function queryResultSchema<S extends Schema.Top>(record: S): Sche
|
|
|
114
114
|
readonly items: Schema.$Array<S>;
|
|
115
115
|
readonly nextCursor: Schema.NullOr<Schema.String>;
|
|
116
116
|
}>;
|
|
117
|
-
/** A field a query can filter and sort on: a system field, a property, or a `<relationship>
|
|
117
|
+
/** A field a query can filter and sort on: a system field, a property, or a `<relationship>_id`. */
|
|
118
118
|
export interface QueryField {
|
|
119
119
|
readonly name: string;
|
|
120
120
|
readonly storage: StorageType;
|
|
@@ -170,12 +170,12 @@ export interface EntityQuerySchemas {
|
|
|
170
170
|
* include. Every struct is closed: an unknown field, operator or key is an issue whatever the parse
|
|
171
171
|
* options, never a silently ignored filter.
|
|
172
172
|
*/
|
|
173
|
-
export declare function entityQuerySchemas(manifest: OntologyManifest, entityName: string): EntityQuerySchemas;
|
|
173
|
+
export declare function entityQuerySchemas(manifest: OntologyManifest, entityName: string, wire?: boolean): EntityQuerySchemas;
|
|
174
174
|
/** The query schema of one entity; see {@link entityQuerySchemas}. */
|
|
175
175
|
export declare function entityQuerySchema(manifest: OntologyManifest, entityName: string): Schema.Codec<Query>;
|
|
176
176
|
/**
|
|
177
177
|
* The record of an entity as the runtime returns it: system fields, properties (`null` when empty;
|
|
178
|
-
* dates as `YYYY-MM-DD`, timestamps as ISO 8601 in UTC) and a `<relationship>
|
|
178
|
+
* dates as `YYYY-MM-DD`, timestamps as ISO 8601 in UTC) and a `<relationship>_id` per `rel.one`. It
|
|
179
179
|
* checks the shape of stored values, not the refinements of the definition (`maxLength`, `min`,
|
|
180
180
|
* patterns), so that tightening a refinement never makes existing records unreadable.
|
|
181
181
|
*/
|
|
@@ -185,7 +185,7 @@ export type JsonRecord = {
|
|
|
185
185
|
readonly [field: string]: Schema.Json;
|
|
186
186
|
};
|
|
187
187
|
/** A page of an entity's records, with the relationships a query may include. */
|
|
188
|
-
export declare function entityQueryResultSchema(manifest: OntologyManifest, entityName: string): Schema.Codec<QueryResult<JsonRecord
|
|
188
|
+
export declare function entityQueryResultSchema(manifest: OntologyManifest, entityName: string, wire?: boolean): Schema.Codec<QueryResult<JsonRecord>, unknown>;
|
|
189
189
|
/** The JSON Schema (draft 2020-12) of an entity's queries, for MCP tools, OpenAPI and the console. */
|
|
190
190
|
export declare function queryJsonSchema(manifest: OntologyManifest, entityName: string): JsonSchemaObject;
|
|
191
191
|
/** The JSON Schema (draft 2020-12) of a page of an entity's records. */
|