service-bridge 2.0.0-alpha.6 → 2.0.0-alpha.8

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,237 @@
1
+ import {
2
+ computeSerializerHash
3
+ } from "./chunk-UWGAMSLS.js";
4
+
5
+ // src/serde/serializer.ts
6
+ import protobuf2 from "protobufjs";
7
+
8
+ // src/serde/json-schema-serde.ts
9
+ import { readFileSync } from "fs";
10
+ import protobuf from "protobufjs";
11
+ function buildSchemaPairFromJsonFile(spec) {
12
+ let raw;
13
+ try {
14
+ raw = readFileSync(spec.schemaFile, "utf8");
15
+ } catch (err) {
16
+ throw new Error(
17
+ `serde: read schema file ${spec.schemaFile}: ${err.message}`
18
+ );
19
+ }
20
+ let parsed;
21
+ try {
22
+ parsed = JSON.parse(raw);
23
+ } catch (err) {
24
+ throw new Error(
25
+ `serde: parse schema file ${spec.schemaFile}: ${err.message}`
26
+ );
27
+ }
28
+ if (!parsed.input || !parsed.output) {
29
+ throw new Error(
30
+ `serde: schema file ${spec.schemaFile} must have top-level "input" and "output" objects`
31
+ );
32
+ }
33
+ const root = new protobuf.Root();
34
+ const inputType = buildType(root, "Input", firstMessage(parsed.input));
35
+ const outputType = buildType(root, "Output", firstMessage(parsed.output));
36
+ root.add(inputType);
37
+ root.add(outputType);
38
+ return {
39
+ input: typeToSerializer(inputType),
40
+ output: typeToSerializer(outputType)
41
+ };
42
+ }
43
+ function firstMessage(section) {
44
+ const names = Object.keys(section);
45
+ if (names.length !== 1) {
46
+ throw new Error(
47
+ `serde: each of input/output must declare exactly one message, got ${names.length}`
48
+ );
49
+ }
50
+ return section[names[0]];
51
+ }
52
+ function buildType(root, typeName, fields) {
53
+ const type = new protobuf.Type(typeName);
54
+ const seenNumbers = /* @__PURE__ */ new Set();
55
+ for (const [name, field] of Object.entries(fields)) {
56
+ if (typeof field.fieldNumber !== "number") {
57
+ throw new Error(
58
+ `serde: field "${name}" missing required fieldNumber \u2014 schema evolution requires explicit numbers`
59
+ );
60
+ }
61
+ if (seenNumbers.has(field.fieldNumber)) {
62
+ throw new Error(
63
+ `serde: duplicate fieldNumber ${field.fieldNumber} in ${typeName}`
64
+ );
65
+ }
66
+ seenNumbers.add(field.fieldNumber);
67
+ if (field.type === "object") {
68
+ if (!field.nested) {
69
+ throw new Error(
70
+ `serde: field "${name}" type=object requires nested fields`
71
+ );
72
+ }
73
+ const nestedTypeName = `${typeName}_${name}`;
74
+ const nestedType = buildType(root, nestedTypeName, field.nested);
75
+ root.add(nestedType);
76
+ type.add(new protobuf.Field(name, field.fieldNumber, nestedTypeName));
77
+ continue;
78
+ }
79
+ if (field.type === "array") {
80
+ if (!field.array) {
81
+ throw new Error(
82
+ `serde: field "${name}" type=array requires array spec`
83
+ );
84
+ }
85
+ if (field.array.type === "object") {
86
+ if (!field.array.nested) {
87
+ throw new Error(
88
+ `serde: array field "${name}" of objects requires nested fields`
89
+ );
90
+ }
91
+ const nestedTypeName = `${typeName}_${name}_elem`;
92
+ const nestedType = buildType(root, nestedTypeName, field.array.nested);
93
+ root.add(nestedType);
94
+ type.add(
95
+ new protobuf.Field(
96
+ name,
97
+ field.fieldNumber,
98
+ nestedTypeName,
99
+ "repeated"
100
+ )
101
+ );
102
+ } else {
103
+ type.add(
104
+ new protobuf.Field(
105
+ name,
106
+ field.fieldNumber,
107
+ field.array.type,
108
+ "repeated"
109
+ )
110
+ );
111
+ }
112
+ continue;
113
+ }
114
+ type.add(new protobuf.Field(name, field.fieldNumber, field.type));
115
+ }
116
+ return type;
117
+ }
118
+ function typeToSerializer(type) {
119
+ const jsonSchema = type.toJSON();
120
+ const hash = computeSerializerHash(jsonSchema);
121
+ return {
122
+ encode(value) {
123
+ const err = type.verify(value);
124
+ if (err) {
125
+ throw new Error(`serde: encode ${type.name}: ${err}`);
126
+ }
127
+ const msg = type.fromObject(value);
128
+ return type.encode(msg).finish();
129
+ },
130
+ decode(bytes) {
131
+ const msg = type.decode(bytes);
132
+ return type.toObject(msg, { defaults: true });
133
+ },
134
+ contractHash() {
135
+ return hash;
136
+ },
137
+ toJsonSchema() {
138
+ return jsonSchema;
139
+ }
140
+ };
141
+ }
142
+
143
+ // src/serde/serializer.ts
144
+ function isProtoFileSpec(spec) {
145
+ return spec.protoFile !== void 0;
146
+ }
147
+ async function buildSchemaPair(spec) {
148
+ if (isProtoFileSpec(spec)) {
149
+ let root;
150
+ try {
151
+ root = await protobuf2.load(spec.protoFile);
152
+ } catch (err) {
153
+ throw new Error(
154
+ `serde: load proto file ${spec.protoFile}: ${err.message}`
155
+ );
156
+ }
157
+ const { input, output } = resolveProtoMessages(root, spec);
158
+ return {
159
+ input: typeToSerializer2(root, spec.protoFile, input),
160
+ output: typeToSerializer2(root, spec.protoFile, output)
161
+ };
162
+ }
163
+ return buildSchemaPairFromJsonFile(spec);
164
+ }
165
+ function resolveProtoMessages(root, spec) {
166
+ if (spec.input && spec.output) {
167
+ return { input: spec.input, output: spec.output };
168
+ }
169
+ const method = spec.method;
170
+ if (method) {
171
+ const fromService = findInService(root, method);
172
+ if (fromService) return fromService;
173
+ }
174
+ throw new Error(
175
+ `serde: cannot resolve input/output for ${spec.protoFile}` + (method ? ` (method=${method})` : "") + ". Add a `service { rpc <method>(In) returns (Out); }` block or pass `input` and `output` explicitly."
176
+ );
177
+ }
178
+ function findInService(root, method) {
179
+ let found = null;
180
+ walk(root, (obj) => {
181
+ if (!(obj instanceof protobuf2.Service)) return;
182
+ const m = obj.methods[method];
183
+ if (m) {
184
+ found = { input: m.requestType, output: m.responseType };
185
+ }
186
+ });
187
+ return found;
188
+ }
189
+ function walk(obj, fn) {
190
+ fn(obj);
191
+ const nested = obj.nested;
192
+ if (!nested) return;
193
+ for (const child of Object.values(nested)) {
194
+ if (child instanceof protobuf2.Namespace) {
195
+ walk(child, fn);
196
+ } else {
197
+ fn(child);
198
+ }
199
+ }
200
+ }
201
+ function typeToSerializer2(root, file, messageName) {
202
+ let type;
203
+ try {
204
+ type = root.lookupType(messageName);
205
+ } catch (err) {
206
+ throw new Error(
207
+ `serde: message ${messageName} not found in ${file}: ${err.message}`
208
+ );
209
+ }
210
+ const jsonSchema = type.toJSON();
211
+ const hash = computeSerializerHash(jsonSchema);
212
+ return {
213
+ encode(value) {
214
+ const err = type.verify(value);
215
+ if (err) {
216
+ throw new Error(`serde: encode ${messageName}: ${err}`);
217
+ }
218
+ const msg = type.fromObject(value);
219
+ return type.encode(msg).finish();
220
+ },
221
+ decode(bytes) {
222
+ const msg = type.decode(bytes);
223
+ return type.toObject(msg, { defaults: true });
224
+ },
225
+ contractHash() {
226
+ return hash;
227
+ },
228
+ toJsonSchema() {
229
+ return jsonSchema;
230
+ }
231
+ };
232
+ }
233
+
234
+ export {
235
+ buildSchemaPair
236
+ };
237
+ //# sourceMappingURL=chunk-3RW45BQJ.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/serde/serializer.ts","../src/serde/json-schema-serde.ts"],"sourcesContent":["import protobuf from \"protobufjs\";\nimport {\n\tbuildSchemaPairFromJsonFile,\n\ttype JsonSchemaFileSpec,\n} from \"./json-schema-serde\";\n\n// ProtoFileSpec references a user .proto file. `input`/`output` are optional —\n// when omitted, the SDK resolves them from the file's `service` definition by\n// matching the registered method name (gRPC-native, the only supported path).\n// Pass `input`/`output` explicitly for .proto files without a service block.\nexport interface ProtoFileSpec {\n\tprotoFile: string;\n\t// Optional: explicit message names. Required only when the .proto file has\n\t// no matching `service { rpc <method>(In) returns (Out); }` entry.\n\tinput?: string;\n\toutput?: string;\n\t// Optional: method name to resolve against the service definition. Defaults\n\t// to the name passed to `Handle.rpc`/`stream` (set automatically by Handle).\n\tmethod?: string;\n}\n\n// SchemaSpec is the public type for handler/caller schema declaration.\n// Two sources supported: .proto file or .schema.json file (with explicit\n// fieldNumber per property). See ./README.md and ./json-schema-serde.ts.\nexport type SchemaSpec = ProtoFileSpec | JsonSchemaFileSpec;\n\nfunction isProtoFileSpec(spec: SchemaSpec): spec is ProtoFileSpec {\n\treturn (spec as ProtoFileSpec).protoFile !== undefined;\n}\n\n// Serializer encodes/decodes a single Protobuf message type at runtime,\n// produced from a user-supplied .proto file via protobufjs.\nexport interface Serializer {\n\tencode(value: unknown): Uint8Array;\n\tdecode(bytes: Uint8Array): unknown;\n\tcontractHash(): string;\n\ttoJsonSchema(): Record<string, unknown>;\n}\n\nexport interface SchemaPair {\n\tinput: Serializer;\n\toutput: Serializer;\n}\n\n// buildSchemaPair dispatches to the correct loader based on SchemaSpec kind.\n// .proto files load via protobufjs.load (async); .schema.json files via\n// json-schema-serde (sync). Result is always a SchemaPair with stable contract\n// hashes.\nexport async function buildSchemaPair(spec: SchemaSpec): Promise<SchemaPair> {\n\tif (isProtoFileSpec(spec)) {\n\t\tlet root: protobuf.Root;\n\t\ttry {\n\t\t\troot = await protobuf.load(spec.protoFile);\n\t\t} catch (err) {\n\t\t\tthrow new Error(\n\t\t\t\t`serde: load proto file ${spec.protoFile}: ${(err as Error).message}`,\n\t\t\t);\n\t\t}\n\t\tconst { input, output } = resolveProtoMessages(root, spec);\n\t\treturn {\n\t\t\tinput: typeToSerializer(root, spec.protoFile, input),\n\t\t\toutput: typeToSerializer(root, spec.protoFile, output),\n\t\t};\n\t}\n\t// JsonSchemaFileSpec — synchronous file read + dynamic Type build.\n\treturn buildSchemaPairFromJsonFile(spec);\n}\n\n// resolveProtoMessages picks input/output message names from a ProtoFileSpec.\n// Resolution order:\n// 1. Explicit spec.input + spec.output → used as-is.\n// 2. spec.method provided + service { rpc <method>(In) returns (Out); } found\n// → take requestType/responseType from the service definition.\n//\n// Throws with a clear error otherwise — convention-based and unique-pair\n// fallbacks were removed (ADR 0001 / cleanup #32) because they hid contract\n// mistakes behind name similarity instead of failing loudly.\nfunction resolveProtoMessages(\n\troot: protobuf.Root,\n\tspec: ProtoFileSpec,\n): { input: string; output: string } {\n\tif (spec.input && spec.output) {\n\t\treturn { input: spec.input, output: spec.output };\n\t}\n\n\tconst method = spec.method;\n\tif (method) {\n\t\tconst fromService = findInService(root, method);\n\t\tif (fromService) return fromService;\n\t}\n\n\tthrow new Error(\n\t\t`serde: cannot resolve input/output for ${spec.protoFile}` +\n\t\t\t(method ? ` (method=${method})` : \"\") +\n\t\t\t\". Add a `service { rpc <method>(In) returns (Out); }` block or pass `input` and `output` explicitly.\",\n\t);\n}\n\nfunction findInService(\n\troot: protobuf.Root,\n\tmethod: string,\n): { input: string; output: string } | null {\n\tlet found: { input: string; output: string } | null = null;\n\twalk(root, (obj) => {\n\t\tif (!(obj instanceof protobuf.Service)) return;\n\t\tconst m = obj.methods[method];\n\t\tif (m) {\n\t\t\tfound = { input: m.requestType, output: m.responseType };\n\t\t}\n\t});\n\treturn found;\n}\n\nfunction walk(\n\tobj: protobuf.NamespaceBase,\n\tfn: (item: protobuf.ReflectionObject) => void,\n): void {\n\tfn(obj);\n\tconst nested = obj.nested;\n\tif (!nested) return;\n\tfor (const child of Object.values(nested)) {\n\t\tif (child instanceof protobuf.Namespace) {\n\t\t\twalk(child, fn);\n\t\t} else {\n\t\t\tfn(child);\n\t\t}\n\t}\n}\n\nfunction typeToSerializer(\n\troot: protobuf.Root,\n\tfile: string,\n\tmessageName: string,\n): Serializer {\n\tlet type: protobuf.Type;\n\ttry {\n\t\ttype = root.lookupType(messageName);\n\t} catch (err) {\n\t\tthrow new Error(\n\t\t\t`serde: message ${messageName} not found in ${file}: ${(err as Error).message}`,\n\t\t);\n\t}\n\n\tconst jsonSchema = type.toJSON() as unknown as Record<string, unknown>;\n\t// The per-serializer hash here is informational only; the cross-side\n\t// compatibility hash that LB filters on is computeContractHash(pair) from\n\t// ./contract-hash.ts, which combines input + output.\n\tconst hash = perSerializerHash(jsonSchema);\n\n\treturn {\n\t\tencode(value: unknown): Uint8Array {\n\t\t\tconst err = type.verify(value as object);\n\t\t\tif (err) {\n\t\t\t\tthrow new Error(`serde: encode ${messageName}: ${err}`);\n\t\t\t}\n\t\t\tconst msg = type.fromObject(value as object);\n\t\t\treturn type.encode(msg).finish();\n\t\t},\n\t\tdecode(bytes: Uint8Array): unknown {\n\t\t\tconst msg = type.decode(bytes);\n\t\t\treturn type.toObject(msg, { defaults: true });\n\t\t},\n\t\tcontractHash(): string {\n\t\t\treturn hash;\n\t\t},\n\t\ttoJsonSchema(): Record<string, unknown> {\n\t\t\treturn jsonSchema;\n\t\t},\n\t};\n}\n\n// perSerializerHash reuses the shared canonical-JSON algorithm from\n// contract-hash.ts so the per-serializer informational hash and the\n// SchemaPair compatibility hash stay in lockstep.\nimport { computeSerializerHash as perSerializerHash } from \"./contract-hash\";\n","import { readFileSync } from \"node:fs\";\nimport protobuf from \"protobufjs\";\nimport { computeSerializerHash } from \"./contract-hash\";\nimport type { SchemaPair, Serializer } from \"./serializer\";\n\n// JsonSchemaFileSpec references a .schema.json file describing both input and\n// output messages with explicit field numbers per property. Unlike inline JSON\n// schema (removed permanently), this format is safe for schema evolution\n// because field numbers are pinned by the schema author.\nexport interface JsonSchemaFileSpec {\n\tschemaFile: string;\n}\n\n// .schema.json file shape:\n// {\n// \"input\": { \"<MessageName>\": { \"<field>\": { type, fieldNumber }, ... } },\n// \"output\": { \"<MessageName>\": { ... } }\n// }\ninterface SchemaFile {\n\tinput: Record<string, SchemaFields>;\n\toutput: Record<string, SchemaFields>;\n}\n\ntype SchemaFields = Record<string, SchemaField>;\n\ninterface SchemaField {\n\ttype: ProtoFieldType;\n\tfieldNumber?: number;\n\t// For type=\"array\": the element type and its own field number rules.\n\t// The repeated field itself uses the outer `fieldNumber`.\n\tarray?: { type: ProtoFieldType; nested?: SchemaFields };\n\t// For type=\"object\": nested message fields with their own field numbers.\n\tnested?: SchemaFields;\n}\n\ntype ProtoFieldType =\n\t| \"string\"\n\t| \"bool\"\n\t| \"int32\"\n\t| \"int64\"\n\t| \"uint32\"\n\t| \"uint64\"\n\t| \"float\"\n\t| \"double\"\n\t| \"bytes\"\n\t| \"object\"\n\t| \"array\";\n\n// buildSchemaPairFromJsonFile loads the schema file, validates field numbers,\n// constructs protobufjs Types dynamically, and returns a SchemaPair compatible\n// with the rest of the rpc/serde stack.\nexport function buildSchemaPairFromJsonFile(\n\tspec: JsonSchemaFileSpec,\n): SchemaPair {\n\tlet raw: string;\n\ttry {\n\t\traw = readFileSync(spec.schemaFile, \"utf8\");\n\t} catch (err) {\n\t\tthrow new Error(\n\t\t\t`serde: read schema file ${spec.schemaFile}: ${(err as Error).message}`,\n\t\t);\n\t}\n\n\tlet parsed: SchemaFile;\n\ttry {\n\t\tparsed = JSON.parse(raw) as SchemaFile;\n\t} catch (err) {\n\t\tthrow new Error(\n\t\t\t`serde: parse schema file ${spec.schemaFile}: ${(err as Error).message}`,\n\t\t);\n\t}\n\n\tif (!parsed.input || !parsed.output) {\n\t\tthrow new Error(\n\t\t\t`serde: schema file ${spec.schemaFile} must have top-level \"input\" and \"output\" objects`,\n\t\t);\n\t}\n\n\tconst root = new protobuf.Root();\n\tconst inputType = buildType(root, \"Input\", firstMessage(parsed.input));\n\tconst outputType = buildType(root, \"Output\", firstMessage(parsed.output));\n\troot.add(inputType);\n\troot.add(outputType);\n\n\treturn {\n\t\tinput: typeToSerializer(inputType),\n\t\toutput: typeToSerializer(outputType),\n\t};\n}\n\n// firstMessage extracts the single message definition from { \"<MessageName>\": fields }.\n// Throws if the section is empty or has more than one entry — exactly one\n// message per input/output section is supported.\nfunction firstMessage(section: Record<string, SchemaFields>): SchemaFields {\n\tconst names = Object.keys(section);\n\tif (names.length !== 1) {\n\t\tthrow new Error(\n\t\t\t`serde: each of input/output must declare exactly one message, got ${names.length}`,\n\t\t);\n\t}\n\treturn section[names[0]!]!;\n}\n\n// buildType recursively constructs a protobufjs.Type from a SchemaFields tree.\nfunction buildType(\n\troot: protobuf.Root,\n\ttypeName: string,\n\tfields: SchemaFields,\n): protobuf.Type {\n\tconst type = new protobuf.Type(typeName);\n\tconst seenNumbers = new Set<number>();\n\n\tfor (const [name, field] of Object.entries(fields)) {\n\t\tif (typeof field.fieldNumber !== \"number\") {\n\t\t\tthrow new Error(\n\t\t\t\t`serde: field \"${name}\" missing required fieldNumber — schema evolution requires explicit numbers`,\n\t\t\t);\n\t\t}\n\t\tif (seenNumbers.has(field.fieldNumber)) {\n\t\t\tthrow new Error(\n\t\t\t\t`serde: duplicate fieldNumber ${field.fieldNumber} in ${typeName}`,\n\t\t\t);\n\t\t}\n\t\tseenNumbers.add(field.fieldNumber);\n\n\t\tif (field.type === \"object\") {\n\t\t\tif (!field.nested) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`serde: field \"${name}\" type=object requires nested fields`,\n\t\t\t\t);\n\t\t\t}\n\t\t\tconst nestedTypeName = `${typeName}_${name}`;\n\t\t\tconst nestedType = buildType(root, nestedTypeName, field.nested);\n\t\t\troot.add(nestedType);\n\t\t\ttype.add(new protobuf.Field(name, field.fieldNumber, nestedTypeName));\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (field.type === \"array\") {\n\t\t\tif (!field.array) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`serde: field \"${name}\" type=array requires array spec`,\n\t\t\t\t);\n\t\t\t}\n\t\t\tif (field.array.type === \"object\") {\n\t\t\t\tif (!field.array.nested) {\n\t\t\t\t\tthrow new Error(\n\t\t\t\t\t\t`serde: array field \"${name}\" of objects requires nested fields`,\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\tconst nestedTypeName = `${typeName}_${name}_elem`;\n\t\t\t\tconst nestedType = buildType(root, nestedTypeName, field.array.nested);\n\t\t\t\troot.add(nestedType);\n\t\t\t\ttype.add(\n\t\t\t\t\tnew protobuf.Field(\n\t\t\t\t\t\tname,\n\t\t\t\t\t\tfield.fieldNumber,\n\t\t\t\t\t\tnestedTypeName,\n\t\t\t\t\t\t\"repeated\",\n\t\t\t\t\t),\n\t\t\t\t);\n\t\t\t} else {\n\t\t\t\ttype.add(\n\t\t\t\t\tnew protobuf.Field(\n\t\t\t\t\t\tname,\n\t\t\t\t\t\tfield.fieldNumber,\n\t\t\t\t\t\tfield.array.type,\n\t\t\t\t\t\t\"repeated\",\n\t\t\t\t\t),\n\t\t\t\t);\n\t\t\t}\n\t\t\tcontinue;\n\t\t}\n\n\t\t// Scalar.\n\t\ttype.add(new protobuf.Field(name, field.fieldNumber, field.type));\n\t}\n\n\treturn type;\n}\n\nfunction typeToSerializer(type: protobuf.Type): Serializer {\n\tconst jsonSchema = type.toJSON() as unknown as Record<string, unknown>;\n\t// Per-serializer hash is informational only — LB uses the SchemaPair hash\n\t// from contract-hash.ts:computeContractHash.\n\tconst hash = computeSerializerHash(jsonSchema);\n\treturn {\n\t\tencode(value: unknown): Uint8Array {\n\t\t\tconst err = type.verify(value as object);\n\t\t\tif (err) {\n\t\t\t\tthrow new Error(`serde: encode ${type.name}: ${err}`);\n\t\t\t}\n\t\t\tconst msg = type.fromObject(value as object);\n\t\t\treturn type.encode(msg).finish();\n\t\t},\n\t\tdecode(bytes: Uint8Array): unknown {\n\t\t\tconst msg = type.decode(bytes);\n\t\t\treturn type.toObject(msg, { defaults: true });\n\t\t},\n\t\tcontractHash(): string {\n\t\t\treturn hash;\n\t\t},\n\t\ttoJsonSchema(): Record<string, unknown> {\n\t\t\treturn jsonSchema;\n\t\t},\n\t};\n}\n"],"mappings":"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","names":["protobuf","protobuf","typeToSerializer"]}
@@ -0,0 +1,53 @@
1
+ import {
2
+ mintRootContext,
3
+ parseXSbTrace
4
+ } from "./chunk-BNKAGKEP.js";
5
+
6
+ // src/http/_common/body-capture.ts
7
+ var RAW_JSON_CONTRACT = "raw/json";
8
+ var encoder = new TextEncoder();
9
+ function bodyToBytes(body) {
10
+ if (body === void 0 || body === null) return null;
11
+ if (body instanceof Uint8Array) return body.byteLength ? body : null;
12
+ if (typeof body === "string") {
13
+ const s = body.trim();
14
+ if (!s || s === "{}" || s === "null") return null;
15
+ return encoder.encode(body);
16
+ }
17
+ try {
18
+ const json = JSON.stringify(body);
19
+ if (!json || json === "{}" || json === "null") return null;
20
+ return encoder.encode(json);
21
+ } catch {
22
+ return null;
23
+ }
24
+ }
25
+
26
+ // src/http/_common/trace-wrap.ts
27
+ function contextFromXSbTrace(header) {
28
+ if (header == null) return mintRootContext();
29
+ const parsed = parseXSbTrace(header);
30
+ if (parsed == null) return mintRootContext();
31
+ return parsed;
32
+ }
33
+
34
+ // src/http/endpoint.ts
35
+ var _warned = false;
36
+ function resolveHttpAdvertiseHost(explicit) {
37
+ if (explicit && explicit.length > 0) return explicit;
38
+ if (!_warned) {
39
+ _warned = true;
40
+ console.warn(
41
+ "[ServiceBridge] http advertise host not configured \u2014 falling back to 127.0.0.1. Pass { host } to the HTTP plugin for cross-host reachability."
42
+ );
43
+ }
44
+ return "127.0.0.1";
45
+ }
46
+
47
+ export {
48
+ RAW_JSON_CONTRACT,
49
+ bodyToBytes,
50
+ contextFromXSbTrace,
51
+ resolveHttpAdvertiseHost
52
+ };
53
+ //# sourceMappingURL=chunk-7BOF2Y52.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/http/_common/body-capture.ts","../src/http/_common/trace-wrap.ts","../src/http/endpoint.ts"],"sourcesContent":["// body-capture.ts — serialize an HTTP request/response body into the raw-JSON\n// payload bytes the runtime stores verbatim (mirrors runtime\n// telemetry.ContractRawJSON). HTTP bodies have no proto schema, so they are\n// captured as-is and rendered without proto decoding.\n// @internal — см. ../README.md\n\n/** Contract-hash marker for already-JSON payloads. Must equal the runtime's\n * telemetry.ContractRawJSON so DecodePayload returns the bytes verbatim. */\nexport const RAW_JSON_CONTRACT = \"raw/json\";\n\nconst encoder = new TextEncoder();\n\n/**\n * Best-effort serialize an HTTP body to bytes for payload capture. Returns\n * null when there is nothing worth capturing (empty / `{}` / unserializable),\n * so bodyless requests (e.g. GET) don't emit empty Input payloads.\n */\nexport function bodyToBytes(body: unknown): Uint8Array | null {\n\tif (body === undefined || body === null) return null;\n\tif (body instanceof Uint8Array) return body.byteLength ? body : null;\n\tif (typeof body === \"string\") {\n\t\tconst s = body.trim();\n\t\tif (!s || s === \"{}\" || s === \"null\") return null;\n\t\treturn encoder.encode(body);\n\t}\n\ttry {\n\t\tconst json = JSON.stringify(body);\n\t\tif (!json || json === \"{}\" || json === \"null\") return null;\n\t\treturn encoder.encode(json);\n\t} catch {\n\t\treturn null;\n\t}\n}\n","// trace-wrap.ts — общий helper для HTTP plugins.\n// Парсит X-SB-Trace header в TraceContext; при отсутствии/невалидном — мин’ит новый root.\n// @internal — см. ../README.md.\n\nimport {\n\tmintRootContext,\n\ttype TraceContext,\n} from \"../../telemetry/trace-context\";\nimport { parseXSbTrace } from \"../../telemetry/wire-trace\";\n\n/**\n * Парсит входящий X-SB-Trace header. Возвращает propagated context или\n * свежий root, если header отсутствует/невалиден. Никогда не throw'ит.\n */\nexport function contextFromXSbTrace(\n\theader: string | null | undefined,\n): TraceContext {\n\tif (header == null) return mintRootContext();\n\tconst parsed = parseXSbTrace(header);\n\tif (parsed == null) return mintRootContext();\n\treturn parsed;\n}\n","/**\n * Resolve the host that an SDK instance advertises as its public HTTP server\n * (ADR 0001). Mirrors `resolveAdvertise` semantics in src/connection, but for\n * the HTTP plane:\n *\n * 1. explicit `host` argument → used as-is\n * 2. fallback \"127.0.0.1\" → одноразовый console.warn\n *\n * @internal — потребляется только интеграциями.\n */\n\nlet _warned = false;\n\n/** Сбрасывает warn-флаг (для тестов). */\nexport function _resetHostWarn(): void {\n\t_warned = false;\n}\n\nexport function resolveHttpAdvertiseHost(explicit?: string): string {\n\tif (explicit && explicit.length > 0) return explicit;\n\tif (!_warned) {\n\t\t_warned = true;\n\t\tconsole.warn(\n\t\t\t\"[ServiceBridge] http advertise host not configured — falling back to 127.0.0.1. \" +\n\t\t\t\t\"Pass { host } to the HTTP plugin for cross-host reachability.\",\n\t\t);\n\t}\n\treturn \"127.0.0.1\";\n}\n"],"mappings":";;;;;;AAQO,IAAM,oBAAoB;AAEjC,IAAM,UAAU,IAAI,YAAY;AAOzB,SAAS,YAAY,MAAkC;AAC7D,MAAI,SAAS,UAAa,SAAS,KAAM,QAAO;AAChD,MAAI,gBAAgB,WAAY,QAAO,KAAK,aAAa,OAAO;AAChE,MAAI,OAAO,SAAS,UAAU;AAC7B,UAAM,IAAI,KAAK,KAAK;AACpB,QAAI,CAAC,KAAK,MAAM,QAAQ,MAAM,OAAQ,QAAO;AAC7C,WAAO,QAAQ,OAAO,IAAI;AAAA,EAC3B;AACA,MAAI;AACH,UAAM,OAAO,KAAK,UAAU,IAAI;AAChC,QAAI,CAAC,QAAQ,SAAS,QAAQ,SAAS,OAAQ,QAAO;AACtD,WAAO,QAAQ,OAAO,IAAI;AAAA,EAC3B,QAAQ;AACP,WAAO;AAAA,EACR;AACD;;;AClBO,SAAS,oBACf,QACe;AACf,MAAI,UAAU,KAAM,QAAO,gBAAgB;AAC3C,QAAM,SAAS,cAAc,MAAM;AACnC,MAAI,UAAU,KAAM,QAAO,gBAAgB;AAC3C,SAAO;AACR;;;ACVA,IAAI,UAAU;AAOP,SAAS,yBAAyB,UAA2B;AACnE,MAAI,YAAY,SAAS,SAAS,EAAG,QAAO;AAC5C,MAAI,CAAC,SAAS;AACb,cAAU;AACV,YAAQ;AAAA,MACP;AAAA,IAED;AAAA,EACD;AACA,SAAO;AACR;","names":[]}