@x1a0f3n9/dsh-typert-protocol 0.1.2-alpha.6

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.
@@ -0,0 +1,162 @@
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 @x1a0f3n9/dsh-typert-protocol
6
+ */
7
+ import { Service } from '@deepseek-ai/cordis';
8
+ export { RemoteError, remoteErrorOf } from "./remote-error.js";
9
+ const TYPERT_REMOTE_SEGMENT_PATTERN = /^[A-Za-z0-9_$.-]+$/;
10
+ /**
11
+ * Test one generated Remote name against the Connection endpoint grammar.
12
+ * @param value - namespace, method, lookup, or Context segment.
13
+ * @returns whether the value can cross the shared RPC carrier unchanged.
14
+ */
15
+ export function isTypertRemoteSegment(value) {
16
+ return value !== '.' && value !== '..' && TYPERT_REMOTE_SEGMENT_PATTERN.test(value);
17
+ }
18
+ const REMOTE_METHOD_DESCRIPTOR = '@x1a0f3n9/dsh-typert-protocol/remote-methods';
19
+ /**
20
+ * Bind one visible Service field to a Cordis key and Remote namespace.
21
+ * @param service - owning Service instance, normally `this`.
22
+ * @param serviceKey - exact Cordis service key.
23
+ * @param options - optional distinct wire namespace.
24
+ * @returns a frozen, inspectable binding with no compiler-injected metadata.
25
+ */
26
+ export function bindTypertRemote(service, serviceKey, options = {}) {
27
+ validateName('service key', serviceKey);
28
+ const namespace = options.namespace ?? serviceKey;
29
+ validateName('namespace', namespace);
30
+ return Object.freeze({ service, serviceKey, namespace });
31
+ }
32
+ /** Cordis Service base that exposes its registered name through Typert Gateway. */
33
+ export class TypertRemoteService extends Service {
34
+ /** Visible binding consumed by the Gateway's source-mode discovery. */
35
+ typertRemote;
36
+ /**
37
+ * Register the Service and bind the same key to Typert Gateway.
38
+ * @param ctx - owning Cordis Context.
39
+ * @param serviceKey - exact Cordis service key and default wire namespace.
40
+ * @param options - optional distinct wire namespace.
41
+ */
42
+ constructor(ctx, serviceKey, options = {}) {
43
+ super(ctx, serviceKey);
44
+ this.typertRemote = bindTypertRemote(this, this.name, options);
45
+ }
46
+ }
47
+ export function Remote(methodExportOrOptions, context) {
48
+ if (typeof methodExportOrOptions === 'string') {
49
+ validateName('Remote export name', methodExportOrOptions);
50
+ return remoteDecorator({ kind: 'direct' }, undefined, methodExportOrOptions);
51
+ }
52
+ if (typeof methodExportOrOptions === 'object') {
53
+ if (remoteOptionMode(methodExportOrOptions) !== 'stream'
54
+ || Reflect.ownKeys(methodExportOrOptions).length !== 1) {
55
+ throw new TypeError('typert-protocol: Remote options must contain exactly mode: "stream"');
56
+ }
57
+ return remoteDecorator({ kind: 'direct' }, 'stream');
58
+ }
59
+ if (context === undefined)
60
+ throw new TypeError('typert-protocol: Remote decorator context is missing');
61
+ addMarkerInitializer(context, { kind: 'direct' });
62
+ }
63
+ function remoteOptionMode(options) {
64
+ return Reflect.get(options, 'mode');
65
+ }
66
+ function remoteDecorator(invocation, mode, exportName) {
67
+ return function (_method, context) {
68
+ addMarkerInitializer(context, invocation, mode, exportName);
69
+ };
70
+ }
71
+ /**
72
+ * Create a decorator for a method resolved from one Remote Scope.
73
+ * @param key - scope key declared through the Context map.
74
+ * @param exportName - optional Remote export name; defaults to the method name.
75
+ * @returns a standard method decorator that records a versioned prototype descriptor.
76
+ */
77
+ export function RemoteScope(key, exportName) {
78
+ validateName('Scope key', key);
79
+ if (exportName !== undefined)
80
+ validateName('Remote export name', exportName);
81
+ return remoteDecorator({ kind: 'context', context: key }, undefined, exportName);
82
+ }
83
+ /**
84
+ * Read Remote markers attached to a live Service's class prototype.
85
+ * The returned snapshot cannot mutate the stored descriptor.
86
+ * @param service - live Service instance.
87
+ * @returns markers in class declaration order.
88
+ */
89
+ export function remoteMethods(service) {
90
+ const prototype = Object.getPrototypeOf(service);
91
+ if (prototype === null)
92
+ return [];
93
+ return (readRemoteMethodDescriptor(prototype)?.methods ?? []).map(marker => ({ ...marker }));
94
+ }
95
+ function readRemoteMethodDescriptor(prototype) {
96
+ const property = Object.getOwnPropertyDescriptor(prototype, REMOTE_METHOD_DESCRIPTOR);
97
+ if (property === undefined)
98
+ return undefined;
99
+ const descriptor = property.value;
100
+ if (descriptor === null || typeof descriptor !== 'object') {
101
+ throw new TypeError('typert-protocol: Remote method descriptor must be an object');
102
+ }
103
+ const version = Reflect.get(descriptor, 'version');
104
+ if (version !== 1) {
105
+ throw new TypeError(`typert-protocol: unsupported Remote method descriptor version ${String(version)}`);
106
+ }
107
+ const methods = Reflect.get(descriptor, 'methods');
108
+ if (!Array.isArray(methods)) {
109
+ throw new TypeError('typert-protocol: Remote method descriptor methods must be an array');
110
+ }
111
+ return descriptor;
112
+ }
113
+ function addMarkerInitializer(context, invocation, mode, exportName) {
114
+ if (context.private || context.static || typeof context.name !== 'string') {
115
+ throw new TypeError('typert-protocol: Remote decorators require a public instance method with a string name');
116
+ }
117
+ const method = context.name;
118
+ context.addInitializer(function () {
119
+ const prototype = Object.getPrototypeOf(this);
120
+ if (prototype === null) {
121
+ throw new TypeError(`typert-protocol: cannot mark Remote method "${method}" on an object without a prototype`);
122
+ }
123
+ mark(prototype, method, invocation, mode, exportName);
124
+ });
125
+ }
126
+ function mark(prototype, method, invocation, mode, exportName) {
127
+ const descriptor = readRemoteMethodDescriptor(prototype);
128
+ const marker = Object.freeze({
129
+ method,
130
+ ...(exportName === undefined || exportName === method ? {} : { exportName }),
131
+ ...(mode === undefined ? {} : { mode }),
132
+ invocation: Object.freeze(invocation),
133
+ });
134
+ const current = descriptor?.methods.find(candidate => candidate.method === method);
135
+ if (current !== undefined) {
136
+ if (current.exportName === marker.exportName
137
+ && current.mode === marker.mode
138
+ && sameInvocation(current.invocation, invocation))
139
+ return;
140
+ throw new Error(`typert-protocol: Remote method "${method}" has conflicting invocation markers`);
141
+ }
142
+ Object.defineProperty(prototype, REMOTE_METHOD_DESCRIPTOR, {
143
+ configurable: true,
144
+ value: Object.freeze({
145
+ version: 1,
146
+ methods: Object.freeze([...(descriptor?.methods ?? []), marker]),
147
+ }),
148
+ });
149
+ }
150
+ function sameInvocation(left, right) {
151
+ if (left.kind === 'direct')
152
+ return right.kind === 'direct';
153
+ if (right.kind === 'direct')
154
+ return false;
155
+ return left.context === right.context;
156
+ }
157
+ function validateName(subject, value) {
158
+ if (!isTypertRemoteSegment(value)) {
159
+ throw new TypeError(`typert-protocol: ${subject} must contain only RPC endpoint segment characters`);
160
+ }
161
+ }
162
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,31 @@
1
+ /** The one Remote failure class shared by owners, the Gateway, and consumers. */
2
+ import type { RemoteErrorCode, RemoteErrorDetailsMap, RemoteFailure } from './types.ts';
3
+ /**
4
+ * One Remote call failure: a real Error carrying its stable code and typed
5
+ * details. Owners throw it at the failure point; the Host Gateway encodes it
6
+ * onto the wire unchanged; the Client face rebuilds an instance for the
7
+ * `RemoteResult` error branch, so `throw result.error` keeps throw semantics.
8
+ * Discrimination is always by `code`, never by instanceof.
9
+ */
10
+ export declare class RemoteError<Code extends RemoteErrorCode = RemoteErrorCode> extends Error {
11
+ readonly code: Code;
12
+ readonly details: RemoteErrorDetailsMap[Code];
13
+ /** Structural marker: cross-realm/bundle identification never uses instanceof. */
14
+ readonly isDSHRemoteError: true;
15
+ /**
16
+ * @param code - stable failure code declared in {@link RemoteErrorDetailsMap}.
17
+ * @param message - human diagnostic carried across the wire.
18
+ * @param details - structured payload typed by the code.
19
+ * @param options - standard Error options (`cause` survives in-process only).
20
+ */
21
+ constructor(code: Code, message: string, details: RemoteErrorDetailsMap[Code], options?: ErrorOptions);
22
+ }
23
+ /**
24
+ * Structurally identify a RemoteError thrown across module or realm copies of
25
+ * this class. Mechanism-internal: the Gateway and test assertions use it;
26
+ * business code receives typed failures and never needs it.
27
+ * @param value - a caught value.
28
+ * @returns the failure when the marker matches, otherwise undefined.
29
+ */
30
+ export declare function remoteErrorOf(value: unknown): RemoteFailure | undefined;
31
+ //# sourceMappingURL=remote-error.d.ts.map
@@ -0,0 +1,44 @@
1
+ /** The one Remote failure class shared by owners, the Gateway, and consumers. */
2
+ /**
3
+ * One Remote call failure: a real Error carrying its stable code and typed
4
+ * details. Owners throw it at the failure point; the Host Gateway encodes it
5
+ * onto the wire unchanged; the Client face rebuilds an instance for the
6
+ * `RemoteResult` error branch, so `throw result.error` keeps throw semantics.
7
+ * Discrimination is always by `code`, never by instanceof.
8
+ */
9
+ export class RemoteError extends Error {
10
+ code;
11
+ details;
12
+ /** Structural marker: cross-realm/bundle identification never uses instanceof. */
13
+ isDSHRemoteError = true;
14
+ /**
15
+ * @param code - stable failure code declared in {@link RemoteErrorDetailsMap}.
16
+ * @param message - human diagnostic carried across the wire.
17
+ * @param details - structured payload typed by the code.
18
+ * @param options - standard Error options (`cause` survives in-process only).
19
+ */
20
+ constructor(code, message, details, options) {
21
+ super(message, options);
22
+ this.code = code;
23
+ this.details = details;
24
+ this.name = 'RemoteError';
25
+ }
26
+ }
27
+ /**
28
+ * Structurally identify a RemoteError thrown across module or realm copies of
29
+ * this class. Mechanism-internal: the Gateway and test assertions use it;
30
+ * business code receives typed failures and never needs it.
31
+ * @param value - a caught value.
32
+ * @returns the failure when the marker matches, otherwise undefined.
33
+ */
34
+ export function remoteErrorOf(value) {
35
+ // Structural, not instanceof: an Error thrown in another realm (iframe, VM)
36
+ // fails instanceof Error here, so the marker plus the code field is the test.
37
+ if (typeof value === 'object' && value !== null
38
+ && value.isDSHRemoteError === true
39
+ && typeof value.code === 'string') {
40
+ return value;
41
+ }
42
+ return undefined;
43
+ }
44
+ //# sourceMappingURL=remote-error.js.map