@hediet/linkrpc 0.0.1 → 0.0.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.md +59 -0
- package/dist/chunks/{boundedTrafficSubscription-1L592xc7.js → boundedTrafficSubscription-XZ1KwXZC.js} +267 -6
- package/dist/chunks/boundedTrafficSubscription-XZ1KwXZC.js.map +1 -0
- package/dist/chunks/{hub.interfaces-BzWfsVT2.js → hub.interfaces-Bf4VfWNX.js} +2 -2
- package/dist/chunks/{hub.interfaces-BzWfsVT2.js.map → hub.interfaces-Bf4VfWNX.js.map} +1 -1
- package/dist/chunks/{hubAccess-DwTZPiI8.d.ts → hubAccess-C06qAKao.d.ts} +2 -2
- package/dist/chunks/{hubAccess-DwTZPiI8.d.ts.map → hubAccess-C06qAKao.d.ts.map} +1 -1
- package/dist/chunks/{hubFacade-CQflVkVC.js → hubFacade-BIB9CoAv.js} +2 -2
- package/dist/chunks/{hubFacade-CQflVkVC.js.map → hubFacade-BIB9CoAv.js.map} +1 -1
- package/dist/chunks/{hubFacade-Dkgw2pTi.d.ts → hubFacade-DtQpGpUy.d.ts} +2 -2
- package/dist/chunks/{hubFacade-Dkgw2pTi.d.ts.map → hubFacade-DtQpGpUy.d.ts.map} +1 -1
- package/dist/chunks/{linkRpcConnection-CtlQmetO.d.ts → linkRpcConnection-BadD37ok.d.ts} +125 -37
- package/dist/chunks/linkRpcConnection-BadD37ok.d.ts.map +1 -0
- package/dist/chunks/{src-D3NUIwyo.js → src-CAhXne71.js} +160 -198
- package/dist/chunks/src-CAhXne71.js.map +1 -0
- package/dist/hub/client/index.d.ts +3 -3
- package/dist/hub/client/index.js +3 -3
- package/dist/hub/common/index.d.ts +14 -14
- package/dist/hub/common/index.js +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +3 -3
- package/dist/inspection/index.d.ts +1 -1
- package/dist/inspection/index.js +1 -1
- package/dist/node.d.ts +3 -3
- package/dist/node.js +4 -4
- package/dist/web.d.ts +2 -2
- package/dist/web.js +3 -3
- package/package.json +1 -1
- package/dist/chunks/boundedTrafficSubscription-1L592xc7.js.map +0 -1
- package/dist/chunks/linkRpcConnection-CtlQmetO.d.ts.map +0 -1
- package/dist/chunks/src-D3NUIwyo.js.map +0 -1
package/README.md
CHANGED
|
@@ -132,6 +132,65 @@ g.shout({ msg: 'boom' }); // fire-and-forget notif
|
|
|
132
132
|
Pass the wrong shape and TypeScript stops you at compile time; if a bad value reaches the wire
|
|
133
133
|
anyway, the provider rejects it with a JSON-RPC `-32602 invalidParams`.
|
|
134
134
|
|
|
135
|
+
## Typed application errors
|
|
136
|
+
|
|
137
|
+
Checked application errors are opt-in and remain ordinary JSON-RPC errors on the wire. Declare
|
|
138
|
+
stable code/message pairs (and, optionally, a data schema), then attach them to a request:
|
|
139
|
+
|
|
140
|
+
```ts
|
|
141
|
+
import { applicationError } from '@hediet/linkrpc';
|
|
142
|
+
|
|
143
|
+
const notFound = applicationError(404, 'Not found');
|
|
144
|
+
const conflict = applicationError(409, 'Conflict', z.object({ currentVersion: z.number() }));
|
|
145
|
+
|
|
146
|
+
const documents = defineInterface({ id: 'acme.documents' }, {
|
|
147
|
+
read: requestType(z.object({ id: z.string() }), z.string())
|
|
148
|
+
.withErrors([notFound, conflict] as const),
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
connection.register(documents, {
|
|
152
|
+
read: ({ id }) => id === 'missing' ? notFound.create() : load(id),
|
|
153
|
+
});
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
Calls are still real promises: `await client.read(...)` succeeds or rejects exactly as before.
|
|
157
|
+
For explicit handling, methods declaring application errors also expose `result()`:
|
|
158
|
+
|
|
159
|
+
```ts
|
|
160
|
+
const client = connection.get(documents);
|
|
161
|
+
const outcome = await client.read({ id }).result();
|
|
162
|
+
if (!outcome.ok) {
|
|
163
|
+
if (outcome.error.kind === 'application') {
|
|
164
|
+
if (outcome.error.code === 409) {
|
|
165
|
+
console.log(outcome.error.data.currentVersion); // statically typed
|
|
166
|
+
} else {
|
|
167
|
+
console.log('Document not found'); // code 404, no data
|
|
168
|
+
}
|
|
169
|
+
} else if (outcome.error.kind === 'remote') {
|
|
170
|
+
console.error(outcome.error.code, outcome.error.message); // undeclared/malformed peer error
|
|
171
|
+
} else {
|
|
172
|
+
console.error(outcome.error.cause); // local validation or transport failure
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
Promotion to `kind: 'application'` requires an exact code and message match. A descriptor without a
|
|
178
|
+
data schema requires `data` to be absent; one with a schema requires valid data (including explicit
|
|
179
|
+
`null` only when the schema allows it). Unknown or malformed peer errors remain generic remote
|
|
180
|
+
errors. The legacy third `requestType` error-schema argument is still accepted for source
|
|
181
|
+
compatibility, but does not opt a method into checked errors.
|
|
182
|
+
|
|
183
|
+
Codes must be unique within the method, fit a signed 32-bit integer, and avoid
|
|
184
|
+
`-32768..-32000` and LinkRPC's `-32800` cancellation code. Created errors are branded locally
|
|
185
|
+
so an ordinary success object with similar fields is never mistaken for an error. Payloads
|
|
186
|
+
must be JSON values matching the declared wire schema; encoding does not silently strip
|
|
187
|
+
properties, insert defaults, or turn missing data into `null`.
|
|
188
|
+
|
|
189
|
+
Interface JSON includes these declarations and their referenced data schemas in the hash.
|
|
190
|
+
CLI-generated definitions expose the same typed constructors through
|
|
191
|
+
`definition.members.method.errors[index].create(...)`. See the
|
|
192
|
+
[Rust/TypeScript interoperability proof](../../../interop/README.md) for both authoring directions.
|
|
193
|
+
|
|
135
194
|
## Reflection — discoverability built in
|
|
136
195
|
|
|
137
196
|
A connection is self-describing. Call `enableReflection()` and it exposes three standard interfaces
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
*--------------------------------------------------------------------------------------------*/
|
|
5
5
|
import { any, array, boolean, enum as enum$1, int, literal, nonnegative, number, object, optional, record, string, union, unknown, void as void$1 } from "zod/mini";
|
|
6
6
|
import { toJSONSchema } from "zod/v4/core";
|
|
7
|
+
import { z } from "zod";
|
|
7
8
|
//#region src/schema/normalize.ts
|
|
8
9
|
/**
|
|
9
10
|
* Keys that describe structural shape and are part of the decidable subset.
|
|
@@ -153,6 +154,43 @@ function _constPropNames(props) {
|
|
|
153
154
|
}
|
|
154
155
|
//#endregion
|
|
155
156
|
//#region src/schema/memberTypes.ts
|
|
157
|
+
const applicationErrorBrand = Symbol("linkrpc.applicationError");
|
|
158
|
+
function applicationError(code, message, dataSchema) {
|
|
159
|
+
assertApplicationErrorCode(code);
|
|
160
|
+
if (typeof message !== "string") throw new Error("Application error message must be a string.");
|
|
161
|
+
const create = dataSchema === void 0 ? function() {
|
|
162
|
+
if (arguments.length !== 0) throw new Error(`Application error ${code} does not accept data.`);
|
|
163
|
+
return Object.freeze({
|
|
164
|
+
kind: "application",
|
|
165
|
+
code,
|
|
166
|
+
message,
|
|
167
|
+
[applicationErrorBrand]: true
|
|
168
|
+
});
|
|
169
|
+
} : function(data) {
|
|
170
|
+
if (arguments.length !== 1) throw new Error(`Application error ${code} requires exactly one data value.`);
|
|
171
|
+
return Object.freeze({
|
|
172
|
+
kind: "application",
|
|
173
|
+
code,
|
|
174
|
+
message,
|
|
175
|
+
[applicationErrorBrand]: true,
|
|
176
|
+
data
|
|
177
|
+
});
|
|
178
|
+
};
|
|
179
|
+
return Object.freeze({
|
|
180
|
+
code,
|
|
181
|
+
message,
|
|
182
|
+
...dataSchema === void 0 ? {} : { dataSchema },
|
|
183
|
+
create
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
/** Runtime nominal check for values produced by an application-error descriptor. */
|
|
187
|
+
function isApplicationErrorValue(value) {
|
|
188
|
+
return typeof value === "object" && value !== null && value[applicationErrorBrand] === true;
|
|
189
|
+
}
|
|
190
|
+
function assertApplicationErrorCode(code) {
|
|
191
|
+
if (!Number.isInteger(code) || code < -2147483648 || code > 2147483647) throw new Error(`Application error code ${code} must be a signed 32-bit integer.`);
|
|
192
|
+
if (code >= -32768 && code <= -32e3 || code === -32800) throw new Error(`Application error code ${code} is reserved by JSON-RPC or LinkRPC.`);
|
|
193
|
+
}
|
|
156
194
|
var RequestType = class RequestType {
|
|
157
195
|
paramsSchema;
|
|
158
196
|
resultSchema;
|
|
@@ -160,22 +198,45 @@ var RequestType = class RequestType {
|
|
|
160
198
|
docs;
|
|
161
199
|
clientStreamSchema;
|
|
162
200
|
serverStreamSchema;
|
|
201
|
+
applicationErrors;
|
|
163
202
|
kind = "request";
|
|
164
|
-
constructor(paramsSchema, resultSchema, errorSchema, docs = {}, clientStreamSchema, serverStreamSchema) {
|
|
203
|
+
constructor(paramsSchema, resultSchema, errorSchema, docs = {}, clientStreamSchema, serverStreamSchema, applicationErrors = []) {
|
|
165
204
|
this.paramsSchema = paramsSchema;
|
|
166
205
|
this.resultSchema = resultSchema;
|
|
167
206
|
this.errorSchema = errorSchema;
|
|
168
207
|
this.docs = docs;
|
|
169
208
|
this.clientStreamSchema = clientStreamSchema;
|
|
170
209
|
this.serverStreamSchema = serverStreamSchema;
|
|
210
|
+
this.applicationErrors = applicationErrors;
|
|
211
|
+
const seen = /* @__PURE__ */ new Set();
|
|
212
|
+
for (const error of applicationErrors) {
|
|
213
|
+
assertApplicationErrorCode(error.code);
|
|
214
|
+
if (typeof error.message !== "string") throw new Error("Application error message must be a string.");
|
|
215
|
+
if (seen.has(error.code)) throw new Error(`Duplicate application error code ${error.code}.`);
|
|
216
|
+
seen.add(error.code);
|
|
217
|
+
}
|
|
218
|
+
this.applicationErrors = Object.freeze([...applicationErrors]);
|
|
219
|
+
this.errors = this.applicationErrors;
|
|
171
220
|
}
|
|
221
|
+
/** Declared checked errors, retained as a tuple for generated-contract consumers. */
|
|
222
|
+
errors;
|
|
172
223
|
/**
|
|
173
224
|
* Return a copy of this request type with stream payload schemas
|
|
174
225
|
* attached. Pass `undefined` for either direction to leave it
|
|
175
226
|
* closed.
|
|
227
|
+
* `NoInfer` keeps a contextual MemberMap's `any` stream slots from
|
|
228
|
+
* widening undeclared directions; only the supplied schemas infer payloads.
|
|
176
229
|
*/
|
|
177
230
|
withStream(opts) {
|
|
178
|
-
return new RequestType(this.paramsSchema, this.resultSchema, this.errorSchema, this.docs, opts.client, opts.server);
|
|
231
|
+
return new RequestType(this.paramsSchema, this.resultSchema, this.errorSchema, this.docs, opts.client, opts.server, this.applicationErrors);
|
|
232
|
+
}
|
|
233
|
+
/**
|
|
234
|
+
* Return a copy with an explicit, typed set of checked application errors.
|
|
235
|
+
* The legacy third `requestType` schema remains accepted for compatibility,
|
|
236
|
+
* but does not opt a method into checked errors.
|
|
237
|
+
*/
|
|
238
|
+
withErrors(errors) {
|
|
239
|
+
return new RequestType(this.paramsSchema, this.resultSchema, this.errorSchema, this.docs, this.clientStreamSchema, this.serverStreamSchema, errors);
|
|
179
240
|
}
|
|
180
241
|
};
|
|
181
242
|
var NotificationType = class {
|
|
@@ -578,6 +639,181 @@ function stripNonNormative(value, isRoot = false) {
|
|
|
578
639
|
return out;
|
|
579
640
|
}
|
|
580
641
|
//#endregion
|
|
642
|
+
//#region src/schema/assertSchemaReferences.ts
|
|
643
|
+
const componentPrefix = "#/components/schemas/";
|
|
644
|
+
function componentSchemaRef(name) {
|
|
645
|
+
return componentPrefix + name.replace(/~/g, "~0").replace(/\//g, "~1");
|
|
646
|
+
}
|
|
647
|
+
function componentSchemaName(ref) {
|
|
648
|
+
if (!ref.startsWith(componentPrefix)) throw new Error(`Invalid component reference: ${ref}`);
|
|
649
|
+
const token = ref.slice(21);
|
|
650
|
+
if (token.includes("/") || /~(?:[^01]|$)/.test(token)) throw new Error(`Invalid component reference token: ${ref}`);
|
|
651
|
+
return token.replace(/~1/g, "/").replace(/~0/g, "~");
|
|
652
|
+
}
|
|
653
|
+
/**
|
|
654
|
+
* Check the finite schema graph without interpreting JSON literal payloads.
|
|
655
|
+
* A reference/union-only cycle is invalid; descending into an object member
|
|
656
|
+
* or an array item guards a cycle, but does not imply the schema is nonempty.
|
|
657
|
+
*/
|
|
658
|
+
function assertSchemaReferences(roots, components = {}) {
|
|
659
|
+
const nodes = /* @__PURE__ */ new Set();
|
|
660
|
+
const pending = [...roots, ...Object.values(components)];
|
|
661
|
+
const unguarded = /* @__PURE__ */ new Map();
|
|
662
|
+
while (pending.length > 0) {
|
|
663
|
+
const node = pending.pop();
|
|
664
|
+
if (typeof node === "boolean" || nodes.has(node)) continue;
|
|
665
|
+
nodes.add(node);
|
|
666
|
+
const edges = [];
|
|
667
|
+
if ("$ref" in node) {
|
|
668
|
+
const name = componentSchemaName(node.$ref);
|
|
669
|
+
if (!Object.hasOwn(components, name)) throw new Error(`Unresolved component reference: ${node.$ref}`);
|
|
670
|
+
edges.push(components[name]);
|
|
671
|
+
}
|
|
672
|
+
if ("anyOf" in node) edges.push(...node.anyOf);
|
|
673
|
+
if ("oneOf" in node) edges.push(...node.oneOf);
|
|
674
|
+
unguarded.set(node, edges);
|
|
675
|
+
pending.push(...edges);
|
|
676
|
+
if ("type" in node && node.type === "object") pending.push(...Object.values(node.properties ?? {}), node.additionalProperties);
|
|
677
|
+
if ("type" in node && node.type === "array") {
|
|
678
|
+
if ("prefixItems" in node) pending.push(...node.prefixItems);
|
|
679
|
+
if (node.items !== void 0) pending.push(node.items);
|
|
680
|
+
}
|
|
681
|
+
}
|
|
682
|
+
const visiting = /* @__PURE__ */ new Set();
|
|
683
|
+
const checked = /* @__PURE__ */ new Set();
|
|
684
|
+
const visit = (node) => {
|
|
685
|
+
if (typeof node === "boolean" || checked.has(node)) return;
|
|
686
|
+
if (visiting.has(node)) throw new Error("Unguarded recursive schema: cycle does not descend into a child value");
|
|
687
|
+
visiting.add(node);
|
|
688
|
+
for (const child of unguarded.get(node) ?? []) visit(child);
|
|
689
|
+
visiting.delete(node);
|
|
690
|
+
checked.add(node);
|
|
691
|
+
};
|
|
692
|
+
for (const node of nodes) visit(node);
|
|
693
|
+
}
|
|
694
|
+
//#endregion
|
|
695
|
+
//#region src/schema/schemaToZod.ts
|
|
696
|
+
/**
|
|
697
|
+
* Create a recursive LinkRPC-schema validator materializer. Component
|
|
698
|
+
* references are lazy, so self and mutual recursion remain guarded.
|
|
699
|
+
*/
|
|
700
|
+
function createSchemaToZod(components = {}) {
|
|
701
|
+
assertSchemaReferences([], components);
|
|
702
|
+
const refs = /* @__PURE__ */ new Map();
|
|
703
|
+
const component = (name) => {
|
|
704
|
+
const existing = refs.get(name);
|
|
705
|
+
if (existing !== void 0) return existing;
|
|
706
|
+
const source = components[name];
|
|
707
|
+
if (source === void 0) throw new Error(`schemaToZod: dangling component reference "${name}"`);
|
|
708
|
+
const lazy = z.lazy(() => compile(source));
|
|
709
|
+
refs.set(name, lazy);
|
|
710
|
+
return lazy;
|
|
711
|
+
};
|
|
712
|
+
const compile = (schema) => {
|
|
713
|
+
if (schema === true) return z.unknown();
|
|
714
|
+
if (schema === false) return z.never();
|
|
715
|
+
const raw = schema;
|
|
716
|
+
if (Array.isArray(raw.type)) return union$1(raw.type.map((type) => compile({
|
|
717
|
+
...raw,
|
|
718
|
+
type
|
|
719
|
+
})));
|
|
720
|
+
let base;
|
|
721
|
+
if ("$ref" in schema) base = component(componentSchemaName(schema.$ref));
|
|
722
|
+
else if ("const" in schema) base = valueSchema(schema.const);
|
|
723
|
+
else if ("enum" in schema) base = union$1(schema.enum.map(valueSchema));
|
|
724
|
+
else if ("anyOf" in schema) base = union$1(schema.anyOf.map(compile));
|
|
725
|
+
else if ("oneOf" in schema) base = union$1(schema.oneOf.map(compile));
|
|
726
|
+
else if (isAnnotationOnlySchema(raw)) base = z.unknown();
|
|
727
|
+
else switch (schema.type) {
|
|
728
|
+
case "null":
|
|
729
|
+
base = z.null();
|
|
730
|
+
break;
|
|
731
|
+
case "boolean":
|
|
732
|
+
base = z.boolean();
|
|
733
|
+
break;
|
|
734
|
+
case "string":
|
|
735
|
+
base = stringSchema(schema.format);
|
|
736
|
+
break;
|
|
737
|
+
case "integer":
|
|
738
|
+
base = z.number().int();
|
|
739
|
+
break;
|
|
740
|
+
case "number":
|
|
741
|
+
base = z.number();
|
|
742
|
+
break;
|
|
743
|
+
case "array":
|
|
744
|
+
if ("prefixItems" in schema) {
|
|
745
|
+
const tuple = z.tuple(schema.prefixItems.map(compile));
|
|
746
|
+
base = schema.items !== void 0 && schema.items !== false ? tuple.rest(compile(schema.items)) : tuple;
|
|
747
|
+
} else base = z.array(compile(schema.items));
|
|
748
|
+
break;
|
|
749
|
+
case "object": {
|
|
750
|
+
const required = new Set(schema.required ?? []);
|
|
751
|
+
const shape = {};
|
|
752
|
+
for (const [name, property] of Object.entries(schema.properties)) {
|
|
753
|
+
const validator = compile(property);
|
|
754
|
+
shape[name] = required.has(name) ? validator : validator.optional();
|
|
755
|
+
}
|
|
756
|
+
const object = z.object(shape);
|
|
757
|
+
if (schema.additionalProperties === false) base = object.strict();
|
|
758
|
+
else if (schema.additionalProperties === true) base = object.loose();
|
|
759
|
+
else base = object.catchall(compile(schema.additionalProperties));
|
|
760
|
+
break;
|
|
761
|
+
}
|
|
762
|
+
}
|
|
763
|
+
return base;
|
|
764
|
+
};
|
|
765
|
+
return { toZod(schema) {
|
|
766
|
+
assertSchemaReferences([schema], components);
|
|
767
|
+
return compile(schema);
|
|
768
|
+
} };
|
|
769
|
+
}
|
|
770
|
+
function isAnnotationOnlySchema(schema) {
|
|
771
|
+
return ![
|
|
772
|
+
"$ref",
|
|
773
|
+
"const",
|
|
774
|
+
"enum",
|
|
775
|
+
"anyOf",
|
|
776
|
+
"oneOf",
|
|
777
|
+
"type",
|
|
778
|
+
"properties",
|
|
779
|
+
"items",
|
|
780
|
+
"prefixItems",
|
|
781
|
+
"additionalProperties"
|
|
782
|
+
].some((key) => Object.hasOwn(schema, key));
|
|
783
|
+
}
|
|
784
|
+
function schemaToZod(schema, components = {}) {
|
|
785
|
+
return createSchemaToZod(components).toZod(schema);
|
|
786
|
+
}
|
|
787
|
+
function union$1(items) {
|
|
788
|
+
if (items.length === 0) return z.never();
|
|
789
|
+
if (items.length === 1) return items[0];
|
|
790
|
+
return z.union(items);
|
|
791
|
+
}
|
|
792
|
+
function valueSchema(expected) {
|
|
793
|
+
return z.unknown().refine((actual) => jsonValueEqual(actual, expected), { message: `Expected ${JSON.stringify(expected)}` });
|
|
794
|
+
}
|
|
795
|
+
function jsonValueEqual(actual, expected) {
|
|
796
|
+
if (Object.is(actual, expected)) return true;
|
|
797
|
+
if (Array.isArray(actual) && Array.isArray(expected)) return actual.length === expected.length && actual.every((value, index) => jsonValueEqual(value, expected[index]));
|
|
798
|
+
if (actual !== null && expected !== null && typeof actual === "object" && typeof expected === "object" && !Array.isArray(actual) && !Array.isArray(expected)) {
|
|
799
|
+
const actualRecord = actual;
|
|
800
|
+
const expectedRecord = expected;
|
|
801
|
+
const keys = Object.keys(actualRecord);
|
|
802
|
+
return keys.length === Object.keys(expectedRecord).length && keys.every((key) => Object.hasOwn(expectedRecord, key) && jsonValueEqual(actualRecord[key], expectedRecord[key]));
|
|
803
|
+
}
|
|
804
|
+
return false;
|
|
805
|
+
}
|
|
806
|
+
function stringSchema(format) {
|
|
807
|
+
switch (format) {
|
|
808
|
+
case "email": return z.email();
|
|
809
|
+
case "uri":
|
|
810
|
+
case "url": return z.url();
|
|
811
|
+
case "uuid": return z.uuid();
|
|
812
|
+
case "date-time": return z.iso.datetime();
|
|
813
|
+
default: return z.string();
|
|
814
|
+
}
|
|
815
|
+
}
|
|
816
|
+
//#endregion
|
|
581
817
|
//#region src/connection/interfaceDefinition.ts
|
|
582
818
|
const interfaceDefinitionState = /* @__PURE__ */ new WeakMap();
|
|
583
819
|
var InterfaceDefinition = class {
|
|
@@ -588,6 +824,7 @@ var InterfaceDefinition = class {
|
|
|
588
824
|
constructor(info, members, opts = {}) {
|
|
589
825
|
this.info = info;
|
|
590
826
|
this.members = members;
|
|
827
|
+
validateInterfaceErrors(opts.frozenSchema);
|
|
591
828
|
interfaceDefinitionState.set(this, {
|
|
592
829
|
frozenSchema: opts.frozenSchema,
|
|
593
830
|
schemaCache: void 0,
|
|
@@ -639,6 +876,14 @@ function toMethodSchema(name, member, components) {
|
|
|
639
876
|
};
|
|
640
877
|
if (member.clientStreamSchema !== void 0) m.clientStream = convertMemberSchema(name, "clientStream", member.clientStreamSchema, components);
|
|
641
878
|
if (member.serverStreamSchema !== void 0) m.serverStream = convertMemberSchema(name, "serverStream", member.serverStreamSchema, components);
|
|
879
|
+
if (member.applicationErrors.length > 0) m.errors = member.applicationErrors.map((error) => {
|
|
880
|
+
const result = {
|
|
881
|
+
code: error.code,
|
|
882
|
+
message: error.message
|
|
883
|
+
};
|
|
884
|
+
if (error.dataSchema !== void 0) result.data = convertMemberSchema(name, `error=${error.code}`, error.dataSchema, components);
|
|
885
|
+
return result;
|
|
886
|
+
});
|
|
642
887
|
if (docs.description !== void 0) m.description = docs.description;
|
|
643
888
|
if (docs.comment !== void 0) m.comment = docs.comment;
|
|
644
889
|
if (docs.annotations !== void 0) m.annotations = docs.annotations;
|
|
@@ -650,6 +895,20 @@ function toMethodSchema(name, member, components) {
|
|
|
650
895
|
if (docs.annotations !== void 0) m.annotations = docs.annotations;
|
|
651
896
|
return m;
|
|
652
897
|
}
|
|
898
|
+
function validateInterfaceErrors(schema) {
|
|
899
|
+
if (schema === void 0) return;
|
|
900
|
+
for (const [methodName, method] of Object.entries(schema.methods)) {
|
|
901
|
+
if (method.result === void 0 && method.errors !== void 0) throw new Error(`Notification "${methodName}" cannot declare application errors.`);
|
|
902
|
+
const seen = /* @__PURE__ */ new Set();
|
|
903
|
+
for (const error of method.errors ?? []) {
|
|
904
|
+
if (typeof error.message !== "string") throw new Error(`Application error message on "${methodName}" must be a string.`);
|
|
905
|
+
if (!Number.isInteger(error.code) || error.code < -2147483648 || error.code > 2147483647) throw new Error(`Application error code ${error.code} on "${methodName}" must be a signed 32-bit integer.`);
|
|
906
|
+
if (error.code >= -32768 && error.code <= -32e3 || error.code === -32800) throw new Error(`Application error code ${error.code} on "${methodName}" is reserved by JSON-RPC or LinkRPC.`);
|
|
907
|
+
if (seen.has(error.code)) throw new Error(`Duplicate application error code ${error.code} on "${methodName}".`);
|
|
908
|
+
seen.add(error.code);
|
|
909
|
+
}
|
|
910
|
+
}
|
|
911
|
+
}
|
|
653
912
|
function convertMemberSchema(methodName, position, schema, components) {
|
|
654
913
|
return zodToSvcJsonSchema(schema, {
|
|
655
914
|
methodName,
|
|
@@ -686,7 +945,9 @@ function defineInterface(info, members) {
|
|
|
686
945
|
* consumers see the real wire contract. Member-level params / results /
|
|
687
946
|
* streams use `z.any()` because no zod source is available — call-site
|
|
688
947
|
* validation is therefore a no-op and the caller is responsible for
|
|
689
|
-
* shape-checking inputs and outputs.
|
|
948
|
+
* shape-checking inputs and outputs. Declared application-error payloads
|
|
949
|
+
* are materialized and validated because recognizing a typed error must
|
|
950
|
+
* never rely on its code alone.
|
|
690
951
|
*
|
|
691
952
|
* Methods with no `result` descriptor become notifications; methods with
|
|
692
953
|
* `clientStream` / `serverStream` get pass-through stream payload
|
|
@@ -699,7 +960,7 @@ function interfaceFromSchema(schema) {
|
|
|
699
960
|
members[name] = new NotificationType(any());
|
|
700
961
|
continue;
|
|
701
962
|
}
|
|
702
|
-
const base = new RequestType(any(), any(), void$1());
|
|
963
|
+
const base = new RequestType(any(), any(), void$1()).withErrors((method.errors ?? []).map((error) => error.data === void 0 ? applicationError(error.code, error.message) : applicationError(error.code, error.message, schemaToZod(error.data, schema.components?.schemas))));
|
|
703
964
|
members[name] = method.clientStream !== void 0 || method.serverStream !== void 0 ? base.withStream({
|
|
704
965
|
client: method.clientStream !== void 0 ? any() : void 0,
|
|
705
966
|
server: method.serverStream !== void 0 ? any() : void 0
|
|
@@ -1121,6 +1382,6 @@ function capPayload(value, maxBytes) {
|
|
|
1121
1382
|
return preview;
|
|
1122
1383
|
}
|
|
1123
1384
|
//#endregion
|
|
1124
|
-
export {
|
|
1385
|
+
export { EXTENSION_PREFIX as A, requestType as B, defineInterface as C, assertSchemaReferences as D, schemaToZod as E, NotificationType as F, normalizeJsonSchema as H, RequestType as I, applicationError as L, sha256 as M, jcsCanonicalize as N, componentSchemaName as O, jcsCanonicalizeBytes as P, isApplicationErrorValue as R, InterfaceDefinition as S, createSchemaToZod as T, zodToSvcJsonSchema as V, zTrafficEvent as _, topologyInterface as a, zTrafficTransitEvent as b, zParticipantDescriptorSource as c, zTopologyLink as d, zTopologyLinkEndpoint as f, zTopologyTransportInfo as g, zTopologyTransportEndpoint as h, nodeInterface as i, computeInterfaceHash as j, componentSchemaRef as k, zRouteClaim as l, zTopologyPort as m, TrafficFlowFilter as n, trafficInterface as o, zTopologyNode as p, TrafficWatchFlowTracker as r, zParticipantDescriptor as s, BoundedTrafficSubscription as t, zTopologyGraph as u, zTrafficOverflowEvent as v, interfaceFromSchema as w, zTrafficWatchResult as x, zTrafficTransitEndpoint as y, notificationType as z };
|
|
1125
1386
|
|
|
1126
|
-
//# sourceMappingURL=boundedTrafficSubscription-
|
|
1387
|
+
//# sourceMappingURL=boundedTrafficSubscription-XZ1KwXZC.js.map
|