dsh-plugin-xujie-memory 0.5.0 → 0.7.0
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/ACCEPTANCE.md +37 -0
- package/BUILD-PROVENANCE.json +53 -0
- package/CHANGELOG.md +40 -1
- package/LICENSE +21 -21
- package/README.md +67 -17
- package/README_EN.md +54 -8
- package/cordis.patch.yml +49 -49
- package/lib/client.js +17219 -0
- package/lib/controller.js +307 -0
- package/lib/index.js +200 -14
- package/lib/typert.host.d.ts +3 -0
- package/lib/typert.host.js +15104 -0
- package/lib/typert.remote-client.d.ts +40 -0
- package/lib/typert.remote-client.d.ts.map +1 -0
- package/lib/typert.remote-client.js +15093 -0
- package/lib/types.d.ts +13 -0
- package/lib/types.js +1 -0
- package/package.json +49 -5
- package/python/specs/characters/aloof.json +14 -14
- package/python/specs/characters/gentle.json +14 -14
- package/python/specs/characters/tsundere.json +14 -14
- package/python/specs/debt.v1.json +31 -31
- package/python/specs/nickname.v1.json +16 -16
- package/python/specs/preference.v1.json +16 -16
- package/python/xujie_memory/__init__.py +123 -123
- package/python/xujie_memory/admission.py +211 -211
- package/python/xujie_memory/audit.py +377 -377
- package/python/xujie_memory/benchmark.py +281 -281
- package/python/xujie_memory/character.py +199 -199
- package/python/xujie_memory/console.py +41 -41
- package/python/xujie_memory/consolidate.py +356 -356
- package/python/xujie_memory/due.py +121 -121
- package/python/xujie_memory/emotion.py +573 -573
- package/python/xujie_memory/engine.py +1333 -1312
- package/python/xujie_memory/errors.py +98 -98
- package/python/xujie_memory/importance.py +104 -104
- package/python/xujie_memory/judge.py +350 -350
- package/python/xujie_memory/management.py +203 -0
- package/python/xujie_memory/memory.py +71 -10
- package/python/xujie_memory/orchestrator.py +690 -666
- package/python/xujie_memory/phrasing.py +292 -292
- package/python/xujie_memory/privacy.py +105 -0
- package/python/xujie_memory/schema.sql +492 -480
- package/python/xujie_memory/server.py +76 -0
- package/python/xujie_memory/specs.py +170 -170
- package/python/xujie_memory/summary.py +238 -238
- package/scripts/build.mjs +19 -0
- package/scripts/prepack.mjs +9 -0
- package/scripts/test-all.mjs +9 -0
- package/scripts/test-node.mjs +10 -0
- package/src/client/MemoryPanel.tsx +536 -0
- package/src/client/NeuralField.tsx +88 -0
- package/src/client/css.d.ts +4 -0
- package/src/client/icons.tsx +28 -0
- package/src/client/index.tsx +59 -0
- package/src/client/memory-adapter.ts +67 -0
- package/src/client/model.ts +79 -0
- package/src/client/neural/engine.ts +999 -0
- package/src/client/neural/morphology.ts +219 -0
- package/src/client/panel.css +337 -0
- package/src/client/types.ts +6 -0
- package/src/controller.ts +88 -0
- package/src/generated/typert.host.js +449 -0
- package/src/generated/typert.remote-client.js +438 -0
- package/src/index.ts +2 -0
- package/src/types.ts +13 -0
- package/test/cordis-fixture.mjs +5 -0
- package/test/management_regression.py +187 -0
- package/test/node-regression.entry.mjs +52 -0
- package/vendor/DSH-LICENSE.txt +21 -0
- package/vendor/THIRD-PARTY.md +6 -0
- package/vendor/dsh-protocol/index.ts +484 -0
- package/vendor/dsh-protocol/json-value.ts +53 -0
- package/vendor/dsh-protocol/owned-value.ts +39 -0
- package/vendor/dsh-protocol/principal.ts +17 -0
- package/vendor/dsh-protocol/remote-access.ts +38 -0
- package/vendor/dsh-protocol/remote-error.ts +49 -0
- package/vendor/dsh-protocol/remote-info.ts +161 -0
- package/vendor/dsh-protocol/types.ts +939 -0
- package/vendor/licenses/jridgewell-gen-mapping-MIT.txt +19 -0
- package/vendor/licenses/jridgewell-resolve-uri-MIT.txt +19 -0
- package/vendor/licenses/jridgewell-sourcemap-codec-MIT.txt +19 -0
- package/vendor/licenses/jridgewell-trace-mapping-MIT.txt +19 -0
- package/vendor/licenses/zod-MIT.txt +21 -0
|
@@ -0,0 +1,484 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Remote decorators and explicit Gateway bindings backed by versioned
|
|
3
|
+
* descriptors carried on decorated class prototypes. Strict reflection
|
|
4
|
+
* remains a Typert compiler responsibility.
|
|
5
|
+
* @module @deepseek-ai/dsh-typert-protocol
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { Context, Service } from '@deepseek-ai/cordis'
|
|
9
|
+
import type {
|
|
10
|
+
RemoteWireArgs,
|
|
11
|
+
TypertContextMap,
|
|
12
|
+
TypertResourceReader,
|
|
13
|
+
TypertResourceReaders,
|
|
14
|
+
} from './types.ts'
|
|
15
|
+
|
|
16
|
+
export { RemoteError, remoteErrorOf } from './remote-error.ts'
|
|
17
|
+
export { TYPERT_OWNED_VALUE, isTypertOwnedValue, typertOwnedValue } from './owned-value.ts'
|
|
18
|
+
export type { TypertOwnedValue } from './owned-value.ts'
|
|
19
|
+
export { isRemoteJsonValue, isRemoteUplinkItem } from './json-value.ts'
|
|
20
|
+
export { createTypertRemoteInfo, isTypertRemoteMethod } from './remote-info.ts'
|
|
21
|
+
export { principalOf } from './principal.ts'
|
|
22
|
+
export type { RemoteAccessResource, RemoteAccessResourceKind, RemoteAccessResourceOf } from './remote-access.ts'
|
|
23
|
+
|
|
24
|
+
const TYPERT_REMOTE_SEGMENT_PATTERN = /^[A-Za-z0-9_$.-]+$/
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Test one generated Remote name against the Connection endpoint grammar.
|
|
28
|
+
* @param value - namespace, method, lookup, or Context segment.
|
|
29
|
+
* @returns whether the value can cross the shared RPC carrier unchanged.
|
|
30
|
+
*/
|
|
31
|
+
export function isTypertRemoteSegment(value: string): boolean {
|
|
32
|
+
return value !== '.' && value !== '..' && TYPERT_REMOTE_SEGMENT_PATTERN.test(value)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export type {
|
|
36
|
+
InvocationDescriptor,
|
|
37
|
+
InvocationParameterDescriptor,
|
|
38
|
+
InvocationSourceLocation,
|
|
39
|
+
PeerId,
|
|
40
|
+
PeerScope,
|
|
41
|
+
Principal,
|
|
42
|
+
PrincipalMap,
|
|
43
|
+
RemoteDecoded,
|
|
44
|
+
RemoteErrorCode,
|
|
45
|
+
RemoteErrorDetailsMap,
|
|
46
|
+
RemoteFailure,
|
|
47
|
+
RemoteInvocation,
|
|
48
|
+
RemoteResult,
|
|
49
|
+
RemoteStream,
|
|
50
|
+
RemoteStreamHandle,
|
|
51
|
+
RemoteUplinkWindow,
|
|
52
|
+
RemoteWireArgs,
|
|
53
|
+
RemoteWireValue,
|
|
54
|
+
TypertClientEventListener,
|
|
55
|
+
TypertClientRemote,
|
|
56
|
+
TypertClientContextAdapter,
|
|
57
|
+
TypertCodec,
|
|
58
|
+
TypertContext,
|
|
59
|
+
TypertContextMap,
|
|
60
|
+
TypertContextRegistry,
|
|
61
|
+
TypertContextWire,
|
|
62
|
+
TypertDisposer,
|
|
63
|
+
TypertForwardableEvent,
|
|
64
|
+
TypertForwardableEventEntry,
|
|
65
|
+
TypertHostContextAdapter,
|
|
66
|
+
TypertHostContextResolver,
|
|
67
|
+
TypertResource,
|
|
68
|
+
TypertResourceDeclaration,
|
|
69
|
+
TypertResourceEntry,
|
|
70
|
+
TypertResourceId,
|
|
71
|
+
TypertResourceKind,
|
|
72
|
+
TypertResourceMap,
|
|
73
|
+
TypertResourceReader,
|
|
74
|
+
TypertResourceReaders,
|
|
75
|
+
TypertResourceRegistry,
|
|
76
|
+
TypertLocalRegistry,
|
|
77
|
+
TypertLookup,
|
|
78
|
+
TypertLookupDefinition,
|
|
79
|
+
TypertLookupHost,
|
|
80
|
+
TypertLookupMap,
|
|
81
|
+
TypertLookupProvider,
|
|
82
|
+
TypertLookupResolver,
|
|
83
|
+
TypertLookupRegistry,
|
|
84
|
+
TypertLookupWire,
|
|
85
|
+
TypertRemoteScopeApi,
|
|
86
|
+
TypertRemoteScopeMap,
|
|
87
|
+
TypertRemoteScopeNamespace,
|
|
88
|
+
TypertRemoteContribution,
|
|
89
|
+
TypertRemoteInfo,
|
|
90
|
+
TypertRemoteInputOptions,
|
|
91
|
+
TypertRemoteMethod,
|
|
92
|
+
TypertRemoteValue,
|
|
93
|
+
TypertRemoteCodecSpec,
|
|
94
|
+
TypertRemoteParameterSpec,
|
|
95
|
+
TypertRemoteMethodSpec,
|
|
96
|
+
TypertRemoteSpec,
|
|
97
|
+
TypertRemoteEvent,
|
|
98
|
+
TypertRemoteEventSelection,
|
|
99
|
+
TypertRemoteMap,
|
|
100
|
+
TypertRemoteNamespace,
|
|
101
|
+
TypertRemoteNamespaceMap,
|
|
102
|
+
TypertRemoteRegistry,
|
|
103
|
+
TypertRegistryChange,
|
|
104
|
+
TypertRegistryListener,
|
|
105
|
+
TypertSchema,
|
|
106
|
+
TypertRegistryContract,
|
|
107
|
+
} from './types.ts'
|
|
108
|
+
|
|
109
|
+
/** Options for an explicit Service-to-Gateway binding. */
|
|
110
|
+
export interface TypertGatewayBindingOptions {
|
|
111
|
+
/** Wire namespace; defaults to the Cordis service key. */
|
|
112
|
+
readonly namespace?: string
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/** Visible declaration that one Service participates in Typert Gateway export. */
|
|
116
|
+
export interface TypertGatewayBinding<Service extends object = object> {
|
|
117
|
+
readonly service: Service
|
|
118
|
+
readonly serviceKey: string
|
|
119
|
+
readonly namespace: string
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/** Invocation mode recorded by a Remote method decorator. */
|
|
123
|
+
export type RemoteInvocationMarker =
|
|
124
|
+
| { readonly kind: 'direct' }
|
|
125
|
+
| { readonly kind: 'context'; readonly context: string }
|
|
126
|
+
|
|
127
|
+
/** One decorator marker discovered for a live Service instance. */
|
|
128
|
+
export interface RemoteMethodMarker {
|
|
129
|
+
/** Public instance method carrying the implementation. */
|
|
130
|
+
readonly method: string
|
|
131
|
+
/** Endpoint method when it differs from the implementation member. */
|
|
132
|
+
readonly exportName?: string
|
|
133
|
+
/** Stream methods yield many independently validated result items. */
|
|
134
|
+
readonly mode?: 'stream'
|
|
135
|
+
readonly invocation: RemoteInvocationMarker
|
|
136
|
+
/**
|
|
137
|
+
* Declared `resources` readers keyed by resource kind. Callers spread the
|
|
138
|
+
* method's wire arguments into each one and validate its result.
|
|
139
|
+
*/
|
|
140
|
+
readonly resources?: Readonly<Record<string, TypertResourceReader>>
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Explicit endpoint and resource choices for one Remote Scope method.
|
|
145
|
+
* @template WireArgs - the decorated method's positional parameters as the
|
|
146
|
+
* wire carries them; the default `never` admits readers of any tuple.
|
|
147
|
+
*/
|
|
148
|
+
export interface RemoteScopeOptions<WireArgs extends readonly unknown[] = never> {
|
|
149
|
+
/** Exported endpoint method; omission retains the implementation member name. */
|
|
150
|
+
readonly name?: string
|
|
151
|
+
/**
|
|
152
|
+
* Per-kind readers of the resources one call touches. Each receives the
|
|
153
|
+
* method's arguments as its own parameters, JSON arguments as their Typert
|
|
154
|
+
* codecs validated them, a lookup parameter as its wire identity and a
|
|
155
|
+
* cancellation parameter as the call's signal, so it performs plain reads
|
|
156
|
+
* only, and returns one identity, several, or `undefined` for none. A
|
|
157
|
+
* throw, an empty list, or an identity other than a non-empty string
|
|
158
|
+
* refuses the call. For a kind without a reader, the Gateway reads the
|
|
159
|
+
* identity of a scoped Context whose Host adapter declares that `resource`
|
|
160
|
+
* and of lookup parameters declaring it, else the field registered in
|
|
161
|
+
* `typert.resources`: a top-level argument or, failing that, the first
|
|
162
|
+
* object argument. A call that carries no identity touches `global`. The
|
|
163
|
+
* readers and these defaults together must name every resource the method
|
|
164
|
+
* accesses: access control never checks a resource they omit.
|
|
165
|
+
*/
|
|
166
|
+
readonly resources?: TypertResourceReaders<WireArgs>
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Explicit endpoint, stream, and resource choices for one direct Remote method.
|
|
171
|
+
* @template WireArgs - the decorated method's positional parameters as the
|
|
172
|
+
* wire carries them; the default `never` admits readers of any tuple.
|
|
173
|
+
*/
|
|
174
|
+
export interface RemoteMethodOptions<WireArgs extends readonly unknown[] = never> extends RemoteScopeOptions<WireArgs> {
|
|
175
|
+
/** `stream`: deliver each Iterable item over the shared logical-stream carrier. */
|
|
176
|
+
readonly mode?: 'stream'
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/** A decorator whose `resources` readers were checked against the decorated method's parameters. */
|
|
180
|
+
type RemoteTypedMethodDecorator<This extends object, Args extends unknown[], Result> = (
|
|
181
|
+
method: (this: This, ...args: Args) => Result,
|
|
182
|
+
context: ClassMethodDecoratorContext<This, (this: This, ...args: Args) => Result>,
|
|
183
|
+
) => void
|
|
184
|
+
|
|
185
|
+
type RemoteMethodDecorator = <This extends object, Args extends unknown[], Result>(
|
|
186
|
+
method: (this: This, ...args: Args) => Result,
|
|
187
|
+
context: ClassMethodDecoratorContext<This, (this: This, ...args: Args) => Result>,
|
|
188
|
+
) => void
|
|
189
|
+
|
|
190
|
+
interface RemoteInitializerContext<This extends object> {
|
|
191
|
+
readonly private: boolean
|
|
192
|
+
readonly static: boolean
|
|
193
|
+
readonly name: string | symbol
|
|
194
|
+
addInitializer(initializer: (this: This) => void): void
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
interface StoredRemoteMethodMarker {
|
|
198
|
+
readonly exportName?: string
|
|
199
|
+
readonly mode?: 'stream'
|
|
200
|
+
readonly invocation: RemoteInvocationMarker
|
|
201
|
+
readonly resources?: Readonly<Record<string, TypertResourceReader>>
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/** Marker fields selected by decorator arguments. */
|
|
205
|
+
interface RemoteMarkerOptions {
|
|
206
|
+
readonly mode?: 'stream'
|
|
207
|
+
readonly exportName?: string
|
|
208
|
+
readonly resources?: Readonly<Record<string, TypertResourceReader>>
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
interface StoredRemoteMethod extends StoredRemoteMethodMarker {
|
|
212
|
+
readonly method: string
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
interface RemoteMethodDescriptorV1 {
|
|
216
|
+
readonly version: 1
|
|
217
|
+
readonly methods: readonly StoredRemoteMethod[]
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
const REMOTE_METHOD_DESCRIPTOR = '@deepseek-ai/dsh-typert-protocol/remote-methods'
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* Bind one visible Service field to a Cordis key and Remote namespace. A
|
|
224
|
+
* service that owns a Cordis Context also gives its tree `ctx.invocation`,
|
|
225
|
+
* `undefined` outside a Remote call, so no `TypertRemoteService` is needed for
|
|
226
|
+
* a Host composition to read it.
|
|
227
|
+
* @param service - owning Service instance, normally `this`.
|
|
228
|
+
* @param serviceKey - exact Cordis service key.
|
|
229
|
+
* @param options - optional wire namespace.
|
|
230
|
+
* @returns a frozen, inspectable binding with no compiler-injected metadata.
|
|
231
|
+
*/
|
|
232
|
+
export function bindTypertRemote<Service extends object>(
|
|
233
|
+
service: Service,
|
|
234
|
+
serviceKey: string,
|
|
235
|
+
options: TypertGatewayBindingOptions = {},
|
|
236
|
+
): TypertGatewayBinding<Service> {
|
|
237
|
+
validateName('service key', serviceKey)
|
|
238
|
+
const namespace = options.namespace ?? serviceKey
|
|
239
|
+
validateName('namespace', namespace)
|
|
240
|
+
const ctx: unknown = Reflect.get(service, 'ctx')
|
|
241
|
+
if (Context.is(ctx)) provideInvocationAccessor(ctx)
|
|
242
|
+
return Object.freeze({ service, serviceKey, namespace })
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/** Cordis Service base that exposes its registered name through Typert Gateway. */
|
|
246
|
+
export abstract class TypertRemoteService<out T = never> extends Service<T> {
|
|
247
|
+
/** Visible binding consumed by the Gateway's source-mode discovery. */
|
|
248
|
+
readonly typertRemote: TypertGatewayBinding<this>
|
|
249
|
+
|
|
250
|
+
/**
|
|
251
|
+
* Register the Service and bind the same key to Typert Gateway.
|
|
252
|
+
* @param ctx - owning Cordis Context.
|
|
253
|
+
* @param serviceKey - exact Cordis service key and default wire namespace.
|
|
254
|
+
* @param options - optional wire namespace.
|
|
255
|
+
*/
|
|
256
|
+
protected constructor(ctx: Context, serviceKey: string, options: TypertGatewayBindingOptions = {}) {
|
|
257
|
+
super(ctx, serviceKey)
|
|
258
|
+
this.typertRemote = bindTypertRemote(this, this.name, options)
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* Make `ctx.invocation` read as `undefined` outside a Remote call instead of the
|
|
264
|
+
* reflect service's "cannot get property" error; a call-derived Context shadows
|
|
265
|
+
* the accessor with its own property. The first Remote Service constructed in a
|
|
266
|
+
* tree registers it on the root, where it outlives any one Service.
|
|
267
|
+
*/
|
|
268
|
+
function provideInvocationAccessor(ctx: Context): void {
|
|
269
|
+
if (Object.hasOwn(ctx.root.reflect.props, 'invocation')) return
|
|
270
|
+
ctx.root.accessor('invocation', { get: () => undefined })
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* Mark one public instance method as a direct Remote invocation.
|
|
275
|
+
* @param _method - decorated method; retained only by the class itself.
|
|
276
|
+
* @param context - standard decorator context used to schedule private marking.
|
|
277
|
+
*/
|
|
278
|
+
export function Remote<This extends object, Args extends unknown[], Result>(
|
|
279
|
+
_method: (this: This, ...args: Args) => Result,
|
|
280
|
+
context: ClassMethodDecoratorContext<This, (this: This, ...args: Args) => Result>,
|
|
281
|
+
): void
|
|
282
|
+
/**
|
|
283
|
+
* Mark one public instance method with an endpoint name.
|
|
284
|
+
* @param name - exported endpoint method.
|
|
285
|
+
* @returns a standard method decorator.
|
|
286
|
+
*/
|
|
287
|
+
export function Remote(name: string): RemoteMethodDecorator
|
|
288
|
+
/**
|
|
289
|
+
* Mark one public instance method with endpoint, stream, or resource options.
|
|
290
|
+
* @param options - Remote options; each reader's parameters are inferred from the decorated method.
|
|
291
|
+
* @returns a standard method decorator.
|
|
292
|
+
*/
|
|
293
|
+
export function Remote<This extends object, Args extends unknown[], Result>(
|
|
294
|
+
options: NoInfer<RemoteMethodOptions<RemoteWireArgs<Args>>>,
|
|
295
|
+
): RemoteTypedMethodDecorator<This, Args, Result>
|
|
296
|
+
export function Remote<This extends object, Args extends unknown[], Result>(
|
|
297
|
+
methodExportOrOptions: string | RemoteMethodOptions | ((this: This, ...args: Args) => Result),
|
|
298
|
+
context?: ClassMethodDecoratorContext<This, (this: This, ...args: Args) => Result>,
|
|
299
|
+
): void | RemoteMethodDecorator {
|
|
300
|
+
if (typeof methodExportOrOptions === 'string') {
|
|
301
|
+
validateName('Remote export name', methodExportOrOptions)
|
|
302
|
+
return remoteDecorator({ kind: 'direct' }, { exportName: methodExportOrOptions })
|
|
303
|
+
}
|
|
304
|
+
if (typeof methodExportOrOptions === 'object') {
|
|
305
|
+
return remoteDecorator({ kind: 'direct' }, markerOptions(
|
|
306
|
+
methodExportOrOptions,
|
|
307
|
+
REMOTE_OPTION_KEYS,
|
|
308
|
+
'Remote options require name, mode: "stream", or resources',
|
|
309
|
+
))
|
|
310
|
+
}
|
|
311
|
+
if (context === undefined) throw new TypeError('typert-protocol: Remote decorator context is missing')
|
|
312
|
+
addMarkerInitializer(context, { kind: 'direct' })
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
const REMOTE_OPTION_KEYS: ReadonlySet<string> = new Set(['name', 'mode', 'resources'])
|
|
316
|
+
const REMOTE_SCOPE_OPTION_KEYS: ReadonlySet<string> = new Set(['name', 'resources'])
|
|
317
|
+
|
|
318
|
+
/** Validate one decorator options object against its accepted keys and select the marker fields. */
|
|
319
|
+
function markerOptions(options: RemoteMethodOptions, accepted: ReadonlySet<string>, requirement: string): RemoteMarkerOptions {
|
|
320
|
+
const keys = Reflect.ownKeys(options)
|
|
321
|
+
if (keys.length === 0 || keys.some(key => typeof key !== 'string' || !accepted.has(key))
|
|
322
|
+
|| (Object.hasOwn(options, 'mode') && options.mode !== 'stream')) {
|
|
323
|
+
throw new TypeError(`typert-protocol: ${requirement}`)
|
|
324
|
+
}
|
|
325
|
+
const { mode, name, resources } = options
|
|
326
|
+
if (name !== undefined) validateName('Remote export name', name)
|
|
327
|
+
return {
|
|
328
|
+
...(mode === undefined ? {} : { mode }),
|
|
329
|
+
...(name === undefined ? {} : { exportName: name }),
|
|
330
|
+
...(resources === undefined ? {} : { resources: resourceReaders(resources) }),
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
/** Freeze declared readers into the marker record; a misdeclared reader fails at decoration. */
|
|
335
|
+
function resourceReaders(resources: object): Readonly<Record<string, TypertResourceReader>> {
|
|
336
|
+
const readers: Record<string, TypertResourceReader> = {}
|
|
337
|
+
for (const [kind, reader] of Object.entries(resources as Readonly<Record<string, unknown>>)) {
|
|
338
|
+
if (typeof reader !== 'function') {
|
|
339
|
+
throw new TypeError(`typert-protocol: Remote resource reader "${kind}" must be a function`)
|
|
340
|
+
}
|
|
341
|
+
readers[kind] = reader as TypertResourceReader
|
|
342
|
+
}
|
|
343
|
+
if (Object.keys(readers).length === 0) throw new TypeError('typert-protocol: Remote resources require at least one reader')
|
|
344
|
+
return Object.freeze(readers)
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
function remoteDecorator(
|
|
348
|
+
invocation: RemoteInvocationMarker,
|
|
349
|
+
options: RemoteMarkerOptions = {},
|
|
350
|
+
): RemoteMethodDecorator {
|
|
351
|
+
return function <This extends object, Args extends unknown[], Result>(
|
|
352
|
+
_method: (this: This, ...args: Args) => Result,
|
|
353
|
+
context: ClassMethodDecoratorContext<This, (this: This, ...args: Args) => Result>,
|
|
354
|
+
): void {
|
|
355
|
+
addMarkerInitializer(context, invocation, options)
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
/**
|
|
360
|
+
* Create a decorator for a method resolved from one Remote Scope.
|
|
361
|
+
* @param key - scope key declared through the Context map.
|
|
362
|
+
* @param name - optional Remote export name; defaults to the method name.
|
|
363
|
+
* @returns a standard method decorator that records a versioned prototype descriptor.
|
|
364
|
+
*/
|
|
365
|
+
export function RemoteScope(key: Extract<keyof TypertContextMap, string>, name?: string): RemoteMethodDecorator
|
|
366
|
+
/**
|
|
367
|
+
* Create a decorator for a Remote Scope method with endpoint or resource options.
|
|
368
|
+
* @param key - scope key declared through the Context map.
|
|
369
|
+
* @param options - Remote Scope options; each reader's parameters are inferred from the decorated method.
|
|
370
|
+
* @returns a standard method decorator that records a versioned prototype descriptor.
|
|
371
|
+
*/
|
|
372
|
+
export function RemoteScope<This extends object, Args extends unknown[], Result>(
|
|
373
|
+
key: Extract<keyof TypertContextMap, string>,
|
|
374
|
+
options: NoInfer<RemoteScopeOptions<RemoteWireArgs<Args>>>,
|
|
375
|
+
): RemoteTypedMethodDecorator<This, Args, Result>
|
|
376
|
+
export function RemoteScope(
|
|
377
|
+
key: Extract<keyof TypertContextMap, string>,
|
|
378
|
+
option?: string | RemoteScopeOptions,
|
|
379
|
+
): RemoteMethodDecorator {
|
|
380
|
+
validateName('Scope key', key)
|
|
381
|
+
const invocation: RemoteInvocationMarker = { kind: 'context', context: key }
|
|
382
|
+
if (option === undefined) return remoteDecorator(invocation)
|
|
383
|
+
if (typeof option === 'string') {
|
|
384
|
+
validateName('Remote export name', option)
|
|
385
|
+
return remoteDecorator(invocation, { exportName: option })
|
|
386
|
+
}
|
|
387
|
+
return remoteDecorator(invocation, markerOptions(
|
|
388
|
+
option,
|
|
389
|
+
REMOTE_SCOPE_OPTION_KEYS,
|
|
390
|
+
'RemoteScope options require name or resources',
|
|
391
|
+
))
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
/**
|
|
395
|
+
* Read Remote markers attached to a live Service's class prototype.
|
|
396
|
+
* The returned snapshot cannot mutate the stored descriptor.
|
|
397
|
+
* @param service - live Service instance.
|
|
398
|
+
* @returns markers in class declaration order.
|
|
399
|
+
*/
|
|
400
|
+
export function remoteMethods(service: object): readonly RemoteMethodMarker[] {
|
|
401
|
+
const prototype = Object.getPrototypeOf(service) as object | null
|
|
402
|
+
if (prototype === null) return []
|
|
403
|
+
return (readRemoteMethodDescriptor(prototype)?.methods ?? []).map(marker => ({ ...marker }))
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
function readRemoteMethodDescriptor(prototype: object): RemoteMethodDescriptorV1 | undefined {
|
|
407
|
+
const property = Object.getOwnPropertyDescriptor(prototype, REMOTE_METHOD_DESCRIPTOR)
|
|
408
|
+
if (property === undefined) return undefined
|
|
409
|
+
const descriptor: unknown = property.value
|
|
410
|
+
if (descriptor === null || typeof descriptor !== 'object') {
|
|
411
|
+
throw new TypeError('typert-protocol: Remote method descriptor must be an object')
|
|
412
|
+
}
|
|
413
|
+
const version: unknown = Reflect.get(descriptor, 'version')
|
|
414
|
+
if (version !== 1) {
|
|
415
|
+
throw new TypeError(`typert-protocol: unsupported Remote method descriptor version ${String(version)}`)
|
|
416
|
+
}
|
|
417
|
+
const methods: unknown = Reflect.get(descriptor, 'methods')
|
|
418
|
+
if (!Array.isArray(methods)) {
|
|
419
|
+
throw new TypeError('typert-protocol: Remote method descriptor methods must be an array')
|
|
420
|
+
}
|
|
421
|
+
return descriptor as RemoteMethodDescriptorV1
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
function addMarkerInitializer<This extends object>(
|
|
425
|
+
context: RemoteInitializerContext<This>,
|
|
426
|
+
invocation: RemoteInvocationMarker,
|
|
427
|
+
options: RemoteMarkerOptions = {},
|
|
428
|
+
): void {
|
|
429
|
+
if (context.private || context.static || typeof context.name !== 'string') {
|
|
430
|
+
throw new TypeError('typert-protocol: Remote decorators require a public instance method with a string name')
|
|
431
|
+
}
|
|
432
|
+
const method = context.name
|
|
433
|
+
context.addInitializer(function (this: This) {
|
|
434
|
+
const prototype = Object.getPrototypeOf(this) as object | null
|
|
435
|
+
if (prototype === null) {
|
|
436
|
+
throw new TypeError(`typert-protocol: cannot mark Remote method "${method}" on an object without a prototype`)
|
|
437
|
+
}
|
|
438
|
+
mark(prototype, method, invocation, options)
|
|
439
|
+
})
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
function mark(
|
|
443
|
+
prototype: object,
|
|
444
|
+
method: string,
|
|
445
|
+
invocation: RemoteInvocationMarker,
|
|
446
|
+
{ mode, exportName, resources }: RemoteMarkerOptions,
|
|
447
|
+
): void {
|
|
448
|
+
const descriptor = readRemoteMethodDescriptor(prototype)
|
|
449
|
+
const marker: StoredRemoteMethod = Object.freeze({
|
|
450
|
+
method,
|
|
451
|
+
...(exportName === undefined || exportName === method ? {} : { exportName }),
|
|
452
|
+
...(mode === undefined ? {} : { mode }),
|
|
453
|
+
invocation: Object.freeze(invocation),
|
|
454
|
+
...(resources === undefined ? {} : { resources }),
|
|
455
|
+
})
|
|
456
|
+
const current = descriptor?.methods.find(candidate => candidate.method === method)
|
|
457
|
+
if (current !== undefined) {
|
|
458
|
+
// One decorator application owns one frozen reader record, so identity marks the same declaration.
|
|
459
|
+
if (current.exportName === marker.exportName
|
|
460
|
+
&& current.mode === marker.mode
|
|
461
|
+
&& current.resources === marker.resources
|
|
462
|
+
&& sameInvocation(current.invocation, invocation)) return
|
|
463
|
+
throw new Error(`typert-protocol: Remote method "${method}" has conflicting invocation markers`)
|
|
464
|
+
}
|
|
465
|
+
Object.defineProperty(prototype, REMOTE_METHOD_DESCRIPTOR, {
|
|
466
|
+
configurable: true,
|
|
467
|
+
value: Object.freeze({
|
|
468
|
+
version: 1,
|
|
469
|
+
methods: Object.freeze([...(descriptor?.methods ?? []), marker]),
|
|
470
|
+
} satisfies RemoteMethodDescriptorV1),
|
|
471
|
+
})
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
function sameInvocation(left: RemoteInvocationMarker, right: RemoteInvocationMarker): boolean {
|
|
475
|
+
if (left.kind === 'direct') return right.kind === 'direct'
|
|
476
|
+
if (right.kind === 'direct') return false
|
|
477
|
+
return left.context === right.context
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
function validateName(subject: string, value: string): void {
|
|
481
|
+
if (!isTypertRemoteSegment(value)) {
|
|
482
|
+
throw new TypeError(`typert-protocol: ${subject} must contain only RPC endpoint segment characters`)
|
|
483
|
+
}
|
|
484
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Lossless JSON checks every Remote carrier shares: the Client handle before it
|
|
3
|
+
* queues an uplink item, the Gateway at its wire and codec-less uplink
|
|
4
|
+
* boundaries, and the in-process mock.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Test whether a value crosses JSON transport without coercion or omission.
|
|
9
|
+
* @param value - candidate boundary value.
|
|
10
|
+
* @returns whether the value is losslessly JSON-compatible.
|
|
11
|
+
*/
|
|
12
|
+
export function isRemoteJsonValue(value: unknown): boolean {
|
|
13
|
+
return visitJsonValue(value, new Set<object>())
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Test whether a value may travel as one uplink item: a lossless JSON value, or
|
|
18
|
+
* a top-level `undefined`, which the wire carries as an `item` frame without
|
|
19
|
+
* `value`. Nested `undefined`, `NaN`, and infinities stay rejected.
|
|
20
|
+
* @param value - candidate uplink item.
|
|
21
|
+
* @returns whether the item crosses every carrier unchanged.
|
|
22
|
+
*/
|
|
23
|
+
export function isRemoteUplinkItem(value: unknown): boolean {
|
|
24
|
+
return value === undefined || isRemoteJsonValue(value)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function visitJsonValue(value: unknown, ancestors: Set<object>): boolean {
|
|
28
|
+
if (value === null || typeof value === 'string' || typeof value === 'boolean') return true
|
|
29
|
+
if (typeof value === 'number') return Number.isFinite(value) && !Object.is(value, -0)
|
|
30
|
+
if (typeof value !== 'object') return false
|
|
31
|
+
if (ancestors.has(value)) return false
|
|
32
|
+
ancestors.add(value)
|
|
33
|
+
try {
|
|
34
|
+
if (Array.isArray(value)) {
|
|
35
|
+
if (Object.getPrototypeOf(value) !== Array.prototype
|
|
36
|
+
|| Reflect.ownKeys(value).length !== value.length + 1) return false
|
|
37
|
+
for (let index = 0; index < value.length; index++) {
|
|
38
|
+
if (!Object.hasOwn(value, index) || !visitJsonValue(value[index], ancestors)) return false
|
|
39
|
+
}
|
|
40
|
+
return true
|
|
41
|
+
}
|
|
42
|
+
const prototype: unknown = Object.getPrototypeOf(value)
|
|
43
|
+
if (prototype !== Object.prototype && prototype !== null) return false
|
|
44
|
+
for (const key of Reflect.ownKeys(value)) {
|
|
45
|
+
if (typeof key !== 'string') return false
|
|
46
|
+
const descriptor = Object.getOwnPropertyDescriptor(value, key)
|
|
47
|
+
if (descriptor?.enumerable !== true || !visitJsonValue(Reflect.get(value, key), ancestors)) return false
|
|
48
|
+
}
|
|
49
|
+
return true
|
|
50
|
+
} finally {
|
|
51
|
+
ancestors.delete(value)
|
|
52
|
+
}
|
|
53
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/** Generic invocation-owned values returned by synchronous Client Context resolvers. */
|
|
2
|
+
|
|
3
|
+
/** Shared identity across independently bundled Context providers and Gateway. */
|
|
4
|
+
export const TYPERT_OWNED_VALUE = Symbol.for('dsh.typert.owned-value')
|
|
5
|
+
|
|
6
|
+
/** A borrowed payload paired with the invocation owner's idempotent cleanup. */
|
|
7
|
+
export interface TypertOwnedValue<Value> extends Disposable {
|
|
8
|
+
readonly [TYPERT_OWNED_VALUE]: true
|
|
9
|
+
readonly value: Value
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Transfer cleanup ownership without adding another resource reference count.
|
|
14
|
+
* @param value - resolved payload passed to the invocation.
|
|
15
|
+
* @param release - non-throwing synchronous release, called at most once.
|
|
16
|
+
* @returns an owned payload disposed after invocation and reply settlement.
|
|
17
|
+
*/
|
|
18
|
+
export function typertOwnedValue<Value>(value: Value, release: () => void): TypertOwnedValue<Value> {
|
|
19
|
+
let active = true
|
|
20
|
+
return {
|
|
21
|
+
[TYPERT_OWNED_VALUE]: true,
|
|
22
|
+
value,
|
|
23
|
+
[Symbol.dispose]() {
|
|
24
|
+
if (!active) return
|
|
25
|
+
active = false
|
|
26
|
+
release()
|
|
27
|
+
},
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Identify invocation-owned values using the shared marker.
|
|
33
|
+
* @param value - borrowed or owned resolver result.
|
|
34
|
+
* @returns whether the result carries invocation cleanup.
|
|
35
|
+
*/
|
|
36
|
+
export function isTypertOwnedValue(value: unknown): value is TypertOwnedValue<unknown> {
|
|
37
|
+
return typeof value === 'object' && value !== null
|
|
38
|
+
&& TYPERT_OWNED_VALUE in value && value[TYPERT_OWNED_VALUE] === true
|
|
39
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reads the non-owner caller of the current Remote call.
|
|
3
|
+
* @module @deepseek-ai/dsh-typert-protocol/principal
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { Principal, RemoteInvocation } from './types.ts'
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* The identity a Remote-call producer records on durable output.
|
|
10
|
+
* @param invocation - `ctx.invocation`; `undefined` outside a Remote call.
|
|
11
|
+
* @returns the caller's principal when it is not the owner; `undefined` for the
|
|
12
|
+
* owner and for trusted in-process code outside a Remote call.
|
|
13
|
+
*/
|
|
14
|
+
export function principalOf(invocation: RemoteInvocation | undefined): Principal | undefined {
|
|
15
|
+
const principal = invocation?.peer.principal
|
|
16
|
+
return principal?.kind === 'owner' ? undefined : principal
|
|
17
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resources of Remote access control: the built-in identity-less `global`
|
|
3
|
+
* resource and one resource per identity of a kind declared in
|
|
4
|
+
* `TypertResourceMap`. Resources are independent: access to one resource
|
|
5
|
+
* implies no access to any other.
|
|
6
|
+
* @module @deepseek-ai/dsh-typert-protocol/remote-access
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
/* oxlint-disable typescript/no-redundant-type-constituents --
|
|
10
|
+
* `TypertResourceKind` is the declare-merge key pattern: the resource map is
|
|
11
|
+
* empty in this compilation unit, so the rule sees `never`, while every
|
|
12
|
+
* domain that declares a kind merges its key in. */
|
|
13
|
+
import type {
|
|
14
|
+
TypertResourceId,
|
|
15
|
+
TypertResourceKind,
|
|
16
|
+
TypertResourceMap,
|
|
17
|
+
} from './types.ts'
|
|
18
|
+
|
|
19
|
+
/** One resource kind: the built-in `global` kind or a declared kind. */
|
|
20
|
+
export type RemoteAccessResourceKind = 'global' | TypertResourceKind
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* One resource a call or forwarded event touches: `global`, or one identity
|
|
24
|
+
* of a declared kind. Identities are wire values, read before any lookup
|
|
25
|
+
* resolves or validates them.
|
|
26
|
+
*/
|
|
27
|
+
export type RemoteAccessResource =
|
|
28
|
+
| { readonly kind: 'global' }
|
|
29
|
+
| {
|
|
30
|
+
[Kind in TypertResourceKind]: { readonly kind: Kind; readonly id: TypertResourceId<TypertResourceMap[Kind]> }
|
|
31
|
+
}[TypertResourceKind]
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* The resource variant of one kind: `{ kind: 'global' }` for `global`, else
|
|
35
|
+
* that kind with its identity.
|
|
36
|
+
* @template K - the resource kind to select.
|
|
37
|
+
*/
|
|
38
|
+
export type RemoteAccessResourceOf<K extends RemoteAccessResourceKind> = Extract<RemoteAccessResource, { readonly kind: K }>
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/** The one Remote failure class shared by owners, the Gateway, and consumers. */
|
|
2
|
+
|
|
3
|
+
import type { RemoteErrorCode, RemoteErrorDetailsMap, RemoteFailure } from './types.ts'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* One Remote call failure: a real Error carrying its stable code and typed
|
|
7
|
+
* details. Owners throw it at the failure point; the Host Gateway encodes it
|
|
8
|
+
* onto the wire unchanged; the Client face rebuilds an instance for the
|
|
9
|
+
* `RemoteResult` error branch, so `throw result.error` keeps throw semantics.
|
|
10
|
+
* Discrimination is always by `code`, never by instanceof.
|
|
11
|
+
*/
|
|
12
|
+
export class RemoteError<Code extends RemoteErrorCode = RemoteErrorCode> extends Error {
|
|
13
|
+
/** Structural marker: cross-realm/bundle identification never uses instanceof. */
|
|
14
|
+
readonly isDSHRemoteError: true = true
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* @param code - stable failure code declared in {@link RemoteErrorDetailsMap}.
|
|
18
|
+
* @param message - human diagnostic carried across the wire.
|
|
19
|
+
* @param details - structured payload typed by the code.
|
|
20
|
+
* @param options - standard Error options (`cause` survives in-process only).
|
|
21
|
+
*/
|
|
22
|
+
constructor(
|
|
23
|
+
readonly code: Code,
|
|
24
|
+
message: string,
|
|
25
|
+
readonly details: RemoteErrorDetailsMap[Code],
|
|
26
|
+
options?: ErrorOptions,
|
|
27
|
+
) {
|
|
28
|
+
super(message, options)
|
|
29
|
+
this.name = 'RemoteError'
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Structurally identify a RemoteError thrown across module or realm copies of
|
|
35
|
+
* this class. Mechanism-internal: the Gateway and test assertions use it;
|
|
36
|
+
* business code receives typed failures and never needs it.
|
|
37
|
+
* @param value - a caught value.
|
|
38
|
+
* @returns the failure when the marker matches, otherwise undefined.
|
|
39
|
+
*/
|
|
40
|
+
export function remoteErrorOf(value: unknown): RemoteFailure | undefined {
|
|
41
|
+
// Structural, not instanceof: an Error thrown in another realm (iframe, VM)
|
|
42
|
+
// fails instanceof Error here, so the marker plus the code field is the test.
|
|
43
|
+
if (typeof value === 'object' && value !== null
|
|
44
|
+
&& (value as { isDSHRemoteError?: unknown }).isDSHRemoteError === true
|
|
45
|
+
&& typeof (value as { code?: unknown }).code === 'string') {
|
|
46
|
+
return value as RemoteFailure
|
|
47
|
+
}
|
|
48
|
+
return undefined
|
|
49
|
+
}
|