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
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { AsyncLocalStorage } from 'node:async_hooks'
|
|
2
1
|
import { randomUUID } from 'node:crypto'
|
|
2
|
+
import { createPrivateAsyncLocalStorage } from './async-local-storage-intrinsics.js'
|
|
3
3
|
import { create as createDomain } from 'node:domain'
|
|
4
4
|
import { createRequire, registerHooks } from 'node:module'
|
|
5
5
|
import { resolve } from 'node:path'
|
|
@@ -8,7 +8,6 @@ import { PassThrough } from 'node:stream'
|
|
|
8
8
|
import { inspect } from 'node:util'
|
|
9
9
|
import { pathToFileURL } from 'node:url'
|
|
10
10
|
import { parentPort, workerData } from 'node:worker_threads'
|
|
11
|
-
import { runInContext } from 'node:vm'
|
|
12
11
|
import { prepareConsoleProgram } from './compiler-service.js'
|
|
13
12
|
import { createStatefulRootRuntime } from './stateful-root-runtime.js'
|
|
14
13
|
import { managedModuleImport, managedRequire, readModuleImport } from './stateful-module-runtime.js'
|
|
@@ -18,21 +17,24 @@ import { moduleRuntimeIntrinsics } from './compiler-intrinsics.js'
|
|
|
18
17
|
import { stringifyCompilerData } from './compiler-data.js'
|
|
19
18
|
import { createCellCompletionObserver } from './cell-completion.js'
|
|
20
19
|
import { WORKER_SHUTDOWN_ACKNOWLEDGEMENT, WORKER_SHUTDOWN_REQUEST } from './worker-shutdown.js'
|
|
20
|
+
import { WORKER_REPL_OPTIONS, createWorkerControlPromise, createWorkerReplErrorHandler,
|
|
21
|
+
disableWorkerReplDomain,
|
|
22
|
+
captureWorkerReplGlobals, protectWorkerReplAsyncContext, restoreWorkerReplGlobals, runInWorkerReplRealm,
|
|
23
|
+
workerReplContext } from './worker-repl-realm.js'
|
|
21
24
|
|
|
22
25
|
const { Object, Set, setHas, setAdd, setDelete, appendArray } = moduleRuntimeIntrinsics
|
|
26
|
+
const Promise = globalThis.Promise
|
|
27
|
+
const process = globalThis.process
|
|
28
|
+
const hasProperty = globalThis.Reflect.has
|
|
29
|
+
const ControlPromise = createWorkerControlPromise()
|
|
23
30
|
|
|
24
|
-
const
|
|
25
|
-
const
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
const completionObserver = createCellCompletionObserver(runInContext('Function', server.context))
|
|
30
|
-
server.context.console = console
|
|
31
|
-
const errorDomain = server.eval.domain ?? createDomain()
|
|
32
|
-
errorDomain.removeAllListeners('error')
|
|
33
|
-
errorDomain.on('error', error => evaluations.getStore()?.finish(true, error))
|
|
31
|
+
const evaluations = createPrivateAsyncLocalStorage()
|
|
32
|
+
const context = globalThis
|
|
33
|
+
const completionObserver = createCellCompletionObserver(runInWorkerReplRealm('Function'))
|
|
34
|
+
context.console = console
|
|
35
|
+
let retainedEvaluator
|
|
34
36
|
const completionKey = `__ptc_console_${randomUUID().replaceAll('-', '')}`
|
|
35
|
-
Object.defineProperty(
|
|
37
|
+
Object.defineProperty(context, completionKey, { value: value => {
|
|
36
38
|
evaluations.getStore().completed = true
|
|
37
39
|
return value
|
|
38
40
|
} })
|
|
@@ -44,16 +46,16 @@ const adapterParents = new Set()
|
|
|
44
46
|
const knownBindings = new Set()
|
|
45
47
|
const roots = createStatefulRootRuntime({
|
|
46
48
|
importModule: (source, options) => managedModuleImport(replParent, source, options, replModuleRealm),
|
|
47
|
-
errors:
|
|
48
|
-
dynamicIntrinsics:
|
|
49
|
-
readAmbient: name =>
|
|
50
|
-
typeofAmbient: name =>
|
|
49
|
+
errors: runInWorkerReplRealm('({ ReferenceError, TypeError })'),
|
|
50
|
+
dynamicIntrinsics: runInWorkerReplRealm('({intrinsicEval:eval,realmFunction:Function,globalObject:globalThis,errors:{ReferenceError,TypeError,SyntaxError},reflect:Reflect,object:Object})'),
|
|
51
|
+
readAmbient: name => runInWorkerReplRealm(name),
|
|
52
|
+
typeofAmbient: name => runInWorkerReplRealm(`typeof ${name}`),
|
|
51
53
|
writeAmbient(name, value, strict) {
|
|
52
54
|
const argument = name === '__ptc_value' ? '__ptc_other_value' : '__ptc_value'
|
|
53
|
-
|
|
55
|
+
runInWorkerReplRealm(`${strict ? '"use strict";' : ''}(${argument}) => (${name} = ${argument})`)(value)
|
|
54
56
|
},
|
|
55
|
-
deleteAmbient: name =>
|
|
56
|
-
hasAmbient: name =>
|
|
57
|
+
deleteAmbient: name => runInWorkerReplRealm(`delete ${name}`),
|
|
58
|
+
hasAmbient: name => hasProperty(context, name),
|
|
57
59
|
publish: name => setAdd(knownBindings, name),
|
|
58
60
|
})
|
|
59
61
|
class ConsoleReturn {
|
|
@@ -62,7 +64,8 @@ class ConsoleReturn {
|
|
|
62
64
|
const moduleCompilation = createUserModuleCompilationHooks({
|
|
63
65
|
transformForParent: parent => parent === replParent || parent === parentUrl ? USER_BINDING_TRANSFORM : undefined,
|
|
64
66
|
})
|
|
65
|
-
|
|
67
|
+
const providedRequire = managedRequire(parentUrl, createRequire(parentUrl))
|
|
68
|
+
Object.defineProperty(context, 'require', { configurable: true, value: providedRequire })
|
|
66
69
|
const importCanary = 'data:text/javascript,export default 1'
|
|
67
70
|
registerHooks({ resolve(specifier, context, nextResolve) {
|
|
68
71
|
if (specifier === importCanary) replParent = context.parentURL
|
|
@@ -74,14 +77,34 @@ const describe = value => inspect(value, {
|
|
|
74
77
|
colors: false, customInspect: false, getters: false, depth: 4,
|
|
75
78
|
maxArrayLength: 40, maxStringLength: Math.min(workerData.maxOutputBytes, 8192), breakLength: 100,
|
|
76
79
|
})
|
|
80
|
+
process.on('uncaughtException', error => {
|
|
81
|
+
const state = evaluations.getStore()
|
|
82
|
+
if (state?.finish(true, error) === true || state?.settled === true) return
|
|
83
|
+
parentPort.postMessage({ fatal: true, error: describe(error) })
|
|
84
|
+
})
|
|
77
85
|
|
|
78
|
-
|
|
86
|
+
function evaluate(source) {
|
|
79
87
|
const prepared = prepareConsoleProgram(source, { languageSemantics: 'stateful-v1', knownBindings })
|
|
80
88
|
const helpers = [prepared.rootRuntimeName, prepared.returnSignal]
|
|
81
|
-
Object.defineProperty(
|
|
89
|
+
Object.defineProperty(context, prepared.rootRuntimeName, {
|
|
82
90
|
configurable: true, value: roots.begin({ ...prepared.rootBindings, committed: () => {} }),
|
|
83
91
|
})
|
|
84
|
-
Object.defineProperty(
|
|
92
|
+
Object.defineProperty(context, prepared.returnSignal, { configurable: true, value: ConsoleReturn })
|
|
93
|
+
if (prepared.moduleLoads.length === 0) {
|
|
94
|
+
const operation = evaluatePrepared(prepared.code, prepared.asyncCompletion)
|
|
95
|
+
return new ControlPromise((resolve, reject) => operation.then(
|
|
96
|
+
value => { removeHelpers(helpers); resolve(value) },
|
|
97
|
+
error => { removeHelpers(helpers); reject(error) },
|
|
98
|
+
))
|
|
99
|
+
}
|
|
100
|
+
return evaluateWithModules(prepared, helpers)
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function removeHelpers(helpers) {
|
|
104
|
+
for (let index = 0; index < helpers.length; index++) delete context[helpers[index]]
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
async function evaluateWithModules(prepared, helpers) {
|
|
85
108
|
try {
|
|
86
109
|
for (let index = 0; index < prepared.moduleLoads.length; index++) {
|
|
87
110
|
const load = prepared.moduleLoads[index]
|
|
@@ -109,30 +132,48 @@ async function evaluate(source) {
|
|
|
109
132
|
} finally { setDelete(adapterParents, adapter) }
|
|
110
133
|
if (load.global !== undefined) {
|
|
111
134
|
appendArray(helpers, load.global)
|
|
112
|
-
Object.defineProperty(
|
|
135
|
+
Object.defineProperty(context, load.global, { configurable: true, value: namespace })
|
|
113
136
|
}
|
|
114
137
|
}
|
|
115
138
|
return await evaluatePrepared(prepared.code, prepared.asyncCompletion)
|
|
116
139
|
} finally {
|
|
117
|
-
|
|
140
|
+
removeHelpers(helpers)
|
|
118
141
|
}
|
|
119
142
|
}
|
|
120
143
|
|
|
121
144
|
function evaluatePrepared(javascript, asyncCompletion = false) {
|
|
122
|
-
|
|
145
|
+
releaseRetainedEvaluator()
|
|
146
|
+
const input = new PassThrough()
|
|
147
|
+
const output = new PassThrough()
|
|
148
|
+
output.resume()
|
|
149
|
+
const workerGlobalBaseline = captureWorkerReplGlobals(context)
|
|
150
|
+
const server = repl.start({ input, output, terminal: false, prompt: '', ...WORKER_REPL_OPTIONS,
|
|
151
|
+
handleError: createWorkerReplErrorHandler(evaluations, (state, error) => state.finish(true, error)) })
|
|
152
|
+
protectWorkerReplAsyncContext(server)
|
|
153
|
+
workerReplContext(server)
|
|
154
|
+
restoreWorkerReplGlobals(workerGlobalBaseline, context)
|
|
155
|
+
Object.defineProperty(context, 'require', { configurable: true, value: providedRequire })
|
|
156
|
+
const errorDomain = disableWorkerReplDomain(server.eval.domain ?? createDomain())
|
|
157
|
+
const evaluator = { server, input, output, errorDomain }
|
|
158
|
+
retainedEvaluator = evaluator
|
|
159
|
+
return new ControlPromise((resolve, reject) => {
|
|
123
160
|
const complete = `this[${stringifyCompilerData(completionKey)}]`
|
|
124
161
|
// Preserve expression results while distinguishing rejection with null/undefined.
|
|
125
162
|
const code = `${javascript}\n;{let completed = ${complete}();}\n;`
|
|
126
163
|
let settled = false
|
|
127
164
|
const state = { completed: false, finish(failed, value) {
|
|
128
|
-
if (settled) return
|
|
165
|
+
if (settled) return false
|
|
129
166
|
settled = true
|
|
167
|
+
state.settled = true
|
|
130
168
|
if (failed && value instanceof ConsoleReturn) completionObserver.settle(value.value, resolve, reject)
|
|
131
169
|
else if (failed) reject(value)
|
|
132
170
|
else completionObserver.settle(value, resolve, reject)
|
|
171
|
+
return true
|
|
133
172
|
} }
|
|
173
|
+
evaluator.state = state
|
|
174
|
+
errorDomain.on('error', error => state.finish(true, error))
|
|
134
175
|
evaluations.run(state, () => errorDomain.run(() => {
|
|
135
|
-
server.eval(`${code}\n`,
|
|
176
|
+
server.eval(`${code}\n`, context, 'ptc-plus-console', (error, value) => {
|
|
136
177
|
if (asyncCompletion && (error === null || error === undefined)) {
|
|
137
178
|
completionObserver.observe(value, value => state.finish(false, value), error => state.finish(true, error))
|
|
138
179
|
return
|
|
@@ -143,6 +184,16 @@ function evaluatePrepared(javascript, asyncCompletion = false) {
|
|
|
143
184
|
}))
|
|
144
185
|
})
|
|
145
186
|
}
|
|
187
|
+
|
|
188
|
+
function releaseRetainedEvaluator() {
|
|
189
|
+
const evaluator = retainedEvaluator
|
|
190
|
+
if (evaluator === undefined) return undefined
|
|
191
|
+
retainedEvaluator = undefined
|
|
192
|
+
evaluator.errorDomain.removeAllListeners('error')
|
|
193
|
+
evaluator.server.close()
|
|
194
|
+
evaluator.input.destroy()
|
|
195
|
+
evaluator.output.destroy()
|
|
196
|
+
}
|
|
146
197
|
try {
|
|
147
198
|
replModuleRealm = await evaluatePrepared('({Promise,stringify:value=>`${value}`,importModule:(source,options)=>import(source,options)})')
|
|
148
199
|
const prepared = compileStatefulModule(workerData.source)
|
|
@@ -154,30 +205,39 @@ try {
|
|
|
154
205
|
for (let index = 0; index < names.length; index++) {
|
|
155
206
|
const name = names[index]
|
|
156
207
|
setAdd(knownBindings, name)
|
|
157
|
-
Object.defineProperty(
|
|
208
|
+
Object.defineProperty(context, name, { configurable: true, enumerable: true, get: () => namespace[name] })
|
|
158
209
|
}
|
|
159
|
-
parentPort.on('message',
|
|
210
|
+
parentPort.on('message', (message) => {
|
|
160
211
|
if (message?.type === WORKER_SHUTDOWN_REQUEST) {
|
|
161
212
|
// Release the realm, REPL and domain this console owns, then report the
|
|
162
213
|
// release before the host gives up on the thread; see worker-shutdown.js.
|
|
163
|
-
|
|
164
|
-
input.destroy()
|
|
165
|
-
output.destroy()
|
|
166
|
-
errorDomain.removeAllListeners('error')
|
|
214
|
+
releaseRetainedEvaluator()
|
|
167
215
|
parentPort.postMessage({ type: WORKER_SHUTDOWN_ACKNOWLEDGEMENT })
|
|
168
216
|
parentPort.close()
|
|
169
217
|
return
|
|
170
218
|
}
|
|
171
219
|
const { id, code } = message
|
|
172
|
-
|
|
220
|
+
const send = result => {
|
|
221
|
+
const flushed = new ControlPromise(resolve => process.stdout.write('', () => process.stderr.write('', resolve)))
|
|
222
|
+
moduleRuntimeIntrinsics.observeOwnedPromise(flushed, () => {
|
|
223
|
+
parentPort.postMessage(result)
|
|
224
|
+
releaseRetainedEvaluator()
|
|
225
|
+
/* c8 ignore next 3 -- flushed has no reject capability */
|
|
226
|
+
}, error => {
|
|
227
|
+
releaseRetainedEvaluator()
|
|
228
|
+
parentPort.postMessage({ id, error: describe(error) })
|
|
229
|
+
})
|
|
230
|
+
}
|
|
231
|
+
let operation
|
|
173
232
|
try {
|
|
174
|
-
|
|
175
|
-
result = { id, output: describe(value) }
|
|
233
|
+
operation = evaluate(code)
|
|
176
234
|
} catch (error) {
|
|
177
|
-
|
|
235
|
+
send({ id, error: describe(error) })
|
|
236
|
+
return
|
|
178
237
|
}
|
|
179
|
-
|
|
180
|
-
|
|
238
|
+
moduleRuntimeIntrinsics.observeOwnedPromise(operation,
|
|
239
|
+
value => send({ id, output: describe(value) }),
|
|
240
|
+
error => send({ id, error: describe(error) }))
|
|
181
241
|
})
|
|
182
242
|
} catch (error) {
|
|
183
243
|
parentPort.postMessage({ fatal: true, error: describe(error) })
|
|
@@ -62,11 +62,19 @@ function selectedDescriptors(source, symbols, transform) {
|
|
|
62
62
|
|
|
63
63
|
|
|
64
64
|
function declarationFor(entry, descriptors) {
|
|
65
|
+
const typeNamespace = `__ptcBinding_${createHash('sha256').update(entry.id).digest('hex').slice(0, 12)}`
|
|
66
|
+
const typeNamespaceTokens = new Set(descriptors.map(item => item.typeNamespaceToken))
|
|
67
|
+
if (typeNamespaceTokens.size !== 1 || typeNamespaceTokens.has(undefined)) throw new TypeError('binding symbols do not share one compiler type namespace')
|
|
68
|
+
const [typeNamespaceToken] = typeNamespaceTokens
|
|
69
|
+
const render = text => text.replaceAll(typeNamespaceToken, typeNamespace)
|
|
70
|
+
const typeDeclarations = [...new Set(descriptors.flatMap(item => item.typeDeclarations ?? []))]
|
|
71
|
+
const typePrefix = typeDeclarations.length === 0 ? '' : `declare namespace ${typeNamespace} {\n${typeDeclarations
|
|
72
|
+
.map(item => render(item).split('\n').map(line => ` ${line}`).join('\n')).join('\n')}\n}\n`
|
|
65
73
|
const comment = entry.purpose === '' ? '' : `/** ${entry.purpose.replaceAll('*/', '* /')} */\n`
|
|
66
74
|
if (entry.scope === 'namespace') {
|
|
67
|
-
return `${comment}declare const ${entry.name}: {\n${descriptors.map(item => ` ${item.member};`).join('\n')}\n}`
|
|
75
|
+
return `${typePrefix}${comment}declare const ${entry.name}: {\n${descriptors.map(item => ` ${render(item.member)};`).join('\n')}\n}`
|
|
68
76
|
}
|
|
69
|
-
return descriptors.map((item, index) => `${index === 0 ? comment : ''}declare ${item.declaration}`).join('\n')
|
|
77
|
+
return `${typePrefix}${descriptors.map((item, index) => `${index === 0 ? comment : ''}declare ${render(item.declaration)}`).join('\n')}`
|
|
70
78
|
}
|
|
71
79
|
|
|
72
80
|
export function normalizeUserBindingEntry(value, { transform = USER_BINDING_TRANSFORM } = {}) {
|