dsh-ptc-plus 0.4.1 → 0.4.3
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 +164 -78
- package/README.md +165 -79
- package/assets/dsh-ptc-plus-banner-en.webp +0 -0
- package/assets/dsh-ptc-plus-banner-zh.webp +0 -0
- package/assets/ptc-plus-icon.svg +5 -0
- package/client.js +854 -336
- package/compiler-core.cjs +161 -157
- package/compiler-core.cjs.map +3 -3
- package/docs/README.md +4 -1
- package/docs/adr/0005-temporary-rejected-cell-edit-transport.md +8 -7
- package/docs/adr/0008-define-agent-operating-contract.md +2 -2
- package/docs/adr/0010-session-log-derived-recovery-tips.md +7 -0
- package/docs/adr/0016-archive-resolved-review-findings.md +3 -3
- package/docs/adr/0019-plugin-settings-and-kill-switch.md +3 -3
- package/docs/adr/0020-optional-cordis-tools-in-ptc-mode.md +2 -0
- package/docs/adr/0021-separate-host-bootstrap-from-session-runtime.md +14 -3
- package/docs/adr/0023-global-user-bindings.md +7 -7
- package/docs/adr/0025-use-versioned-logical-binding-identities.md +3 -3
- package/docs/adr/0026-persist-the-delivery-verdict-against-the-verified-candidate.md +23 -7
- package/docs/adr/0027-run-the-session-kernel-in-a-killable-helper-process.md +9 -5
- package/docs/adr/0028-attach-to-the-host-ptc-execution-seam.md +26 -6
- package/docs/adr/0031-close-semantic-obligations-by-owner.md +29 -0
- package/docs/adr/README.md +4 -4
- package/docs/architecture.md +15 -10
- package/docs/binding-review-placement-design.md +1 -1
- package/docs/client-ui.md +9 -5
- package/docs/durability-design.md +12 -9
- package/docs/installation.md +27 -9
- package/docs/runtime-reference.md +23 -12
- package/docs/semantic-validation.md +7 -3
- package/docs/stateful-binding-updates-proposal.md +8 -4
- package/docs/user-bindings.en.md +10 -7
- package/docs/user-bindings.md +9 -6
- package/docs/value-wire.md +1 -1
- package/docs/verification-optimization-work.md +41 -0
- package/docs/verification.md +53 -2
- package/index.js +39 -34
- package/internal/async-local-storage-intrinsics.js +43 -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/callable-source-facts.js +37 -1
- package/internal/cell-analysis.js +85 -17
- package/internal/compiler-intrinsics.js +9 -43
- package/internal/compiler-module-links.js +7 -4
- package/internal/config-spec.js +1 -1
- package/internal/cordis-tools-owner.js +20 -10
- 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/edit-transport-owner.js +80 -20
- package/internal/execution-seam-compat.js +192 -30
- package/internal/failure-reporting.js +38 -30
- package/internal/isolated-worker.js +17 -10
- package/internal/kernel-worker.js +645 -393
- package/internal/message-sources.js +14 -0
- package/internal/module-compilation.js +7 -5
- package/internal/module-transform-contract.js +32 -2
- package/internal/record-utils.js +24 -15
- package/internal/repl-memory-projection.js +10 -0
- package/internal/repl-scope-normalizer.js +72 -35
- package/internal/repl-value-observer.js +145 -46
- package/internal/runtime-config.js +62 -2
- package/internal/runtime-intrinsics.js +104 -0
- package/internal/runtime-messages.js +6 -4
- package/internal/session-cell-executor.js +26 -2
- package/internal/session-journal-recovery.js +267 -67
- package/internal/session-journal-schema.js +30 -13
- package/internal/session-journal.js +485 -44
- package/internal/session-log-view.js +5 -4
- package/internal/session-runtime.js +46 -21
- package/internal/session-state.js +7 -29
- package/internal/settings-compat.js +66 -1
- package/internal/source-position-map.js +38 -0
- package/internal/stateful-root-compiler.js +3 -1
- package/internal/typescript-transform.js +4 -2
- package/internal/user-binding-console-worker.js +115 -41
- package/internal/user-binding-draft-projection.js +3 -2
- package/internal/user-bindings-owner.js +2 -1
- package/internal/user-bindings.js +16 -3
- package/internal/validated-parse-repair.js +6 -2
- package/internal/value-wire.js +189 -94
- package/internal/worker-client.js +99 -8
- package/internal/worker-cwd-virtualization.js +82 -54
- package/internal/worker-environment.js +20 -0
- package/internal/worker-output-capture.js +187 -0
- package/internal/worker-output-fence.js +26 -0
- package/internal/worker-realm-surfaces.js +197 -0
- package/internal/worker-repl-realm.js +140 -0
- package/package.json +37 -4
- package/src/client-activity.js +12 -2
- package/src/client-authoring-view.js +121 -62
- package/src/client-console.js +4 -2
- package/src/client-copy.js +4 -2
- package/src/client-feature-gates.js +7 -9
- package/src/client-host-compat.js +104 -27
- package/src/client-repl-view.js +3 -1
- package/src/client-styles.js +12 -1
- package/src/client-tool-view.js +3 -1
- package/src/client-transport.js +2 -2
- package/src/client-workbench.js +4 -3
- package/src/client.js +56 -17
|
@@ -1,23 +1,29 @@
|
|
|
1
|
-
import { AsyncLocalStorage } from 'node:async_hooks'
|
|
2
1
|
import { create as createDomain } from 'node:domain'
|
|
2
|
+
import { Console } from 'node:console'
|
|
3
3
|
import { createRequire, registerHooks } from 'node:module'
|
|
4
4
|
import { managedModuleImport, managedRequire, readModuleImport, statefulModuleLink } from './stateful-module-runtime.js'
|
|
5
5
|
import { staticModuleLinkReference } from './compiler-module-links.js'
|
|
6
6
|
import { compileStatefulModule, createUserModuleCompilationHooks } from './stateful-module-compiler.js'
|
|
7
|
-
import { USER_BINDING_TRANSFORM, LEGACY_USER_BINDING_TRANSFORM
|
|
7
|
+
import { USER_BINDING_TRANSFORM, LEGACY_USER_BINDING_TRANSFORM } from './module-transform-contract.js'
|
|
8
8
|
import { isAbsolute, resolve } from 'node:path'
|
|
9
9
|
import repl from 'node:repl'
|
|
10
|
-
import { PassThrough } from 'node:stream'
|
|
10
|
+
import { PassThrough, Writable } from 'node:stream'
|
|
11
11
|
import { pathToFileURL } from 'node:url'
|
|
12
|
-
import { formatWithOptions } from 'node:util'
|
|
13
12
|
import { MessageChannel, parentPort, workerData } from 'node:worker_threads'
|
|
14
|
-
import { runInContext } from 'node:vm'
|
|
15
13
|
import { synchronizeBuiltinEsmExports } from './builtin-esm-sync.js'
|
|
14
|
+
import { createPrivateAsyncLocalStorage } from './async-local-storage-intrinsics.js'
|
|
16
15
|
import { createExceptionOriginScope, errorDetails, messageOf, programBindingError } from './failure-reporting.js'
|
|
17
|
-
import {
|
|
16
|
+
import { DURABLE_IMPORTS, FORBIDDEN_IMPORTS } from './module-policy.js'
|
|
18
17
|
import { decodeValue, encodeValue } from './value-wire.js'
|
|
19
18
|
import { LEGACY_USER_BINDINGS_REUSE_POLICY, LIVE_USER_BINDINGS_SHADOW_POLICY, normalizeUserBindingNames } from './session-journal-schema.js'
|
|
20
19
|
import { installWorkerCwdVirtualization } from './worker-cwd-virtualization.js'
|
|
20
|
+
import {
|
|
21
|
+
WORKER_REALM_MUTATION,
|
|
22
|
+
assignWorkerRealmProperty,
|
|
23
|
+
defineWorkerRealmProperty,
|
|
24
|
+
deleteWorkerRealmProperty,
|
|
25
|
+
installProcessControlGuards,
|
|
26
|
+
} from './worker-realm-surfaces.js'
|
|
21
27
|
import { createReplValueObserver, supportsAwaitLexicals, previewBindingValue } from './repl-value-observer.js'
|
|
22
28
|
import { createStatefulRootRuntime } from './stateful-root-runtime.js'
|
|
23
29
|
import { createNativeRootDynamic } from './native-root-dynamic.js'
|
|
@@ -25,9 +31,80 @@ import { moduleRuntimeIntrinsics } from './compiler-intrinsics.js'
|
|
|
25
31
|
import { compilerDescriptors } from './compiler-descriptors.js'
|
|
26
32
|
import { createCellCompletionObserver } from './cell-completion.js'
|
|
27
33
|
import { WORKER_SHUTDOWN_ACKNOWLEDGEMENT, WORKER_SHUTDOWN_REQUEST } from './worker-shutdown.js'
|
|
34
|
+
import { outputFenceMarker } from './worker-output-fence.js'
|
|
35
|
+
import { WORKER_REPL_OPTIONS, createWorkerControlPromise, createWorkerReplErrorHandler,
|
|
36
|
+
createWorkerUncaughtExceptionHandler,
|
|
37
|
+
disableWorkerReplDomain,
|
|
38
|
+
captureWorkerReplGlobals, protectWorkerReplAsyncContext, restoreWorkerReplGlobals, runInWorkerReplRealm,
|
|
39
|
+
workerReplContext } from './worker-repl-realm.js'
|
|
40
|
+
import { installProgramAmbientResolver } from './dynamic-environment-runtime.js'
|
|
28
41
|
|
|
29
|
-
const { Object,
|
|
30
|
-
|
|
42
|
+
const { Object, Map, Set, Proxy, Error,
|
|
43
|
+
mapGet, mapSet, mapHas, mapDelete, mapClear, mapSize, mapForEach,
|
|
44
|
+
setHas, setAdd, setDelete, setClear, setForEach,
|
|
45
|
+
everyArray, someArray, appendArray, copyArray, join, sliceString, endsWith,
|
|
46
|
+
bufferConcat, bufferIsBuffer, bufferToString,
|
|
47
|
+
Reflect: privateReflect, promiseResolve, promiseReject } = moduleRuntimeIntrinsics
|
|
48
|
+
const PublicPromise = globalThis.Promise
|
|
49
|
+
const jsonStringify = globalThis.JSON.stringify
|
|
50
|
+
const bufferByteLength = globalThis.Buffer.byteLength
|
|
51
|
+
const String = globalThis.String
|
|
52
|
+
const process = globalThis.process
|
|
53
|
+
const ControlPromise = createWorkerControlPromise()
|
|
54
|
+
const hasProperty = privateReflect.has
|
|
55
|
+
|
|
56
|
+
function mapArray(values, visit) {
|
|
57
|
+
const result = []
|
|
58
|
+
for (let index = 0; index < values.length; index++) appendArray(result, visit(values[index], index))
|
|
59
|
+
return result
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function filterArray(values, select) {
|
|
63
|
+
const result = []
|
|
64
|
+
for (let index = 0; index < values.length; index++) {
|
|
65
|
+
if (select(values[index], index)) appendArray(result, values[index])
|
|
66
|
+
}
|
|
67
|
+
return result
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function mapFromArray(values, visit) {
|
|
71
|
+
const result = new Map()
|
|
72
|
+
for (let index = 0; index < values.length; index++) {
|
|
73
|
+
const entry = visit(values[index], index)
|
|
74
|
+
mapSet(result, entry[0], entry[1])
|
|
75
|
+
}
|
|
76
|
+
return result
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function setFromArray(values) {
|
|
80
|
+
const result = new Set()
|
|
81
|
+
for (let index = 0; index < values.length; index++) setAdd(result, values[index])
|
|
82
|
+
return result
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function mapKeysArray(value) {
|
|
86
|
+
const result = []
|
|
87
|
+
mapForEach(value, (_item, key) => appendArray(result, key))
|
|
88
|
+
return result
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function mapEntriesArray(value) {
|
|
92
|
+
const result = []
|
|
93
|
+
mapForEach(value, (item, key) => appendArray(result, [key, item]))
|
|
94
|
+
return result
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function mapValuesArray(value) {
|
|
98
|
+
const result = []
|
|
99
|
+
mapForEach(value, item => appendArray(result, item))
|
|
100
|
+
return result
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function setValuesArray(value) {
|
|
104
|
+
const result = []
|
|
105
|
+
setForEach(value, item => appendArray(result, item))
|
|
106
|
+
return result
|
|
107
|
+
}
|
|
31
108
|
|
|
32
109
|
if (parentPort === null) throw new Error('ptc-plus kernel worker started without a parent port')
|
|
33
110
|
const { port1, port2: channel } = new MessageChannel()
|
|
@@ -35,42 +112,50 @@ const { port1, port2: channel } = new MessageChannel()
|
|
|
35
112
|
const input = new PassThrough()
|
|
36
113
|
const output = new PassThrough()
|
|
37
114
|
output.resume()
|
|
38
|
-
const evaluationScope =
|
|
115
|
+
const evaluationScope = createPrivateAsyncLocalStorage()
|
|
39
116
|
const sessionCwd = typeof workerData?.cwd === 'string' ? workerData.cwd : undefined
|
|
40
117
|
if (sessionCwd !== undefined && !isAbsolute(sessionCwd)) {
|
|
41
|
-
throw new Error(`ptc-plus session cwd must be absolute, got ${
|
|
118
|
+
throw new Error(`ptc-plus session cwd must be absolute, got ${jsonStringify(sessionCwd)}`)
|
|
42
119
|
}
|
|
120
|
+
const workerGlobalBaseline = captureWorkerReplGlobals()
|
|
43
121
|
const server = repl.start({
|
|
44
122
|
input,
|
|
45
123
|
output,
|
|
46
124
|
terminal: false,
|
|
47
125
|
prompt: '',
|
|
48
|
-
|
|
126
|
+
...WORKER_REPL_OPTIONS,
|
|
127
|
+
handleError: createWorkerReplErrorHandler(evaluationScope, (finish, error) => finish(true, error)),
|
|
49
128
|
ignoreUndefined: true,
|
|
50
129
|
})
|
|
130
|
+
protectWorkerReplAsyncContext(server)
|
|
131
|
+
const originalRequire = server.context.require
|
|
51
132
|
// domain.bind exposes its owner on a bound evaluator in older Node REPLs.
|
|
52
133
|
// Newer REPLs honor an explicitly entered domain. Capture before REPL error
|
|
53
134
|
// formatting can evaluate user-defined stack/name getters.
|
|
54
|
-
const errorDomain = server.eval.domain ?? createDomain()
|
|
135
|
+
const errorDomain = disableWorkerReplDomain(server.eval.domain ?? createDomain())
|
|
55
136
|
errorDomain.removeAllListeners('error')
|
|
56
137
|
errorDomain.on('error', error => evaluationScope.getStore()?.(true, error))
|
|
57
|
-
const
|
|
58
|
-
|
|
59
|
-
const
|
|
138
|
+
const handleUncaughtException = createWorkerUncaughtExceptionHandler(evaluationScope, process)
|
|
139
|
+
process.on('uncaughtException', handleUncaughtException)
|
|
140
|
+
const context = workerReplContext(server)
|
|
141
|
+
restoreWorkerReplGlobals(workerGlobalBaseline, context)
|
|
142
|
+
const workerBaselineGlobals = setFromArray(mapKeysArray(workerGlobalBaseline))
|
|
143
|
+
const contextGlobal = runInWorkerReplRealm('globalThis')
|
|
144
|
+
const completionObserver = createCellCompletionObserver(runInWorkerReplRealm('Function'))
|
|
60
145
|
let replModuleRealm
|
|
61
146
|
const statefulRoots = createStatefulRootRuntime({
|
|
62
147
|
refresh: name => refreshLegacyPublication(name),
|
|
63
148
|
importModule: (source, options) => managedModuleImport(replParent, source, options, replModuleRealm),
|
|
64
|
-
errors:
|
|
65
|
-
dynamicIntrinsics:
|
|
66
|
-
readAmbient: name =>
|
|
67
|
-
typeofAmbient: name =>
|
|
149
|
+
errors: runInWorkerReplRealm('({ ReferenceError, TypeError })'),
|
|
150
|
+
dynamicIntrinsics: runInWorkerReplRealm('({intrinsicEval:eval,realmFunction:Function,globalObject:globalThis,errors:{ReferenceError,TypeError,SyntaxError},reflect:Reflect,object:Object})'),
|
|
151
|
+
readAmbient: name => runInWorkerReplRealm(name),
|
|
152
|
+
typeofAmbient: name => runInWorkerReplRealm(`typeof ${name}`),
|
|
68
153
|
canWriteAmbient: name => writableRootProperty(name),
|
|
69
154
|
writeAmbient(name, value, strict) {
|
|
70
155
|
const argument = name === '__ptc_value' ? '__ptc_other_value' : '__ptc_value'
|
|
71
|
-
|
|
156
|
+
runInWorkerReplRealm(`${strict ? '"use strict";' : ''}(${argument}) => (${name} = ${argument})`)(value)
|
|
72
157
|
},
|
|
73
|
-
deleteAmbient: name =>
|
|
158
|
+
deleteAmbient: name => runInWorkerReplRealm(`delete ${name}`),
|
|
74
159
|
hasAmbient: name => setHas(installedGlobals, name)
|
|
75
160
|
? mapGet(installedGlobalOriginals, name).descriptor !== undefined : hasProperty(context, name),
|
|
76
161
|
// Request namespaces stay on globalThis; source declarations own separate
|
|
@@ -83,15 +168,15 @@ const statefulRoots = createStatefulRootRuntime({
|
|
|
83
168
|
})
|
|
84
169
|
const nativeRootDynamic = createNativeRootDynamic({
|
|
85
170
|
logicalReference: (name, writable) => statefulRoots.legacyReference(name, writable),
|
|
86
|
-
intrinsics:
|
|
171
|
+
intrinsics: runInWorkerReplRealm('({intrinsicEval:eval,realmFunction:Function,globalObject:globalThis,errors:{ReferenceError,TypeError,SyntaxError},reflect:Reflect,object:Object})'),
|
|
87
172
|
importModule: (source, options) => managedModuleImport(replParent, source, options, replModuleRealm),
|
|
88
|
-
read: name =>
|
|
89
|
-
typeOf: name =>
|
|
173
|
+
read: name => runInWorkerReplRealm(name),
|
|
174
|
+
typeOf: name => runInWorkerReplRealm(`typeof ${name}`),
|
|
90
175
|
write(name, value, strict) {
|
|
91
176
|
const argument = name === '__ptc_value' ? '__ptc_other_value' : '__ptc_value'
|
|
92
|
-
|
|
177
|
+
runInWorkerReplRealm(`${strict ? '"use strict";' : ''}(${argument}) => (${name} = ${argument})`)(value)
|
|
93
178
|
},
|
|
94
|
-
remove: name =>
|
|
179
|
+
remove: name => runInWorkerReplRealm(`delete ${name}`),
|
|
95
180
|
})
|
|
96
181
|
let valueObserver
|
|
97
182
|
const REPL_IMPORT_CANARY = 'data:text/javascript,export default 1'
|
|
@@ -101,7 +186,7 @@ const staticAdapterParents = new Set()
|
|
|
101
186
|
const userBindingModuleParents = new Map()
|
|
102
187
|
const userModuleCompilation = createUserModuleCompilationHooks({
|
|
103
188
|
transformForParent: parent => (parent === replParent || parent === sessionReplParent) && activeExecution !== undefined
|
|
104
|
-
?
|
|
189
|
+
? activeExecution.moduleTransform : undefined,
|
|
105
190
|
})
|
|
106
191
|
registerHooks({
|
|
107
192
|
resolve(specifier, context, nextResolve) {
|
|
@@ -117,12 +202,13 @@ registerHooks({
|
|
|
117
202
|
},
|
|
118
203
|
load: userModuleCompilation.load,
|
|
119
204
|
})
|
|
120
|
-
const logScope =
|
|
121
|
-
const userBindingActivationScope =
|
|
205
|
+
const logScope = createPrivateAsyncLocalStorage()
|
|
206
|
+
const userBindingActivationScope = createPrivateAsyncLocalStorage()
|
|
122
207
|
const pending = new Map()
|
|
208
|
+
const pendingOutputStarts = new Map()
|
|
123
209
|
const installedGlobals = new Set()
|
|
124
210
|
const installedGlobalOriginals = new Map()
|
|
125
|
-
const PROCESS_CONTROLS =
|
|
211
|
+
const PROCESS_CONTROLS = setFromArray(['exit', 'abort', 'kill', 'chdir'])
|
|
126
212
|
const CELL_FRAME_SUFFIX = '\n;'
|
|
127
213
|
let filenameSequence = 0
|
|
128
214
|
let activeFilename = 'ptc-plus-repl'
|
|
@@ -131,12 +217,12 @@ const CONFORMANCE_CELL = `"use strict";
|
|
|
131
217
|
if (this !== globalThis) throw new Error('invalid REPL global receiver semantics')
|
|
132
218
|
const __ptc_canary = await Promise.resolve(1)
|
|
133
219
|
if (__ptc_canary !== 1) throw new Error('invalid REPL await semantics')
|
|
134
|
-
const __ptc_import_canary = await import(${
|
|
220
|
+
const __ptc_import_canary = await import(${jsonStringify(REPL_IMPORT_CANARY)})
|
|
135
221
|
if (__ptc_import_canary.default !== 1) throw new Error('invalid REPL import semantics')
|
|
136
222
|
}`
|
|
137
223
|
let activeRun
|
|
138
224
|
let activeExecution
|
|
139
|
-
let
|
|
225
|
+
let suppressReplCwdObservation = false
|
|
140
226
|
let nextCallId = 0
|
|
141
227
|
let nextStaticAdapterId = 0
|
|
142
228
|
let nextUserBindingModuleId = 0
|
|
@@ -145,7 +231,20 @@ const userBindingNames = new Map()
|
|
|
145
231
|
const userBindingSources = new Map()
|
|
146
232
|
const dynamicNamespaces = new Map()
|
|
147
233
|
const originalDynamicNamespaceGlobals = new Map()
|
|
148
|
-
|
|
234
|
+
const dynamicNamespaceViews = new Map()
|
|
235
|
+
const dynamicNamespaceGlobalGetters = new Map()
|
|
236
|
+
let retainedLegacyUserBindingRuntime = false
|
|
237
|
+
|
|
238
|
+
installProgramAmbientResolver(name => {
|
|
239
|
+
if (!mapHas(dynamicNamespaces, name) && !mapHas(originalDynamicNamespaceGlobals, name)
|
|
240
|
+
&& !mapHas(dynamicNamespaceViews, name)) return undefined
|
|
241
|
+
return {
|
|
242
|
+
get: () => dynamicNamespaceView(name),
|
|
243
|
+
set() { throw new TypeError(`${name} cannot be overwritten because reserved program bindings are not shadowable`) },
|
|
244
|
+
typeof: () => 'object',
|
|
245
|
+
delete: () => false,
|
|
246
|
+
}
|
|
247
|
+
})
|
|
149
248
|
|
|
150
249
|
class StaticImportFailure {
|
|
151
250
|
constructor(error, position) {
|
|
@@ -164,50 +263,79 @@ class CellReturn extends Error {
|
|
|
164
263
|
evaluationScope.getStore().completed = true
|
|
165
264
|
}
|
|
166
265
|
}
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
266
|
+
/**
|
|
267
|
+
* Convert one captured descriptor chunk with plugin-private operations.
|
|
268
|
+
*
|
|
269
|
+
* The private Writable decodes string writes into Buffers, and a cell may
|
|
270
|
+
* replace `Buffer.prototype.toString` or a typed-array join. Converting here
|
|
271
|
+
* with captured operations keeps the model-visible log record from running user
|
|
272
|
+
* code that could rewrite, forge, or fail the logs of the cell that printed
|
|
273
|
+
* them.
|
|
274
|
+
*/
|
|
275
|
+
function outputText(chunk) {
|
|
276
|
+
if (typeof chunk === 'string') return chunk
|
|
277
|
+
// The private Writable hands over Buffers, and a direct write may pass any
|
|
278
|
+
// Uint8Array view. Any other chunk type fails in the captured conversion,
|
|
279
|
+
// exactly as the replaced stream method would reject it.
|
|
280
|
+
return bufferToString(bufferIsBuffer(chunk) ? chunk : bufferConcat([chunk]), 'utf8')
|
|
171
281
|
}
|
|
172
282
|
|
|
173
283
|
function appendText(current, text) {
|
|
174
|
-
if (current.open !== true
|
|
175
|
-
|
|
284
|
+
if (current.open !== true) {
|
|
285
|
+
if (!current.lateOutputReported) {
|
|
286
|
+
current.lateOutputReported = true
|
|
287
|
+
channel.postMessage({ type: 'late-output', id: current.id })
|
|
288
|
+
}
|
|
289
|
+
return
|
|
290
|
+
}
|
|
291
|
+
if (current.outputLimited) return
|
|
292
|
+
const bytes = bufferByteLength(jsonStringify(text), 'utf8') + (current.logs.length === 0 ? 0 : 1)
|
|
176
293
|
if (current.logBytes + bytes > current.maxOutputBytes) {
|
|
177
294
|
current.outputLimited = true
|
|
178
295
|
channel.postMessage({ type: 'output-limit', id: current.id, logs: current.logs })
|
|
179
296
|
return
|
|
180
297
|
}
|
|
181
298
|
current.logBytes += bytes
|
|
182
|
-
current.logs
|
|
299
|
+
appendArray(current.logs, text)
|
|
183
300
|
}
|
|
184
301
|
|
|
185
|
-
const
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
302
|
+
const consoleOutput = new Writable({
|
|
303
|
+
write(chunk, _encoding, callback) {
|
|
304
|
+
const current = logScope.getStore()
|
|
305
|
+
if (current !== undefined) {
|
|
306
|
+
const text = outputText(chunk)
|
|
307
|
+
appendText(current, endsWith(text, '\n') ? sliceString(text, 0, -1) : text)
|
|
308
|
+
}
|
|
309
|
+
callback()
|
|
310
|
+
},
|
|
192
311
|
})
|
|
193
|
-
|
|
312
|
+
const nativeStdoutWrite = process.stdout.write.bind(process.stdout)
|
|
313
|
+
const nativeStderrWrite = process.stderr.write.bind(process.stderr)
|
|
314
|
+
const consoleView = new Console({
|
|
315
|
+
stdout: consoleOutput,
|
|
316
|
+
stderr: consoleOutput,
|
|
317
|
+
colorMode: false,
|
|
318
|
+
inspectOptions: { depth: 4, maxArrayLength: 100, maxStringLength: 10_000 },
|
|
319
|
+
})
|
|
320
|
+
defineWorkerRealmProperty(WORKER_REALM_MUTATION.STABLE, 'console',
|
|
321
|
+
context, 'console', { configurable: true, value: consoleView })
|
|
194
322
|
|
|
195
323
|
function captureWrite(chunk, ...rest) {
|
|
196
324
|
const current = logScope.getStore()
|
|
197
|
-
if (current
|
|
198
|
-
const callback =
|
|
325
|
+
if (current !== undefined) appendText(current, outputText(chunk))
|
|
326
|
+
const callback = typeof rest[0] === 'function' ? rest[0]
|
|
327
|
+
: typeof rest[1] === 'function' ? rest[1] : undefined
|
|
199
328
|
if (callback !== undefined) queueMicrotask(() => callback(null))
|
|
200
329
|
return true
|
|
201
330
|
}
|
|
202
|
-
process.stdout.write
|
|
203
|
-
process.
|
|
331
|
+
assignWorkerRealmProperty(WORKER_REALM_MUTATION.STABLE, 'process.stdout.write/process.stderr.write',
|
|
332
|
+
process.stdout, 'write', captureWrite)
|
|
333
|
+
assignWorkerRealmProperty(WORKER_REALM_MUTATION.STABLE, 'process.stdout.write/process.stderr.write',
|
|
334
|
+
process.stderr, 'write', captureWrite)
|
|
204
335
|
|
|
205
336
|
function markVolatile(reason) {
|
|
206
337
|
const current = activeExecution
|
|
207
|
-
if (current === undefined)
|
|
208
|
-
pendingVolatileReason ??= reason
|
|
209
|
-
return
|
|
210
|
-
}
|
|
338
|
+
if (current === undefined) return
|
|
211
339
|
if (current.durability === 'volatile') return
|
|
212
340
|
current.durability = 'volatile'
|
|
213
341
|
current.volatileReason ??= reason
|
|
@@ -221,101 +349,32 @@ function completionDurability(execution) {
|
|
|
221
349
|
}
|
|
222
350
|
}
|
|
223
351
|
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
Object.defineProperty(process, property, {
|
|
230
|
-
configurable: false,
|
|
231
|
-
enumerable: descriptor?.enumerable ?? true,
|
|
232
|
-
writable: false,
|
|
233
|
-
value: () => {
|
|
234
|
-
throw new Error(`process.${property} is forbidden inside the REPL kernel`)
|
|
235
|
-
},
|
|
236
|
-
})
|
|
352
|
+
installProcessControlGuards(process, setValuesArray(PROCESS_CONTROLS))
|
|
353
|
+
installWorkerCwdVirtualization(sessionCwd, originalRequire, reason => {
|
|
354
|
+
if (suppressReplCwdObservation) {
|
|
355
|
+
suppressReplCwdObservation = false
|
|
356
|
+
return
|
|
237
357
|
}
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
guardProcessControls()
|
|
241
|
-
installWorkerCwdVirtualization(sessionCwd, originalRequire)
|
|
358
|
+
markVolatile(reason)
|
|
359
|
+
})
|
|
242
360
|
synchronizeBuiltinEsmExports()
|
|
243
|
-
const originalGlobals = Object.fromEntries(
|
|
244
|
-
[...AMBIENT_GLOBALS].filter(name => name !== 'require')
|
|
245
|
-
.map(name => [name, name === 'eval' || name === 'Function' ? runInContext(name, context) : globalThis[name]]),
|
|
246
|
-
)
|
|
247
361
|
const providedRequire = sessionReplParent === undefined ? originalRequire : createRequire(sessionReplParent)
|
|
248
362
|
function selectProvidedRequire(args) {
|
|
249
363
|
const specifier = args[0]
|
|
250
|
-
if (FORBIDDEN_IMPORTS
|
|
251
|
-
if (!DURABLE_IMPORTS
|
|
364
|
+
if (setHas(FORBIDDEN_IMPORTS, specifier)) throw new Error(`module ${specifier} is forbidden because it exposes kernel control`)
|
|
365
|
+
if (!setHas(DURABLE_IMPORTS, specifier)) markVolatile(`require(${jsonStringify(specifier)})`)
|
|
252
366
|
return managedRequire(activeExecution?.languageSemantics === 'legacy-v1' ? replParent : sessionReplParent ?? replParent,
|
|
253
367
|
activeExecution?.languageSemantics === 'legacy-v1' ? originalRequire : providedRequire)
|
|
254
368
|
}
|
|
255
|
-
|
|
369
|
+
defineWorkerRealmProperty(WORKER_REALM_MUTATION.STABLE, 'cell module metadata', context, 'require', {
|
|
256
370
|
configurable: true,
|
|
257
371
|
value: new Proxy(providedRequire, {
|
|
258
|
-
apply: (_, receiver, args) =>
|
|
259
|
-
construct: (_, args, newTarget) =>
|
|
372
|
+
apply: (_, receiver, args) => privateReflect.apply(selectProvidedRequire(args), receiver, args),
|
|
373
|
+
construct: (_, args, newTarget) => privateReflect.construct(selectProvidedRequire(args), args, newTarget),
|
|
260
374
|
}),
|
|
261
375
|
})
|
|
262
|
-
|
|
263
|
-
for (const [name, value] of Object.entries(originalGlobals)) {
|
|
264
|
-
Object.defineProperty(context, name, {
|
|
265
|
-
configurable: true,
|
|
266
|
-
get() {
|
|
267
|
-
markVolatile(`ambient ${name}`)
|
|
268
|
-
return value
|
|
269
|
-
},
|
|
270
|
-
set(next) {
|
|
271
|
-
markVolatile(`ambient ${name}`)
|
|
272
|
-
Object.defineProperty(context, name, { configurable: true, writable: true, value: next })
|
|
273
|
-
},
|
|
274
|
-
})
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
const capturedOutput = Object.freeze({ write: captureWrite })
|
|
278
|
-
const processView = new Proxy(process, {
|
|
279
|
-
get(target, property) {
|
|
280
|
-
if (property === 'stdout' || property === 'stderr') return capturedOutput
|
|
281
|
-
if (property === 'cwd') {
|
|
282
|
-
if (sessionCwd !== undefined) return () => sessionCwd
|
|
283
|
-
markVolatile('process.cwd')
|
|
284
|
-
return target.cwd.bind(target)
|
|
285
|
-
}
|
|
286
|
-
if (PROCESS_CONTROLS.has(property)) return Reflect.get(target, property, target)
|
|
287
|
-
markVolatile(`process.${String(property)}`)
|
|
288
|
-
const value = Reflect.get(target, property, target)
|
|
289
|
-
return typeof value === 'function' ? value.bind(target) : value
|
|
290
|
-
},
|
|
291
|
-
set(target, property, value) {
|
|
292
|
-
if (property === 'stdout' || property === 'stderr') return false
|
|
293
|
-
markVolatile(`process.${String(property)}`)
|
|
294
|
-
return Reflect.set(target, property, value, target)
|
|
295
|
-
},
|
|
296
|
-
ownKeys(target) {
|
|
297
|
-
markVolatile('process reflection')
|
|
298
|
-
return Reflect.ownKeys(target)
|
|
299
|
-
},
|
|
300
|
-
})
|
|
301
|
-
Object.defineProperty(context, 'process', { configurable: true, value: processView })
|
|
302
|
-
|
|
303
|
-
const mathDescriptors = Object.getOwnPropertyDescriptors(Math)
|
|
304
|
-
mathDescriptors.random = {
|
|
305
|
-
...mathDescriptors.random,
|
|
306
|
-
value: () => {
|
|
307
|
-
markVolatile('Math.random')
|
|
308
|
-
return Math.random()
|
|
309
|
-
},
|
|
310
|
-
}
|
|
311
|
-
const mathView = Object.defineProperties(Object.create(Object.getPrototypeOf(Math)), mathDescriptors)
|
|
312
|
-
Object.defineProperty(context, 'Math', {
|
|
313
|
-
configurable: true,
|
|
314
|
-
value: Object.freeze(mathView),
|
|
315
|
-
})
|
|
316
|
-
|
|
317
376
|
function evaluate(program, completionSignal, asyncCompletion = false) {
|
|
318
|
-
return new
|
|
377
|
+
return new ControlPromise((resolve, reject) => {
|
|
319
378
|
let settled = false
|
|
320
379
|
const finish = (failed, value) => {
|
|
321
380
|
if (settled) return
|
|
@@ -334,6 +393,7 @@ function evaluate(program, completionSignal, asyncCompletion = false) {
|
|
|
334
393
|
}
|
|
335
394
|
}
|
|
336
395
|
activeFilename = `ptc-plus-repl-${++filenameSequence}`
|
|
396
|
+
suppressReplCwdObservation = true
|
|
337
397
|
evaluationScope.run(finish, () => errorDomain.run(() => {
|
|
338
398
|
// Older REPL callbacks conflate null/undefined await rejection with a
|
|
339
399
|
// successful empty result. A reached EOF distinguishes those outcomes.
|
|
@@ -341,7 +401,7 @@ function evaluate(program, completionSignal, asyncCompletion = false) {
|
|
|
341
401
|
// preceding non-await expression value without adding a session binding.
|
|
342
402
|
// Keep the final semicolon so REPL cannot guess a block is an object literal.
|
|
343
403
|
const suffix = completionSignal === undefined || asyncCompletion ? CELL_FRAME_SUFFIX
|
|
344
|
-
: `${CELL_FRAME_SUFFIX}{ let completed = this[${
|
|
404
|
+
: `${CELL_FRAME_SUFFIX}{ let completed = this[${jsonStringify(completionSignal)}].complete(); }${CELL_FRAME_SUFFIX}`
|
|
345
405
|
server.eval(program + suffix, context, activeFilename, (error, value) => {
|
|
346
406
|
if (asyncCompletion && (error === null || error === undefined)) {
|
|
347
407
|
completionObserver.observe(value, value => finish(false, value), error => finish(true, error))
|
|
@@ -351,6 +411,7 @@ function evaluate(program, completionSignal, asyncCompletion = false) {
|
|
|
351
411
|
finish(failed, failed ? error : value)
|
|
352
412
|
})
|
|
353
413
|
}))
|
|
414
|
+
suppressReplCwdObservation = false
|
|
354
415
|
})
|
|
355
416
|
}
|
|
356
417
|
|
|
@@ -358,12 +419,19 @@ async function verifyEvaluation() {
|
|
|
358
419
|
await evaluate(CONFORMANCE_CELL)
|
|
359
420
|
const marker = new Error('PTC Plus REPL settlement probe')
|
|
360
421
|
Object.defineProperty(marker, 'stack', { get() { throw new Error('REPL must not format an error before settlement') } })
|
|
361
|
-
|
|
362
|
-
|
|
422
|
+
assignWorkerRealmProperty(WORKER_REALM_MUTATION.TEMPORARY, undefined,
|
|
423
|
+
context, '__ptc_settlement_probe__', marker)
|
|
424
|
+
assignWorkerRealmProperty(WORKER_REALM_MUTATION.TEMPORARY, undefined,
|
|
425
|
+
context, '__ptc_completion_probe__', CellReturn)
|
|
363
426
|
try {
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
427
|
+
const values = [marker, null, undefined, false, 0]
|
|
428
|
+
for (let valueIndex = 0; valueIndex < values.length; valueIndex += 1) {
|
|
429
|
+
const value = values[valueIndex]
|
|
430
|
+
assignWorkerRealmProperty(WORKER_REALM_MUTATION.TEMPORARY, undefined,
|
|
431
|
+
context, '__ptc_settlement_probe__', value)
|
|
432
|
+
const sources = ['throw __ptc_settlement_probe__', 'await Promise.reject(__ptc_settlement_probe__)']
|
|
433
|
+
for (let sourceIndex = 0; sourceIndex < sources.length; sourceIndex += 1) {
|
|
434
|
+
const source = sources[sourceIndex]
|
|
367
435
|
let caught = false
|
|
368
436
|
try { await evaluate(source, '__ptc_completion_probe__') } catch (error) {
|
|
369
437
|
if (error !== value) throw new Error('REPL did not preserve the original thrown value')
|
|
@@ -375,7 +443,8 @@ async function verifyEvaluation() {
|
|
|
375
443
|
let syntaxRejected = false
|
|
376
444
|
try { await evaluate('const =') } catch (error) { syntaxRejected = error?.name === 'SyntaxError' }
|
|
377
445
|
if (!syntaxRejected) throw new Error('REPL did not preserve syntax failure')
|
|
378
|
-
|
|
446
|
+
assignWorkerRealmProperty(WORKER_REALM_MUTATION.TEMPORARY, undefined,
|
|
447
|
+
context, '__ptc_settlement_probe__', new CellReturn(promiseResolve(marker)))
|
|
379
448
|
const returned = await evaluate('throw __ptc_settlement_probe__')
|
|
380
449
|
if (!returned.hasValue || returned.value !== marker) throw new Error('REPL did not preserve cell return')
|
|
381
450
|
await evaluate(CONFORMANCE_CELL)
|
|
@@ -383,54 +452,54 @@ async function verifyEvaluation() {
|
|
|
383
452
|
const expression = await evaluate('42', '__ptc_completion_probe__')
|
|
384
453
|
if (expression.value !== 42) throw new Error('REPL did not preserve expression completion')
|
|
385
454
|
} finally {
|
|
386
|
-
|
|
387
|
-
|
|
455
|
+
deleteWorkerRealmProperty(WORKER_REALM_MUTATION.RESTORE, undefined,
|
|
456
|
+
context, '__ptc_settlement_probe__')
|
|
457
|
+
deleteWorkerRealmProperty(WORKER_REALM_MUTATION.RESTORE, undefined,
|
|
458
|
+
context, '__ptc_completion_probe__')
|
|
388
459
|
}
|
|
389
460
|
}
|
|
390
461
|
|
|
391
462
|
function staticImportClause(options) {
|
|
392
463
|
if (options === undefined) return { attributes: undefined, clause: '' }
|
|
393
|
-
const
|
|
394
|
-
const
|
|
395
|
-
|
|
396
|
-
|
|
464
|
+
const option = Object.entries(options)[0]
|
|
465
|
+
const keyword = option[0]
|
|
466
|
+
const attributes = option[1]
|
|
467
|
+
const entries = mapArray(Object.entries(attributes), entry => `${jsonStringify(entry[0])}: ${jsonStringify(entry[1])}`)
|
|
468
|
+
return { attributes, clause: ` ${keyword} { ${join(entries, ', ')} }` }
|
|
397
469
|
}
|
|
398
470
|
|
|
399
471
|
function staticAdapterSource(load) {
|
|
400
472
|
const { attributes, clause } = staticImportClause(load.options)
|
|
401
|
-
const source =
|
|
473
|
+
const source = jsonStringify(statefulModuleLink(load.source, staticModuleLinkReference(load.source, attributes)))
|
|
402
474
|
if (load.global === undefined) return `import ${source}${clause};`
|
|
403
|
-
const requirements = load.requiredExports
|
|
404
|
-
const imported = name === 'default' ? 'default' :
|
|
475
|
+
const requirements = load.requiredExports === undefined ? [] : mapArray(load.requiredExports, (name, index) => {
|
|
476
|
+
const imported = name === 'default' ? 'default' : jsonStringify(name)
|
|
405
477
|
return `${imported} as __required_${index}__`
|
|
406
|
-
})
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
]),
|
|
412
|
-
'export { namespace };',
|
|
413
|
-
].join('\n')
|
|
478
|
+
})
|
|
479
|
+
const lines = [`import * as namespace from ${source}${clause};`]
|
|
480
|
+
if (requirements.length > 0) appendArray(lines, `export { ${join(requirements, ', ')} } from ${source}${clause};`)
|
|
481
|
+
appendArray(lines, 'export { namespace };')
|
|
482
|
+
return join(lines, '\n')
|
|
414
483
|
}
|
|
415
484
|
|
|
416
485
|
async function loadStaticModule(load) {
|
|
417
486
|
if (load.operation === 'native-dynamic') return { namespace: nativeRootDynamic.environment(load.callableSources,
|
|
418
|
-
load.awaitRoot === true, load.logicalRoots
|
|
487
|
+
load.awaitRoot === true, filterArray(load.logicalRoots, name => statefulRoots.has(name)
|
|
419
488
|
&& rootBindingStorage(name, moduleRuntimeIntrinsics.includes(load.nativeLexicals, name)) !== 'lexical'), load.writableRoots) }
|
|
420
489
|
if (load.operation === 'import') {
|
|
421
490
|
return { namespace: (source, options) => managedModuleImport(replParent, source, options, replModuleRealm) }
|
|
422
491
|
}
|
|
423
492
|
const adapter = `data:text/javascript,${encodeURIComponent(staticAdapterSource(load))}#${++nextStaticAdapterId}`
|
|
424
|
-
staticAdapterParents
|
|
493
|
+
setAdd(staticAdapterParents, adapter)
|
|
425
494
|
userModuleCompilation.mark(adapter, { compiled: true,
|
|
426
|
-
transform:
|
|
495
|
+
transform: activeExecution?.moduleTransform })
|
|
427
496
|
try {
|
|
428
|
-
const completion = await evaluate(`import(${
|
|
497
|
+
const completion = await evaluate(`import(${jsonStringify(adapter)})`)
|
|
429
498
|
if (load.global === undefined) return { namespace: undefined }
|
|
430
499
|
const namespace = readModuleImport(adapter, load.source, null, () => completion.value.namespace, load.options?.with)
|
|
431
500
|
return { namespace }
|
|
432
501
|
} finally {
|
|
433
|
-
staticAdapterParents
|
|
502
|
+
setDelete(staticAdapterParents, adapter)
|
|
434
503
|
}
|
|
435
504
|
}
|
|
436
505
|
|
|
@@ -443,28 +512,30 @@ function hasExecutionLease(runId) {
|
|
|
443
512
|
// call is pending, and the call must keep its submitted error identity.
|
|
444
513
|
function callHost(runId, global, member, args, BoundError) {
|
|
445
514
|
if (!hasExecutionLease(runId)) {
|
|
446
|
-
return
|
|
515
|
+
return promiseReject(programBindingError('lease', 'PTC execution lease expired'))
|
|
447
516
|
}
|
|
448
517
|
const activation = userBindingActivationScope.getStore()
|
|
449
518
|
if (activation !== undefined) {
|
|
450
519
|
activation.called = true
|
|
451
520
|
if (activation.failed) {
|
|
452
|
-
markVolatile(`failed user binding ${
|
|
521
|
+
markVolatile(`failed user binding ${jsonStringify(activation.id)} issued a host call`)
|
|
453
522
|
}
|
|
454
523
|
}
|
|
455
524
|
const id = ++nextCallId
|
|
456
525
|
const valueLimits = activeExecution.valueLimits
|
|
457
526
|
let settle
|
|
458
|
-
const result = new
|
|
459
|
-
const settled =
|
|
460
|
-
|
|
527
|
+
const result = new PublicPromise((resolve, reject) => { settle = { resolve, reject } })
|
|
528
|
+
const settled = new ControlPromise(resolve => {
|
|
529
|
+
moduleRuntimeIntrinsics.Reflect.apply(moduleRuntimeIntrinsics.promiseThen, result, [resolve, resolve])
|
|
530
|
+
})
|
|
531
|
+
mapSet(pending, id, { ...settle, runId, valueLimits, settled, member, BoundError })
|
|
461
532
|
try {
|
|
462
533
|
channel.postMessage({
|
|
463
534
|
type: 'call', runId, id, global, member,
|
|
464
535
|
args: encodeValue(args, valueLimits),
|
|
465
536
|
})
|
|
466
537
|
} catch (error) {
|
|
467
|
-
pending
|
|
538
|
+
mapDelete(pending, id)
|
|
468
539
|
settle.reject(error)
|
|
469
540
|
}
|
|
470
541
|
return result
|
|
@@ -480,36 +551,39 @@ function dynamicNamespace(name) {
|
|
|
480
551
|
get(_target, property) {
|
|
481
552
|
if (typeof property !== 'string') return undefined
|
|
482
553
|
assertLease()
|
|
483
|
-
const descriptor = dynamicNamespaces
|
|
484
|
-
if (descriptor === undefined || !descriptor.members
|
|
554
|
+
const descriptor = mapGet(dynamicNamespaces, name)
|
|
555
|
+
if (descriptor === undefined || !setHas(descriptor.members, property)) return undefined
|
|
485
556
|
return (...args) => {
|
|
486
557
|
if (!hasExecutionLease(logScope.getStore()?.id)) {
|
|
487
|
-
return
|
|
558
|
+
return promiseReject(programBindingError('lease', 'PTC execution lease expired'))
|
|
488
559
|
}
|
|
489
|
-
const current = dynamicNamespaces
|
|
490
|
-
if (current === undefined || !current.members
|
|
491
|
-
return
|
|
560
|
+
const current = mapGet(dynamicNamespaces, name)
|
|
561
|
+
if (current === undefined || !setHas(current.members, property)) {
|
|
562
|
+
return promiseReject(programBindingError('capability', `unknown binding ${name}.${property}`))
|
|
492
563
|
}
|
|
493
564
|
return callHost(
|
|
494
565
|
logScope.getStore()?.id,
|
|
495
566
|
name,
|
|
496
567
|
property,
|
|
497
|
-
args.length === 0 && current.emptyObjectMembers
|
|
568
|
+
args.length === 0 && setHas(current.emptyObjectMembers, property) ? {} : args[0],
|
|
498
569
|
current.BoundError,
|
|
499
570
|
)
|
|
500
571
|
}
|
|
501
572
|
},
|
|
502
573
|
has(_target, property) {
|
|
503
574
|
assertLease()
|
|
504
|
-
|
|
575
|
+
const descriptor = mapGet(dynamicNamespaces, name)
|
|
576
|
+
return typeof property === 'string' && descriptor !== undefined && setHas(descriptor.members, property)
|
|
505
577
|
},
|
|
506
578
|
ownKeys() {
|
|
507
579
|
assertLease()
|
|
508
|
-
|
|
580
|
+
const descriptor = mapGet(dynamicNamespaces, name)
|
|
581
|
+
return descriptor === undefined ? [] : setValuesArray(descriptor.members)
|
|
509
582
|
},
|
|
510
583
|
getOwnPropertyDescriptor(_target, property) {
|
|
511
584
|
assertLease()
|
|
512
|
-
|
|
585
|
+
const descriptor = mapGet(dynamicNamespaces, name)
|
|
586
|
+
return typeof property === 'string' && descriptor !== undefined && setHas(descriptor.members, property)
|
|
513
587
|
? compilerDescriptors.descriptor({ configurable: true, enumerable: true })
|
|
514
588
|
: undefined
|
|
515
589
|
},
|
|
@@ -517,50 +591,98 @@ function dynamicNamespace(name) {
|
|
|
517
591
|
})
|
|
518
592
|
}
|
|
519
593
|
|
|
594
|
+
function dynamicNamespaceView(name) {
|
|
595
|
+
let view = mapGet(dynamicNamespaceViews, name)
|
|
596
|
+
if (view === undefined) {
|
|
597
|
+
view = dynamicNamespace(name)
|
|
598
|
+
mapSet(dynamicNamespaceViews, name, view)
|
|
599
|
+
}
|
|
600
|
+
return view
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
function namespaceGlobalGetter(name) {
|
|
604
|
+
let getter = mapGet(dynamicNamespaceGlobalGetters, name)
|
|
605
|
+
if (getter === undefined) {
|
|
606
|
+
getter = () => mapGet(dynamicNamespaces, name)?.cellView ?? dynamicNamespaceView(name)
|
|
607
|
+
mapSet(dynamicNamespaceGlobalGetters, name, getter)
|
|
608
|
+
}
|
|
609
|
+
return getter
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
function installNamespaceGlobal(name) {
|
|
613
|
+
defineWorkerRealmProperty(WORKER_REALM_MUTATION.PROGRAM, undefined, globalThis, name, {
|
|
614
|
+
configurable: true,
|
|
615
|
+
get: namespaceGlobalGetter(name),
|
|
616
|
+
})
|
|
617
|
+
}
|
|
618
|
+
|
|
520
619
|
function installDynamicNamespaceGlobals() {
|
|
521
620
|
const pendingGlobals = []
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
const
|
|
525
|
-
if (
|
|
621
|
+
const names = mapKeysArray(dynamicNamespaces)
|
|
622
|
+
for (let index = 0; index < names.length; index += 1) {
|
|
623
|
+
const name = names[index]
|
|
624
|
+
if (mapHas(originalDynamicNamespaceGlobals, name)) {
|
|
625
|
+
installNamespaceGlobal(name)
|
|
626
|
+
continue
|
|
627
|
+
}
|
|
628
|
+
const original = setHas(installedGlobals, name)
|
|
629
|
+
? mapGet(installedGlobalOriginals, name)
|
|
630
|
+
: capturedGlobalDescriptor(name)
|
|
631
|
+
if (setHas(workerBaselineGlobals, name)) {
|
|
526
632
|
throw new Error(
|
|
527
|
-
`program namespace ${
|
|
633
|
+
`program namespace ${jsonStringify(name)} cannot be bridged into Global User Binding modules because the worker global already exists`,
|
|
528
634
|
)
|
|
529
635
|
}
|
|
530
|
-
pendingGlobals.
|
|
636
|
+
appendArray(pendingGlobals, [name, original.descriptor])
|
|
531
637
|
}
|
|
532
|
-
for (
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
})
|
|
638
|
+
for (let index = 0; index < pendingGlobals.length; index += 1) {
|
|
639
|
+
const entry = pendingGlobals[index]
|
|
640
|
+
const name = entry[0]
|
|
641
|
+
const descriptor = entry[1]
|
|
642
|
+
mapSet(originalDynamicNamespaceGlobals, name, descriptor)
|
|
643
|
+
if (!setHas(installedGlobals, name)) installNamespaceGlobal(name)
|
|
539
644
|
}
|
|
540
645
|
}
|
|
541
646
|
|
|
542
647
|
function restoreDynamicNamespaceGlobals() {
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
648
|
+
const names = mapKeysArray(originalDynamicNamespaceGlobals)
|
|
649
|
+
for (let index = 0; index < names.length; index += 1) {
|
|
650
|
+
const name = names[index]
|
|
651
|
+
setHas(installedGlobals, name)
|
|
652
|
+
? installNamespaceGlobal(name)
|
|
653
|
+
: deleteWorkerRealmProperty(WORKER_REALM_MUTATION.RESTORE, undefined, globalThis, name)
|
|
654
|
+
}
|
|
655
|
+
mapClear(originalDynamicNamespaceGlobals)
|
|
547
656
|
}
|
|
548
657
|
|
|
549
658
|
function installBindings(message) {
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
659
|
+
const installedNames = setValuesArray(installedGlobals)
|
|
660
|
+
for (let index = 0; index < installedNames.length; index += 1) {
|
|
661
|
+
const name = installedNames[index]
|
|
662
|
+
const original = mapGet(installedGlobalOriginals, name)
|
|
663
|
+
const attachedEntry = original.userGlobalEntryId === undefined
|
|
664
|
+
? undefined : mapGet(userBindingEntries, original.userGlobalEntryId)
|
|
665
|
+
const attachment = attachedEntry === undefined ? undefined : mapGet(attachedEntry.descriptors, name)
|
|
666
|
+
if (mapHas(originalDynamicNamespaceGlobals, name)) {
|
|
667
|
+
installNamespaceGlobal(name)
|
|
668
|
+
} else if (original.userGlobalEntryId !== undefined
|
|
669
|
+
&& (mapGet(userBindingNames, name) !== original.userGlobalEntryId
|
|
670
|
+
|| attachment !== original.attachment)) {
|
|
671
|
+
deleteWorkerRealmProperty(WORKER_REALM_MUTATION.RESTORE, undefined, context, name)
|
|
672
|
+
} else if (original.descriptor === undefined) {
|
|
673
|
+
deleteWorkerRealmProperty(WORKER_REALM_MUTATION.RESTORE, undefined, context, name)
|
|
674
|
+
} else {
|
|
675
|
+
defineWorkerRealmProperty(WORKER_REALM_MUTATION.RESTORE, undefined,
|
|
676
|
+
context, name, original.descriptor)
|
|
677
|
+
}
|
|
558
678
|
}
|
|
559
|
-
installedGlobals
|
|
560
|
-
installedGlobalOriginals
|
|
561
|
-
dynamicNamespaces
|
|
679
|
+
setClear(installedGlobals)
|
|
680
|
+
mapClear(installedGlobalOriginals)
|
|
681
|
+
mapClear(dynamicNamespaces)
|
|
682
|
+
let namespaceError
|
|
562
683
|
|
|
563
|
-
for (
|
|
684
|
+
for (let namespaceIndex = 0; namespaceIndex < message.namespaces.length; namespaceIndex += 1) {
|
|
685
|
+
const namespace = message.namespaces[namespaceIndex]
|
|
564
686
|
const descriptor = namespace.errorClass
|
|
565
687
|
// The wrapper owns the constructor, so a pending call keeps its submitted
|
|
566
688
|
// error identity even after the cell shadows the context binding.
|
|
@@ -573,42 +695,56 @@ function installBindings(message) {
|
|
|
573
695
|
}
|
|
574
696
|
}
|
|
575
697
|
const view = Object.create(null)
|
|
576
|
-
const emptyObjectMembers =
|
|
577
|
-
dynamicNamespaces
|
|
578
|
-
members:
|
|
698
|
+
const emptyObjectMembers = setFromArray(namespace.emptyObjectMembers ?? [])
|
|
699
|
+
mapSet(dynamicNamespaces, namespace.global, {
|
|
700
|
+
members: setFromArray(namespace.members),
|
|
579
701
|
shadowable: namespace.shadowable === true,
|
|
580
702
|
emptyObjectMembers,
|
|
581
703
|
BoundError,
|
|
704
|
+
cellView: view,
|
|
582
705
|
})
|
|
583
|
-
for (
|
|
706
|
+
for (let memberIndex = 0; memberIndex < namespace.members.length; memberIndex += 1) {
|
|
707
|
+
const member = namespace.members[memberIndex]
|
|
584
708
|
Object.defineProperty(view, member, {
|
|
585
709
|
enumerable: true,
|
|
586
710
|
value: (...args) => callHost(
|
|
587
711
|
message.id,
|
|
588
712
|
namespace.global,
|
|
589
713
|
member,
|
|
590
|
-
args.length === 0 && emptyObjectMembers
|
|
714
|
+
args.length === 0 && setHas(emptyObjectMembers, member) ? {} : args[0],
|
|
591
715
|
BoundError,
|
|
592
716
|
),
|
|
593
717
|
})
|
|
594
718
|
}
|
|
595
719
|
Object.freeze(view)
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
720
|
+
const original = capturedGlobalDescriptor(namespace.global)
|
|
721
|
+
mapSet(installedGlobalOriginals, namespace.global, original)
|
|
722
|
+
if (setHas(workerBaselineGlobals, namespace.global)) {
|
|
723
|
+
namespaceError ??= new Error(
|
|
724
|
+
`program namespace ${jsonStringify(namespace.global)} cannot be bridged into the worker realm because the global already exists`,
|
|
725
|
+
)
|
|
726
|
+
} else {
|
|
727
|
+
installNamespaceGlobal(namespace.global)
|
|
728
|
+
setAdd(installedGlobals, namespace.global)
|
|
729
|
+
}
|
|
599
730
|
|
|
600
731
|
if (descriptor !== undefined) {
|
|
601
|
-
installedGlobalOriginals
|
|
602
|
-
|
|
603
|
-
|
|
732
|
+
mapSet(installedGlobalOriginals, descriptor.name, capturedGlobalDescriptor(descriptor.name))
|
|
733
|
+
defineWorkerRealmProperty(WORKER_REALM_MUTATION.PROGRAM, undefined,
|
|
734
|
+
context, descriptor.name, { configurable: true, value: BoundError })
|
|
735
|
+
setAdd(installedGlobals, descriptor.name)
|
|
604
736
|
}
|
|
605
737
|
}
|
|
738
|
+
return namespaceError
|
|
606
739
|
}
|
|
607
740
|
|
|
608
741
|
function capturedGlobalDescriptor(name) {
|
|
609
|
-
const descriptor =
|
|
610
|
-
|
|
611
|
-
|
|
742
|
+
const descriptor = mapHas(originalDynamicNamespaceGlobals, name)
|
|
743
|
+
? mapGet(originalDynamicNamespaceGlobals, name)
|
|
744
|
+
: Object.getOwnPropertyDescriptor(context, name)
|
|
745
|
+
const userGlobalEntryId = mapGet(userBindingNames, name)
|
|
746
|
+
const entry = mapGet(userBindingEntries, userGlobalEntryId)
|
|
747
|
+
const installed = entry === undefined ? undefined : mapGet(entry.descriptors, name)
|
|
612
748
|
return {
|
|
613
749
|
descriptor,
|
|
614
750
|
...(userGlobalEntryId !== undefined && descriptorsEqual(descriptor, installed)
|
|
@@ -618,8 +754,8 @@ function capturedGlobalDescriptor(name) {
|
|
|
618
754
|
}
|
|
619
755
|
|
|
620
756
|
function underlyingUserBindingDescriptor(name) {
|
|
621
|
-
return installedGlobals
|
|
622
|
-
? installedGlobalOriginals
|
|
757
|
+
return setHas(installedGlobals, name)
|
|
758
|
+
? mapGet(installedGlobalOriginals, name).descriptor
|
|
623
759
|
: Object.getOwnPropertyDescriptor(context, name)
|
|
624
760
|
}
|
|
625
761
|
|
|
@@ -646,7 +782,7 @@ function rootBindingStorage(name, nativeLexical = false) {
|
|
|
646
782
|
try {
|
|
647
783
|
// Both an own data property and an initialized lexical can be read
|
|
648
784
|
// without executing an accessor. Either proves an available local name.
|
|
649
|
-
|
|
785
|
+
runInWorkerReplRealm(name, { displayErrors: false })
|
|
650
786
|
return nativeLexical ? 'lexical' : 'local'
|
|
651
787
|
} catch {
|
|
652
788
|
return 'unknown'
|
|
@@ -654,14 +790,18 @@ function rootBindingStorage(name, nativeLexical = false) {
|
|
|
654
790
|
}
|
|
655
791
|
let propertyRead = false
|
|
656
792
|
try {
|
|
657
|
-
|
|
658
|
-
|
|
793
|
+
defineWorkerRealmProperty(WORKER_REALM_MUTATION.TEMPORARY, undefined,
|
|
794
|
+
context, name, { configurable: true, get() { propertyRead = true } })
|
|
795
|
+
runInWorkerReplRealm(name, { displayErrors: false })
|
|
659
796
|
return propertyRead ? 'property' : 'lexical'
|
|
660
797
|
} catch {
|
|
661
798
|
return 'unknown'
|
|
662
799
|
} finally {
|
|
663
|
-
if (original === undefined)
|
|
664
|
-
|
|
800
|
+
if (original === undefined) {
|
|
801
|
+
deleteWorkerRealmProperty(WORKER_REALM_MUTATION.RESTORE, undefined, context, name)
|
|
802
|
+
} else {
|
|
803
|
+
defineWorkerRealmProperty(WORKER_REALM_MUTATION.RESTORE, undefined, context, name, original)
|
|
804
|
+
}
|
|
665
805
|
}
|
|
666
806
|
}
|
|
667
807
|
|
|
@@ -673,7 +813,8 @@ function writableRootProperty(name) {
|
|
|
673
813
|
|
|
674
814
|
function userBindingRootStorage(name) {
|
|
675
815
|
if (statefulRoots.has(name)) return 'local'
|
|
676
|
-
const
|
|
816
|
+
const namespaces = activeExecution.importBindingNamespaces
|
|
817
|
+
const namespace = namespaces === undefined ? undefined : mapGet(namespaces, name)
|
|
677
818
|
if (namespace === undefined) return rootBindingStorage(name)
|
|
678
819
|
// Compiler-validated imports use native namespace slots, not public alias
|
|
679
820
|
// properties. Probe only the slot; reading an export could invoke user code.
|
|
@@ -681,145 +822,181 @@ function userBindingRootStorage(name) {
|
|
|
681
822
|
}
|
|
682
823
|
|
|
683
824
|
function reconcileUserBindingNames() {
|
|
684
|
-
|
|
825
|
+
const names = mapKeysArray(userBindingSources)
|
|
826
|
+
for (let index = 0; index < names.length; index += 1) {
|
|
827
|
+
const name = names[index]
|
|
685
828
|
const storage = userBindingRootStorage(name)
|
|
686
829
|
const descriptor = underlyingUserBindingDescriptor(name)
|
|
687
|
-
const id = userBindingNames
|
|
688
|
-
const
|
|
830
|
+
const id = mapGet(userBindingNames, name)
|
|
831
|
+
const entry = mapGet(userBindingEntries, id)
|
|
832
|
+
const installed = entry === undefined ? undefined : mapGet(entry.descriptors, name)
|
|
689
833
|
const state = storage === 'lexical' || storage === 'local' ? 'local'
|
|
690
834
|
: storage === 'unknown' ? 'unknown'
|
|
691
835
|
: descriptor === undefined ? 'absent'
|
|
692
836
|
: installed !== undefined && descriptorsEqual(descriptor, installed) ? 'provider' : 'local'
|
|
693
|
-
userBindingSources
|
|
694
|
-
if (state !== 'provider') userBindingNames
|
|
837
|
+
mapSet(userBindingSources, name, { state, ...(state === 'provider' ? { entryId: id } : {}) })
|
|
838
|
+
if (state !== 'provider') mapDelete(userBindingNames, name)
|
|
695
839
|
}
|
|
696
840
|
}
|
|
697
841
|
|
|
698
842
|
function removePerNameUserBindingEntry(id) {
|
|
699
|
-
const entry = userBindingEntries
|
|
700
|
-
for (
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
843
|
+
const entry = mapGet(userBindingEntries, id)
|
|
844
|
+
for (let index = 0; index < entry.names.length; index += 1) {
|
|
845
|
+
const name = entry.names[index]
|
|
846
|
+
if (mapGet(userBindingNames, name) !== id) continue
|
|
847
|
+
const installed = mapGet(entry.descriptors, name)
|
|
848
|
+
mapDelete(userBindingNames, name)
|
|
849
|
+
if (setHas(installedGlobals, name)) {
|
|
850
|
+
const original = mapGet(installedGlobalOriginals, name)
|
|
706
851
|
if (original.attachment === installed) original.descriptor = undefined
|
|
707
|
-
} else if (descriptorsEqual(Object.getOwnPropertyDescriptor(context, name), installed))
|
|
852
|
+
} else if (descriptorsEqual(Object.getOwnPropertyDescriptor(context, name), installed)) {
|
|
853
|
+
deleteWorkerRealmProperty(WORKER_REALM_MUTATION.RESTORE, undefined, context, name)
|
|
854
|
+
}
|
|
708
855
|
// Lifecycle removal permits reattachment; explicit deletion retains absent.
|
|
709
|
-
userBindingSources
|
|
856
|
+
mapDelete(userBindingSources, name)
|
|
710
857
|
}
|
|
711
|
-
userBindingEntries
|
|
858
|
+
mapDelete(userBindingEntries, id)
|
|
712
859
|
}
|
|
713
860
|
|
|
714
861
|
async function activatePerNameUserBindings(snapshot, shadowedNames, cwd, reusePolicy, initialFailures = []) {
|
|
715
862
|
// Import preceding legacy attachments into the new source owner once.
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
863
|
+
const existingNames = mapEntriesArray(userBindingNames)
|
|
864
|
+
for (let index = 0; index < existingNames.length; index += 1) {
|
|
865
|
+
const name = existingNames[index][0]
|
|
866
|
+
const entryId = existingNames[index][1]
|
|
867
|
+
if (mapHas(userBindingSources, name)) continue
|
|
868
|
+
mapSet(userBindingSources, name, { state: 'provider', entryId })
|
|
719
869
|
// The legacy catalog includes actual setter writes as well as descriptor
|
|
720
870
|
// changes. Do not reattach a restored local accessor by descriptor equality.
|
|
721
|
-
if (shadowedNames
|
|
871
|
+
if (setHas(shadowedNames, name) && !setHas(installedGlobals, name)) mapDelete(userBindingNames, name)
|
|
722
872
|
}
|
|
723
873
|
reconcileUserBindingNames()
|
|
724
|
-
const blockedIds =
|
|
725
|
-
const desired =
|
|
874
|
+
const blockedIds = setFromArray(mapArray(initialFailures, failure => failure.id))
|
|
875
|
+
const desired = mapFromArray(filterArray(snapshot?.entries ?? [], entry => !setHas(blockedIds, entry.id)), entry => [entry.id, entry])
|
|
726
876
|
try {
|
|
727
|
-
if (desired
|
|
877
|
+
if (mapSize(desired) > 0 || retainedLegacyUserBindingRuntime) installDynamicNamespaceGlobals()
|
|
728
878
|
} catch (error) {
|
|
729
|
-
|
|
730
|
-
|
|
879
|
+
const ids = mapKeysArray(userBindingEntries)
|
|
880
|
+
for (let index = 0; index < ids.length; index += 1) removePerNameUserBindingEntry(ids[index])
|
|
881
|
+
const failures = copyArray(initialFailures)
|
|
882
|
+
const desiredFailures = mapArray(mapKeysArray(desired), id => ({ id, error: messageOf(error) }))
|
|
883
|
+
for (let index = 0; index < desiredFailures.length; index += 1) appendArray(failures, desiredFailures[index])
|
|
884
|
+
return { activated: [], failures, error }
|
|
731
885
|
}
|
|
732
|
-
|
|
733
|
-
|
|
886
|
+
const currentEntries = mapEntriesArray(userBindingEntries)
|
|
887
|
+
for (let index = 0; index < currentEntries.length; index += 1) {
|
|
888
|
+
const id = currentEntries[index][0]
|
|
889
|
+
const current = currentEntries[index][1]
|
|
890
|
+
const next = mapGet(desired, id)
|
|
734
891
|
if (next === undefined || current.transform !== snapshot.transform || !(reusePolicy === LEGACY_USER_BINDINGS_REUSE_POLICY
|
|
735
892
|
? next.fingerprint === current.entry.fingerprint : userBindingImplementationMatches(next, current.entry))) {
|
|
736
893
|
removePerNameUserBindingEntry(id)
|
|
737
894
|
}
|
|
738
895
|
}
|
|
739
896
|
const activated = []
|
|
740
|
-
const failures =
|
|
741
|
-
|
|
742
|
-
|
|
897
|
+
const failures = copyArray(initialFailures)
|
|
898
|
+
const desiredEntries = mapValuesArray(desired)
|
|
899
|
+
for (let entryIndex = 0; entryIndex < desiredEntries.length; entryIndex += 1) {
|
|
900
|
+
const entry = desiredEntries[entryIndex]
|
|
901
|
+
const current = mapGet(userBindingEntries, entry.id)
|
|
743
902
|
if (current !== undefined) {
|
|
744
903
|
current.entry = entry
|
|
745
|
-
activated
|
|
904
|
+
appendArray(activated, entry.id)
|
|
746
905
|
continue
|
|
747
906
|
}
|
|
748
907
|
const names = entry.scope === 'namespace' ? [entry.name] : entry.symbols
|
|
749
|
-
const previousSources =
|
|
908
|
+
const previousSources = mapFromArray(names, name => [name, mapGet(userBindingSources, name)])
|
|
750
909
|
const installedNames = []
|
|
751
910
|
let evaluated
|
|
752
911
|
const activation = { id: entry.id, called: false, failed: false }
|
|
753
912
|
try {
|
|
754
|
-
if (entry.durability === 'volatile') markVolatile(`user binding ${
|
|
913
|
+
if (entry.durability === 'volatile') markVolatile(`user binding ${jsonStringify(entry.id)}: ${entry.volatileReason ?? 'non-replayable source'}`)
|
|
755
914
|
evaluated = await userBindingActivationScope.run(activation, () => evaluateUserBinding(entry, cwd, snapshot.transform))
|
|
756
915
|
const namespace = evaluated.namespace
|
|
757
|
-
for (
|
|
758
|
-
|
|
916
|
+
for (let symbolIndex = 0; symbolIndex < entry.symbols.length; symbolIndex += 1) {
|
|
917
|
+
const symbol = entry.symbols[symbolIndex]
|
|
918
|
+
if (!Object.hasOwn(namespace, symbol)) throw new Error(`named export ${jsonStringify(symbol)} is unavailable after evaluation`)
|
|
759
919
|
}
|
|
760
920
|
const view = Object.create(null)
|
|
761
|
-
for (
|
|
921
|
+
for (let symbolIndex = 0; symbolIndex < entry.symbols.length; symbolIndex += 1) {
|
|
922
|
+
const symbol = entry.symbols[symbolIndex]
|
|
923
|
+
Object.defineProperty(view, symbol, { enumerable: true, get: () => namespace[symbol] })
|
|
924
|
+
}
|
|
762
925
|
Object.freeze(view)
|
|
763
926
|
const descriptors = new Map()
|
|
764
|
-
for (
|
|
927
|
+
for (let nameIndex = 0; nameIndex < names.length; nameIndex += 1) {
|
|
928
|
+
const name = names[nameIndex]
|
|
765
929
|
const storage = userBindingRootStorage(name)
|
|
766
|
-
const prior = userBindingSources
|
|
930
|
+
const prior = mapGet(userBindingSources, name)
|
|
767
931
|
if (storage === 'lexical' || storage === 'unknown') {
|
|
768
|
-
userBindingSources
|
|
769
|
-
} else if (prior === undefined && shadowedNames
|
|
932
|
+
mapSet(userBindingSources, name, { state: storage === 'lexical' ? 'local' : 'unknown' })
|
|
933
|
+
} else if (prior === undefined && setHas(shadowedNames, name)) {
|
|
770
934
|
const descriptor = underlyingUserBindingDescriptor(name)
|
|
771
|
-
userBindingSources
|
|
935
|
+
mapSet(userBindingSources, name, { state: descriptor === undefined ? 'unknown' : 'local' })
|
|
772
936
|
}
|
|
773
|
-
const source = userBindingSources
|
|
937
|
+
const source = mapGet(userBindingSources, name)
|
|
774
938
|
if (source !== undefined && source.state !== 'provider') continue
|
|
775
939
|
const descriptor = Object.getOwnPropertyDescriptor(context, name)
|
|
776
|
-
|
|
940
|
+
defineWorkerRealmProperty(WORKER_REALM_MUTATION.USER, undefined, context, name, {
|
|
777
941
|
configurable: true,
|
|
778
942
|
enumerable: true,
|
|
779
943
|
get: () => entry.scope === 'namespace' ? view : namespace[name],
|
|
780
944
|
set(value) {
|
|
781
945
|
const receiver = this === contextGlobal ? context : this
|
|
782
|
-
|
|
946
|
+
defineWorkerRealmProperty(WORKER_REALM_MUTATION.USER, undefined,
|
|
947
|
+
receiver, name, { configurable: true, enumerable: true, writable: true, value })
|
|
783
948
|
if (receiver === context) recordUserBindingAssignment(name)
|
|
784
949
|
},
|
|
785
950
|
})
|
|
786
|
-
installedNames
|
|
787
|
-
descriptors
|
|
788
|
-
userBindingNames
|
|
789
|
-
userBindingSources
|
|
951
|
+
appendArray(installedNames, { name, descriptor })
|
|
952
|
+
mapSet(descriptors, name, Object.getOwnPropertyDescriptor(context, name))
|
|
953
|
+
mapSet(userBindingNames, name, entry.id)
|
|
954
|
+
mapSet(userBindingSources, name, { state: 'provider', entryId: entry.id })
|
|
790
955
|
}
|
|
791
|
-
userBindingEntries
|
|
792
|
-
|
|
793
|
-
activated
|
|
956
|
+
mapSet(userBindingEntries, entry.id, { entry, transform: snapshot.transform, moduleUrl: evaluated.moduleUrl, names, descriptors })
|
|
957
|
+
if (snapshot.transform !== USER_BINDING_TRANSFORM) retainedLegacyUserBindingRuntime = true
|
|
958
|
+
appendArray(activated, entry.id)
|
|
794
959
|
} catch (error) {
|
|
795
960
|
activation.failed = true
|
|
796
|
-
if (activation.called) markVolatile(`failed user binding ${
|
|
797
|
-
for (
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
961
|
+
if (activation.called) markVolatile(`failed user binding ${jsonStringify(entry.id)} issued a host call`)
|
|
962
|
+
for (let index = installedNames.length - 1; index >= 0; index--) {
|
|
963
|
+
const installed = installedNames[index]
|
|
964
|
+
mapDelete(userBindingNames, installed.name)
|
|
965
|
+
if (installed.descriptor === undefined) {
|
|
966
|
+
deleteWorkerRealmProperty(WORKER_REALM_MUTATION.RESTORE, undefined,
|
|
967
|
+
context, installed.name)
|
|
968
|
+
} else {
|
|
969
|
+
defineWorkerRealmProperty(WORKER_REALM_MUTATION.RESTORE, undefined,
|
|
970
|
+
context, installed.name, installed.descriptor)
|
|
971
|
+
}
|
|
801
972
|
}
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
973
|
+
const previousEntries = mapEntriesArray(previousSources)
|
|
974
|
+
for (let previousIndex = 0; previousIndex < previousEntries.length; previousIndex += 1) {
|
|
975
|
+
const name = previousEntries[previousIndex][0]
|
|
976
|
+
const prior = previousEntries[previousIndex][1]
|
|
977
|
+
if (prior === undefined) mapDelete(userBindingSources, name)
|
|
978
|
+
else mapSet(userBindingSources, name, prior)
|
|
805
979
|
}
|
|
806
|
-
if (evaluated !== undefined) userBindingModuleParents
|
|
807
|
-
failures
|
|
980
|
+
if (evaluated !== undefined) mapDelete(userBindingModuleParents, evaluated.moduleUrl)
|
|
981
|
+
appendArray(failures, { id: entry.id, error: messageOf(error) })
|
|
808
982
|
}
|
|
809
983
|
}
|
|
810
|
-
if (userBindingEntries
|
|
984
|
+
if (mapSize(userBindingEntries) === 0 && !retainedLegacyUserBindingRuntime) restoreDynamicNamespaceGlobals()
|
|
811
985
|
return { activated, failures }
|
|
812
986
|
}
|
|
813
987
|
|
|
814
988
|
function removeUserBindingEntry(id, shadowedNames) {
|
|
815
|
-
const previous = userBindingEntries
|
|
989
|
+
const previous = mapGet(userBindingEntries, id)
|
|
816
990
|
if (previous === undefined) return
|
|
817
|
-
for (
|
|
818
|
-
|
|
819
|
-
userBindingNames
|
|
820
|
-
|
|
991
|
+
for (let index = 0; index < previous.names.length; index += 1) {
|
|
992
|
+
const name = previous.names[index]
|
|
993
|
+
if (mapGet(userBindingNames, name) !== id) continue
|
|
994
|
+
mapDelete(userBindingNames, name)
|
|
995
|
+
if (!setHas(shadowedNames, name)) {
|
|
996
|
+
deleteWorkerRealmProperty(WORKER_REALM_MUTATION.RESTORE, undefined, context, name)
|
|
997
|
+
}
|
|
821
998
|
}
|
|
822
|
-
userBindingEntries
|
|
999
|
+
mapDelete(userBindingEntries, id)
|
|
823
1000
|
}
|
|
824
1001
|
|
|
825
1002
|
function descriptorsEqual(left, right) {
|
|
@@ -833,12 +1010,16 @@ function descriptorsEqual(left, right) {
|
|
|
833
1010
|
}
|
|
834
1011
|
|
|
835
1012
|
function reconcileUserBindingShadows(shadowedNames) {
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
1013
|
+
const entries = mapEntriesArray(userBindingEntries)
|
|
1014
|
+
for (let entryIndex = 0; entryIndex < entries.length; entryIndex += 1) {
|
|
1015
|
+
const id = entries[entryIndex][0]
|
|
1016
|
+
const entry = entries[entryIndex][1]
|
|
1017
|
+
for (let nameIndex = 0; nameIndex < entry.names.length; nameIndex += 1) {
|
|
1018
|
+
const name = entry.names[nameIndex]
|
|
1019
|
+
if (mapGet(userBindingNames, name) !== id) continue
|
|
1020
|
+
const installed = mapGet(entry.descriptors, name)
|
|
840
1021
|
const current = Object.getOwnPropertyDescriptor(context, name)
|
|
841
|
-
if (!descriptorsEqual(current, installed)) shadowedNames
|
|
1022
|
+
if (!descriptorsEqual(current, installed)) setAdd(shadowedNames, name)
|
|
842
1023
|
}
|
|
843
1024
|
}
|
|
844
1025
|
}
|
|
@@ -847,15 +1028,18 @@ async function evaluateUserBinding(entry, cwd, transform) {
|
|
|
847
1028
|
if (typeof cwd !== 'string' || !isAbsolute(cwd)) {
|
|
848
1029
|
throw new Error('user binding activation requires an absolute storage directory')
|
|
849
1030
|
}
|
|
850
|
-
const prepared = compileStatefulModule(entry.source, {
|
|
1031
|
+
const prepared = compileStatefulModule(entry.source, {
|
|
1032
|
+
transform,
|
|
1033
|
+
programBindings: transform === USER_BINDING_TRANSFORM,
|
|
1034
|
+
})
|
|
851
1035
|
const url = `data:text/javascript,${encodeURIComponent(prepared.code)}#ptc-plus-${entry.fingerprint}-${++nextUserBindingModuleId}`
|
|
852
|
-
userBindingModuleParents
|
|
1036
|
+
mapSet(userBindingModuleParents, url, pathToFileURL(resolve(cwd, 'bindings.json')).href)
|
|
853
1037
|
userModuleCompilation.mark(url, { transform, compiled: true, moduleInterface: prepared.moduleInterface, sourceRegions: prepared.sourceRegions })
|
|
854
1038
|
try {
|
|
855
1039
|
return { namespace: transform === LEGACY_USER_BINDING_TRANSFORM ? await import(url)
|
|
856
1040
|
: await managedModuleImport(url, url), moduleUrl: url }
|
|
857
1041
|
} catch (error) {
|
|
858
|
-
userBindingModuleParents
|
|
1042
|
+
mapDelete(userBindingModuleParents, url)
|
|
859
1043
|
throw error
|
|
860
1044
|
}
|
|
861
1045
|
}
|
|
@@ -865,22 +1049,31 @@ function userBindingImplementationMatches(left, right) {
|
|
|
865
1049
|
&& left.scope === right.scope
|
|
866
1050
|
&& (left.scope !== 'namespace' || left.name === right.name)
|
|
867
1051
|
&& left.symbols.length === right.symbols.length
|
|
868
|
-
&& left.symbols
|
|
1052
|
+
&& everyArray(left.symbols, (symbol, index) => symbol === right.symbols[index])
|
|
869
1053
|
}
|
|
870
1054
|
|
|
871
1055
|
async function activateUserBindings(snapshot, shadowedNames, cwd, reusePolicy, initialFailures = []) {
|
|
872
1056
|
reconcileUserBindingShadows(shadowedNames)
|
|
873
|
-
const blockedIds =
|
|
874
|
-
const desired =
|
|
875
|
-
.filter((entry) => {
|
|
1057
|
+
const blockedIds = setFromArray(mapArray(initialFailures, failure => failure.id))
|
|
1058
|
+
const desired = mapFromArray(filterArray(snapshot?.entries ?? [], (entry) => {
|
|
876
1059
|
const names = entry.scope === 'namespace' ? [entry.name] : entry.symbols
|
|
877
|
-
return !blockedIds
|
|
878
|
-
})
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
const
|
|
883
|
-
|
|
1060
|
+
return !setHas(blockedIds, entry.id) && !someArray(names, name => setHas(shadowedNames, name))
|
|
1061
|
+
}), entry => [entry.id, entry])
|
|
1062
|
+
try {
|
|
1063
|
+
if (mapSize(desired) > 0 || retainedLegacyUserBindingRuntime) installDynamicNamespaceGlobals()
|
|
1064
|
+
} catch (error) {
|
|
1065
|
+
const ids = mapKeysArray(userBindingEntries)
|
|
1066
|
+
for (let index = 0; index < ids.length; index += 1) removeUserBindingEntry(ids[index], shadowedNames)
|
|
1067
|
+
const failures = copyArray(initialFailures)
|
|
1068
|
+
const desiredFailures = mapArray(mapKeysArray(desired), id => ({ id, error: messageOf(error) }))
|
|
1069
|
+
for (let index = 0; index < desiredFailures.length; index += 1) appendArray(failures, desiredFailures[index])
|
|
1070
|
+
return { activated: [], failures, error }
|
|
1071
|
+
}
|
|
1072
|
+
const existingIds = mapKeysArray(userBindingEntries)
|
|
1073
|
+
for (let index = 0; index < existingIds.length; index += 1) {
|
|
1074
|
+
const id = existingIds[index]
|
|
1075
|
+
const current = mapGet(userBindingEntries, id)
|
|
1076
|
+
const next = mapGet(desired, id)
|
|
884
1077
|
// Historical cells retain fingerprint-based resets, including presentation edits.
|
|
885
1078
|
const reusable = next !== undefined && current.transform === snapshot.transform && (reusePolicy === LEGACY_USER_BINDINGS_REUSE_POLICY
|
|
886
1079
|
? next.fingerprint === current.entry.fingerprint
|
|
@@ -890,12 +1083,14 @@ async function activateUserBindings(snapshot, shadowedNames, cwd, reusePolicy, i
|
|
|
890
1083
|
}
|
|
891
1084
|
}
|
|
892
1085
|
const activated = []
|
|
893
|
-
const failures =
|
|
894
|
-
|
|
895
|
-
|
|
1086
|
+
const failures = copyArray(initialFailures)
|
|
1087
|
+
const desiredEntries = mapValuesArray(desired)
|
|
1088
|
+
for (let entryIndex = 0; entryIndex < desiredEntries.length; entryIndex += 1) {
|
|
1089
|
+
const entry = desiredEntries[entryIndex]
|
|
1090
|
+
const current = mapGet(userBindingEntries, entry.id)
|
|
896
1091
|
if (current !== undefined) {
|
|
897
1092
|
current.entry = entry
|
|
898
|
-
activated
|
|
1093
|
+
appendArray(activated, entry.id)
|
|
899
1094
|
continue
|
|
900
1095
|
}
|
|
901
1096
|
let evaluated
|
|
@@ -903,22 +1098,24 @@ async function activateUserBindings(snapshot, shadowedNames, cwd, reusePolicy, i
|
|
|
903
1098
|
const activation = { id: entry.id, called: false, failed: false }
|
|
904
1099
|
try {
|
|
905
1100
|
if (entry.durability === 'volatile') {
|
|
906
|
-
markVolatile(`user binding ${
|
|
1101
|
+
markVolatile(`user binding ${jsonStringify(entry.id)}: ${entry.volatileReason ?? 'non-replayable source'}`)
|
|
907
1102
|
}
|
|
908
1103
|
evaluated = await userBindingActivationScope.run(
|
|
909
1104
|
activation,
|
|
910
1105
|
() => evaluateUserBinding(entry, cwd, snapshot.transform),
|
|
911
1106
|
)
|
|
912
1107
|
const namespace = evaluated.namespace
|
|
913
|
-
for (
|
|
1108
|
+
for (let symbolIndex = 0; symbolIndex < entry.symbols.length; symbolIndex += 1) {
|
|
1109
|
+
const symbol = entry.symbols[symbolIndex]
|
|
914
1110
|
if (!Object.hasOwn(namespace, symbol)) {
|
|
915
|
-
throw new Error(`named export ${
|
|
1111
|
+
throw new Error(`named export ${jsonStringify(symbol)} is unavailable after evaluation`)
|
|
916
1112
|
}
|
|
917
1113
|
}
|
|
918
1114
|
const names = []
|
|
919
1115
|
if (entry.scope === 'namespace') {
|
|
920
1116
|
const view = Object.create(null)
|
|
921
|
-
for (
|
|
1117
|
+
for (let symbolIndex = 0; symbolIndex < entry.symbols.length; symbolIndex += 1) {
|
|
1118
|
+
const symbol = entry.symbols[symbolIndex]
|
|
922
1119
|
Object.defineProperty(view, symbol, {
|
|
923
1120
|
enumerable: true,
|
|
924
1121
|
get: () => namespace[symbol],
|
|
@@ -926,13 +1123,13 @@ async function activateUserBindings(snapshot, shadowedNames, cwd, reusePolicy, i
|
|
|
926
1123
|
}
|
|
927
1124
|
Object.freeze(view)
|
|
928
1125
|
const descriptor = Object.getOwnPropertyDescriptor(context, entry.name)
|
|
929
|
-
|
|
1126
|
+
defineWorkerRealmProperty(WORKER_REALM_MUTATION.USER, undefined, context, entry.name, {
|
|
930
1127
|
configurable: true,
|
|
931
1128
|
enumerable: true,
|
|
932
1129
|
get: () => view,
|
|
933
1130
|
set(value) {
|
|
934
1131
|
recordUserBindingAssignment(entry.name, true)
|
|
935
|
-
|
|
1132
|
+
defineWorkerRealmProperty(WORKER_REALM_MUTATION.USER, undefined, context, entry.name, {
|
|
936
1133
|
configurable: true,
|
|
937
1134
|
enumerable: true,
|
|
938
1135
|
writable: true,
|
|
@@ -940,22 +1137,23 @@ async function activateUserBindings(snapshot, shadowedNames, cwd, reusePolicy, i
|
|
|
940
1137
|
})
|
|
941
1138
|
},
|
|
942
1139
|
})
|
|
943
|
-
installedNames
|
|
1140
|
+
appendArray(installedNames, {
|
|
944
1141
|
name: entry.name,
|
|
945
1142
|
descriptor,
|
|
946
1143
|
})
|
|
947
|
-
userBindingNames
|
|
948
|
-
names
|
|
1144
|
+
mapSet(userBindingNames, entry.name, entry.id)
|
|
1145
|
+
appendArray(names, entry.name)
|
|
949
1146
|
} else {
|
|
950
|
-
for (
|
|
1147
|
+
for (let symbolIndex = 0; symbolIndex < entry.symbols.length; symbolIndex += 1) {
|
|
1148
|
+
const symbol = entry.symbols[symbolIndex]
|
|
951
1149
|
const descriptor = Object.getOwnPropertyDescriptor(context, symbol)
|
|
952
|
-
|
|
1150
|
+
defineWorkerRealmProperty(WORKER_REALM_MUTATION.USER, undefined, context, symbol, {
|
|
953
1151
|
configurable: true,
|
|
954
1152
|
enumerable: true,
|
|
955
1153
|
get: () => namespace[symbol],
|
|
956
1154
|
set(value) {
|
|
957
1155
|
recordUserBindingAssignment(symbol, true)
|
|
958
|
-
|
|
1156
|
+
defineWorkerRealmProperty(WORKER_REALM_MUTATION.USER, undefined, context, symbol, {
|
|
959
1157
|
configurable: true,
|
|
960
1158
|
enumerable: true,
|
|
961
1159
|
writable: true,
|
|
@@ -963,48 +1161,55 @@ async function activateUserBindings(snapshot, shadowedNames, cwd, reusePolicy, i
|
|
|
963
1161
|
})
|
|
964
1162
|
},
|
|
965
1163
|
})
|
|
966
|
-
installedNames
|
|
967
|
-
userBindingNames
|
|
968
|
-
names
|
|
1164
|
+
appendArray(installedNames, { name: symbol, descriptor })
|
|
1165
|
+
mapSet(userBindingNames, symbol, entry.id)
|
|
1166
|
+
appendArray(names, symbol)
|
|
969
1167
|
}
|
|
970
1168
|
}
|
|
971
|
-
userBindingEntries
|
|
1169
|
+
mapSet(userBindingEntries, entry.id, {
|
|
972
1170
|
entry,
|
|
973
1171
|
transform: snapshot.transform,
|
|
974
1172
|
moduleUrl: evaluated.moduleUrl,
|
|
975
1173
|
names,
|
|
976
|
-
descriptors:
|
|
1174
|
+
descriptors: mapFromArray(names, name => [name, Object.getOwnPropertyDescriptor(context, name)]),
|
|
977
1175
|
})
|
|
978
|
-
|
|
979
|
-
activated
|
|
1176
|
+
if (snapshot.transform !== USER_BINDING_TRANSFORM) retainedLegacyUserBindingRuntime = true
|
|
1177
|
+
appendArray(activated, entry.id)
|
|
980
1178
|
} catch (error) {
|
|
981
1179
|
activation.failed = true
|
|
982
1180
|
if (activation.called) {
|
|
983
|
-
markVolatile(`failed user binding ${
|
|
1181
|
+
markVolatile(`failed user binding ${jsonStringify(entry.id)} issued a host call`)
|
|
984
1182
|
}
|
|
985
|
-
for (
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
1183
|
+
for (let index = installedNames.length - 1; index >= 0; index--) {
|
|
1184
|
+
const installed = installedNames[index]
|
|
1185
|
+
mapDelete(userBindingNames, installed.name)
|
|
1186
|
+
if (installed.descriptor === undefined) {
|
|
1187
|
+
deleteWorkerRealmProperty(WORKER_REALM_MUTATION.RESTORE, undefined,
|
|
1188
|
+
context, installed.name)
|
|
1189
|
+
} else {
|
|
1190
|
+
defineWorkerRealmProperty(WORKER_REALM_MUTATION.RESTORE, undefined,
|
|
1191
|
+
context, installed.name, installed.descriptor)
|
|
1192
|
+
}
|
|
989
1193
|
}
|
|
990
|
-
if (evaluated !== undefined) userBindingModuleParents
|
|
1194
|
+
if (evaluated !== undefined) mapDelete(userBindingModuleParents, evaluated.moduleUrl)
|
|
991
1195
|
removeUserBindingEntry(entry.id, shadowedNames)
|
|
992
|
-
failures
|
|
1196
|
+
appendArray(failures, {
|
|
993
1197
|
id: entry.id,
|
|
994
1198
|
error: messageOf(error),
|
|
995
1199
|
})
|
|
996
1200
|
}
|
|
997
1201
|
}
|
|
998
|
-
if (userBindingEntries
|
|
1202
|
+
if (mapSize(userBindingEntries) === 0 && !retainedLegacyUserBindingRuntime) restoreDynamicNamespaceGlobals()
|
|
999
1203
|
return { activated, failures }
|
|
1000
1204
|
}
|
|
1001
1205
|
|
|
1002
1206
|
async function closeExecution(execution) {
|
|
1003
1207
|
activeRun = undefined
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1208
|
+
const settling = []
|
|
1209
|
+
mapForEach(pending, call => {
|
|
1210
|
+
if (call.runId === execution.id) appendArray(settling, call.settled)
|
|
1211
|
+
})
|
|
1212
|
+
for (let index = 0; index < settling.length; index++) await settling[index]
|
|
1008
1213
|
}
|
|
1009
1214
|
|
|
1010
1215
|
function failureOutcome(error, phase, program) {
|
|
@@ -1031,7 +1236,7 @@ function failureOutcome(error, phase, program) {
|
|
|
1031
1236
|
|
|
1032
1237
|
function observeBindings(names) {
|
|
1033
1238
|
const observation = valueObserver.observe(names)
|
|
1034
|
-
return { ...observation, entries: observation.entries
|
|
1239
|
+
return { ...observation, entries: mapArray(observation.entries, entry => {
|
|
1035
1240
|
const local = statefulRoots.localValue(entry.name)
|
|
1036
1241
|
return local === undefined ? entry : { name: entry.name, ...previewBindingValue(local.value) }
|
|
1037
1242
|
}) }
|
|
@@ -1051,27 +1256,44 @@ function refreshLegacyPublication(name) {
|
|
|
1051
1256
|
}
|
|
1052
1257
|
}
|
|
1053
1258
|
|
|
1054
|
-
function
|
|
1055
|
-
|
|
1259
|
+
function writeOutputFence(write, marker) {
|
|
1260
|
+
return new ControlPromise((resolve, reject) => {
|
|
1261
|
+
write(marker, error => error === null || error === undefined ? resolve() : reject(error))
|
|
1262
|
+
})
|
|
1263
|
+
}
|
|
1264
|
+
|
|
1265
|
+
async function sendCompletion(message, execution, userBindings, committedRedeclarations, outcome) {
|
|
1266
|
+
const publicationNames = execution.nativePublications === undefined
|
|
1267
|
+
? [] : mapKeysArray(execution.nativePublications)
|
|
1268
|
+
for (let index = 0; index < publicationNames.length; index += 1) refreshLegacyPublication(publicationNames[index])
|
|
1056
1269
|
const shadowedNames = execution.legacyAssignedUserBindingNames
|
|
1057
1270
|
const perName = message.userBindingsShadowPolicy === LIVE_USER_BINDINGS_SHADOW_POLICY
|
|
1058
1271
|
if (perName) reconcileUserBindingNames()
|
|
1059
1272
|
else reconcileUserBindingShadows(shadowedNames)
|
|
1060
1273
|
const names = message.observeNames ?? []
|
|
1274
|
+
await writeOutputFence(nativeStdoutWrite, outputFenceMarker(message.outputFence, 'stdout', 'end'))
|
|
1275
|
+
await writeOutputFence(nativeStderrWrite, outputFenceMarker(message.outputFence, 'stderr', 'end'))
|
|
1276
|
+
// Program-call settlement and value encoding belong to this physical run, so
|
|
1277
|
+
// structured output from a drained continuation keeps its cell ownership
|
|
1278
|
+
// until this completion is posted. Output from the same scheduling execution
|
|
1279
|
+
// after this point is background output and carries the attribution
|
|
1280
|
+
// diagnostic instead of being attached to a later cell.
|
|
1281
|
+
execution.open = false
|
|
1061
1282
|
channel.postMessage({
|
|
1062
1283
|
type: 'done',
|
|
1063
1284
|
id: message.id,
|
|
1285
|
+
outputFence: message.outputFence,
|
|
1064
1286
|
logs: execution.logs,
|
|
1065
1287
|
...completionDurability(execution),
|
|
1066
|
-
committedRedeclarations:
|
|
1288
|
+
committedRedeclarations: setValuesArray(committedRedeclarations),
|
|
1067
1289
|
...(message.rootBindings === undefined ? {} : { rootBindingFacts: statefulRoots.facts() }),
|
|
1068
|
-
...(perName ? { userBindingNames: normalizeUserBindingNames(
|
|
1069
|
-
name, state:
|
|
1290
|
+
...(perName ? { userBindingNames: normalizeUserBindingNames(mapArray(mapEntriesArray(userBindingSources), entry => ({
|
|
1291
|
+
name: entry[0], state: entry[1].state, ...(entry[1].state === 'provider' ? { entryId: entry[1].entryId } : {}),
|
|
1070
1292
|
}))) } : {}),
|
|
1071
1293
|
...(message.userBindings === undefined ? {} : {
|
|
1072
1294
|
activatedUserBindings: userBindings.activated,
|
|
1073
1295
|
userBindingFailures: userBindings.failures,
|
|
1074
|
-
...(perName ? {} : { shadowedUserBindings:
|
|
1296
|
+
...(perName ? {} : { shadowedUserBindings: setValuesArray(shadowedNames) }),
|
|
1075
1297
|
}),
|
|
1076
1298
|
...outcome,
|
|
1077
1299
|
observing: names.length > 0,
|
|
@@ -1084,12 +1306,18 @@ function sendCompletion(message, execution, userBindings, committedRedeclaration
|
|
|
1084
1306
|
|
|
1085
1307
|
async function runCell(message) {
|
|
1086
1308
|
if (activeExecution !== undefined) throw new Error('kernel received overlapping cells')
|
|
1309
|
+
await writeOutputFence(nativeStdoutWrite, outputFenceMarker(message.outputFence, 'stdout', 'start'))
|
|
1310
|
+
await writeOutputFence(nativeStderrWrite, outputFenceMarker(message.outputFence, 'stderr', 'start'))
|
|
1311
|
+
await new ControlPromise(resolve => {
|
|
1312
|
+
mapSet(pendingOutputStarts, message.id, { outputFence: message.outputFence, resolve })
|
|
1313
|
+
channel.postMessage({ type: 'output-start', id: message.id, outputFence: message.outputFence })
|
|
1314
|
+
})
|
|
1087
1315
|
activeRun = message.id
|
|
1088
|
-
installBindings(message)
|
|
1089
1316
|
const execution = {
|
|
1090
1317
|
id: message.id,
|
|
1091
1318
|
exceptionOrigins: createExceptionOriginScope(),
|
|
1092
1319
|
languageSemantics: message.languageSemantics,
|
|
1320
|
+
moduleTransform: message.moduleTransform,
|
|
1093
1321
|
userBindingsShadowPolicy: message.userBindingsShadowPolicy,
|
|
1094
1322
|
importBindingNamespaces: message.importBindingNamespaces,
|
|
1095
1323
|
legacyAssignedUserBindingNames: new Set(),
|
|
@@ -1099,10 +1327,9 @@ async function runCell(message) {
|
|
|
1099
1327
|
logBytes: 2,
|
|
1100
1328
|
maxOutputBytes: message.maxOutputBytes,
|
|
1101
1329
|
valueLimits: message.valueLimits,
|
|
1102
|
-
durability: message.durability === 'volatile'
|
|
1103
|
-
volatileReason:
|
|
1330
|
+
durability: message.durability === 'volatile' ? 'volatile' : 'durable',
|
|
1331
|
+
volatileReason: message.volatileReason,
|
|
1104
1332
|
}
|
|
1105
|
-
pendingVolatileReason = undefined
|
|
1106
1333
|
activeExecution = execution
|
|
1107
1334
|
const cellGlobals = []
|
|
1108
1335
|
const committedRedeclarations = new Set()
|
|
@@ -1113,56 +1340,62 @@ async function runCell(message) {
|
|
|
1113
1340
|
let userBindings = { activated: [], failures: [] }
|
|
1114
1341
|
try {
|
|
1115
1342
|
completion = await logScope.run(execution, async () => {
|
|
1116
|
-
|
|
1343
|
+
const namespaceError = installBindings(message)
|
|
1344
|
+
defineWorkerRealmProperty(WORKER_REALM_MUTATION.TEMPORARY, undefined, context, message.returnSignal, {
|
|
1117
1345
|
configurable: true,
|
|
1118
1346
|
value: CellReturn,
|
|
1119
1347
|
})
|
|
1120
|
-
cellGlobals
|
|
1121
|
-
|
|
1348
|
+
appendArray(cellGlobals, message.returnSignal)
|
|
1349
|
+
defineWorkerRealmProperty(WORKER_REALM_MUTATION.TEMPORARY, undefined, context, message.commitSignal, {
|
|
1122
1350
|
configurable: true,
|
|
1123
1351
|
value(name) {
|
|
1124
1352
|
setAdd(committedRedeclarations, name)
|
|
1125
1353
|
},
|
|
1126
1354
|
})
|
|
1127
|
-
cellGlobals
|
|
1355
|
+
appendArray(cellGlobals, message.commitSignal)
|
|
1128
1356
|
if (message.rootRuntimeName !== undefined) {
|
|
1129
|
-
const nativeLexicals =
|
|
1130
|
-
const legacyStorage =
|
|
1131
|
-
|
|
1132
|
-
const legacyLexicals = message.rootBindings.legacyLexicals
|
|
1133
|
-
const legacyNative =
|
|
1134
|
-
const legacyWritable = message.rootBindings.legacyWritable
|
|
1135
|
-
const storage = legacyStorage
|
|
1136
|
-
return storage === 'lexical' || legacyNative
|
|
1357
|
+
const nativeLexicals = setFromArray(message.rootBindings.legacyNativeLexicals)
|
|
1358
|
+
const legacyStorage = mapFromArray(filterArray(message.rootBindings.legacyLexicals,
|
|
1359
|
+
name => !statefulRoots.has(name)), name => [name, rootBindingStorage(name, setHas(nativeLexicals, name))])
|
|
1360
|
+
const legacyLexicals = filterArray(message.rootBindings.legacyLexicals, name => mapGet(legacyStorage, name) === 'lexical')
|
|
1361
|
+
const legacyNative = setFromArray(message.rootBindings.legacyNative)
|
|
1362
|
+
const legacyWritable = filterArray(message.rootBindings.legacyWritable, name => {
|
|
1363
|
+
const storage = mapGet(legacyStorage, name)
|
|
1364
|
+
return storage === 'lexical' || setHas(legacyNative, name)
|
|
1137
1365
|
&& (storage === 'local' || storage === 'property') && writableRootProperty(name)
|
|
1138
1366
|
})
|
|
1139
|
-
|
|
1367
|
+
defineWorkerRealmProperty(WORKER_REALM_MUTATION.TEMPORARY, undefined,
|
|
1368
|
+
context, message.rootRuntimeName, {
|
|
1140
1369
|
configurable: true,
|
|
1141
1370
|
value: statefulRoots.begin({ ...message.rootBindings,
|
|
1142
1371
|
legacyLexicals,
|
|
1143
1372
|
legacyWritable,
|
|
1144
|
-
legacyObjects: legacyWritable
|
|
1373
|
+
legacyObjects: filterArray(legacyWritable, name => mapGet(legacyStorage, name) !== 'lexical'),
|
|
1145
1374
|
committed(name) {
|
|
1146
1375
|
setDelete(committedRedeclarations, name)
|
|
1147
1376
|
setAdd(committedRedeclarations, name)
|
|
1148
1377
|
} }),
|
|
1149
1378
|
})
|
|
1150
|
-
cellGlobals
|
|
1379
|
+
appendArray(cellGlobals, message.rootRuntimeName)
|
|
1151
1380
|
}
|
|
1152
1381
|
const activate = message.userBindingsShadowPolicy === LIVE_USER_BINDINGS_SHADOW_POLICY
|
|
1153
1382
|
? activatePerNameUserBindings : activateUserBindings
|
|
1154
1383
|
userBindings = await activate(
|
|
1155
1384
|
message.userBindings,
|
|
1156
|
-
|
|
1385
|
+
setFromArray(message.shadowedUserBindingNames ?? []),
|
|
1157
1386
|
message.userBindingsCwd,
|
|
1158
1387
|
message.userBindingsReusePolicy,
|
|
1159
1388
|
message.userBindingFailures,
|
|
1160
1389
|
)
|
|
1161
1390
|
if (userBindings.error !== undefined) throw userBindings.error
|
|
1162
|
-
|
|
1163
|
-
|
|
1391
|
+
if (namespaceError !== undefined) throw namespaceError
|
|
1392
|
+
for (let failureIndex = 0; failureIndex < userBindings.failures.length; failureIndex += 1) {
|
|
1393
|
+
const failure = userBindings.failures[failureIndex]
|
|
1394
|
+
appendText(execution, `Global binding ${jsonStringify(failure.id)} was not activated: ${failure.error}`)
|
|
1164
1395
|
}
|
|
1165
|
-
|
|
1396
|
+
const moduleLoads = message.moduleLoads ?? []
|
|
1397
|
+
for (let loadIndex = 0; loadIndex < moduleLoads.length; loadIndex += 1) {
|
|
1398
|
+
const load = moduleLoads[loadIndex]
|
|
1166
1399
|
let namespace
|
|
1167
1400
|
try {
|
|
1168
1401
|
namespace = (await loadStaticModule(load)).namespace
|
|
@@ -1170,18 +1403,25 @@ async function runCell(message) {
|
|
|
1170
1403
|
throw new StaticImportFailure(error, load.position)
|
|
1171
1404
|
}
|
|
1172
1405
|
if (load.global !== undefined) {
|
|
1173
|
-
|
|
1406
|
+
defineWorkerRealmProperty(WORKER_REALM_MUTATION.TEMPORARY, undefined, context, load.global, {
|
|
1174
1407
|
configurable: true,
|
|
1175
1408
|
value: namespace,
|
|
1176
1409
|
})
|
|
1177
|
-
cellGlobals
|
|
1410
|
+
appendArray(cellGlobals, load.global)
|
|
1178
1411
|
}
|
|
1179
1412
|
}
|
|
1180
1413
|
// Preload failure leaves prior aliases authoritative. Once evaluation
|
|
1181
1414
|
// starts, new slots must prove their own initialization, including TDZ.
|
|
1182
1415
|
execution.importBindingNamespaces = message.preparedImportBindingNamespaces
|
|
1183
|
-
execution.nativePublications = new Map(
|
|
1184
|
-
|
|
1416
|
+
execution.nativePublications = new Map()
|
|
1417
|
+
for (let loadIndex = 0; loadIndex < moduleLoads.length; loadIndex += 1) {
|
|
1418
|
+
const load = moduleLoads[loadIndex]
|
|
1419
|
+
const publications = load.nativePublications ?? []
|
|
1420
|
+
for (let publicationIndex = 0; publicationIndex < publications.length; publicationIndex += 1) {
|
|
1421
|
+
const publication = publications[publicationIndex]
|
|
1422
|
+
mapSet(execution.nativePublications, publication.name, publication)
|
|
1423
|
+
}
|
|
1424
|
+
}
|
|
1185
1425
|
return execution.exceptionOrigins.run(() => evaluate(message.program, message.returnSignal, message.asyncCompletion))
|
|
1186
1426
|
})
|
|
1187
1427
|
} catch (error) {
|
|
@@ -1203,9 +1443,12 @@ async function runCell(message) {
|
|
|
1203
1443
|
outcome = failureOutcome(error, 'encode', message.program)
|
|
1204
1444
|
}
|
|
1205
1445
|
}
|
|
1206
|
-
sendCompletion(message, execution, userBindings, committedRedeclarations, outcome)
|
|
1446
|
+
await sendCompletion(message, execution, userBindings, committedRedeclarations, outcome)
|
|
1207
1447
|
} finally {
|
|
1208
|
-
for (
|
|
1448
|
+
for (let index = 0; index < cellGlobals.length; index += 1) {
|
|
1449
|
+
deleteWorkerRealmProperty(WORKER_REALM_MUTATION.RESTORE, undefined,
|
|
1450
|
+
context, cellGlobals[index])
|
|
1451
|
+
}
|
|
1209
1452
|
activeRun = undefined
|
|
1210
1453
|
activeExecution = undefined
|
|
1211
1454
|
execution.exceptionOrigins.close()
|
|
@@ -1221,6 +1464,7 @@ parentPort.on('message', (message) => {
|
|
|
1221
1464
|
input.destroy()
|
|
1222
1465
|
output.destroy()
|
|
1223
1466
|
errorDomain.removeAllListeners('error')
|
|
1467
|
+
process.removeListener('uncaughtException', handleUncaughtException)
|
|
1224
1468
|
channel.close()
|
|
1225
1469
|
parentPort.postMessage({ type: WORKER_SHUTDOWN_ACKNOWLEDGEMENT })
|
|
1226
1470
|
parentPort.close()
|
|
@@ -1238,9 +1482,9 @@ channel.on('message', (message) => {
|
|
|
1238
1482
|
return
|
|
1239
1483
|
}
|
|
1240
1484
|
if (message?.type === 'reply') {
|
|
1241
|
-
const call = pending
|
|
1485
|
+
const call = mapGet(pending, message.id)
|
|
1242
1486
|
if (call === undefined || call.runId !== message.runId) return
|
|
1243
|
-
pending
|
|
1487
|
+
mapDelete(pending, message.id)
|
|
1244
1488
|
try {
|
|
1245
1489
|
if (message.ok) call.resolve(decodeValue(message.value, call.valueLimits))
|
|
1246
1490
|
else if (call.BoundError === undefined) {
|
|
@@ -1253,6 +1497,14 @@ channel.on('message', (message) => {
|
|
|
1253
1497
|
}
|
|
1254
1498
|
return
|
|
1255
1499
|
}
|
|
1500
|
+
if (message?.type === 'output-start-ack') {
|
|
1501
|
+
const pendingStart = mapGet(pendingOutputStarts, message.id)
|
|
1502
|
+
if (pendingStart !== undefined && pendingStart.outputFence === message.outputFence) {
|
|
1503
|
+
mapDelete(pendingOutputStarts, message.id)
|
|
1504
|
+
pendingStart.resolve()
|
|
1505
|
+
}
|
|
1506
|
+
return
|
|
1507
|
+
}
|
|
1256
1508
|
if (message?.type === 'run') void runCell(message)
|
|
1257
1509
|
})
|
|
1258
1510
|
|