@hyperscale0/udl 1.0.251 → 1.0.258

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/src/index.ts CHANGED
@@ -1,10 +1,12 @@
1
1
  export * from "./schema.js";
2
+ export { sarCurrency, sarMinorUnitExponent, currencySchema } from "./money.js";
2
3
  export { serializeUdl, canonicalDigest } from "./canonical.js";
3
4
  export {
4
5
  validateUdl,
5
6
  RESERVED_OBJECT_NAMES,
6
7
  assertValidUdl,
7
8
  resolveField,
9
+ sameAccount,
8
10
  resolveSubjectRequirement,
9
11
  UdlError,
10
12
  type UdlValidationResult,
package/src/money.ts ADDED
@@ -0,0 +1,5 @@
1
+ import * as z from "zod";
2
+
3
+ export const sarCurrency = "SAR";
4
+ export const sarMinorUnitExponent = 2;
5
+ export const currencySchema = z.literal(sarCurrency);
package/src/reporting.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import * as z from "zod";
2
+ import { currencySchema } from "./money.js";
2
3
 
3
4
  const name = z
4
5
  .string()
@@ -12,7 +13,7 @@ const integer = z.number().int().safe();
12
13
  export const reportTypeSchema = z.discriminatedUnion("kind", [
13
14
  z.strictObject({
14
15
  kind: z.literal("money"),
15
- currency: z.string().regex(/^[A-Z]{3}$/),
16
+ currency: currencySchema,
16
17
  }),
17
18
  z.strictObject({ kind: z.enum(["text", "integer", "boolean", "date"]) }),
18
19
  ]);
@@ -94,7 +95,7 @@ export const reportDefinitionSchema = z.strictObject({
94
95
  scope: z.strictObject({
95
96
  kind: z.enum(["product", "company"]),
96
97
  classification: z.enum(["internal", "personal", "restricted"]),
97
- currency: z.string().regex(/^[A-Z]{3}$/),
98
+ currency: currencySchema,
98
99
  }),
99
100
  datasets: z
100
101
  .array(
package/src/schema.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import * as z from "zod";
2
+ import { currencySchema } from "./money.js";
2
3
  import { reportDefinitionSchema } from "./reporting.js";
3
4
  import { writeJson } from "./json.js";
4
5
 
@@ -553,7 +554,7 @@ export const udlDocumentSchema = z.strictObject({
553
554
  version: integer.positive(),
554
555
  product: name,
555
556
  title: text,
556
- currency: z.literal("SAR"),
557
+ currency: currencySchema,
557
558
  parties: z.record(name, udlPartySchema),
558
559
  objects: z.array(udlObjectKindSchema).max(256),
559
560
  instruments: z.array(udlInstrumentSchema).max(256),
package/src/validation.ts CHANGED
@@ -234,6 +234,47 @@ export function resolveField(
234
234
  return resolvePath(document, instrument, path, input, new Map(), action);
235
235
  }
236
236
 
237
+ /** The account identity used by the distinct-account transfer rule. */
238
+ export function sameAccount(
239
+ document: UdlDocument,
240
+ instrument: UdlInstrument,
241
+ fromPath: string,
242
+ toPath: string,
243
+ input: readonly UdlField[] = [],
244
+ action?: UdlAction,
245
+ ): boolean {
246
+ const from = resolveField(document, instrument, fromPath, input, action);
247
+ const to = resolveField(document, instrument, toPath, input, action);
248
+ const accountOwner = (
249
+ field: Extract<UdlField, { type: "account" }>,
250
+ path: string,
251
+ ): string => {
252
+ if (typeof field.owner === "string") return `party:${field.owner}`;
253
+ const binding = field.owner.adapter;
254
+ const provider =
255
+ path === `self.${field.name}`
256
+ ? Object.values(instrument.actions)
257
+ .flatMap((action) => action.subject?.adapters ?? [])
258
+ .find((entry) => entry.binding === binding && entry.snapshot)
259
+ ?.snapshot?.provider
260
+ : undefined;
261
+ return provider ? `provider:${provider}` : `binding:${binding}`;
262
+ };
263
+ return (
264
+ fromPath === toPath ||
265
+ !!(
266
+ from?.type === "account" &&
267
+ to?.type === "account" &&
268
+ (from.owner !== "self" ||
269
+ (fromPath === `self.${from.name}` && toPath === `self.${to.name}`)) &&
270
+ accountOwner(from, fromPath) === accountOwner(to, toPath) &&
271
+ from.book === to.book &&
272
+ (from.key ?? (from.owner === "self" ? from.name : "balance")) ===
273
+ (to.key ?? (to.owner === "self" ? to.name : "balance"))
274
+ )
275
+ );
276
+ }
277
+
237
278
  /** A subject expression names a requirement; objectField names its stored metadata. */
238
279
  export function resolveSubjectRequirement(
239
280
  instrument: UdlInstrument,
@@ -1101,7 +1142,13 @@ export function validateUdl(value: unknown): UdlValidationResult {
1101
1142
  dependencies.set(
1102
1143
  c.target,
1103
1144
  operands.flatMap((v) =>
1104
- "field" in v && v.field.startsWith("self.")
1145
+ "field" in v &&
1146
+ v.field.startsWith("self.") &&
1147
+ !(
1148
+ act &&
1149
+ v.field === `self.${c.target}` &&
1150
+ !inst.calculate.some((node) => node.target === c.target)
1151
+ )
1105
1152
  ? [v.field.slice(5)]
1106
1153
  : [],
1107
1154
  ),
@@ -1526,33 +1573,16 @@ export function validateUdl(value: unknown): UdlValidationResult {
1526
1573
  from.book !== to.book
1527
1574
  )
1528
1575
  add(where, "moves cannot cross account books", "UDL4001");
1529
- const accountOwner = (
1530
- field: Extract<UdlField, { type: "account" }>,
1531
- path: string,
1532
- ): string => {
1533
- if (typeof field.owner === "string") return `party:${field.owner}`;
1534
- const binding = field.owner.adapter;
1535
- const provider =
1536
- path === `self.${field.name}`
1537
- ? Object.values(inst.actions)
1538
- .flatMap((action) => action.subject?.adapters ?? [])
1539
- .find(
1540
- (entry) => entry.binding === binding && entry.snapshot,
1541
- )?.snapshot?.provider
1542
- : undefined;
1543
- return provider ? `provider:${provider}` : `binding:${binding}`;
1544
- };
1545
- const sameBoundAccount =
1546
- from?.type === "account" &&
1547
- to?.type === "account" &&
1548
- (from.owner !== "self" ||
1549
- (move.from === `self.${from.name}` &&
1550
- move.to === `self.${to.name}`)) &&
1551
- accountOwner(from, move.from) === accountOwner(to, move.to) &&
1552
- from.book === to.book &&
1553
- (from.key ?? (from.owner === "self" ? from.name : "balance")) ===
1554
- (to.key ?? (to.owner === "self" ? to.name : "balance"));
1555
- if (move.from === move.to || sameBoundAccount)
1576
+ if (
1577
+ sameAccount(
1578
+ document,
1579
+ inst,
1580
+ move.from,
1581
+ move.to,
1582
+ action.input,
1583
+ action,
1584
+ )
1585
+ )
1556
1586
  add(where, "a transfer needs distinct accounts", "UDL4001");
1557
1587
  } else expect(move.transfer, "text", where, action.input, action);
1558
1588
  }