@hyperscale0/udl 4.5.1 → 4.6.1

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 (41) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/dist/field-value.d.ts +5 -0
  3. package/dist/field-value.d.ts.map +1 -0
  4. package/dist/field-value.js +85 -0
  5. package/dist/field-value.js.map +1 -0
  6. package/dist/index.d.ts +1 -0
  7. package/dist/index.d.ts.map +1 -1
  8. package/dist/index.js +1 -0
  9. package/dist/index.js.map +1 -1
  10. package/dist/object-contract.d.ts +35 -12
  11. package/dist/object-contract.d.ts.map +1 -1
  12. package/dist/object-contract.js +33 -91
  13. package/dist/object-contract.js.map +1 -1
  14. package/dist/object-semantics-schema.d.ts +5565 -0
  15. package/dist/object-semantics-schema.d.ts.map +1 -0
  16. package/dist/object-semantics-schema.js +119 -0
  17. package/dist/object-semantics-schema.js.map +1 -0
  18. package/dist/object-semantics.d.ts +6 -0
  19. package/dist/object-semantics.d.ts.map +1 -0
  20. package/dist/object-semantics.js +279 -0
  21. package/dist/object-semantics.js.map +1 -0
  22. package/dist/object-structures.d.ts +4 -0
  23. package/dist/object-structures.d.ts.map +1 -0
  24. package/dist/object-structures.js +134 -0
  25. package/dist/object-structures.js.map +1 -0
  26. package/dist/schema.d.ts +205 -0
  27. package/dist/schema.d.ts.map +1 -1
  28. package/dist/schema.js +5 -1
  29. package/dist/schema.js.map +1 -1
  30. package/dist/validation.js +1 -1
  31. package/dist/validation.js.map +1 -1
  32. package/package.json +2 -2
  33. package/spec/udl.schema.json +296 -245
  34. package/src/field-value.ts +87 -0
  35. package/src/index.ts +1 -0
  36. package/src/object-contract.ts +87 -99
  37. package/src/object-semantics-schema.ts +148 -0
  38. package/src/object-semantics.ts +378 -0
  39. package/src/object-structures.ts +255 -0
  40. package/src/schema.ts +5 -1
  41. package/src/validation.ts +1 -1
@@ -0,0 +1,87 @@
1
+ import * as z from "zod";
2
+ import { udlObjectIdSchema, type UdlField } from "./schema.js";
3
+
4
+ const amount = z.string().regex(/^(0|[1-9][0-9]{0,17})$/);
5
+ const text = z.string().min(1).max(2048);
6
+
7
+ /** Values, rather than declarations. The same validator feeds forms and admission. */
8
+ export function udlFieldValueSchema(field: UdlField): z.ZodType {
9
+ let schema: z.ZodType;
10
+ switch (field.type) {
11
+ case "money":
12
+ schema = amount
13
+ .refine(
14
+ (value) =>
15
+ /^(0|[1-9][0-9]{0,17})$/.test(value) &&
16
+ (field.minimum === undefined ||
17
+ BigInt(value) >= BigInt(field.minimum)) &&
18
+ (field.maximum === undefined ||
19
+ BigInt(value) <= BigInt(field.maximum)),
20
+ "Money is outside its declared bounds",
21
+ )
22
+ .meta({
23
+ ...(field.minimum !== undefined
24
+ ? { "x-udl-minimum": field.minimum }
25
+ : {}),
26
+ ...(field.maximum !== undefined
27
+ ? { "x-udl-maximum": field.maximum }
28
+ : {}),
29
+ });
30
+ break;
31
+ case "account":
32
+ schema = text;
33
+ break;
34
+ case "ref":
35
+ schema = field.targetKind === "object" ? udlObjectIdSchema : text;
36
+ break;
37
+ case "date":
38
+ schema = z.iso.datetime({ offset: true });
39
+ break;
40
+ case "duration":
41
+ schema = z.number().int().safe().positive();
42
+ break;
43
+ case "text": {
44
+ let value = z
45
+ .string()
46
+ .min(field.minLength ?? 1)
47
+ .max(field.maxLength ?? 2048);
48
+ if (field.pattern) value = value.regex(new RegExp(field.pattern));
49
+ schema = value;
50
+ break;
51
+ }
52
+ case "integer": {
53
+ let value = z.number().int().safe();
54
+ if (field.minimum !== undefined) value = value.min(field.minimum);
55
+ if (field.maximum !== undefined) value = value.max(field.maximum);
56
+ schema = value;
57
+ break;
58
+ }
59
+ case "percent":
60
+ schema = z.number().int().min(0).max(10000);
61
+ break;
62
+ case "boolean":
63
+ schema = z.boolean();
64
+ break;
65
+ case "enum":
66
+ schema = z.enum(field.values);
67
+ break;
68
+ case "list": {
69
+ const item =
70
+ field.item === "money"
71
+ ? amount
72
+ : field.item === "date"
73
+ ? z.iso.datetime({ offset: true })
74
+ : field.item === "integer"
75
+ ? z.number().int().safe()
76
+ : field.item === "ref" && field.targetKind === "object"
77
+ ? udlObjectIdSchema
78
+ : text;
79
+ schema = z.array(item).max(field.maxItems);
80
+ break;
81
+ }
82
+ }
83
+ if ("value" in field && field.value !== undefined)
84
+ schema = z.literal(field.value);
85
+ if (field.description) schema = schema.describe(field.description);
86
+ return schema;
87
+ }
package/src/index.ts CHANGED
@@ -30,4 +30,5 @@ export {
30
30
  validateReportDefinition,
31
31
  reportExpressionTypes,
32
32
  } from "./reporting-validation.js";
33
+ export { udlFieldValueSchema } from "./field-value.js";
33
34
  export * from "./object-contract.js";
@@ -1,9 +1,18 @@
1
1
  import * as z from "zod";
2
+ import { udlFieldValueSchema } from "./field-value.js";
3
+ import {
4
+ projectDocumentSemantics,
5
+ presentationLabel,
6
+ } from "./object-semantics.js";
7
+ import type { DocumentSemantics } from "./object-semantics-schema.js";
8
+ export * from "./object-semantics-schema.js";
9
+ export {
10
+ projectDocumentSemantics,
11
+ presentationLabel,
12
+ } from "./object-semantics.js";
2
13
  import {
3
14
  udlExternalIdSchema,
4
- udlObjectIdSchema,
5
15
  type UdlDocument,
6
- type UdlField,
7
16
  type UdlObjectField,
8
17
  type UdlObjectKind,
9
18
  type UdlSubjectRequirement,
@@ -43,9 +52,30 @@ export interface ObjectKindDiscovery {
43
52
  fields: readonly UdlObjectField[];
44
53
  authoredFields: readonly string[];
45
54
  columns: readonly string[];
55
+ filters: readonly {
56
+ field: string;
57
+ type: "text" | "enum" | "date" | "boolean" | "integer";
58
+ }[];
46
59
  createSchema: JsonSchemaDocument;
47
60
  actions: readonly ObjectActionDiscovery[];
48
61
  }
62
+ export function objectKindFilters(
63
+ kind: UdlObjectKind,
64
+ ): ObjectKindDiscovery["filters"] {
65
+ return kind.columns.flatMap((column) => {
66
+ const field = kind.fields.find((item) => item.name === column);
67
+ return field &&
68
+ !field.sensitive &&
69
+ ["text", "enum", "date", "boolean", "integer"].includes(field.type)
70
+ ? [
71
+ {
72
+ field: column,
73
+ type: field.type as ObjectKindDiscovery["filters"][number]["type"],
74
+ },
75
+ ]
76
+ : [];
77
+ });
78
+ }
49
79
  export type ObjectActionTarget =
50
80
  | { kind: "attachment"; attachment: string }
51
81
  | { kind: "instance"; attachment: string; instanceId: string };
@@ -69,10 +99,41 @@ export interface ObjectActionDiscovery extends ObjectActionContext {
69
99
  creationOnlyNames?: readonly string[];
70
100
  inputSchema: JsonSchemaDocument;
71
101
  fieldsSchema: JsonSchemaDocument;
72
- availability:
73
- | { status: "available" }
74
- | { status: "unavailable"; code: string; message: string };
102
+ availability: ObjectActionAvailability;
75
103
  }
104
+ export type ObjectActionBlockerCode =
105
+ | "permission_required"
106
+ | "state_changed"
107
+ | "not_due"
108
+ | "deadline_passed"
109
+ | "clock_pending"
110
+ | "condition_not_met"
111
+ | "already_used"
112
+ | "evidence_required"
113
+ | "account_unavailable"
114
+ | "insufficient_money"
115
+ | "setup_required"
116
+ | "invalid_input"
117
+ | "operation_pending";
118
+ export type ObjectActionAvailability =
119
+ | { status: "available" }
120
+ | {
121
+ status: "requires_input";
122
+ subjectFields: readonly string[];
123
+ actionInputs: readonly string[];
124
+ parties: readonly string[];
125
+ reason: string;
126
+ }
127
+ | {
128
+ status: "unavailable";
129
+ blockers: readonly {
130
+ code: ObjectActionBlockerCode;
131
+ reason: string;
132
+ recheckAt?: string;
133
+ }[];
134
+ }
135
+ | { status: "unknown"; reason: string };
136
+
76
137
  export type EvidenceState =
77
138
  | "missing"
78
139
  | "pending"
@@ -100,13 +161,14 @@ export interface ObjectActionState extends ObjectActionDiscovery {
100
161
  /** GET /v1/products/{productId}/objects. Core supplies the frozen Build identity. */
101
162
  export type RetainedObjectKind = Pick<
102
163
  ObjectKindDiscovery,
103
- "kind" | "title" | "fields" | "authoredFields" | "columns"
164
+ "kind" | "title" | "fields" | "authoredFields" | "columns" | "filters"
104
165
  > & {
105
166
  productBuildId: string;
106
167
  digest: string;
107
168
  creation: false;
108
169
  };
109
170
  export interface ObjectDiscovery {
171
+ semantics: DocumentSemantics;
110
172
  navigation: readonly { kind: string; title: string; creation: boolean }[];
111
173
  retainedKinds: readonly RetainedObjectKind[];
112
174
  productBuildId: string;
@@ -131,6 +193,8 @@ export interface ObjectInstance {
131
193
  export interface ObjectListResponse {
132
194
  items: ObjectInstance[];
133
195
  nextCursor?: string;
196
+ total: number;
197
+ facets: Record<string, Record<string, number>>;
134
198
  }
135
199
  export interface ObjectActionResponse {
136
200
  object: ObjectInstance;
@@ -138,90 +202,6 @@ export interface ObjectActionResponse {
138
202
  evidence: readonly ObjectEvidenceSummary[];
139
203
  }
140
204
 
141
- const amount = z.string().regex(/^(0|[1-9][0-9]{0,17})$/);
142
- const text = z.string().min(1).max(2048);
143
-
144
- /** Values, rather than declarations. The same validator feeds forms and admission. */
145
- export function udlFieldValueSchema(field: UdlField): z.ZodType {
146
- let schema: z.ZodType;
147
- switch (field.type) {
148
- case "money":
149
- schema = amount
150
- .refine(
151
- (value) =>
152
- /^(0|[1-9][0-9]{0,17})$/.test(value) &&
153
- (field.minimum === undefined ||
154
- BigInt(value) >= BigInt(field.minimum)) &&
155
- (field.maximum === undefined ||
156
- BigInt(value) <= BigInt(field.maximum)),
157
- "Money is outside its declared bounds",
158
- )
159
- .meta({
160
- ...(field.minimum !== undefined
161
- ? { "x-udl-minimum": field.minimum }
162
- : {}),
163
- ...(field.maximum !== undefined
164
- ? { "x-udl-maximum": field.maximum }
165
- : {}),
166
- });
167
- break;
168
- case "account":
169
- schema = text;
170
- break;
171
- case "ref":
172
- schema = field.targetKind === "object" ? udlObjectIdSchema : text;
173
- break;
174
- case "date":
175
- schema = z.iso.datetime({ offset: true });
176
- break;
177
- case "duration":
178
- schema = z.number().int().safe().positive();
179
- break;
180
- case "text": {
181
- let value = z
182
- .string()
183
- .min(field.minLength ?? 1)
184
- .max(field.maxLength ?? 2048);
185
- if (field.pattern) value = value.regex(new RegExp(field.pattern));
186
- schema = value;
187
- break;
188
- }
189
- case "integer": {
190
- let value = z.number().int().safe();
191
- if (field.minimum !== undefined) value = value.min(field.minimum);
192
- if (field.maximum !== undefined) value = value.max(field.maximum);
193
- schema = value;
194
- break;
195
- }
196
- case "percent":
197
- schema = z.number().int().min(0).max(10000);
198
- break;
199
- case "boolean":
200
- schema = z.boolean();
201
- break;
202
- case "enum":
203
- schema = z.enum(field.values);
204
- break;
205
- case "list": {
206
- const item =
207
- field.item === "money"
208
- ? amount
209
- : field.item === "date"
210
- ? z.iso.datetime({ offset: true })
211
- : field.item === "integer"
212
- ? z.number().int().safe()
213
- : field.item === "ref" && field.targetKind === "object"
214
- ? udlObjectIdSchema
215
- : text;
216
- schema = z.array(item).max(field.maxItems);
217
- break;
218
- }
219
- }
220
- if ("value" in field && field.value !== undefined)
221
- schema = z.literal(field.value);
222
- if (field.description) schema = schema.describe(field.description);
223
- return schema;
224
- }
225
205
  /** Creation accepts the full metadata union, with every value optional. */
226
206
  export function objectCreateSchema(kind: UdlObjectKind): z.ZodObject {
227
207
  return z.strictObject({
@@ -239,20 +219,22 @@ export function objectCreateSchema(kind: UdlObjectKind): z.ZodObject {
239
219
  });
240
220
  }
241
221
 
242
- /** Canonical UDL supplies meaning; core adds authority, lifecycle and readiness. */
222
+ /** Canonical UDL supplies meaning; core adds authority, instance state and readiness. */
243
223
  export function projectObjectDiscovery(
244
224
  document: UdlDocument,
245
225
  build: Pick<ObjectDiscovery, "productBuildId" | "digest">,
246
226
  ): ObjectDiscovery {
227
+ const semantics = projectDocumentSemantics(document);
247
228
  return {
248
229
  ...build,
230
+ semantics,
249
231
  navigation: document.objects.map((kind) => ({
250
232
  kind: kind.id,
251
233
  title: kind.title,
252
234
  creation: true,
253
235
  })),
254
236
  retainedKinds: [],
255
- kinds: document.objects.map((kind) => ({
237
+ kinds: semantics.objects.map((kind) => ({
256
238
  ...build,
257
239
  creation: true,
258
240
  kind: kind.id,
@@ -260,6 +242,7 @@ export function projectObjectDiscovery(
260
242
  fields: kind.fields,
261
243
  authoredFields: kind.authoredFields,
262
244
  columns: kind.columns,
245
+ filters: objectKindFilters(kind),
263
246
  createSchema: z.toJSONSchema(objectCreateSchema(kind), { io: "input" }),
264
247
  actions: document.instruments
265
248
  .filter((instrument) => instrument.subject === kind.id)
@@ -344,9 +327,7 @@ export function projectObjectDiscovery(
344
327
  )!.name,
345
328
  },
346
329
  name: action.publicAction,
347
- title: action.publicAction
348
- .replace(/_/g, " ")
349
- .replace(/^./, (letter) => letter.toUpperCase()),
330
+ title: action.title ?? presentationLabel(action.publicAction),
350
331
  summary: action.summary,
351
332
  instrument: instrument.id,
352
333
  action: name,
@@ -393,8 +374,13 @@ export function projectObjectDiscovery(
393
374
  availability: unbound
394
375
  ? {
395
376
  status: "unavailable" as const,
396
- code: "subject_adapter_unbound",
397
- message: `Adapter declaration ${unbound.binding} is unavailable`,
377
+ blockers: [
378
+ {
379
+ code: "setup_required" as const,
380
+ reason:
381
+ "A required external check has not been set up.",
382
+ },
383
+ ],
398
384
  }
399
385
  : { status: "available" as const },
400
386
  },
@@ -559,13 +545,15 @@ export function validateObjectActionSubject(
559
545
  const issues: SubjectRequirementIssue[] = [];
560
546
  if (
561
547
  action.availability.status === "unavailable" &&
562
- action.availability.code === "subject_adapter_unbound"
548
+ action.availability.blockers.some(
549
+ (blocker) => blocker.code === "setup_required",
550
+ )
563
551
  ) {
564
552
  issues.push({
565
553
  code: "subject_adapter_unbound",
566
554
  action: action.name,
567
555
  origin,
568
- message: action.availability.message,
556
+ message: "A required external check has not been set up.",
569
557
  });
570
558
  }
571
559
  const admitted = new Set(action.requirements.map((field) => field.name));
@@ -0,0 +1,148 @@
1
+ import * as z from "zod";
2
+ import {
3
+ udlActionSchema,
4
+ udlCalculationSchema,
5
+ udlFieldSchema,
6
+ udlInstrumentSchema,
7
+ udlObjectKindSchema,
8
+ udlObjectAttachmentSchema,
9
+ udlPartySchema,
10
+ udlRequirementSchema,
11
+ udlValueSchema,
12
+ udlKernelOperationSchema,
13
+ subjectPartyRoles,
14
+ } from "./schema.js";
15
+
16
+ const text = z.string();
17
+ export const cardinalitySchema = z.strictObject({
18
+ min: z.number().int().nonnegative(),
19
+ // null means the declarations supply no upper bound.
20
+ max: z.number().int().nonnegative().nullable(),
21
+ });
22
+ export const semanticOwnerSchema = z.union([
23
+ z.strictObject({ kind: z.literal("instrument"), instrument: text }),
24
+ z.strictObject({ kind: z.literal("role"), role: z.enum(subjectPartyRoles) }),
25
+ z.strictObject({ kind: z.literal("party"), party: text }),
26
+ z.strictObject({ kind: z.literal("adapter"), adapter: text }),
27
+ ]);
28
+ export const semanticAccountSchema = z.strictObject({
29
+ path: text,
30
+ scope: text,
31
+ instrument: text,
32
+ field: text,
33
+ owner: semanticOwnerSchema,
34
+ book: z.enum(["cash", "claim"]),
35
+ key: text.optional(),
36
+ contra: z.boolean(),
37
+ });
38
+ export const semanticRoleSchema = z.union([
39
+ semanticOwnerSchema,
40
+ z.strictObject({ kind: z.enum(["caller", "clock"]) }),
41
+ z.strictObject({ kind: z.literal("parent"), instruments: z.array(text) }),
42
+ ]);
43
+ export const semanticRelationshipSchema = z.strictObject({
44
+ kind: z.enum(["reference", "child"]),
45
+ via: z.enum(["field", "attachment", "invocation", "parent_actor"]),
46
+ name: text,
47
+ targetKind: z.enum(["instrument", "object"]),
48
+ targets: z.array(text),
49
+ cardinality: cardinalitySchema,
50
+ });
51
+ export const semanticMoneyEffectSchema = z.strictObject({
52
+ key: text,
53
+ operation: udlKernelOperationSchema,
54
+ amount: udlValueSchema.nullable(),
55
+ from: z.array(semanticAccountSchema),
56
+ to: z.array(semanticAccountSchema),
57
+ // Expressions describe declared effects, never a receipt or evaluated quote.
58
+ disposition: z.enum(["held", "paid", "released", "claim", "unresolved"]),
59
+ transfer: text.nullable(),
60
+ reservations: z.array(
61
+ z.strictObject({ instrument: text, action: text, key: text }),
62
+ ),
63
+ });
64
+ export const semanticActionSchema = z.strictObject({
65
+ name: text,
66
+ alias: text.nullable(),
67
+ title: text,
68
+ actor: udlActionSchema.shape.actor,
69
+ roles: z.array(semanticRoleSchema),
70
+ from: z.array(text),
71
+ to: text.nullable(),
72
+ references: z.array(udlFieldSchema).optional(),
73
+ invokedBy: z
74
+ .array(
75
+ z.strictObject({
76
+ instrument: text,
77
+ action: text,
78
+ alias: text.nullable(),
79
+ invocation: udlActionSchema.shape.invoke.unwrap().element,
80
+ }),
81
+ )
82
+ .optional(),
83
+ inputs: udlActionSchema.shape.input,
84
+ subject: udlActionSchema.shape.subject,
85
+ prerequisites: udlActionSchema.shape.requires,
86
+ due: udlActionSchema.shape.due.unwrap().nullable(),
87
+ deadline: udlActionSchema.shape.deadline.unwrap().nullable(),
88
+ calculations: z.array(udlCalculationSchema),
89
+ assignments: udlActionSchema.shape.set.unwrap(),
90
+ moves: udlActionSchema.shape.moves,
91
+ moneyEffects: z.array(semanticMoneyEffectSchema),
92
+ invokes: udlActionSchema.shape.invoke.unwrap(),
93
+ });
94
+ export const semanticStructureSchema = z.enum([
95
+ "stage_and_decisions",
96
+ "payment_review",
97
+ "held_funds",
98
+ "reserved_payment",
99
+ "payment_calendar",
100
+ "funding_progress",
101
+ "cancellation_review",
102
+ "external_checks",
103
+ "report_view",
104
+ ]);
105
+ export const semanticInstrumentSchema = z.strictObject({
106
+ id: text,
107
+ title: text,
108
+ subject: text.optional(),
109
+ fields: z.array(udlFieldSchema),
110
+ accounts: z.array(semanticAccountSchema),
111
+ relationships: z.array(semanticRelationshipSchema),
112
+ calculations: z.array(udlCalculationSchema),
113
+ invariants: z.array(udlRequirementSchema),
114
+ initialState: text,
115
+ lifecycle: z.strictObject({
116
+ initial: text,
117
+ states: z.array(
118
+ z.strictObject({ name: text, label: text, terminal: z.boolean() }),
119
+ ),
120
+ cancellationActions: z.array(text),
121
+ }),
122
+ actions: z.array(semanticActionSchema),
123
+ reports: udlInstrumentSchema.shape.reports.unwrap(),
124
+ structures: z.array(semanticStructureSchema),
125
+ });
126
+ export const documentSemanticsSchema = z.strictObject({
127
+ currency: text,
128
+ parties: z.record(text, udlPartySchema),
129
+ objects: z.array(
130
+ z.strictObject({
131
+ ...udlObjectKindSchema.shape,
132
+ attachments: z.array(
133
+ z.strictObject({
134
+ ...udlObjectAttachmentSchema.shape,
135
+ cardinality: cardinalitySchema,
136
+ }),
137
+ ),
138
+ }),
139
+ ),
140
+ instruments: z.array(semanticInstrumentSchema),
141
+ });
142
+ export type DocumentSemantics = z.infer<typeof documentSemanticsSchema>;
143
+ export type SemanticInstrument = z.infer<typeof semanticInstrumentSchema>;
144
+ export type SemanticAction = z.infer<typeof semanticActionSchema>;
145
+ export type SemanticAccount = z.infer<typeof semanticAccountSchema>;
146
+ export type SemanticOwner = z.infer<typeof semanticOwnerSchema>;
147
+ export type SemanticRelationship = z.infer<typeof semanticRelationshipSchema>;
148
+ export type SemanticMoneyEffect = z.infer<typeof semanticMoneyEffectSchema>;