@highstate/contract 0.19.1 → 0.21.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.
- package/dist/index.js +990 -1004
- package/package.json +11 -11
- package/src/component.spec.ts +912 -2
- package/src/component.ts +531 -95
- package/src/cuidv2d.spec.ts +39 -0
- package/src/cuidv2d.ts +54 -0
- package/src/entity.ts +321 -81
- package/src/evaluation.ts +62 -13
- package/src/index.ts +18 -3
- package/src/instance-input.ts +219 -0
- package/src/instance.ts +302 -67
- package/src/json-schema.ts +115 -0
- package/src/pulumi.ts +45 -16
- package/src/shared.ts +15 -0
- package/src/unit.ts +26 -11
- package/LICENSE +0 -21
- package/dist/index.js.map +0 -1
- package/src/compaction.spec.ts +0 -215
- package/src/compaction.ts +0 -381
package/src/compaction.ts
DELETED
|
@@ -1,381 +0,0 @@
|
|
|
1
|
-
import { mapValues } from "remeda"
|
|
2
|
-
import { HighstateSignature, objectRefSchema, objectWithIdSchema } from "./instance"
|
|
3
|
-
|
|
4
|
-
export function compact<T>(value: T): unknown {
|
|
5
|
-
const counts = new WeakMap<object, number>()
|
|
6
|
-
const cyclic = new WeakSet<object>()
|
|
7
|
-
const expanded = new WeakSet<object>()
|
|
8
|
-
const inStack = new WeakSet<object>()
|
|
9
|
-
const stack: object[] = []
|
|
10
|
-
|
|
11
|
-
function countIdentities(current: unknown): void {
|
|
12
|
-
if (current === null || typeof current !== "object") {
|
|
13
|
-
return
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
counts.set(current, (counts.get(current) ?? 0) + 1)
|
|
17
|
-
|
|
18
|
-
if (inStack.has(current)) {
|
|
19
|
-
cyclic.add(current)
|
|
20
|
-
|
|
21
|
-
for (const entry of stack) {
|
|
22
|
-
cyclic.add(entry)
|
|
23
|
-
}
|
|
24
|
-
return
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
if (expanded.has(current)) {
|
|
28
|
-
return
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
expanded.add(current)
|
|
32
|
-
inStack.add(current)
|
|
33
|
-
stack.push(current)
|
|
34
|
-
|
|
35
|
-
if (Array.isArray(current)) {
|
|
36
|
-
try {
|
|
37
|
-
for (const item of current) {
|
|
38
|
-
countIdentities(item)
|
|
39
|
-
}
|
|
40
|
-
return
|
|
41
|
-
} finally {
|
|
42
|
-
stack.pop()
|
|
43
|
-
inStack.delete(current)
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
try {
|
|
48
|
-
for (const entryValue of Object.values(current)) {
|
|
49
|
-
countIdentities(entryValue)
|
|
50
|
-
}
|
|
51
|
-
} finally {
|
|
52
|
-
stack.pop()
|
|
53
|
-
inStack.delete(current)
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
countIdentities(value)
|
|
58
|
-
|
|
59
|
-
type DefinitionSite = { parent: object; key: string | number }
|
|
60
|
-
|
|
61
|
-
const ids = new WeakMap<object, number>()
|
|
62
|
-
const definitionSites = new WeakMap<object, DefinitionSite>()
|
|
63
|
-
let nextId = 1
|
|
64
|
-
|
|
65
|
-
function ensureId(current: object): number {
|
|
66
|
-
const existing = ids.get(current)
|
|
67
|
-
if (existing !== undefined) {
|
|
68
|
-
return existing
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
const allocated = nextId
|
|
72
|
-
nextId += 1
|
|
73
|
-
ids.set(current, allocated)
|
|
74
|
-
return allocated
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
function assignDefinitionSitesBfs(root: unknown): void {
|
|
78
|
-
if (root === null || typeof root !== "object") {
|
|
79
|
-
return
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
const queue: Array<{ parent: object; key: string | number; value: unknown }> = []
|
|
83
|
-
const expandedQueue = new WeakSet<object>()
|
|
84
|
-
|
|
85
|
-
if (Array.isArray(root)) {
|
|
86
|
-
for (let index = 0; index < root.length; index += 1) {
|
|
87
|
-
queue.push({ parent: root, key: index, value: root[index] })
|
|
88
|
-
}
|
|
89
|
-
} else {
|
|
90
|
-
for (const [key, entryValue] of Object.entries(root)) {
|
|
91
|
-
queue.push({ parent: root, key, value: entryValue })
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
while (queue.length > 0) {
|
|
96
|
-
const current = queue.shift()
|
|
97
|
-
if (current === undefined) {
|
|
98
|
-
continue
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
if (current.value === null || typeof current.value !== "object") {
|
|
102
|
-
continue
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
const occurrences = counts.get(current.value) ?? 0
|
|
106
|
-
if (occurrences > 1 && definitionSites.get(current.value) === undefined) {
|
|
107
|
-
definitionSites.set(current.value, { parent: current.parent, key: current.key })
|
|
108
|
-
ensureId(current.value)
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
if (expandedQueue.has(current.value)) {
|
|
112
|
-
continue
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
expandedQueue.add(current.value)
|
|
116
|
-
|
|
117
|
-
if (Array.isArray(current.value)) {
|
|
118
|
-
for (let index = 0; index < current.value.length; index += 1) {
|
|
119
|
-
queue.push({ parent: current.value, key: index, value: current.value[index] })
|
|
120
|
-
}
|
|
121
|
-
continue
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
for (const [key, entryValue] of Object.entries(current.value)) {
|
|
125
|
-
queue.push({ parent: current.value, key, value: entryValue })
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
assignDefinitionSitesBfs(value)
|
|
131
|
-
|
|
132
|
-
const emitted = new WeakSet<object>()
|
|
133
|
-
|
|
134
|
-
function buildTreeChildren(current: object, rootOfIdValue?: object): unknown {
|
|
135
|
-
if (Array.isArray(current)) {
|
|
136
|
-
return current.map(item => buildTree(item, rootOfIdValue))
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
return mapValues(current, entryValue => buildTree(entryValue, rootOfIdValue))
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
function buildTree(current: unknown, rootOfIdValue?: object): unknown {
|
|
143
|
-
if (current === null || typeof current !== "object") {
|
|
144
|
-
return current
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
// Inside an Id value, never emit nested Id wrappers.
|
|
148
|
-
// Match existing real-world snapshot: do not introduce refs for nested objects
|
|
149
|
-
// unless the nested object has already been defined earlier.
|
|
150
|
-
if (rootOfIdValue !== undefined && emitted.has(current)) {
|
|
151
|
-
const id = ids.get(current)
|
|
152
|
-
if (id === undefined) {
|
|
153
|
-
throw new Error("Compaction invariant violation: missing id for repeated object")
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
return {
|
|
157
|
-
[HighstateSignature.Ref]: true,
|
|
158
|
-
id,
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
// Cycles inside an Id value require defining the cyclic objects inline,
|
|
163
|
-
// otherwise we would infinitely recurse.
|
|
164
|
-
if (rootOfIdValue !== undefined && cyclic.has(current) && !emitted.has(current)) {
|
|
165
|
-
const id = ensureId(current)
|
|
166
|
-
emitted.add(current)
|
|
167
|
-
|
|
168
|
-
return {
|
|
169
|
-
[HighstateSignature.Id]: true,
|
|
170
|
-
id,
|
|
171
|
-
value: buildTreeChildren(current, current),
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
return buildTreeChildren(current, rootOfIdValue)
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
function buildAt(parent: object, key: string | number, current: unknown): unknown {
|
|
179
|
-
if (current === null || typeof current !== "object") {
|
|
180
|
-
return current
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
const occurrences = counts.get(current) ?? 0
|
|
184
|
-
if (occurrences <= 1) {
|
|
185
|
-
if (Array.isArray(current)) {
|
|
186
|
-
return current.map((item, index) => buildAt(current, index, item))
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
return mapValues(current, (entryValue, entryKey) => buildAt(current, entryKey, entryValue))
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
const site = definitionSites.get(current)
|
|
193
|
-
const shouldDefineHere = site?.parent === parent && site.key === key
|
|
194
|
-
|
|
195
|
-
const id = ids.get(current)
|
|
196
|
-
if (id === undefined) {
|
|
197
|
-
throw new Error("Compaction invariant violation: missing id for repeated object")
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
if (!shouldDefineHere || emitted.has(current)) {
|
|
201
|
-
return {
|
|
202
|
-
[HighstateSignature.Ref]: true,
|
|
203
|
-
id,
|
|
204
|
-
}
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
emitted.add(current)
|
|
208
|
-
|
|
209
|
-
return {
|
|
210
|
-
[HighstateSignature.Id]: true,
|
|
211
|
-
id,
|
|
212
|
-
value: buildTreeChildren(current, current),
|
|
213
|
-
}
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
// Prefer keeping the top-level value unwrapped for stability.
|
|
217
|
-
if (value === null || typeof value !== "object") {
|
|
218
|
-
return value
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
const topLevelOccurrences = counts.get(value) ?? 0
|
|
222
|
-
if (topLevelOccurrences > 1) {
|
|
223
|
-
const id = ensureId(value)
|
|
224
|
-
emitted.add(value)
|
|
225
|
-
|
|
226
|
-
return {
|
|
227
|
-
[HighstateSignature.Id]: true,
|
|
228
|
-
id,
|
|
229
|
-
value: buildTreeChildren(value, value),
|
|
230
|
-
}
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
if (Array.isArray(value)) {
|
|
234
|
-
return value.map((item, index) => buildAt(value, index, item))
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
return mapValues(value as Record<string, unknown>, (entryValue, entryKey) =>
|
|
238
|
-
buildAt(value as Record<string, unknown>, entryKey, entryValue),
|
|
239
|
-
)
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
export function decompact<T>(value: unknown): T {
|
|
243
|
-
const rawValuesById = new Map<number, unknown>()
|
|
244
|
-
const placeholdersById = new Map<number, unknown>()
|
|
245
|
-
|
|
246
|
-
function collect(current: unknown, visited: WeakSet<object>): void {
|
|
247
|
-
const result = objectWithIdSchema.safeParse(current)
|
|
248
|
-
if (result.success) {
|
|
249
|
-
const { id } = result.data
|
|
250
|
-
if (rawValuesById.has(id)) {
|
|
251
|
-
throw new Error(`Duplicate compacted id ${id}`)
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
rawValuesById.set(id, result.data.value)
|
|
255
|
-
collect(result.data.value, visited)
|
|
256
|
-
return
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
if (current === null || current === undefined || typeof current !== "object") {
|
|
260
|
-
return
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
if (visited.has(current)) {
|
|
264
|
-
return
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
visited.add(current)
|
|
268
|
-
|
|
269
|
-
if (Array.isArray(current)) {
|
|
270
|
-
for (const item of current) {
|
|
271
|
-
collect(item, visited)
|
|
272
|
-
}
|
|
273
|
-
return
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
for (const entryValue of Object.values(current)) {
|
|
277
|
-
collect(entryValue, visited)
|
|
278
|
-
}
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
function ensurePlaceholder(id: number): unknown {
|
|
282
|
-
const existing = placeholdersById.get(id)
|
|
283
|
-
if (existing !== undefined) {
|
|
284
|
-
return existing
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
const raw = rawValuesById.get(id)
|
|
288
|
-
if (raw === undefined) {
|
|
289
|
-
throw new Error(`Unresolved compacted ref id ${id}`)
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
let placeholder: unknown
|
|
293
|
-
if (raw !== null && typeof raw === "object") {
|
|
294
|
-
placeholder = Array.isArray(raw) ? [] : {}
|
|
295
|
-
} else {
|
|
296
|
-
placeholder = raw
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
placeholdersById.set(id, placeholder)
|
|
300
|
-
return placeholder
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
function resolve(current: unknown): unknown {
|
|
304
|
-
const refResult = objectRefSchema.safeParse(current)
|
|
305
|
-
if (refResult.success) {
|
|
306
|
-
return ensurePlaceholder(refResult.data.id)
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
const withIdResult = objectWithIdSchema.safeParse(current)
|
|
310
|
-
if (withIdResult.success) {
|
|
311
|
-
return ensurePlaceholder(withIdResult.data.id)
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
if (current === null || current === undefined || typeof current !== "object") {
|
|
315
|
-
return current
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
if (Array.isArray(current)) {
|
|
319
|
-
return current.map(item => resolve(item))
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
return mapValues(current, entryValue => resolve(entryValue))
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
function fillPlaceholder(id: number, visitedIds: Set<number>): void {
|
|
326
|
-
if (visitedIds.has(id)) {
|
|
327
|
-
return
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
visitedIds.add(id)
|
|
331
|
-
|
|
332
|
-
const raw = rawValuesById.get(id)
|
|
333
|
-
if (raw === undefined) {
|
|
334
|
-
throw new Error(`Unresolved compacted ref id ${id}`)
|
|
335
|
-
}
|
|
336
|
-
|
|
337
|
-
const placeholder = ensurePlaceholder(id)
|
|
338
|
-
if (placeholder === null || typeof placeholder !== "object") {
|
|
339
|
-
return
|
|
340
|
-
}
|
|
341
|
-
|
|
342
|
-
if (raw === null || typeof raw !== "object") {
|
|
343
|
-
throw new Error(`Compaction invariant violation: id ${id} points to non-object value`)
|
|
344
|
-
}
|
|
345
|
-
|
|
346
|
-
const resolved = resolve(raw)
|
|
347
|
-
|
|
348
|
-
if (Array.isArray(placeholder)) {
|
|
349
|
-
if (!Array.isArray(resolved)) {
|
|
350
|
-
throw new Error(`Compaction invariant violation: id ${id} array placeholder mismatch`)
|
|
351
|
-
}
|
|
352
|
-
|
|
353
|
-
placeholder.length = 0
|
|
354
|
-
for (const item of resolved) {
|
|
355
|
-
placeholder.push(item)
|
|
356
|
-
}
|
|
357
|
-
return
|
|
358
|
-
}
|
|
359
|
-
|
|
360
|
-
if (Array.isArray(resolved) || resolved === null || typeof resolved !== "object") {
|
|
361
|
-
throw new Error(`Compaction invariant violation: id ${id} object placeholder mismatch`)
|
|
362
|
-
}
|
|
363
|
-
|
|
364
|
-
for (const [key, entryValue] of Object.entries(resolved)) {
|
|
365
|
-
;(placeholder as Record<string, unknown>)[key] = entryValue
|
|
366
|
-
}
|
|
367
|
-
}
|
|
368
|
-
|
|
369
|
-
collect(value, new WeakSet<object>())
|
|
370
|
-
|
|
371
|
-
for (const id of rawValuesById.keys()) {
|
|
372
|
-
ensurePlaceholder(id)
|
|
373
|
-
}
|
|
374
|
-
|
|
375
|
-
const visitedIds = new Set<number>()
|
|
376
|
-
for (const id of rawValuesById.keys()) {
|
|
377
|
-
fillPlaceholder(id, visitedIds)
|
|
378
|
-
}
|
|
379
|
-
|
|
380
|
-
return resolve(value) as T
|
|
381
|
-
}
|