@ontrails/core 1.0.0-beta.14 → 1.0.0-beta.15

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 (280) hide show
  1. package/.turbo/turbo-lint.log +1 -1
  2. package/CHANGELOG.md +11 -0
  3. package/README.md +17 -13
  4. package/dist/branded.d.ts +1 -1
  5. package/dist/branded.d.ts.map +1 -1
  6. package/dist/branded.js +1 -1
  7. package/dist/branded.js.map +1 -1
  8. package/dist/context.d.ts +13 -1
  9. package/dist/context.d.ts.map +1 -1
  10. package/dist/context.js +19 -3
  11. package/dist/context.js.map +1 -1
  12. package/dist/contour.d.ts +81 -0
  13. package/dist/contour.d.ts.map +1 -0
  14. package/dist/contour.js +176 -0
  15. package/dist/contour.js.map +1 -0
  16. package/dist/cross-schema.d.ts +18 -0
  17. package/dist/cross-schema.d.ts.map +1 -0
  18. package/dist/cross-schema.js +29 -0
  19. package/dist/cross-schema.js.map +1 -0
  20. package/dist/draft.d.ts +5 -5
  21. package/dist/draft.d.ts.map +1 -1
  22. package/dist/draft.js +28 -27
  23. package/dist/draft.js.map +1 -1
  24. package/dist/errors.d.ts +62 -4
  25. package/dist/errors.d.ts.map +1 -1
  26. package/dist/errors.js +44 -1
  27. package/dist/errors.js.map +1 -1
  28. package/dist/execute.d.ts +26 -9
  29. package/dist/execute.d.ts.map +1 -1
  30. package/dist/execute.js +441 -20
  31. package/dist/execute.js.map +1 -1
  32. package/dist/fire.d.ts +43 -0
  33. package/dist/fire.d.ts.map +1 -0
  34. package/dist/fire.js +185 -0
  35. package/dist/fire.js.map +1 -0
  36. package/dist/index.d.ts +31 -20
  37. package/dist/index.d.ts.map +1 -1
  38. package/dist/index.js +23 -13
  39. package/dist/index.js.map +1 -1
  40. package/dist/internal/cross-batch.d.ts +44 -0
  41. package/dist/internal/cross-batch.d.ts.map +1 -0
  42. package/dist/internal/cross-batch.js +58 -0
  43. package/dist/internal/cross-batch.js.map +1 -0
  44. package/dist/internal/fork-ctx.d.ts +41 -0
  45. package/dist/internal/fork-ctx.d.ts.map +1 -0
  46. package/dist/internal/fork-ctx.js +47 -0
  47. package/dist/internal/fork-ctx.js.map +1 -0
  48. package/dist/internal/signal-ref.d.ts +15 -0
  49. package/dist/internal/signal-ref.d.ts.map +1 -0
  50. package/dist/internal/signal-ref.js +41 -0
  51. package/dist/internal/signal-ref.js.map +1 -0
  52. package/dist/internal/topo-saves.d.ts +8 -2
  53. package/dist/internal/topo-saves.d.ts.map +1 -1
  54. package/dist/internal/topo-saves.js +162 -23
  55. package/dist/internal/topo-saves.js.map +1 -1
  56. package/dist/internal/topo-snapshots.d.ts +57 -0
  57. package/dist/internal/topo-snapshots.d.ts.map +1 -0
  58. package/dist/internal/topo-snapshots.js +372 -0
  59. package/dist/internal/topo-snapshots.js.map +1 -0
  60. package/dist/internal/topo-store-read.d.ts +23 -20
  61. package/dist/internal/topo-store-read.d.ts.map +1 -1
  62. package/dist/internal/topo-store-read.js +82 -83
  63. package/dist/internal/topo-store-read.js.map +1 -1
  64. package/dist/internal/topo-store.d.ts +19 -5
  65. package/dist/internal/topo-store.d.ts.map +1 -1
  66. package/dist/internal/topo-store.js +174 -124
  67. package/dist/internal/topo-store.js.map +1 -1
  68. package/dist/internal/tracing.d.ts +98 -0
  69. package/dist/internal/tracing.d.ts.map +1 -0
  70. package/dist/internal/tracing.js +102 -0
  71. package/dist/internal/tracing.js.map +1 -0
  72. package/dist/internal/trails-db.d.ts +2 -2
  73. package/dist/internal/trails-db.d.ts.map +1 -1
  74. package/dist/internal/trails-db.js +8 -8
  75. package/dist/internal/trails-db.js.map +1 -1
  76. package/dist/internal/zod-wrappers.d.ts +40 -0
  77. package/dist/internal/zod-wrappers.d.ts.map +1 -0
  78. package/dist/internal/zod-wrappers.js +59 -0
  79. package/dist/internal/zod-wrappers.js.map +1 -0
  80. package/dist/layer.d.ts +22 -5
  81. package/dist/layer.d.ts.map +1 -1
  82. package/dist/layer.js +20 -3
  83. package/dist/layer.js.map +1 -1
  84. package/dist/path-security.d.ts +1 -1
  85. package/dist/path-security.d.ts.map +1 -1
  86. package/dist/path-security.js +1 -1
  87. package/dist/path-security.js.map +1 -1
  88. package/dist/resilience.d.ts +1 -1
  89. package/dist/resilience.d.ts.map +1 -1
  90. package/dist/resilience.js +3 -3
  91. package/dist/resilience.js.map +1 -1
  92. package/dist/resource-config.d.ts +22 -0
  93. package/dist/resource-config.d.ts.map +1 -0
  94. package/dist/resource-config.js +210 -0
  95. package/dist/resource-config.js.map +1 -0
  96. package/dist/resource.d.ts +74 -0
  97. package/dist/resource.d.ts.map +1 -0
  98. package/dist/resource.js +61 -0
  99. package/dist/resource.js.map +1 -0
  100. package/dist/run.d.ts +4 -3
  101. package/dist/run.d.ts.map +1 -1
  102. package/dist/run.js +5 -4
  103. package/dist/run.js.map +1 -1
  104. package/dist/serialization.d.ts.map +1 -1
  105. package/dist/serialization.js +2 -1
  106. package/dist/serialization.js.map +1 -1
  107. package/dist/store/accessor-protocol.d.ts +51 -0
  108. package/dist/store/accessor-protocol.d.ts.map +1 -0
  109. package/dist/store/accessor-protocol.js +2 -0
  110. package/dist/store/accessor-protocol.js.map +1 -0
  111. package/dist/store/index.d.ts +2 -0
  112. package/dist/store/index.d.ts.map +1 -0
  113. package/dist/store/index.js +2 -0
  114. package/dist/store/index.js.map +1 -0
  115. package/dist/surface-filter.d.ts +13 -0
  116. package/dist/surface-filter.d.ts.map +1 -0
  117. package/dist/surface-filter.js +76 -0
  118. package/dist/surface-filter.js.map +1 -0
  119. package/dist/topo-store.d.ts +36 -22
  120. package/dist/topo-store.d.ts.map +1 -1
  121. package/dist/topo-store.js +215 -71
  122. package/dist/topo-store.js.map +1 -1
  123. package/dist/topo.d.ts +23 -9
  124. package/dist/topo.d.ts.map +1 -1
  125. package/dist/topo.js +175 -46
  126. package/dist/topo.js.map +1 -1
  127. package/dist/trail.d.ts +86 -19
  128. package/dist/trail.d.ts.map +1 -1
  129. package/dist/trail.js +59 -4
  130. package/dist/trail.js.map +1 -1
  131. package/dist/trails/derive-trail.d.ts +66 -0
  132. package/dist/trails/derive-trail.d.ts.map +1 -0
  133. package/dist/trails/derive-trail.js +378 -0
  134. package/dist/trails/derive-trail.js.map +1 -0
  135. package/dist/trails/index.d.ts +5 -0
  136. package/dist/trails/index.d.ts.map +1 -0
  137. package/dist/trails/index.js +3 -0
  138. package/dist/trails/index.js.map +1 -0
  139. package/dist/trails/ingest.d.ts +24 -0
  140. package/dist/trails/ingest.d.ts.map +1 -0
  141. package/dist/trails/ingest.js +63 -0
  142. package/dist/trails/ingest.js.map +1 -0
  143. package/dist/transport-error-map.d.ts +106 -0
  144. package/dist/transport-error-map.d.ts.map +1 -0
  145. package/dist/transport-error-map.js +24 -0
  146. package/dist/transport-error-map.js.map +1 -0
  147. package/dist/type-checks.test-d.d.ts +71 -0
  148. package/dist/type-checks.test-d.d.ts.map +1 -0
  149. package/dist/type-checks.test-d.js +12 -0
  150. package/dist/type-checks.test-d.js.map +1 -0
  151. package/dist/type-utils.d.ts +11 -2
  152. package/dist/type-utils.d.ts.map +1 -1
  153. package/dist/type-utils.js.map +1 -1
  154. package/dist/types.d.ts +113 -7
  155. package/dist/types.d.ts.map +1 -1
  156. package/dist/types.js.map +1 -1
  157. package/dist/validate-established-topo.js +2 -2
  158. package/dist/validate-established-topo.js.map +1 -1
  159. package/dist/validate-topo.d.ts.map +1 -1
  160. package/dist/validate-topo.js +24 -7
  161. package/dist/validate-topo.js.map +1 -1
  162. package/dist/workspace.d.ts +1 -1
  163. package/dist/workspace.d.ts.map +1 -1
  164. package/dist/workspace.js +1 -1
  165. package/dist/workspace.js.map +1 -1
  166. package/package.json +8 -2
  167. package/src/__tests__/branded.test.ts +5 -5
  168. package/src/__tests__/context.test.ts +3 -3
  169. package/src/__tests__/contour.test.ts +263 -0
  170. package/src/__tests__/derive-trail.test.ts +668 -0
  171. package/src/__tests__/errors.test.ts +118 -0
  172. package/src/__tests__/execute.test.ts +1179 -141
  173. package/src/__tests__/fire.test.ts +1056 -0
  174. package/src/__tests__/fork-ctx.test.ts +83 -0
  175. package/src/__tests__/ingest.test.ts +191 -0
  176. package/src/__tests__/{gate.test.ts → layer.test.ts} +21 -21
  177. package/src/__tests__/path-security.test.ts +7 -7
  178. package/src/__tests__/resilience.test.ts +8 -8
  179. package/src/__tests__/resource.test.ts +204 -0
  180. package/src/__tests__/run.test.ts +13 -13
  181. package/src/__tests__/service-config.test.ts +28 -32
  182. package/src/__tests__/surface-filter.test.ts +196 -0
  183. package/src/__tests__/topo-store-read.test.ts +41 -36
  184. package/src/__tests__/topo-store.test.ts +493 -93
  185. package/src/__tests__/topo.test.ts +350 -54
  186. package/src/__tests__/trail.test.ts +189 -24
  187. package/src/__tests__/trails-db.test.ts +32 -25
  188. package/src/__tests__/transport-error-map.test.ts +82 -0
  189. package/src/__tests__/type-utils.test.ts +29 -1
  190. package/src/__tests__/validate-topo.test.ts +147 -25
  191. package/src/__tests__/workspace.test.ts +6 -6
  192. package/src/__tests__/zod-wrappers.test.ts +62 -0
  193. package/src/branded.ts +1 -1
  194. package/src/context.ts +24 -4
  195. package/src/contour.ts +344 -0
  196. package/src/cross-schema.ts +36 -0
  197. package/src/draft.ts +56 -52
  198. package/src/errors.ts +62 -18
  199. package/src/execute.ts +840 -29
  200. package/src/fire.ts +295 -0
  201. package/src/index.ts +116 -29
  202. package/src/internal/cross-batch.ts +77 -0
  203. package/src/internal/fork-ctx.ts +69 -0
  204. package/src/internal/signal-ref.ts +87 -0
  205. package/src/internal/topo-snapshots.ts +517 -0
  206. package/src/internal/topo-store-read.ts +140 -132
  207. package/src/internal/topo-store.ts +291 -186
  208. package/src/internal/tracing.ts +187 -0
  209. package/src/internal/trails-db.ts +9 -11
  210. package/src/internal/zod-wrappers.ts +78 -0
  211. package/src/layer.ts +50 -0
  212. package/src/path-security.ts +1 -1
  213. package/src/resilience.ts +3 -3
  214. package/src/{provision-config.ts → resource-config.ts} +109 -123
  215. package/src/resource.ts +155 -0
  216. package/src/run.ts +5 -4
  217. package/src/serialization.ts +2 -0
  218. package/src/store/accessor-protocol.ts +56 -0
  219. package/src/store/index.ts +4 -0
  220. package/src/surface-filter.ts +182 -0
  221. package/src/topo-store.ts +313 -104
  222. package/src/topo.ts +328 -57
  223. package/src/trail.ts +199 -31
  224. package/src/trails/derive-trail.ts +842 -0
  225. package/src/trails/index.ts +9 -0
  226. package/src/trails/ingest.ts +146 -0
  227. package/src/transport-error-map.ts +56 -0
  228. package/src/type-checks.test-d.ts +106 -0
  229. package/src/type-utils.ts +17 -2
  230. package/src/types.ts +148 -11
  231. package/src/validate-established-topo.ts +2 -2
  232. package/src/validate-topo.ts +32 -8
  233. package/src/workspace.ts +1 -1
  234. package/tsconfig.tests.json +10 -0
  235. package/tsconfig.tsbuildinfo +1 -1
  236. package/dist/adapters.d.ts +0 -39
  237. package/dist/adapters.d.ts.map +0 -1
  238. package/dist/adapters.js +0 -2
  239. package/dist/adapters.js.map +0 -1
  240. package/dist/dispatch.d.ts +0 -27
  241. package/dist/dispatch.d.ts.map +0 -1
  242. package/dist/dispatch.js +0 -34
  243. package/dist/dispatch.js.map +0 -1
  244. package/dist/event.d.ts +0 -8
  245. package/dist/event.d.ts.map +0 -1
  246. package/dist/event.js +0 -7
  247. package/dist/event.js.map +0 -1
  248. package/dist/gate.d.ts +0 -17
  249. package/dist/gate.d.ts.map +0 -1
  250. package/dist/gate.js +0 -21
  251. package/dist/gate.js.map +0 -1
  252. package/dist/health.d.ts +0 -18
  253. package/dist/health.d.ts.map +0 -1
  254. package/dist/health.js +0 -5
  255. package/dist/health.js.map +0 -1
  256. package/dist/job.d.ts +0 -24
  257. package/dist/job.d.ts.map +0 -1
  258. package/dist/job.js +0 -17
  259. package/dist/job.js.map +0 -1
  260. package/dist/provision-config.d.ts +0 -22
  261. package/dist/provision-config.d.ts.map +0 -1
  262. package/dist/provision-config.js +0 -210
  263. package/dist/provision-config.js.map +0 -1
  264. package/dist/provision.d.ts +0 -71
  265. package/dist/provision.d.ts.map +0 -1
  266. package/dist/provision.js +0 -56
  267. package/dist/provision.js.map +0 -1
  268. package/dist/service-config.d.ts +0 -22
  269. package/dist/service-config.d.ts.map +0 -1
  270. package/dist/service-config.js +0 -210
  271. package/dist/service-config.js.map +0 -1
  272. package/dist/service.d.ts +0 -71
  273. package/dist/service.d.ts.map +0 -1
  274. package/dist/service.js +0 -56
  275. package/dist/service.js.map +0 -1
  276. package/src/__tests__/service.test.ts +0 -197
  277. package/src/event.ts +0 -15
  278. package/src/gate.ts +0 -44
  279. package/src/internal/topo-saves.ts +0 -429
  280. package/src/provision.ts +0 -148
@@ -8,7 +8,7 @@ import {
8
8
  nonEmptyString,
9
9
  positiveInt,
10
10
  shortId,
11
- hashId,
11
+ deriveIdHash,
12
12
  } from '../branded';
13
13
 
14
14
  describe('branded', () => {
@@ -131,18 +131,18 @@ describe('branded', () => {
131
131
  });
132
132
  });
133
133
 
134
- describe('hashId()', () => {
134
+ describe('deriveIdHash()', () => {
135
135
  test('returns a hex string', () => {
136
- const id = hashId('test');
136
+ const id = deriveIdHash('test');
137
137
  expect(id).toMatch(/^[0-9a-f]{8}$/);
138
138
  });
139
139
 
140
140
  test('is deterministic', () => {
141
- expect(hashId('hello')).toBe(hashId('hello'));
141
+ expect(deriveIdHash('hello')).toBe(deriveIdHash('hello'));
142
142
  });
143
143
 
144
144
  test('different inputs produce different outputs', () => {
145
- expect(hashId('a')).not.toBe(hashId('b'));
145
+ expect(deriveIdHash('a')).not.toBe(deriveIdHash('b'));
146
146
  });
147
147
  });
148
148
  });
@@ -57,14 +57,14 @@ describe('createTrailContext', () => {
57
57
  expect(ctx.extensions?.['nested']).toEqual({ key: 'value' });
58
58
  });
59
59
 
60
- test('provides a provision accessor backed by extensions', () => {
60
+ test('provides a resource accessor backed by extensions', () => {
61
61
  const widget = { id: 'widget-1' };
62
62
  const ctx = createTrailContext({
63
63
  extensions: { 'widget.main': widget },
64
64
  });
65
65
 
66
- expect(ctx.provision('widget.main')).toBe(widget);
67
- expect(ctx.provision<{ id: string }>({ id: 'widget.main' }).id).toBe(
66
+ expect(ctx.resource('widget.main')).toBe(widget);
67
+ expect(ctx.resource<{ id: string }>({ id: 'widget.main' }).id).toBe(
68
68
  'widget-1'
69
69
  );
70
70
  });
@@ -0,0 +1,263 @@
1
+ import { describe, expect, test } from 'bun:test';
2
+
3
+ import { z } from 'zod';
4
+
5
+ import {
6
+ contour,
7
+ getContourIdMetadata,
8
+ getContourReferences,
9
+ } from '../contour.js';
10
+
11
+ const user = contour(
12
+ 'user',
13
+ {
14
+ email: z.string().email(),
15
+ id: z.string().uuid(),
16
+ name: z.string(),
17
+ },
18
+ {
19
+ examples: [
20
+ {
21
+ email: 'ada@example.com',
22
+ id: '550e8400-e29b-41d4-a716-446655440000',
23
+ name: 'Ada',
24
+ },
25
+ ],
26
+ identity: 'id',
27
+ }
28
+ );
29
+
30
+ const gist = contour(
31
+ 'gist',
32
+ {
33
+ id: z.string().uuid(),
34
+ ownerId: user.id(),
35
+ title: z.string(),
36
+ },
37
+ { identity: 'id' }
38
+ );
39
+
40
+ type UserId = z.infer<ReturnType<typeof user.id>>;
41
+ type GistId = z.infer<ReturnType<typeof gist.id>>;
42
+
43
+ const _ownerId: z.infer<typeof gist.shape.ownerId> = {} as UserId;
44
+ // @ts-expect-error distinct contour ids should not be assignable
45
+ const _mixedIds: UserId = {} as GistId;
46
+
47
+ describe('contour()', () => {
48
+ describe('basics', () => {
49
+ test("returns kind 'contour'", () => {
50
+ expect(user.kind).toBe('contour');
51
+ });
52
+
53
+ test('preserves the contour name', () => {
54
+ expect(user.name).toBe('user');
55
+ });
56
+
57
+ test('preserves schema behavior', () => {
58
+ const parsed = user.safeParse({
59
+ email: 'ada@example.com',
60
+ id: '550e8400-e29b-41d4-a716-446655440000',
61
+ name: 'Ada',
62
+ });
63
+
64
+ expect(parsed.success).toBe(true);
65
+ });
66
+ });
67
+
68
+ describe('identity', () => {
69
+ test('stores the identity field name', () => {
70
+ expect(user.identity).toBe('id');
71
+ });
72
+
73
+ test('exposes the identity field schema', () => {
74
+ expect(user.identitySchema).toBe(user.shape.id);
75
+ });
76
+
77
+ test('returns a branded identity schema', () => {
78
+ const schema = user.id();
79
+ expect(
80
+ schema.safeParse('550e8400-e29b-41d4-a716-446655440000').success
81
+ ).toBe(true);
82
+ expect(schema).toBe(user.id());
83
+ });
84
+
85
+ test('attaches contour metadata to branded identity schemas', () => {
86
+ expect(getContourIdMetadata(user.id())).toEqual({
87
+ contour: 'user',
88
+ identity: 'id',
89
+ });
90
+ });
91
+
92
+ test('preserves first-write metadata when identity schemas are shared', () => {
93
+ const baseUser = contour(
94
+ 'user',
95
+ { id: z.string().uuid(), name: z.string() },
96
+ { identity: 'id' }
97
+ );
98
+ const admin = contour(
99
+ 'admin',
100
+ { id: baseUser.shape.id, role: z.string() },
101
+ { identity: 'id' }
102
+ );
103
+
104
+ // The first contour to brand the schema wins — user, not admin.
105
+ expect(getContourIdMetadata(baseUser.id())).toEqual({
106
+ contour: 'user',
107
+ identity: 'id',
108
+ });
109
+ // admin still has its own id() accessor
110
+ expect(admin.id()).toBeDefined();
111
+
112
+ // Neither contour should emit its own identity as a cross-contour reference
113
+ expect(getContourReferences(baseUser)).toEqual([]);
114
+ expect(getContourReferences(admin)).toEqual([]);
115
+ });
116
+
117
+ test('rejects identity keys that are not in the shape', () => {
118
+ expect(() =>
119
+ contour(
120
+ 'broken',
121
+ {
122
+ id: z.string(),
123
+ },
124
+ { identity: 'missing' as 'id' }
125
+ )
126
+ ).toThrow('identity "missing" must match a declared field');
127
+ });
128
+ });
129
+
130
+ describe('examples', () => {
131
+ test('stores examples as a frozen array', () => {
132
+ expect(user.examples).toHaveLength(1);
133
+ expect(Object.isFrozen(user.examples)).toBe(true);
134
+ });
135
+
136
+ test('validates examples against the contour schema', () => {
137
+ expect(() =>
138
+ contour(
139
+ 'broken',
140
+ {
141
+ id: z.string().uuid(),
142
+ name: z.string(),
143
+ },
144
+ {
145
+ examples: [{ id: 'not-a-uuid', name: 42 as never }],
146
+ identity: 'id',
147
+ }
148
+ )
149
+ ).toThrow('example 0 is invalid');
150
+ });
151
+ });
152
+
153
+ describe('zod forwarding', () => {
154
+ test('supports .pick()', () => {
155
+ const schema = user.pick({ name: true });
156
+ expect(schema.safeParse({ name: 'Ada' }).success).toBe(true);
157
+ expect(schema.safeParse({ email: 'ada@example.com' }).success).toBe(
158
+ false
159
+ );
160
+ });
161
+
162
+ test('supports .extend()', () => {
163
+ const schema = user.extend({ active: z.boolean() });
164
+ expect(
165
+ schema.safeParse({
166
+ active: true,
167
+ email: 'ada@example.com',
168
+ id: '550e8400-e29b-41d4-a716-446655440000',
169
+ name: 'Ada',
170
+ }).success
171
+ ).toBe(true);
172
+ });
173
+
174
+ test('supports .array()', () => {
175
+ const schema = user.array();
176
+ expect(
177
+ schema.safeParse([
178
+ {
179
+ email: 'ada@example.com',
180
+ id: '550e8400-e29b-41d4-a716-446655440000',
181
+ name: 'Ada',
182
+ },
183
+ ]).success
184
+ ).toBe(true);
185
+ });
186
+
187
+ test('supports cross-contour references via .id()', () => {
188
+ expect(
189
+ gist.safeParse({
190
+ id: '0f31f6ba-6ff0-41ce-9f6b-8d132b6c4b81',
191
+ ownerId: '550e8400-e29b-41d4-a716-446655440000',
192
+ title: 'Hello',
193
+ }).success
194
+ ).toBe(true);
195
+ expect(getContourIdMetadata(gist.shape.ownerId)).toEqual({
196
+ contour: 'user',
197
+ identity: 'id',
198
+ });
199
+ });
200
+
201
+ test('lists declared contour references', () => {
202
+ expect(getContourReferences(gist)).toEqual([
203
+ {
204
+ contour: 'user',
205
+ field: 'ownerId',
206
+ identity: 'id',
207
+ },
208
+ ]);
209
+ });
210
+ });
211
+
212
+ describe('wrapper unwrapping', () => {
213
+ test('resolves .optional() contour id metadata', () => {
214
+ expect(getContourIdMetadata(user.id().optional())).toEqual({
215
+ contour: 'user',
216
+ identity: 'id',
217
+ });
218
+ });
219
+
220
+ test('resolves .nullable() contour id metadata', () => {
221
+ expect(getContourIdMetadata(user.id().nullable())).toEqual({
222
+ contour: 'user',
223
+ identity: 'id',
224
+ });
225
+ });
226
+
227
+ test('resolves .nullish() contour id metadata (two-level wrapper)', () => {
228
+ expect(getContourIdMetadata(user.id().nullish())).toEqual({
229
+ contour: 'user',
230
+ identity: 'id',
231
+ });
232
+ });
233
+
234
+ test('resolves deeply nested wrappers', () => {
235
+ expect(
236
+ getContourIdMetadata(user.id().nullable().optional().default(null))
237
+ ).toEqual({
238
+ contour: 'user',
239
+ identity: 'id',
240
+ });
241
+ });
242
+
243
+ test('detects references through .nullish() wrappers in contour shapes', () => {
244
+ const comment = contour(
245
+ 'comment',
246
+ {
247
+ authorId: user.id().nullish(),
248
+ id: z.string().uuid(),
249
+ text: z.string(),
250
+ },
251
+ { identity: 'id' }
252
+ );
253
+
254
+ expect(getContourReferences(comment)).toEqual([
255
+ {
256
+ contour: 'user',
257
+ field: 'authorId',
258
+ identity: 'id',
259
+ },
260
+ ]);
261
+ });
262
+ });
263
+ });