create-githolon 0.32.0 → 0.34.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-githolon",
3
- "version": "0.32.0",
3
+ "version": "0.34.0",
4
4
  "type": "module",
5
5
  "description": "Scaffold a Nomos domain package: the starter domain + compile config + live e2e. `npm create githolon my-app`.",
6
6
  "license": "SEE LICENSE IN LICENSE.md",
@@ -23,6 +23,63 @@ back structured) · `t.ref(Agg)` (an id-valued reference) ·
23
23
  `t.hasMany(Child).via("parent")` (the virtual read side of a child's ref —
24
24
  nothing stored on the parent). Add `.optional()` where absence is legal.
25
25
 
26
+ ## Value objects: named, composable structured values
27
+
28
+ When a field is a STRUCTURE (an address, a location, a money amount), don't
29
+ hand-roll `t.jsonObject()` plus a private zod — declare a NAMED value object
30
+ once and reuse it everywhere:
31
+
32
+ ```ts
33
+ import { valueObject, geo, z } from "@githolon/dsl";
34
+
35
+ const PostalAddress = valueObject("PostalAddress", {
36
+ streetAddress: z.string(), city: z.string(), countryCode: z.string(),
37
+ });
38
+ const GeoPoint = geo.point("GeoPoint"); // RFC 7946 Point
39
+ const SiteLocation = valueObject("SiteLocation", {
40
+ address: PostalAddress.optional(), // VOs nest in VOs
41
+ point: GeoPoint.optional(),
42
+ });
43
+
44
+ export const EstateSite = aggregate("EstateSite", {
45
+ name: t.string().merge(Lww),
46
+ location: SiteLocation.field().optional(), // ONE json-object leaf
47
+ point: GeoPoint.field().optional(), // give a geometry its OWN field…
48
+ });
49
+ export const sitesByBounds = spatial("sitesByBounds").of(EstateSite).on("point"); // …to index it
50
+ export const recordSite = directive("recordSite").creates(EstateSite)
51
+ .payload(z.object({ name: z.string(), location: SiteLocation.zod })) // the SAME zod validates
52
+ .plan((p) => { create(EstateSite).set("name", p.name).set("location", p.location); return []; });
53
+ ```
54
+
55
+ ONE definition yields the zod (payload validation — a malformed VO is the
56
+ normal typed refusal), the TS type + a named interface in the generated
57
+ `.client.ts`, a typed Dart class (`fromProjected`/`toJson`), and a canonical
58
+ descriptor in the package IR (`nomosValueObjects`) so tooling can see the
59
+ structure. A domain using no VOs is byte-identical to before they existed.
60
+
61
+ **The merge rule (know it):** a VO field is ONE `Lww`-class value leaf,
62
+ replaced WHOLESALE on write — there is NO per-subfield merge. Two offline
63
+ writers touching different subfields of the same VO converge to one writer's
64
+ whole value. When subfields must merge independently, model them as aggregate
65
+ fields or a `t.map(...)` — that is what aggregates and maps are for.
66
+ `.optional()`/`.encrypted()` compose under the usual field rules.
67
+
68
+ **Spatial pairing:** `geo.point()` is the blessed spatial-indexable shape.
69
+ The R*Tree indexes a TOP-LEVEL geometry field — so give the geometry its own
70
+ field (`point: GeoPoint.field()`) and declare `spatial(...).on("point")`. A
71
+ geometry nested inside another VO (`location.point`) is data, not an
72
+ indexable path (a spatial over a non-geo VO field refuses at compile).
73
+ `geo.lineString()` / `geo.polygon()` exist as typed VOs too.
74
+
75
+ VO fields come from a deliberately small set: `z.string()` · `z.number()` ·
76
+ `z.boolean()` · `z.enum([...])` · `z.array(<of these>)` · a nested value
77
+ object · `.optional()` on any. Anything richer refuses at compile — model it
78
+ as an aggregate. A `z.enum` field generates a REAL Dart enum
79
+ (`<VoName><Field>Enum`, `.wire` = the exact wire string) with a TOLERANT
80
+ decode: a wire value this client's law snapshot doesn't know decodes to the
81
+ `unknownWire` sentinel — law can add enum values without crashing old clients.
82
+
26
83
  ## Merge drivers ARE the conflict policy
27
84
 
28
85
  | driver | concurrent writes to the same field… |