dsh-ptc-plus 0.3.1 → 0.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (200) hide show
  1. package/README.en.md +66 -73
  2. package/README.md +66 -73
  3. package/assets/ptc-plus-bindings-en.png +0 -0
  4. package/assets/ptc-plus-bindings-zh.png +0 -0
  5. package/assets/ptc-plus-repair-en.png +0 -0
  6. package/assets/ptc-plus-repair-zh.png +0 -0
  7. package/assets/ptc-plus-repl-workspace-en.png +0 -0
  8. package/assets/ptc-plus-repl-workspace-zh.png +0 -0
  9. package/assets/ptc-plus-settings-en.png +0 -0
  10. package/assets/ptc-plus-settings-zh.png +0 -0
  11. package/client.js +30610 -722
  12. package/compiler-amaro.cjs +5 -0
  13. package/compiler-amaro.cjs.map +6 -0
  14. package/compiler-asset-124f80c1bba92ff5fe79cfec6e8ad518f476de4a42514cae3919538887f510e8.txt +1 -0
  15. package/compiler-assets.json +3 -0
  16. package/compiler-core.cjs +554 -0
  17. package/compiler-core.cjs.map +6 -0
  18. package/compiler-platform.cjs +2 -0
  19. package/compiler-platform.cjs.map +6 -0
  20. package/compiler-typescript.cjs +388 -0
  21. package/compiler-typescript.cjs.map +6 -0
  22. package/docs/README.md +6 -0
  23. package/docs/adr/0004-declare-program-bindings-at-the-host.md +1 -1
  24. package/docs/adr/0006-rewrite-module-syntax-with-an-ast.md +6 -4
  25. package/docs/adr/0007-separate-worker-transport-from-session-semantics.md +15 -3
  26. package/docs/adr/0009-bound-expensive-acceptance-default.md +2 -0
  27. package/docs/adr/0010-session-log-derived-recovery-tips.md +59 -5
  28. package/docs/adr/0011-isolate-the-local-recovery-tip-provider.md +1 -1
  29. package/docs/adr/0012-preserve-mixed-destructuring-initialization.md +3 -1
  30. package/docs/adr/0013-isolate-repl-redeclaration-convenience.md +9 -3
  31. package/docs/adr/0014-persist-cell-rewrite-policy.md +11 -5
  32. package/docs/adr/0015-preserve-source-positions-through-rewrites.md +1 -1
  33. package/docs/adr/0016-archive-resolved-review-findings.md +1 -1
  34. package/docs/adr/0017-track-the-latest-dsh-public-surface.md +49 -0
  35. package/docs/adr/0018-use-versioned-fixture-for-ab-benchmark.md +3 -3
  36. package/docs/adr/0019-plugin-settings-and-kill-switch.md +8 -6
  37. package/docs/adr/0020-optional-cordis-tools-in-ptc-mode.md +2 -2
  38. package/docs/adr/0021-separate-host-bootstrap-from-session-runtime.md +17 -3
  39. package/docs/adr/0023-global-user-bindings.md +104 -0
  40. package/docs/adr/0024-repl-console-observation.md +33 -0
  41. package/docs/adr/0025-use-versioned-logical-binding-identities.md +119 -0
  42. package/docs/adr/0026-persist-the-delivery-verdict-against-the-verified-candidate.md +21 -0
  43. package/docs/adr/0027-run-the-session-kernel-in-a-killable-helper-process.md +37 -0
  44. package/docs/adr/0028-attach-to-the-host-ptc-execution-seam.md +103 -0
  45. package/docs/adr/0029-keep-plugin-unavailability-out-of-the-host-agent-lifecycle.md +27 -0
  46. package/docs/adr/0030-serve-both-typert-codec-generations-from-one-codec.md +27 -0
  47. package/docs/architecture.md +82 -22
  48. package/docs/binding-review-placement-design.md +200 -0
  49. package/docs/capability-projection.md +1 -1
  50. package/docs/client-ui.md +96 -20
  51. package/docs/durability-design.md +61 -24
  52. package/docs/evaluation.md +78 -6
  53. package/docs/installation.md +41 -6
  54. package/docs/program-data-plane.md +6 -0
  55. package/docs/publishing.md +5 -3
  56. package/docs/runtime-reference.md +79 -25
  57. package/docs/semantic-validation.md +29 -0
  58. package/docs/stateful-binding-updates-proposal.md +755 -0
  59. package/docs/user-bindings.en.md +64 -0
  60. package/docs/user-bindings.md +64 -0
  61. package/docs/value-wire.md +3 -3
  62. package/docs/verification.md +57 -0
  63. package/index.js +374 -92
  64. package/internal/ast-traversal.js +5 -2
  65. package/internal/binding-descriptors.js +1 -0
  66. package/internal/binding-pattern-traversal.js +25 -0
  67. package/internal/binding-pattern.js +26 -29
  68. package/internal/binding-source-analysis.js +391 -0
  69. package/internal/binding-update-policy.js +66 -0
  70. package/internal/bounded-worker-round.js +56 -0
  71. package/internal/callable-definition-environment.js +42 -0
  72. package/internal/callable-source-catalog.js +129 -0
  73. package/internal/callable-source-encoding.js +47 -0
  74. package/internal/callable-source-facts.js +459 -0
  75. package/internal/callable-source-format.js +2 -0
  76. package/internal/callable-source-lowering.js +123 -0
  77. package/internal/cell-analysis-contract.js +25 -0
  78. package/internal/cell-analysis.js +252 -242
  79. package/internal/cell-completion.js +15 -0
  80. package/internal/cell-error.js +7 -0
  81. package/internal/cell-parser.js +66 -0
  82. package/internal/cell-rewriter.js +3 -553
  83. package/internal/commonjs-export-evidence.js +63 -0
  84. package/internal/commonjs-source-evidence.js +72 -0
  85. package/internal/compiler-amaro.cjs +2 -0
  86. package/internal/compiler-compression.js +12 -0
  87. package/internal/compiler-data.js +58 -0
  88. package/internal/compiler-descriptors.js +54 -0
  89. package/internal/compiler-dynamic-cache.js +48 -0
  90. package/internal/compiler-entry.js +35 -0
  91. package/internal/compiler-generated-names.js +17 -0
  92. package/internal/compiler-intrinsics.js +84 -0
  93. package/internal/compiler-module-links.js +34 -0
  94. package/internal/compiler-operations.js +61 -0
  95. package/internal/compiler-platform-factory.js +84 -0
  96. package/internal/compiler-platform.js +136 -0
  97. package/internal/compiler-record-roles.js +53 -0
  98. package/internal/compiler-region-output.js +259 -0
  99. package/internal/compiler-resource-lowering.js +103 -0
  100. package/internal/compiler-scope-facts.js +115 -0
  101. package/internal/compiler-service.js +109 -0
  102. package/internal/compiler-source-cache.js +32 -0
  103. package/internal/compiler-source-pieces.js +93 -0
  104. package/internal/compiler-source-regions.js +132 -0
  105. package/internal/compiler-storage-source.js +54 -0
  106. package/internal/compiler-text.js +15 -0
  107. package/internal/compiler-typescript.cjs +2 -0
  108. package/internal/config-spec.js +91 -25
  109. package/internal/cordis-tools-owner.js +130 -21
  110. package/internal/diagnostic.js +27 -1
  111. package/internal/direct-surface-owner.js +76 -23
  112. package/internal/dynamic-binding-evidence.js +16 -0
  113. package/internal/dynamic-environment-compiler.js +779 -0
  114. package/internal/dynamic-environment-integration.js +87 -0
  115. package/internal/dynamic-environment-runtime.js +362 -0
  116. package/internal/dynamic-native-calls.js +150 -0
  117. package/internal/dynamic-native-runtime.js +248 -0
  118. package/internal/dynamic-scope-analysis.js +90 -0
  119. package/internal/edit-transport-owner.js +28 -3
  120. package/internal/execution-seam-compat.js +193 -0
  121. package/internal/failure-reporting.js +118 -18
  122. package/internal/host-rpc.js +66 -0
  123. package/internal/isolated-worker.js +548 -0
  124. package/internal/kernel-child.js +139 -0
  125. package/internal/kernel-worker.js +911 -116
  126. package/internal/language-semantics.js +14 -0
  127. package/internal/legacy-binding-catalog.js +48 -0
  128. package/internal/legacy-cell-analysis.js +600 -0
  129. package/internal/legacy-cell-parser.js +50 -0
  130. package/internal/legacy-cell-rewriter.js +631 -0
  131. package/internal/legacy-repl-convenience.js +291 -0
  132. package/internal/managed-module-operations.js +170 -0
  133. package/internal/module-compilation.js +321 -0
  134. package/internal/module-runtime-platform.js +19 -0
  135. package/internal/module-transform-contract.js +12 -0
  136. package/internal/native-await-declarations.js +68 -0
  137. package/internal/native-root-compilation.js +54 -0
  138. package/internal/native-root-dynamic.js +67 -0
  139. package/internal/recovery-tips.js +27 -15
  140. package/internal/rejected-cell-editor.js +13 -8
  141. package/internal/repl-convenience.js +2 -201
  142. package/internal/repl-memory-projection.js +81 -12
  143. package/internal/repl-observation-interest.js +70 -0
  144. package/internal/repl-rewrite-contract.js +21 -0
  145. package/internal/repl-scope-normalizer.js +2091 -0
  146. package/internal/repl-value-observer.js +114 -0
  147. package/internal/rpc-contract.js +52 -0
  148. package/internal/runtime-bridge-owner.js +172 -28
  149. package/internal/runtime-config.js +48 -51
  150. package/internal/runtime-contexts.js +82 -10
  151. package/internal/runtime-messages.js +111 -0
  152. package/internal/session-cell-executor.js +455 -91
  153. package/internal/session-journal-recovery.js +684 -0
  154. package/internal/session-journal-schema.js +106 -0
  155. package/internal/session-journal.js +237 -440
  156. package/internal/session-log-view.js +42 -9
  157. package/internal/session-runtime.js +325 -80
  158. package/internal/session-state.js +249 -40
  159. package/internal/source-position-map.js +211 -71
  160. package/internal/stateful-module-compiler.js +154 -0
  161. package/internal/stateful-module-runtime.js +303 -0
  162. package/internal/stateful-root-compiler.js +598 -0
  163. package/internal/stateful-root-runtime.js +340 -0
  164. package/internal/typert-codec-compat.js +28 -0
  165. package/internal/typescript-transform.js +29 -0
  166. package/internal/user-binding-authoring.js +70 -0
  167. package/internal/user-binding-console-worker.js +184 -0
  168. package/internal/user-binding-console.js +148 -0
  169. package/internal/user-binding-draft-projection.js +335 -0
  170. package/internal/user-binding-model-context.js +31 -0
  171. package/internal/user-binding-runner.js +56 -0
  172. package/internal/user-bindings-owner.js +1065 -0
  173. package/internal/user-bindings-store.js +293 -0
  174. package/internal/user-bindings.js +353 -0
  175. package/internal/value-wire-schema.js +28 -0
  176. package/internal/value-wire.js +29 -20
  177. package/internal/worker-client.js +57 -67
  178. package/internal/worker-environment.js +45 -0
  179. package/internal/worker-output-relay.js +29 -0
  180. package/internal/worker-shutdown.js +14 -0
  181. package/package.json +81 -11
  182. package/src/client-activity.js +109 -45
  183. package/src/client-authoring-view.js +580 -0
  184. package/src/client-binding-review.js +230 -0
  185. package/src/client-bindings-data.js +29 -0
  186. package/src/client-catalog.js +127 -0
  187. package/src/client-code-editor.js +84 -0
  188. package/src/client-console.js +112 -0
  189. package/src/client-copy.js +370 -0
  190. package/src/client-feature-gates.js +62 -0
  191. package/src/client-host-compat.js +150 -0
  192. package/src/client-indicator-view.js +267 -0
  193. package/src/client-repl-view.js +292 -0
  194. package/src/client-rpc.js +20 -0
  195. package/src/client-settings-view.js +221 -0
  196. package/src/client-styles.js +106 -0
  197. package/src/client-tool-view.js +108 -0
  198. package/src/client-transport.js +159 -0
  199. package/src/client-workbench.js +521 -0
  200. package/src/client.js +233 -615
@@ -1,27 +1,103 @@
1
1
  import { diagnostic } from './diagnostic.js'
2
+ import { AsyncLocalStorage } from 'node:async_hooks'
3
+ import { createHash } from 'node:crypto'
4
+ import { moduleRuntimeIntrinsics as internal } from './compiler-intrinsics.js'
2
5
 
3
6
  export const MAX_ERROR_LOG_BYTES = 4 * 1024
4
7
  export const FAILURE_HINT_THRESHOLD = 3
5
8
  export const LONG_CELL_CODE_UNITS = 2_000
6
9
  const BINDING_FAILURE = Symbol('binding failure')
10
+ const PROGRAM_FAILURES = new internal.WeakMap()
11
+ const MAX_CAUSE_CODE_UNITS = 2048
12
+ const exceptionOrigins = new AsyncLocalStorage()
13
+ const runExceptionScope = exceptionOrigins.run.bind(exceptionOrigins)
14
+ const currentExceptionScope = exceptionOrigins.getStore.bind(exceptionOrigins)
7
15
 
8
- export function markBindingFailure(error) {
9
- Object.defineProperty(error, BINDING_FAILURE, { value: true })
16
+ /** Propagation facts belong to one execution, never to a user-thrown value. */
17
+ export function createExceptionOriginScope() {
18
+ const state = { origins: new internal.Map(), sourceFailures: new internal.Set(), open: true }
19
+ return {
20
+ run: callback => runExceptionScope(state, callback),
21
+ origins: error => internal.mapGet(state.origins, error),
22
+ sourceFailure: error => internal.setHas(state.sourceFailures, error),
23
+ close() { state.open = false; internal.mapClear(state.origins); internal.setClear(state.sourceFailures) },
24
+ }
25
+ }
26
+
27
+ export function recordExceptionOrigin(error, origin, { reset = false, sourceFailure = false } = {}) {
28
+ const state = currentExceptionScope()
29
+ if (state?.open !== true) return
30
+ if (reset) { internal.mapDelete(state.origins, error); internal.setDelete(state.sourceFailures, error) }
31
+ if (sourceFailure) internal.setAdd(state.sourceFailures, error)
32
+ if (typeof origin !== 'string') return
33
+ const origins = internal.mapGet(state.origins, error) ?? []
34
+ if (!internal.includes(origins, origin)) internal.appendArray(origins, origin)
35
+ internal.mapSet(state.origins, error, origins)
36
+ }
37
+
38
+ const SOURCE_LINE_BREAK = /\r\n|[\n\r\u2028\u2029]/
39
+ const sourceLines = source => source.split(SOURCE_LINE_BREAK)
40
+
41
+ /** A cell position is a 1-based line and column inside its source. The column
42
+ * may address the position just after the last character of a line. */
43
+ function boundedPosition(position, lines) {
44
+ if (position === null || typeof position !== 'object'
45
+ || !Number.isSafeInteger(position.line) || position.line < 1
46
+ || !Number.isSafeInteger(position.column) || position.column < 1
47
+ || position.line > lines.length || position.column > lines[position.line - 1].length + 1) return undefined
48
+ return { line: position.line, column: position.column }
49
+ }
50
+
51
+ /** Validate a candidate against the source that owns the reported cell. */
52
+ export function cellPosition(position, source) {
53
+ return boundedPosition(position, sourceLines(source))
54
+ }
55
+
56
+ /** Only a fact belonging to the current original source can bypass its map. */
57
+ export function exceptionOriginPosition(origins, source) {
58
+ if (!Array.isArray(origins)) return undefined
59
+ const prefix = `eval:${createHash('sha256').update(source).digest('hex')}:`
60
+ const lines = sourceLines(source)
61
+ for (const origin of origins) {
62
+ if (typeof origin !== 'string' || !origin.startsWith(prefix)) continue
63
+ const match = /^(\d+):(\d+)$/.exec(origin.slice(prefix.length))
64
+ if (match === null) continue
65
+ const position = boundedPosition({ line: Number(match[1]), column: Number(match[2]) }, lines)
66
+ if (position !== undefined) return position
67
+ }
68
+ return undefined
69
+ }
70
+
71
+ export function markBindingFailure(error, kind = 'lexical') {
72
+ Object.defineProperty(error, BINDING_FAILURE, { value: kind })
10
73
  return error
11
74
  }
12
75
 
13
- function repeatedFailureDiagnostic(kind, streak) {
14
- if (kind === 'binding') {
76
+ /** Preserve worker-owned failure identity without trusting user exception text. */
77
+ export function programBindingError(kind, message) {
78
+ const error = new internal.Error(message)
79
+ internal.weakMapSet(PROGRAM_FAILURES, error, kind)
80
+ return error
81
+ }
82
+
83
+ function repeatedFailureDiagnostic(kind, streak, stateEffect) {
84
+ if (kind === 'lexical' || kind === 'capability') {
85
+ const capability = kind === 'capability'
15
86
  return diagnostic({
16
87
  code: 'PTC-W001',
17
88
  severity: 'warning',
18
89
  phase: 'execute',
19
- message: `this cell failed ${streak} times with the same binding error; inspect the live binding names and change the expression before retrying`,
20
- stateEffect: 'unchanged',
21
- help: [
90
+ message: `this cell failed ${streak} times with the same ${capability ? 'capability' : 'lexical binding'} error; resolve the reported name before continuing`,
91
+ stateEffect,
92
+ cause: { code: capability ? 'PTC-CAPABILITY' : 'PTC-LOCAL', message: capability ? 'current program capability lookup failed' : 'local name or declaration conflict' },
93
+ help: capability ? [
22
94
  'inspect live bindings with capabilities.tree(), capabilities.find(), or capabilities.inspect()',
23
95
  'call available typed members through tools.*',
24
96
  'do not repeat the same unresolved binding expression',
97
+ ] : [
98
+ 'inspect the visible cell source and results for the local declaration, scope, or initialization failure',
99
+ 'correct the name or initialization; use a fresh name or block for a declaration conflict',
100
+ 'capabilities describes program APIs, not session-local variables; discovery cannot prove local state',
25
101
  ],
26
102
  })
27
103
  }
@@ -30,7 +106,7 @@ function repeatedFailureDiagnostic(kind, streak) {
30
106
  severity: 'warning',
31
107
  phase: 'execute',
32
108
  message: `this cell failed ${streak} times with the same error; inspect the reported cause and change the approach before retrying`,
33
- stateEffect: 'unchanged',
109
+ stateEffect,
34
110
  help: ['inspect the reported cause', 'change the inputs or approach before retrying'],
35
111
  })
36
112
  }
@@ -54,6 +130,13 @@ export function messageOf(error, fallback = 'Unprintable error') {
54
130
  }
55
131
  }
56
132
 
133
+ /** Preserve the child-process exit facts in every helper failure diagnostic. */
134
+ export function processExitDescription(code, signal) {
135
+ return signal === null || signal === undefined
136
+ ? `code ${code}`
137
+ : `code ${code}, signal ${signal}`
138
+ }
139
+
57
140
  export function firstLine(value, fallback = undefined) {
58
141
  if (typeof value !== 'string') return fallback
59
142
  const line = value.split(/[\r\n]/, 1)[0]
@@ -92,18 +175,26 @@ export function limitLogs(logs) {
92
175
  return tail
93
176
  }
94
177
 
95
- export function errorPosition(error, filename) {
178
+ /** A frame is only useful when its 1-based line and column are safe integers. */
179
+ function structuralPosition(position) {
180
+ return Number.isSafeInteger(position.line) && position.line >= 1
181
+ && Number.isSafeInteger(position.column) && position.column >= 1 ? position : undefined
182
+ }
183
+
184
+ /** Extract the first frame for the active cell. The frame is reported in the
185
+ * executed text, so it is only a cell position when it lies inside the source
186
+ * that produced that text. */
187
+ export function errorPosition(error, filename, source) {
96
188
  const stack = safeProperty(error, 'stack')
97
189
  if (typeof stack !== 'string') return undefined
98
190
  const escaped = filename.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
99
191
  const match = new RegExp(`${escaped}:(\\d+):(\\d+)`).exec(stack)
100
192
  if (match === null) return undefined
101
- const line = Number(match[1])
102
- const column = Number(match[2])
103
- return line >= 1 && column >= 1 ? { line, column } : undefined
193
+ const position = { line: Number(match[1]), column: Number(match[2]) }
194
+ return source === undefined ? structuralPosition(position) : cellPosition(position, source)
104
195
  }
105
196
 
106
- export function errorDetails(error, filename) {
197
+ export function errorDetails(error, filename, source) {
107
198
  const message = messageOf(error, 'Unprintable thrown value')
108
199
  const rawName = safeProperty(error, 'name')
109
200
  const name = typeof rawName === 'string' && rawName.length > 0 ? rawName : 'Error'
@@ -114,19 +205,28 @@ export function errorDetails(error, filename) {
114
205
  const candidate = safeProperty(error, 'ptcCause')
115
206
  const causeMessage = firstLine(safeProperty(candidate, 'message'))
116
207
  const causeCode = firstLine(safeProperty(candidate, 'code'))
208
+ const stderr = safeProperty(error, 'stderr')
209
+ const stderrText = typeof stderr === 'string' ? stderr.slice(0, MAX_CAUSE_CODE_UNITS)
210
+ : Buffer.isBuffer(stderr) ? stderr.subarray(0, MAX_CAUSE_CODE_UNITS).toString('utf8') : ''
211
+ const boundedMessage = message.slice(0, MAX_CAUSE_CODE_UNITS)
212
+ const continuation = boundedMessage.search(/[\r\n]/)
213
+ const detail = [continuation < 0 ? '' : boundedMessage.slice(continuation + 1), stderrText]
214
+ .join(' ').slice(0, MAX_CAUSE_CODE_UNITS).replace(/\s+/g, ' ').trim()
117
215
  const cause = causeMessage === undefined
118
- ? undefined
216
+ ? detail.length === 0 ? undefined : { message: detail }
119
217
  : {
120
218
  ...(causeCode === undefined ? {} : { code: causeCode }),
121
219
  message: causeMessage,
122
220
  }
123
- const position = errorPosition(error, filename)
221
+ const position = errorPosition(error, filename, source)
222
+ const failureOrigin = internal.weakMapGet(PROGRAM_FAILURES, error)
124
223
  return {
125
224
  name,
126
225
  message,
127
226
  ...(toolName === undefined ? {} : { toolName }),
128
227
  ...(position === undefined ? {} : { position }),
129
228
  ...(cause === undefined ? {} : { cause }),
229
+ ...(failureOrigin === undefined ? {} : { failureOrigin }),
130
230
  }
131
231
  }
132
232
 
@@ -140,16 +240,16 @@ export function createFailureTracker() {
140
240
  kind = undefined
141
241
  streak = 0
142
242
  },
143
- hint(error) {
243
+ hint(error, stateEffect = 'unknown') {
144
244
  const nextFingerprint = `${error.kind}\u0000${error.message}`
145
245
  if (fingerprint === nextFingerprint) streak += 1
146
246
  else {
147
247
  fingerprint = nextFingerprint
148
- kind = error[BINDING_FAILURE] === true ? 'binding' : 'generic'
248
+ kind = error[BINDING_FAILURE] ?? 'generic'
149
249
  streak = 1
150
250
  }
151
251
  if (streak !== FAILURE_HINT_THRESHOLD) return undefined
152
- return repeatedFailureDiagnostic(kind, streak)
252
+ return repeatedFailureDiagnostic(kind, streak, stateEffect)
153
253
  },
154
254
  })
155
255
  }
@@ -0,0 +1,66 @@
1
+ import { bindTypertRemote } from '@deepseek-ai/dsh-typert-protocol'
2
+ import { rpcDescriptor } from './rpc-contract.js'
3
+
4
+ /** Own Remote services and the package's Host invocation contribution. */
5
+ export function createHostRpc(ctx) {
6
+ const handlers = new Map()
7
+ let provider
8
+ let definition
9
+ let epoch
10
+ let disposed = false
11
+ const publish = () => {
12
+ // Registry withdrawal is synchronous, so replacement has no overlapping endpoints.
13
+ definition?.()
14
+ definition = undefined
15
+ if (provider === undefined || disposed || handlers.size === 0) return
16
+ definition = provider.effect(() => provider.typert.register({
17
+ package: 'dsh-ptc-plus', face: 'host', schemas: [],
18
+ model: { services: [], events: [], objects: [] },
19
+ invocations: [...handlers.keys()].map(rpcDescriptor),
20
+ }), 'ptc-plus: Remote definitions')
21
+ }
22
+ const port = {
23
+ register(contract, handler) {
24
+ if (disposed) throw new Error('PTC Plus RPC owner is disposed')
25
+ if (handlers.has(contract)) throw new Error(`PTC Plus RPC service ${contract.service} is already registered`)
26
+ const lifetime = new AbortController()
27
+ const service = {
28
+ invoke(operation, payload, signal) {
29
+ lifetime.signal.throwIfAborted()
30
+ if (epoch === undefined) throw new Error('PTC Plus RPC provider is unavailable')
31
+ return handler(operation, payload, AbortSignal.any([signal, lifetime.signal, epoch.signal]))
32
+ },
33
+ }
34
+ service.typertRemote = bindTypertRemote(service, contract.service)
35
+ const release = ctx.effect(function* () {
36
+ yield ctx.provide(contract.service, service)
37
+ handlers.set(contract, { release: () => release() })
38
+ yield () => {
39
+ lifetime.abort()
40
+ handlers.delete(contract)
41
+ publish()
42
+ }
43
+ publish()
44
+ }, 'ptc-plus: Remote service')
45
+ return release
46
+ },
47
+ }
48
+ const injection = ctx.inject?.(['typert'], scope => {
49
+ provider = scope
50
+ epoch = new AbortController()
51
+ publish()
52
+ scope.effect(() => scope.provide('ptcPlusRpc', port), 'ptc-plus: RPC registration port')
53
+ scope.effect(() => () => {
54
+ epoch.abort()
55
+ epoch = undefined
56
+ provider = undefined
57
+ definition = undefined
58
+ }, 'ptc-plus: RPC provider lifetime')
59
+ })
60
+ return { async dispose() {
61
+ disposed = true
62
+ for (const entry of [...handlers.values()]) await entry.release()
63
+ if (typeof injection === 'function') await injection()
64
+ else await injection?.dispose()
65
+ } }
66
+ }