dsh-ptc-plus 0.4.1 → 0.4.2
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/README.en.md +6 -10
- package/README.md +6 -10
- package/client.js +262 -31
- package/compiler-core.cjs +132 -130
- package/compiler-core.cjs.map +3 -3
- package/docs/README.md +1 -0
- package/docs/adr/0023-global-user-bindings.md +6 -6
- package/docs/adr/0025-use-versioned-logical-binding-identities.md +3 -3
- package/docs/adr/0027-run-the-session-kernel-in-a-killable-helper-process.md +5 -5
- package/docs/architecture.md +9 -5
- package/docs/client-ui.md +1 -1
- package/docs/durability-design.md +6 -3
- package/docs/installation.md +4 -4
- package/docs/runtime-reference.md +5 -1
- package/docs/stateful-binding-updates-proposal.md +8 -4
- package/docs/user-bindings.en.md +5 -3
- package/docs/user-bindings.md +4 -2
- package/docs/verification-optimization-work.md +41 -0
- package/docs/verification.md +15 -1
- package/internal/async-local-storage-intrinsics.js +38 -0
- package/internal/binding-declaration-contract.js +8 -0
- package/internal/binding-source-analysis.js +381 -64
- package/internal/binding-update-policy.js +7 -1
- package/internal/cell-analysis.js +21 -8
- package/internal/compiler-intrinsics.js +9 -43
- package/internal/compiler-module-links.js +7 -4
- package/internal/dynamic-environment-integration.js +8 -4
- package/internal/dynamic-environment-runtime.js +24 -3
- package/internal/dynamic-native-calls.js +4 -1
- package/internal/dynamic-native-runtime.js +15 -1
- package/internal/failure-reporting.js +38 -30
- package/internal/isolated-worker.js +17 -10
- package/internal/kernel-worker.js +493 -331
- package/internal/module-compilation.js +5 -4
- package/internal/module-transform-contract.js +32 -2
- package/internal/record-utils.js +24 -15
- package/internal/repl-value-observer.js +6 -3
- package/internal/runtime-intrinsics.js +101 -0
- package/internal/session-cell-executor.js +2 -1
- package/internal/session-journal-schema.js +30 -13
- package/internal/session-journal.js +19 -4
- package/internal/session-runtime.js +1 -0
- package/internal/source-position-map.js +19 -0
- package/internal/stateful-root-compiler.js +3 -1
- package/internal/typescript-transform.js +4 -2
- package/internal/user-binding-console-worker.js +101 -41
- package/internal/user-bindings.js +10 -2
- package/internal/value-wire.js +101 -71
- package/internal/worker-cwd-virtualization.js +80 -53
- package/internal/worker-environment.js +20 -0
- package/internal/worker-repl-realm.js +134 -0
- package/package.json +2 -1
- package/src/client-activity.js +8 -1
package/internal/value-wire.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { assertFields, isRecord } from './record-utils.js'
|
|
2
|
+
import { runtimeIntrinsics as internal } from './runtime-intrinsics.js'
|
|
2
3
|
import {
|
|
3
4
|
VALUE_CODEC,
|
|
4
5
|
DEFAULT_VALUE_LIMITS,
|
|
@@ -12,12 +13,24 @@ import {
|
|
|
12
13
|
} from './value-wire-schema.js'
|
|
13
14
|
|
|
14
15
|
export { VALUE_CODEC, DEFAULT_VALUE_LIMITS } from './value-wire-schema.js'
|
|
16
|
+
const { Object, Reflect, Array, Map, Set, WeakMap, WeakSet, TypeError,
|
|
17
|
+
isArray, numberIsFinite, numberIsNaN, numberIsSafeInteger, toBigInt,
|
|
18
|
+
toNumber, toString, regexpTest, mapGet, mapHas, mapSet, setHas, setAdd,
|
|
19
|
+
setDelete, setSize, weakMapGet, weakMapSet, weakSetHas, weakSetAdd,
|
|
20
|
+
appendArray, popArray, join } = internal
|
|
15
21
|
const IDENTIFIER = /^[A-Za-z_$][A-Za-z0-9_$]*$/
|
|
22
|
+
const intrinsicFunctionToString = Function.prototype.toString
|
|
23
|
+
const reflectApply = Reflect.apply
|
|
24
|
+
const bufferByteLength = internal.bufferByteLength
|
|
25
|
+
const jsonStringify = JSON.stringify
|
|
16
26
|
|
|
17
27
|
function limitsOf(options = {}) {
|
|
18
28
|
const limits = { ...DEFAULT_VALUE_LIMITS, ...options }
|
|
19
|
-
|
|
20
|
-
|
|
29
|
+
const entries = Object.entries(limits)
|
|
30
|
+
for (let index = 0; index < entries.length; index += 1) {
|
|
31
|
+
const name = entries[index][0]
|
|
32
|
+
const value = entries[index][1]
|
|
33
|
+
if (!numberIsSafeInteger(value) || value < 1) throw new TypeError(`invalid PTC value limit ${name}`)
|
|
21
34
|
}
|
|
22
35
|
return limits
|
|
23
36
|
}
|
|
@@ -28,7 +41,7 @@ function hasIntrinsicConstructor(prototype, name) {
|
|
|
28
41
|
try {
|
|
29
42
|
return constructor.name === name
|
|
30
43
|
&& constructor.prototype === prototype
|
|
31
|
-
&&
|
|
44
|
+
&& reflectApply(intrinsicFunctionToString, constructor, []) === `function ${name}() { [native code] }`
|
|
32
45
|
} catch {
|
|
33
46
|
return false
|
|
34
47
|
}
|
|
@@ -47,7 +60,7 @@ function plainObjectPrototype(value) {
|
|
|
47
60
|
|
|
48
61
|
function hasPlainArrayPrototype(value) {
|
|
49
62
|
const prototype = Object.getPrototypeOf(value)
|
|
50
|
-
if (!
|
|
63
|
+
if (!isArray(prototype) || !hasIntrinsicConstructor(prototype, 'Array')) return false
|
|
51
64
|
const objectPrototype = Object.getPrototypeOf(prototype)
|
|
52
65
|
return objectPrototype !== null && typeof objectPrototype === 'object'
|
|
53
66
|
&& isIntrinsicObjectPrototype(objectPrototype)
|
|
@@ -58,7 +71,7 @@ function invalid(path, detail) {
|
|
|
58
71
|
}
|
|
59
72
|
|
|
60
73
|
function stringBytes(value) {
|
|
61
|
-
return
|
|
74
|
+
return bufferByteLength(value, 'utf8')
|
|
62
75
|
}
|
|
63
76
|
|
|
64
77
|
/** Encode one supported JavaScript graph into the project-owned JSON-safe wire. */
|
|
@@ -82,7 +95,7 @@ export function encodeValue(value, options = {}) {
|
|
|
82
95
|
return current
|
|
83
96
|
}
|
|
84
97
|
if (typeof current === 'number') {
|
|
85
|
-
if (
|
|
98
|
+
if (numberIsNaN(current)) return { tag: 'number', value: 'nan' }
|
|
86
99
|
if (current === Infinity) return { tag: 'number', value: 'infinity' }
|
|
87
100
|
if (current === -Infinity) return { tag: 'number', value: '-infinity' }
|
|
88
101
|
if (Object.is(current, -0)) return { tag: 'number', value: '-0' }
|
|
@@ -90,20 +103,20 @@ export function encodeValue(value, options = {}) {
|
|
|
90
103
|
}
|
|
91
104
|
if (current === undefined) return { tag: 'undefined' }
|
|
92
105
|
if (typeof current === 'bigint') {
|
|
93
|
-
const text =
|
|
106
|
+
const text = toString(current)
|
|
94
107
|
const digits = text[0] === '-' ? text.length - 1 : text.length
|
|
95
108
|
if (digits > limits.maxBigIntDigits) invalid(path, `BigInt exceeds ${limits.maxBigIntDigits} digits`)
|
|
96
109
|
accountText(text, path)
|
|
97
110
|
return { tag: 'bigint', value: text }
|
|
98
111
|
}
|
|
99
112
|
if (typeof current !== 'object') invalid(path, typeof current)
|
|
100
|
-
const prior = seen
|
|
113
|
+
const prior = weakMapGet(seen, current)
|
|
101
114
|
if (prior !== undefined) return { tag: 'reference', index: prior }
|
|
102
115
|
if (nodes.length >= limits.maxNodes) invalid(path, `node budget exceeds ${limits.maxNodes}`)
|
|
103
116
|
const index = nodes.length
|
|
104
|
-
seen
|
|
105
|
-
nodes
|
|
106
|
-
pending
|
|
117
|
+
weakMapSet(seen, current, index)
|
|
118
|
+
appendArray(nodes, undefined)
|
|
119
|
+
appendArray(pending, { value: current, index, path })
|
|
107
120
|
return { tag: 'reference', index }
|
|
108
121
|
}
|
|
109
122
|
|
|
@@ -111,24 +124,26 @@ export function encodeValue(value, options = {}) {
|
|
|
111
124
|
for (let cursor = 0; cursor < pending.length; cursor += 1) {
|
|
112
125
|
const item = pending[cursor]
|
|
113
126
|
const current = item.value
|
|
114
|
-
if (
|
|
127
|
+
if (isArray(current)) {
|
|
115
128
|
if (!hasPlainArrayPrototype(current)) invalid(item.path, 'non-plain array')
|
|
116
129
|
if (current.length > limits.maxArrayLength) invalid(item.path, `array length exceeds ${limits.maxArrayLength}`)
|
|
117
130
|
const entries = []
|
|
118
|
-
|
|
131
|
+
const keys = Reflect.ownKeys(current)
|
|
132
|
+
for (let keyIndex = 0; keyIndex < keys.length; keyIndex += 1) {
|
|
133
|
+
const key = keys[keyIndex]
|
|
119
134
|
if (key === 'length') continue
|
|
120
|
-
if (typeof key !== 'string' ||
|
|
135
|
+
if (typeof key !== 'string' || !regexpTest(/^(0|[1-9][0-9]*)$/, key)) {
|
|
121
136
|
invalid(item.path, 'array has a symbol or non-index property')
|
|
122
137
|
}
|
|
123
|
-
const index =
|
|
124
|
-
if (!
|
|
138
|
+
const index = toNumber(key)
|
|
139
|
+
if (!numberIsSafeInteger(index) || index >= current.length) invalid(`${item.path}[${key}]`, 'invalid array index')
|
|
125
140
|
const descriptor = Object.getOwnPropertyDescriptor(current, key)
|
|
126
141
|
if (descriptor === undefined || !Object.hasOwn(descriptor, 'value') || descriptor.enumerable !== true) {
|
|
127
142
|
invalid(`${item.path}[${key}]`, 'array index must be an enumerable data property')
|
|
128
143
|
}
|
|
129
144
|
edgeCount += 1
|
|
130
145
|
if (edgeCount > limits.maxEdges) invalid(item.path, `edge budget exceeds ${limits.maxEdges}`)
|
|
131
|
-
entries
|
|
146
|
+
appendArray(entries, [index, atom(descriptor.value, `${item.path}[${key}]`)])
|
|
132
147
|
}
|
|
133
148
|
nodes[item.index] = { type: 'array', length: current.length, entries }
|
|
134
149
|
continue
|
|
@@ -137,7 +152,9 @@ export function encodeValue(value, options = {}) {
|
|
|
137
152
|
const prototype = plainObjectPrototype(current)
|
|
138
153
|
if (prototype === undefined) invalid(item.path, 'non-plain object')
|
|
139
154
|
const entries = []
|
|
140
|
-
|
|
155
|
+
const keys = Reflect.ownKeys(current)
|
|
156
|
+
for (let keyIndex = 0; keyIndex < keys.length; keyIndex += 1) {
|
|
157
|
+
const key = keys[keyIndex]
|
|
141
158
|
if (typeof key !== 'string') invalid(item.path, 'object has a symbol key')
|
|
142
159
|
const descriptor = Object.getOwnPropertyDescriptor(current, key)
|
|
143
160
|
if (descriptor === undefined || !Object.hasOwn(descriptor, 'value') || descriptor.enumerable !== true) {
|
|
@@ -146,7 +163,7 @@ export function encodeValue(value, options = {}) {
|
|
|
146
163
|
accountText(key, `${item.path}.${key}`)
|
|
147
164
|
edgeCount += 1
|
|
148
165
|
if (edgeCount > limits.maxEdges) invalid(item.path, `edge budget exceeds ${limits.maxEdges}`)
|
|
149
|
-
entries
|
|
166
|
+
appendArray(entries, [key, atom(descriptor.value, `${item.path}.${key}`)])
|
|
150
167
|
}
|
|
151
168
|
nodes[item.index] = { type: 'object', prototype, entries }
|
|
152
169
|
}
|
|
@@ -161,7 +178,7 @@ function decodeAtom(atom, nodes, limits, budget, path) {
|
|
|
161
178
|
return atom
|
|
162
179
|
}
|
|
163
180
|
if (typeof atom === 'number') {
|
|
164
|
-
if (!
|
|
181
|
+
if (!numberIsFinite(atom) || Object.is(atom, -0)) throw new TypeError(`invalid PTC value number at ${path}`)
|
|
165
182
|
return atom
|
|
166
183
|
}
|
|
167
184
|
if (!isRecord(atom) || typeof atom.tag !== 'string') throw new TypeError(`invalid PTC value atom at ${path}`)
|
|
@@ -179,31 +196,31 @@ function decodeAtom(atom, nodes, limits, budget, path) {
|
|
|
179
196
|
}
|
|
180
197
|
if (atom.tag === 'bigint') {
|
|
181
198
|
assertFields(atom, BIGINT_FIELDS, 'PTC BigInt atom')
|
|
182
|
-
if (typeof atom.value !== 'string' ||
|
|
199
|
+
if (typeof atom.value !== 'string' || !regexpTest(/^(0|-[1-9][0-9]*|[1-9][0-9]*)$/, atom.value)) {
|
|
183
200
|
throw new TypeError('invalid PTC BigInt')
|
|
184
201
|
}
|
|
185
202
|
const digits = atom.value[0] === '-' ? atom.value.length - 1 : atom.value.length
|
|
186
203
|
if (digits > limits.maxBigIntDigits) throw new TypeError('PTC BigInt digit budget exceeded')
|
|
187
204
|
budget.textBytes += stringBytes(atom.value)
|
|
188
205
|
if (budget.textBytes > limits.maxStringBytes) throw new TypeError('PTC value string budget exceeded')
|
|
189
|
-
return
|
|
206
|
+
return toBigInt(atom.value)
|
|
190
207
|
}
|
|
191
208
|
if (atom.tag === 'reference') {
|
|
192
209
|
assertFields(atom, REFERENCE_FIELDS, 'PTC reference atom')
|
|
193
|
-
if (!
|
|
210
|
+
if (!numberIsSafeInteger(atom.index) || atom.index < 0 || atom.index >= nodes.length) {
|
|
194
211
|
throw new TypeError('dangling PTC value reference')
|
|
195
212
|
}
|
|
196
|
-
budget.reachable
|
|
213
|
+
setAdd(budget.reachable, atom.index)
|
|
197
214
|
return nodes[atom.index]
|
|
198
215
|
}
|
|
199
|
-
throw new TypeError(`unknown PTC value atom tag ${
|
|
216
|
+
throw new TypeError(`unknown PTC value atom tag ${jsonStringify(atom.tag)}`)
|
|
200
217
|
}
|
|
201
218
|
|
|
202
219
|
/** Validate, hydrate, and own one canonical PTC value envelope without recursive stack growth. */
|
|
203
220
|
function decodeCanonicalValue(wire, options = {}) {
|
|
204
221
|
const limits = limitsOf(options)
|
|
205
222
|
assertFields(wire, ENVELOPE_FIELDS, 'PTC value envelope')
|
|
206
|
-
if (wire.codec !== VALUE_CODEC || !
|
|
223
|
+
if (wire.codec !== VALUE_CODEC || !isArray(wire.nodes)) throw new TypeError('invalid PTC value codec')
|
|
207
224
|
if (wire.nodes.length > limits.maxNodes) throw new TypeError('PTC value node budget exceeded')
|
|
208
225
|
const targets = new Array(wire.nodes.length)
|
|
209
226
|
for (let index = 0; index < wire.nodes.length; index += 1) {
|
|
@@ -211,14 +228,14 @@ function decodeCanonicalValue(wire, options = {}) {
|
|
|
211
228
|
if (!isRecord(node)) throw new TypeError(`invalid PTC value node ${index}`)
|
|
212
229
|
if (node.type === 'array') {
|
|
213
230
|
assertFields(node, ARRAY_NODE_FIELDS, `PTC array node ${index}`)
|
|
214
|
-
if (!
|
|
231
|
+
if (!numberIsSafeInteger(node.length) || node.length < 0 || node.length > limits.maxArrayLength) {
|
|
215
232
|
throw new TypeError(`invalid PTC array length at node ${index}`)
|
|
216
233
|
}
|
|
217
|
-
if (!
|
|
234
|
+
if (!isArray(node.entries)) throw new TypeError(`invalid PTC array entries at node ${index}`)
|
|
218
235
|
targets[index] = new Array(node.length)
|
|
219
236
|
} else if (node.type === 'object') {
|
|
220
237
|
assertFields(node, OBJECT_NODE_FIELDS, `PTC object node ${index}`)
|
|
221
|
-
if (
|
|
238
|
+
if ((node.prototype !== 'object' && node.prototype !== 'null') || !isArray(node.entries)) {
|
|
222
239
|
throw new TypeError(`invalid PTC object node ${index}`)
|
|
223
240
|
}
|
|
224
241
|
targets[index] = node.prototype === 'null' ? Object.create(null) : {}
|
|
@@ -234,8 +251,9 @@ function decodeCanonicalValue(wire, options = {}) {
|
|
|
234
251
|
const target = targets[nodeIndex]
|
|
235
252
|
if (node.type === 'array') {
|
|
236
253
|
let previous = -1
|
|
237
|
-
for (
|
|
238
|
-
|
|
254
|
+
for (let entryIndex = 0; entryIndex < node.entries.length; entryIndex += 1) {
|
|
255
|
+
const entry = node.entries[entryIndex]
|
|
256
|
+
if (!isArray(entry) || entry.length !== 2 || !numberIsSafeInteger(entry[0])
|
|
239
257
|
|| entry[0] <= previous || entry[0] < 0 || entry[0] >= node.length) {
|
|
240
258
|
throw new TypeError(`invalid PTC array entry at node ${nodeIndex}`)
|
|
241
259
|
}
|
|
@@ -250,11 +268,12 @@ function decodeCanonicalValue(wire, options = {}) {
|
|
|
250
268
|
continue
|
|
251
269
|
}
|
|
252
270
|
const keys = new Set()
|
|
253
|
-
for (
|
|
254
|
-
|
|
271
|
+
for (let entryIndex = 0; entryIndex < node.entries.length; entryIndex += 1) {
|
|
272
|
+
const entry = node.entries[entryIndex]
|
|
273
|
+
if (!isArray(entry) || entry.length !== 2 || typeof entry[0] !== 'string' || setHas(keys, entry[0])) {
|
|
255
274
|
throw new TypeError(`invalid PTC object entry at node ${nodeIndex}`)
|
|
256
275
|
}
|
|
257
|
-
keys
|
|
276
|
+
setAdd(keys, entry[0])
|
|
258
277
|
budget.textBytes += stringBytes(entry[0])
|
|
259
278
|
if (budget.textBytes > limits.maxStringBytes) throw new TypeError('PTC value string budget exceeded')
|
|
260
279
|
budget.edges += 1
|
|
@@ -265,10 +284,10 @@ function decodeCanonicalValue(wire, options = {}) {
|
|
|
265
284
|
})
|
|
266
285
|
}
|
|
267
286
|
}
|
|
268
|
-
if (budget.reachable
|
|
287
|
+
if (setSize(budget.reachable) !== wire.nodes.length) throw new TypeError('PTC value envelope contains unreachable nodes')
|
|
269
288
|
const canonical = encodeValue(root, limits)
|
|
270
|
-
const serialization =
|
|
271
|
-
if (serialization !==
|
|
289
|
+
const serialization = jsonStringify(canonical)
|
|
290
|
+
if (serialization !== jsonStringify(wire)) throw new TypeError('non-canonical PTC value envelope')
|
|
272
291
|
return { value: root, wire: canonical, serialization }
|
|
273
292
|
}
|
|
274
293
|
|
|
@@ -295,31 +314,32 @@ export function isPlainJsonTree(value) {
|
|
|
295
314
|
const pending = [value]
|
|
296
315
|
const seen = new WeakSet()
|
|
297
316
|
while (pending.length > 0) {
|
|
298
|
-
const current = pending
|
|
317
|
+
const current = popArray(pending)
|
|
299
318
|
if (current === null || typeof current === 'string' || typeof current === 'boolean') continue
|
|
300
|
-
if (typeof current === 'number' &&
|
|
319
|
+
if (typeof current === 'number' && numberIsFinite(current) && !Object.is(current, -0)) continue
|
|
301
320
|
if (typeof current !== 'object') return false
|
|
302
|
-
if (seen
|
|
303
|
-
seen
|
|
304
|
-
if (
|
|
321
|
+
if (weakSetHas(seen, current)) return false
|
|
322
|
+
weakSetAdd(seen, current)
|
|
323
|
+
if (isArray(current)) {
|
|
305
324
|
for (let index = 0; index < current.length; index += 1) {
|
|
306
325
|
if (!Object.hasOwn(current, index)) return false
|
|
307
|
-
pending
|
|
326
|
+
appendArray(pending, current[index])
|
|
308
327
|
}
|
|
309
328
|
} else {
|
|
310
|
-
|
|
329
|
+
const keys = Object.keys(current)
|
|
330
|
+
for (let index = 0; index < keys.length; index += 1) appendArray(pending, current[keys[index]])
|
|
311
331
|
}
|
|
312
332
|
}
|
|
313
333
|
return true
|
|
314
334
|
}
|
|
315
335
|
|
|
316
336
|
function renderKey(key) {
|
|
317
|
-
return IDENTIFIER
|
|
337
|
+
return regexpTest(IDENTIFIER, key) ? key : jsonStringify(key)
|
|
318
338
|
}
|
|
319
339
|
|
|
320
340
|
function atomText(atom) {
|
|
321
|
-
if (atom === null || typeof atom === 'boolean' || typeof atom === 'number') return
|
|
322
|
-
if (typeof atom === 'string') return
|
|
341
|
+
if (atom === null || typeof atom === 'boolean' || typeof atom === 'number') return toString(atom)
|
|
342
|
+
if (typeof atom === 'string') return jsonStringify(atom)
|
|
323
343
|
if (atom.tag === 'undefined') return 'undefined'
|
|
324
344
|
if (atom.tag === 'number') {
|
|
325
345
|
return atom.value === 'nan' ? 'NaN'
|
|
@@ -337,16 +357,20 @@ export function renderValueWire(wire, options = {}) {
|
|
|
337
357
|
}
|
|
338
358
|
|
|
339
359
|
function renderCanonicalValue(normalized, limits) {
|
|
340
|
-
const references = new Array(normalized.nodes.length)
|
|
360
|
+
const references = new Array(normalized.nodes.length)
|
|
361
|
+
for (let index = 0; index < references.length; index += 1) references[index] = 0
|
|
341
362
|
const count = (atom) => {
|
|
342
363
|
if (isRecord(atom) && atom.tag === 'reference') references[atom.index] += 1
|
|
343
364
|
}
|
|
344
365
|
count(normalized.root)
|
|
345
|
-
for (
|
|
366
|
+
for (let nodeIndex = 0; nodeIndex < normalized.nodes.length; nodeIndex += 1) {
|
|
367
|
+
const entries = normalized.nodes[nodeIndex].entries
|
|
368
|
+
for (let entryIndex = 0; entryIndex < entries.length; entryIndex += 1) count(entries[entryIndex][1])
|
|
369
|
+
}
|
|
346
370
|
const labels = new Map()
|
|
347
371
|
let nextLabel = 0
|
|
348
372
|
for (let index = 0; index < references.length; index += 1) {
|
|
349
|
-
if (references[index] > 1) labels
|
|
373
|
+
if (references[index] > 1) mapSet(labels, index, ++nextLabel)
|
|
350
374
|
}
|
|
351
375
|
|
|
352
376
|
const chunks = []
|
|
@@ -354,19 +378,19 @@ function renderCanonicalValue(normalized, limits) {
|
|
|
354
378
|
const append = (text) => {
|
|
355
379
|
bytes += stringBytes(text)
|
|
356
380
|
if (bytes > limits.maxStringBytes) throw new TypeError(`rendered PTC value exceeds ${limits.maxStringBytes} bytes`)
|
|
357
|
-
chunks
|
|
381
|
+
appendArray(chunks, text)
|
|
358
382
|
}
|
|
359
383
|
const emitted = new Set()
|
|
360
384
|
const active = new Set()
|
|
361
385
|
const tasks = [{ kind: 'atom', atom: normalized.root }]
|
|
362
386
|
while (tasks.length > 0) {
|
|
363
|
-
const task = tasks
|
|
387
|
+
const task = popArray(tasks)
|
|
364
388
|
if (task.kind === 'text') {
|
|
365
389
|
append(task.text)
|
|
366
390
|
continue
|
|
367
391
|
}
|
|
368
392
|
if (task.kind === 'leave') {
|
|
369
|
-
active
|
|
393
|
+
setDelete(active, task.index)
|
|
370
394
|
continue
|
|
371
395
|
}
|
|
372
396
|
const primitive = atomText(task.atom)
|
|
@@ -375,38 +399,44 @@ function renderCanonicalValue(normalized, limits) {
|
|
|
375
399
|
continue
|
|
376
400
|
}
|
|
377
401
|
const index = task.atom.index
|
|
378
|
-
const label = labels
|
|
379
|
-
if (emitted
|
|
380
|
-
append(active
|
|
402
|
+
const label = mapGet(labels, index)
|
|
403
|
+
if (setHas(emitted, index)) {
|
|
404
|
+
append(setHas(active, index) ? `[Circular *${label}]` : `[Reference *${label}]`)
|
|
381
405
|
continue
|
|
382
406
|
}
|
|
383
|
-
emitted
|
|
384
|
-
active
|
|
407
|
+
setAdd(emitted, index)
|
|
408
|
+
setAdd(active, index)
|
|
385
409
|
if (label !== undefined) append(`<ref *${label}> `)
|
|
386
410
|
const node = normalized.nodes[index]
|
|
387
|
-
tasks
|
|
411
|
+
appendArray(tasks, { kind: 'leave', index })
|
|
388
412
|
if (node.type === 'array') {
|
|
389
413
|
append('[')
|
|
390
|
-
tasks
|
|
391
|
-
const entries = new Map(
|
|
392
|
-
|
|
414
|
+
appendArray(tasks, { kind: 'text', text: ']' })
|
|
415
|
+
const entries = new Map()
|
|
416
|
+
for (let entryIndex = 0; entryIndex < node.entries.length; entryIndex += 1) {
|
|
417
|
+
const entry = node.entries[entryIndex]
|
|
418
|
+
mapSet(entries, entry[0], entry[1])
|
|
419
|
+
}
|
|
420
|
+
if (node.length > 0 && !mapHas(entries, node.length - 1)) appendArray(tasks, { kind: 'text', text: ',' })
|
|
393
421
|
for (let item = node.length - 1; item >= 0; item -= 1) {
|
|
394
|
-
if (item < node.length - 1) tasks
|
|
395
|
-
const value = entries
|
|
396
|
-
if (value !== undefined) tasks
|
|
422
|
+
if (item < node.length - 1) appendArray(tasks, { kind: 'text', text: ', ' })
|
|
423
|
+
const value = mapGet(entries, item)
|
|
424
|
+
if (value !== undefined) appendArray(tasks, { kind: 'atom', atom: value })
|
|
397
425
|
}
|
|
398
426
|
continue
|
|
399
427
|
}
|
|
400
428
|
append('{')
|
|
401
|
-
tasks
|
|
429
|
+
appendArray(tasks, { kind: 'text', text: '}' })
|
|
402
430
|
for (let entryIndex = node.entries.length - 1; entryIndex >= 0; entryIndex -= 1) {
|
|
403
|
-
const
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
431
|
+
const entry = node.entries[entryIndex]
|
|
432
|
+
const key = entry[0]
|
|
433
|
+
const value = entry[1]
|
|
434
|
+
if (entryIndex < node.entries.length - 1) appendArray(tasks, { kind: 'text', text: ', ' })
|
|
435
|
+
appendArray(tasks, { kind: 'atom', atom: value })
|
|
436
|
+
appendArray(tasks, { kind: 'text', text: `${renderKey(key)}: ` })
|
|
407
437
|
}
|
|
408
438
|
}
|
|
409
|
-
return
|
|
439
|
+
return join(chunks, '')
|
|
410
440
|
}
|
|
411
441
|
|
|
412
442
|
/**
|
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
import { isAbsolute, parse, resolve, sep } from 'node:path'
|
|
2
2
|
import { promisify } from 'node:util'
|
|
3
|
+
import { runtimeIntrinsics as internal } from './runtime-intrinsics.js'
|
|
4
|
+
|
|
5
|
+
const { Reflect: privateReflect, appendArray, bufferConcat, bufferIsBuffer, bufferToString,
|
|
6
|
+
copyArray, isArray, regexpTest } = internal
|
|
7
|
+
const TRAILING_SEPARATOR = /[\\/]$/
|
|
3
8
|
|
|
4
9
|
const FILE_SYSTEM_PATH_ARGUMENTS = Object.freeze({
|
|
5
10
|
access: [0], accessSync: [0], appendFile: [0], appendFileSync: [0],
|
|
@@ -34,7 +39,7 @@ const CHILD_PROCESS_PROJECTED_PROPERTIES = Object.freeze({
|
|
|
34
39
|
function wrapBuiltinCallable(original, projectArguments, projectedProperties = []) {
|
|
35
40
|
const wrapped = {
|
|
36
41
|
invoke(...args) {
|
|
37
|
-
return
|
|
42
|
+
return privateReflect.apply(original, this, projectArguments(args))
|
|
38
43
|
},
|
|
39
44
|
}.invoke
|
|
40
45
|
const descriptors = Object.getOwnPropertyDescriptors(original)
|
|
@@ -59,80 +64,101 @@ function wrapBuiltin(owner, name, projectArguments, projectedProperties) {
|
|
|
59
64
|
owner[name] = wrapBuiltinCallable(original, projectArguments, projectedProperties)
|
|
60
65
|
}
|
|
61
66
|
|
|
67
|
+
export function projectChildProcessArguments(name, args, sessionCwd) {
|
|
68
|
+
const sessionChildOptions = options => (
|
|
69
|
+
options === null || typeof options !== 'object' || options.cwd !== undefined
|
|
70
|
+
? options
|
|
71
|
+
: { ...options, cwd: sessionCwd }
|
|
72
|
+
)
|
|
73
|
+
const withDefaultOptions = (index) => {
|
|
74
|
+
const next = copyArray(args)
|
|
75
|
+
next[index] = sessionChildOptions(next[index]) ?? { cwd: sessionCwd }
|
|
76
|
+
return next
|
|
77
|
+
}
|
|
78
|
+
const insertDefaultOptions = (index) => {
|
|
79
|
+
const next = copyArray(args, 0, index)
|
|
80
|
+
appendArray(next, { cwd: sessionCwd })
|
|
81
|
+
return copyArray(args, index, args.length, next)
|
|
82
|
+
}
|
|
83
|
+
if (name === 'exec' || name === 'execSync') {
|
|
84
|
+
const next = copyArray(args)
|
|
85
|
+
if (typeof next[1] === 'function') return insertDefaultOptions(1)
|
|
86
|
+
else next[1] = sessionChildOptions(next[1]) ?? { cwd: sessionCwd }
|
|
87
|
+
return next
|
|
88
|
+
}
|
|
89
|
+
if (name === 'execFile' || name === 'execFileSync') {
|
|
90
|
+
if (args.length === 1 || typeof args[1] === 'function') {
|
|
91
|
+
return insertDefaultOptions(1)
|
|
92
|
+
}
|
|
93
|
+
if (typeof args[2] === 'function') {
|
|
94
|
+
if (args[1] !== undefined && !isArray(args[1])) return withDefaultOptions(1)
|
|
95
|
+
return insertDefaultOptions(2)
|
|
96
|
+
}
|
|
97
|
+
if (args[1] !== undefined && !isArray(args[1])) return withDefaultOptions(1)
|
|
98
|
+
return withDefaultOptions(2)
|
|
99
|
+
}
|
|
100
|
+
return args.length === 1 || (args[1] !== undefined && !isArray(args[1]))
|
|
101
|
+
? withDefaultOptions(1)
|
|
102
|
+
: withDefaultOptions(2)
|
|
103
|
+
}
|
|
104
|
+
|
|
62
105
|
/** Install one session's cwd projection into Node builtins loaded by the worker. */
|
|
63
|
-
export function installWorkerCwdVirtualization(sessionCwd, originalRequire) {
|
|
64
|
-
if (sessionCwd === undefined)
|
|
106
|
+
export function installWorkerCwdVirtualization(sessionCwd, originalRequire, markVolatile) {
|
|
107
|
+
if (sessionCwd === undefined) {
|
|
108
|
+
const nativeCwd = process.cwd
|
|
109
|
+
process.cwd = () => {
|
|
110
|
+
markVolatile('process.cwd')
|
|
111
|
+
return privateReflect.apply(nativeCwd, process, [])
|
|
112
|
+
}
|
|
113
|
+
return
|
|
114
|
+
}
|
|
65
115
|
const nativeResolve = resolve
|
|
66
|
-
const bufferPrefix = Buffer.from(sessionCwd + (
|
|
116
|
+
const bufferPrefix = Buffer.from(sessionCwd + (regexpTest(TRAILING_SEPARATOR, sessionCwd) ? '' : sep))
|
|
67
117
|
const sessionPath = (value) => {
|
|
68
118
|
if (typeof value === 'string') return isAbsolute(value) ? value : nativeResolve(sessionCwd, value)
|
|
69
|
-
if (
|
|
70
|
-
const absolute = isAbsolute(
|
|
71
|
-
return absolute ? value :
|
|
119
|
+
if (bufferIsBuffer(value)) {
|
|
120
|
+
const absolute = isAbsolute(bufferToString(value, 'utf8', 0, 3))
|
|
121
|
+
return absolute ? value : bufferConcat([bufferPrefix, value])
|
|
72
122
|
}
|
|
73
123
|
return value
|
|
74
124
|
}
|
|
75
125
|
const sessionPathPrefix = (value) => {
|
|
76
126
|
if (typeof value !== 'string' || isAbsolute(value) || parse(value).root !== '') return sessionPath(value)
|
|
77
|
-
return sessionCwd + (
|
|
127
|
+
return sessionCwd + (regexpTest(TRAILING_SEPARATOR, sessionCwd) ? '' : sep) + value
|
|
78
128
|
}
|
|
79
129
|
const projectPaths = (args, indices) => {
|
|
80
|
-
const next =
|
|
81
|
-
for (
|
|
130
|
+
const next = copyArray(args)
|
|
131
|
+
for (let offset = 0; offset < indices.length; offset += 1) {
|
|
132
|
+
const index = indices[offset]
|
|
133
|
+
next[index] = sessionPath(next[index])
|
|
134
|
+
}
|
|
82
135
|
return next
|
|
83
136
|
}
|
|
84
137
|
const projectPrefix = (args) => {
|
|
85
|
-
const next =
|
|
138
|
+
const next = copyArray(args)
|
|
86
139
|
next[0] = sessionPathPrefix(next[0])
|
|
87
140
|
return next
|
|
88
141
|
}
|
|
89
|
-
const sessionChildOptions = options => (
|
|
90
|
-
options === null || typeof options !== 'object' || options.cwd !== undefined
|
|
91
|
-
? options
|
|
92
|
-
: { ...options, cwd: sessionCwd }
|
|
93
|
-
)
|
|
94
|
-
const withDefaultOptions = (args, index) => {
|
|
95
|
-
const next = [...args]
|
|
96
|
-
next[index] = sessionChildOptions(next[index]) ?? { cwd: sessionCwd }
|
|
97
|
-
return next
|
|
98
|
-
}
|
|
99
|
-
const childProcessOptions = (name, args) => {
|
|
100
|
-
if (name === 'exec' || name === 'execSync') {
|
|
101
|
-
const next = [...args]
|
|
102
|
-
if (typeof next[1] === 'function') next.splice(1, 0, { cwd: sessionCwd })
|
|
103
|
-
else next[1] = sessionChildOptions(next[1]) ?? { cwd: sessionCwd }
|
|
104
|
-
return next
|
|
105
|
-
}
|
|
106
|
-
if (name === 'execFile' || name === 'execFileSync') {
|
|
107
|
-
if (args.length === 1 || typeof args[1] === 'function') {
|
|
108
|
-
const next = [...args]
|
|
109
|
-
next.splice(1, 0, { cwd: sessionCwd })
|
|
110
|
-
return next
|
|
111
|
-
}
|
|
112
|
-
if (typeof args[2] === 'function') {
|
|
113
|
-
if (args[1] !== undefined && !Array.isArray(args[1])) return withDefaultOptions(args, 1)
|
|
114
|
-
const next = [...args]
|
|
115
|
-
next.splice(2, 0, { cwd: sessionCwd })
|
|
116
|
-
return next
|
|
117
|
-
}
|
|
118
|
-
if (args[1] !== undefined && !Array.isArray(args[1])) return withDefaultOptions(args, 1)
|
|
119
|
-
return withDefaultOptions(args, 2)
|
|
120
|
-
}
|
|
121
|
-
return args.length === 1 || (args[1] !== undefined && !Array.isArray(args[1]))
|
|
122
|
-
? withDefaultOptions(args, 1)
|
|
123
|
-
: withDefaultOptions(args, 2)
|
|
124
|
-
}
|
|
125
142
|
const sessionGlobOptions = (args) => {
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
143
|
+
if (typeof args[1] === 'function') {
|
|
144
|
+
const next = copyArray(args, 0, 1)
|
|
145
|
+
appendArray(next, { cwd: sessionCwd })
|
|
146
|
+
return copyArray(args, 1, args.length, next)
|
|
147
|
+
}
|
|
148
|
+
const next = copyArray(args)
|
|
149
|
+
if (next[1] === undefined) next[1] = { cwd: sessionCwd }
|
|
129
150
|
else if (next[1] !== null && typeof next[1] === 'object' && next[1].cwd === undefined) {
|
|
130
151
|
next[1] = { ...next[1], cwd: sessionCwd }
|
|
131
152
|
}
|
|
132
153
|
return next
|
|
133
154
|
}
|
|
134
155
|
|
|
135
|
-
|
|
156
|
+
Object.defineProperty(process, 'cwd', {
|
|
157
|
+
configurable: false,
|
|
158
|
+
enumerable: true,
|
|
159
|
+
writable: false,
|
|
160
|
+
value: () => sessionCwd,
|
|
161
|
+
})
|
|
136
162
|
const fs = originalRequire('node:fs')
|
|
137
163
|
const path = originalRequire('node:path')
|
|
138
164
|
for (const [name, indices] of Object.entries(FILE_SYSTEM_PATH_ARGUMENTS)) {
|
|
@@ -147,9 +173,10 @@ export function installWorkerCwdVirtualization(sessionCwd, originalRequire) {
|
|
|
147
173
|
wrapBuiltin(fs, name, sessionGlobOptions)
|
|
148
174
|
wrapBuiltin(fs.promises, name, sessionGlobOptions)
|
|
149
175
|
}
|
|
150
|
-
wrapBuiltin(path, 'resolve', args =>
|
|
176
|
+
wrapBuiltin(path, 'resolve', args => copyArray(args, 0, args.length, [sessionCwd]))
|
|
151
177
|
const childProcess = originalRequire('node:child_process')
|
|
152
178
|
for (const name of ['exec', 'execFile', 'execFileSync', 'execSync', 'fork', 'spawn', 'spawnSync']) {
|
|
153
|
-
wrapBuiltin(childProcess, name,
|
|
179
|
+
wrapBuiltin(childProcess, name,
|
|
180
|
+
args => projectChildProcessArguments(name, args, sessionCwd), CHILD_PROCESS_PROJECTED_PROPERTIES[name])
|
|
154
181
|
}
|
|
155
182
|
}
|
|
@@ -23,6 +23,26 @@ export const HOST_ONLY_ENVIRONMENT_NAMES = new Set([
|
|
|
23
23
|
'dsh_ptc_compiler_bytecode',
|
|
24
24
|
])
|
|
25
25
|
|
|
26
|
+
/** Detect whether the Host executable needs Electron's Node startup mode. */
|
|
27
|
+
export function isElectronHost(versions = process.versions) {
|
|
28
|
+
return typeof versions?.electron === 'string'
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** Overlay only the helper process environment needed to boot an Electron executable as Node. */
|
|
32
|
+
export function helperProcessEnvironment(source, platform = process.platform, electronHost = isElectronHost()) {
|
|
33
|
+
if (!electronHost) return source
|
|
34
|
+
const environment = { ...(source ?? process.env) }
|
|
35
|
+
if (platform === 'win32') {
|
|
36
|
+
for (const name of Object.keys(environment)) {
|
|
37
|
+
if (name !== 'ELECTRON_RUN_AS_NODE' && name.toLowerCase() === 'electron_run_as_node') {
|
|
38
|
+
delete environment[name]
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
environment.ELECTRON_RUN_AS_NODE = '1'
|
|
43
|
+
return environment
|
|
44
|
+
}
|
|
45
|
+
|
|
26
46
|
/** Project the host environment once without losing Windows case variants. */
|
|
27
47
|
export function normalizeWorkerEnvironment(source, platform = process.platform) {
|
|
28
48
|
if (platform !== 'win32') {
|