@webda/grpc 4.0.0-beta.3
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/.webda/cache +4 -0
- package/CHANGELOG.md +54 -0
- package/LICENSE +166 -0
- package/README.md +100 -0
- package/lib/grpc-stream.d.ts +109 -0
- package/lib/grpc-stream.d.ts.map +1 -0
- package/lib/grpc-stream.js +162 -0
- package/lib/grpc-stream.js.map +1 -0
- package/lib/grpcservice.service.d.ts +112 -0
- package/lib/grpcservice.service.d.ts.map +1 -0
- package/lib/grpcservice.service.js +459 -0
- package/lib/grpcservice.service.js.map +1 -0
- package/lib/index.d.ts +5 -0
- package/lib/index.d.ts.map +1 -0
- package/lib/index.js +4 -0
- package/lib/index.js.map +1 -0
- package/lib/proto-generator.d.ts +14 -0
- package/lib/proto-generator.d.ts.map +1 -0
- package/lib/proto-generator.js +312 -0
- package/lib/proto-generator.js.map +1 -0
- package/package.json +42 -0
- package/src/build-integration.spec.ts +187 -0
- package/src/grpc-stream.spec.ts +417 -0
- package/src/grpc-stream.ts +192 -0
- package/src/grpcservice.service.ts +462 -0
- package/src/grpcservice.spec.ts +1092 -0
- package/src/index.ts +4 -0
- package/src/proto-generator.spec.ts +696 -0
- package/src/proto-generator.ts +338 -0
- package/tsconfig.json +33 -0
- package/vitest.config.ts +20 -0
- package/webda.module.json +111 -0
|
@@ -0,0 +1,312 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generate a .proto file from registered operations and their schemas.
|
|
3
|
+
*
|
|
4
|
+
* Groups operations by prefix (e.g., "Post.Create" → service PostService)
|
|
5
|
+
* and converts JSON Schema input/output types to protobuf messages.
|
|
6
|
+
* @param operations - map of operation IDs to their definitions
|
|
7
|
+
* @param schemas - all known JSON schemas keyed by schema reference name
|
|
8
|
+
* @param packageName - protobuf package name to use in the generated file
|
|
9
|
+
* @returns the complete .proto file content as a string
|
|
10
|
+
*/
|
|
11
|
+
export function generateProto(operations, schemas, packageName = "webda") {
|
|
12
|
+
const lines = [];
|
|
13
|
+
const messages = new Map(); // messageName → proto definition
|
|
14
|
+
const fieldCounter = 0;
|
|
15
|
+
lines.push('syntax = "proto3";');
|
|
16
|
+
lines.push("");
|
|
17
|
+
lines.push(`package ${packageName};`);
|
|
18
|
+
lines.push("");
|
|
19
|
+
lines.push('import "google/protobuf/empty.proto";');
|
|
20
|
+
lines.push('import "google/protobuf/struct.proto";');
|
|
21
|
+
lines.push("");
|
|
22
|
+
// Group operations by service prefix
|
|
23
|
+
const serviceGroups = new Map();
|
|
24
|
+
for (const [opId, op] of Object.entries(operations)) {
|
|
25
|
+
if (op.hidden)
|
|
26
|
+
continue;
|
|
27
|
+
const dotIdx = opId.indexOf(".");
|
|
28
|
+
const prefix = dotIdx > 0 ? opId.substring(0, dotIdx) : "Default";
|
|
29
|
+
// proto3 RPC method names must match `[A-Za-z_][A-Za-z0-9_]*` — dots are
|
|
30
|
+
// namespace separators and rejected by the parser. Behavior operations
|
|
31
|
+
// are namespaced as `<Model>.<Attribute>.<Action>` (e.g.
|
|
32
|
+
// `Post.MainImage.Delete`), so the trailing portion still carries a
|
|
33
|
+
// dot. Flatten to underscore so the parser accepts the rpc name.
|
|
34
|
+
const rpcName = (dotIdx > 0 ? opId.substring(dotIdx + 1) : opId).replace(/\./g, "_");
|
|
35
|
+
if (!serviceGroups.has(prefix)) {
|
|
36
|
+
serviceGroups.set(prefix, []);
|
|
37
|
+
}
|
|
38
|
+
serviceGroups.get(prefix).push({ opId, op, rpcName });
|
|
39
|
+
}
|
|
40
|
+
// Webda operation inputs ending in `?` are the "ignoreRequired" variant of
|
|
41
|
+
// the base schema; strip the suffix when looking the schema up since only
|
|
42
|
+
// the base key exists in the registry.
|
|
43
|
+
const resolveSchemaRef = (ref) => {
|
|
44
|
+
if (!ref || ref === "void")
|
|
45
|
+
return undefined;
|
|
46
|
+
if (schemas[ref])
|
|
47
|
+
return ref;
|
|
48
|
+
if (ref.endsWith("?") && schemas[ref.slice(0, -1)])
|
|
49
|
+
return ref.slice(0, -1);
|
|
50
|
+
return undefined;
|
|
51
|
+
};
|
|
52
|
+
// Hoist every operation schema's local `definitions` block into the flat
|
|
53
|
+
// schema registry so $refs like `#/definitions/BinaryMap<T>` (which the
|
|
54
|
+
// schema generator emits next to its hoisted definitions block) resolve
|
|
55
|
+
// to a real shape instead of falling back to `google.protobuf.Struct`.
|
|
56
|
+
// The compiler's `normalizeSchemaDefinitions` already lifted these to the
|
|
57
|
+
// schema root; we just need to fold them into the flat registry the proto
|
|
58
|
+
// walker consults.
|
|
59
|
+
const enrichedSchemas = { ...schemas };
|
|
60
|
+
for (const op of Object.values(operations)) {
|
|
61
|
+
if (op.hidden)
|
|
62
|
+
continue;
|
|
63
|
+
for (const ref of [op.input, op.output]) {
|
|
64
|
+
if (!ref || ref === "void")
|
|
65
|
+
continue;
|
|
66
|
+
const baseRef = ref.endsWith("?") ? ref.slice(0, -1) : ref;
|
|
67
|
+
const target = enrichedSchemas[baseRef];
|
|
68
|
+
const localDefs = target?.definitions;
|
|
69
|
+
if (!localDefs)
|
|
70
|
+
continue;
|
|
71
|
+
for (const [k, v] of Object.entries(localDefs)) {
|
|
72
|
+
if (!(k in enrichedSchemas)) {
|
|
73
|
+
enrichedSchemas[k] = v;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
// Generate messages for all referenced schemas
|
|
79
|
+
for (const [opId, op] of Object.entries(operations)) {
|
|
80
|
+
if (op.hidden)
|
|
81
|
+
continue;
|
|
82
|
+
const inputRef = resolveSchemaRef(op.input);
|
|
83
|
+
if (inputRef) {
|
|
84
|
+
const msgName = schemaToMessageName(inputRef);
|
|
85
|
+
if (!messages.has(msgName)) {
|
|
86
|
+
messages.set(msgName, jsonSchemaToMessage(msgName, enrichedSchemas[inputRef], enrichedSchemas, messages));
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
const outputRef = resolveSchemaRef(op.output);
|
|
90
|
+
if (outputRef) {
|
|
91
|
+
const msgName = schemaToMessageName(outputRef);
|
|
92
|
+
if (!messages.has(msgName)) {
|
|
93
|
+
messages.set(msgName, jsonSchemaToMessage(msgName, enrichedSchemas[outputRef], enrichedSchemas, messages));
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
// Write all messages
|
|
98
|
+
for (const [, msgDef] of messages) {
|
|
99
|
+
lines.push(msgDef);
|
|
100
|
+
lines.push("");
|
|
101
|
+
}
|
|
102
|
+
// Write services
|
|
103
|
+
for (const [prefix, ops] of serviceGroups) {
|
|
104
|
+
lines.push(`service ${prefix}Service {`);
|
|
105
|
+
for (const { opId, op, rpcName } of ops) {
|
|
106
|
+
const inputType = getInputType(op, schemas, messages);
|
|
107
|
+
const outputType = getOutputType(op, schemas, messages);
|
|
108
|
+
const streaming = op.grpc?.streaming || "none";
|
|
109
|
+
const inputMod = streaming === "client" || streaming === "bidi" ? "stream " : "";
|
|
110
|
+
const outputMod = streaming === "server" || streaming === "bidi" ? "stream " : "";
|
|
111
|
+
if (op.summary) {
|
|
112
|
+
lines.push(` // ${op.summary}`);
|
|
113
|
+
}
|
|
114
|
+
lines.push(` rpc ${rpcName}(${inputMod}${inputType}) returns (${outputMod}${outputType});`);
|
|
115
|
+
}
|
|
116
|
+
lines.push("}");
|
|
117
|
+
lines.push("");
|
|
118
|
+
}
|
|
119
|
+
return lines.join("\n");
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Convert a schema reference name to a protobuf message name.
|
|
123
|
+
* "WebdaSample/Post" → "Post", "uuidRequest" → "UuidRequest"
|
|
124
|
+
* @param schemaRef - the schema reference string, e.g. "MyApp/Post" or "uuidRequest"
|
|
125
|
+
* @returns the PascalCase protobuf message name derived from the reference
|
|
126
|
+
*/
|
|
127
|
+
function schemaToMessageName(schemaRef) {
|
|
128
|
+
// JSON-Pointer escapes (`%3C%7B%7D%3E`) survive into `$ref` strings when the
|
|
129
|
+
// schema generator names a definition like `BinaryFileInfo<{}>`. Decode
|
|
130
|
+
// them first so we can strip the generic-bracket noise rather than emitting
|
|
131
|
+
// `BinaryFileInfo%3C%7B%7D%3E` as a proto identifier (which the parser
|
|
132
|
+
// rejects as an illegal token).
|
|
133
|
+
let decoded;
|
|
134
|
+
try {
|
|
135
|
+
decoded = decodeURIComponent(schemaRef);
|
|
136
|
+
}
|
|
137
|
+
catch {
|
|
138
|
+
decoded = schemaRef;
|
|
139
|
+
}
|
|
140
|
+
const tail = (decoded.includes("/") ? decoded.split("/").pop() : decoded) ?? decoded;
|
|
141
|
+
// Drop generic args (`<...>`) entirely — proto3 has no generic concept and
|
|
142
|
+
// the unresolved `T` made the generated schema collapse them to `<{}>`.
|
|
143
|
+
const stripped = tail.replace(/<[^>]*>/g, "");
|
|
144
|
+
// Sanitize anything left that proto3 doesn't accept in an identifier.
|
|
145
|
+
// proto3 idents match `[A-Za-z_][A-Za-z0-9_]*`; splitting on every other
|
|
146
|
+
// char and PascalCasing keeps the original word boundaries readable.
|
|
147
|
+
const cleaned = stripped
|
|
148
|
+
.split(/[^A-Za-z0-9]+/)
|
|
149
|
+
.filter(Boolean)
|
|
150
|
+
.map(s => s.charAt(0).toUpperCase() + s.slice(1))
|
|
151
|
+
.join("") || "Empty";
|
|
152
|
+
// proto3 identifiers must start with a letter or underscore; underscore
|
|
153
|
+
// prefix the cleaned name when it begins with a digit (e.g. `1Metadata`
|
|
154
|
+
// from a `&1$metadata` schema-generator artefact for unbound generics).
|
|
155
|
+
return /^[A-Za-z_]/.test(cleaned) ? cleaned : `_${cleaned}`;
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* Get the protobuf input type for an operation.
|
|
159
|
+
* Merges parameters + input into a single request message if both exist.
|
|
160
|
+
* @param op - the operation definition containing optional input and parameters schema references
|
|
161
|
+
* @param schemas - all known JSON schemas keyed by schema reference name
|
|
162
|
+
* @param messages - accumulator map of already-generated protobuf message definitions
|
|
163
|
+
* @returns the protobuf type name to use as the RPC input type
|
|
164
|
+
*/
|
|
165
|
+
function getInputType(op, schemas, messages) {
|
|
166
|
+
const ref = op.input && op.input !== "void"
|
|
167
|
+
? (schemas[op.input]
|
|
168
|
+
? op.input
|
|
169
|
+
: op.input.endsWith("?") && schemas[op.input.slice(0, -1)]
|
|
170
|
+
? op.input.slice(0, -1)
|
|
171
|
+
: undefined)
|
|
172
|
+
: undefined;
|
|
173
|
+
if (ref) {
|
|
174
|
+
return schemaToMessageName(ref);
|
|
175
|
+
}
|
|
176
|
+
return "google.protobuf.Empty";
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* Get the protobuf output type for an operation.
|
|
180
|
+
* @param op - the operation definition containing an optional output schema reference
|
|
181
|
+
* @param schemas - all known JSON schemas keyed by schema reference name
|
|
182
|
+
* @param messages - accumulator map of already-generated protobuf message definitions
|
|
183
|
+
* @returns the protobuf type name to use as the RPC output type
|
|
184
|
+
*/
|
|
185
|
+
function getOutputType(op, schemas, messages) {
|
|
186
|
+
if (op.output && op.output !== "void" && schemas[op.output]) {
|
|
187
|
+
return schemaToMessageName(op.output);
|
|
188
|
+
}
|
|
189
|
+
return "google.protobuf.Empty";
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* Convert a JSON Schema object to a protobuf message definition.
|
|
193
|
+
* @param name - the protobuf message name to assign to this definition
|
|
194
|
+
* @param schema - the JSON Schema object describing the message fields
|
|
195
|
+
* @param allSchemas - all known JSON schemas, used to resolve nested references
|
|
196
|
+
* @param messages - accumulator map updated with any nested message definitions generated
|
|
197
|
+
* @returns the protobuf message block as a string
|
|
198
|
+
*/
|
|
199
|
+
function jsonSchemaToMessage(name, schema, allSchemas, messages) {
|
|
200
|
+
const lines = [];
|
|
201
|
+
lines.push(`message ${name} {`);
|
|
202
|
+
if (schema.properties) {
|
|
203
|
+
// JSON-Schema `required` list drives proto3 `optional` marker so that
|
|
204
|
+
// fields the caller didn't set aren't sent as empty-string defaults and
|
|
205
|
+
// trip pattern/format/minLength validations server-side.
|
|
206
|
+
const required = new Set(schema.required || []);
|
|
207
|
+
let fieldNum = 1;
|
|
208
|
+
for (const [propName, propSchema] of Object.entries(schema.properties)) {
|
|
209
|
+
const prop = propSchema;
|
|
210
|
+
const protoType = jsonTypeToProto(propName, prop, name, allSchemas, messages);
|
|
211
|
+
const fieldType = prop.type === "array" ? jsonTypeToProto(propName, prop.items || {}, name, allSchemas, messages) : protoType;
|
|
212
|
+
if (prop.type === "array") {
|
|
213
|
+
lines.push(` repeated ${fieldType} ${propName} = ${fieldNum};`);
|
|
214
|
+
}
|
|
215
|
+
else if (required.has(propName)) {
|
|
216
|
+
lines.push(` ${protoType} ${propName} = ${fieldNum};`);
|
|
217
|
+
}
|
|
218
|
+
else {
|
|
219
|
+
lines.push(` optional ${protoType} ${propName} = ${fieldNum};`);
|
|
220
|
+
}
|
|
221
|
+
fieldNum++;
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
lines.push("}");
|
|
225
|
+
return lines.join("\n");
|
|
226
|
+
}
|
|
227
|
+
/**
|
|
228
|
+
* Map a JSON Schema type to a protobuf type.
|
|
229
|
+
* @param fieldName - the property name in the parent schema, used to derive nested message names
|
|
230
|
+
* @param schema - the JSON Schema describing this field's type
|
|
231
|
+
* @param parentName - the name of the enclosing protobuf message, used for nested message naming
|
|
232
|
+
* @param allSchemas - all known JSON schemas, used to resolve nested references
|
|
233
|
+
* @param messages - accumulator map updated with any nested message definitions generated
|
|
234
|
+
* @returns the protobuf scalar or message type name for this field
|
|
235
|
+
*/
|
|
236
|
+
function jsonTypeToProto(fieldName, schema, parentName, allSchemas, messages) {
|
|
237
|
+
if (schema.$ref) {
|
|
238
|
+
// Distinguish two ref shapes:
|
|
239
|
+
// - `#/components/schemas/<name>` points at a top-level schema in the
|
|
240
|
+
// OpenAPI components registry. The original behaviour was to emit
|
|
241
|
+
// the bare message name and trust that some other schema source
|
|
242
|
+
// defines it; preserve that to keep `componentsSchemaRef` passing.
|
|
243
|
+
// - `#/definitions/<name>` is the typescript-json-schema shape: refs
|
|
244
|
+
// are local to the same schema's `definitions` block. The compiler
|
|
245
|
+
// already hoists those into `allSchemas` for us. If the lookup
|
|
246
|
+
// misses, we have a genuinely-unresolvable inline ref (e.g. a
|
|
247
|
+
// dropped placeholder from a generic-class method) — fall back to
|
|
248
|
+
// `google.protobuf.Struct` so proto-loader doesn't reject the
|
|
249
|
+
// output.
|
|
250
|
+
const isComponentsRef = schema.$ref.startsWith("#/components/schemas/");
|
|
251
|
+
const rawRefName = schema.$ref.replace("#/definitions/", "").replace("#/components/schemas/", "");
|
|
252
|
+
let decodedRefName;
|
|
253
|
+
try {
|
|
254
|
+
decodedRefName = decodeURIComponent(rawRefName);
|
|
255
|
+
}
|
|
256
|
+
catch {
|
|
257
|
+
decodedRefName = rawRefName;
|
|
258
|
+
}
|
|
259
|
+
const msgName = schemaToMessageName(rawRefName);
|
|
260
|
+
const targetSchema = allSchemas[rawRefName] || allSchemas[decodedRefName];
|
|
261
|
+
if (!messages.has(msgName) && targetSchema) {
|
|
262
|
+
messages.set(msgName, ""); // placeholder to break recursion cycles
|
|
263
|
+
messages.set(msgName, jsonSchemaToMessage(msgName, targetSchema, allSchemas, messages));
|
|
264
|
+
}
|
|
265
|
+
if (!isComponentsRef && !targetSchema && !messages.has(msgName)) {
|
|
266
|
+
return "google.protobuf.Struct";
|
|
267
|
+
}
|
|
268
|
+
return msgName;
|
|
269
|
+
}
|
|
270
|
+
switch (schema.type) {
|
|
271
|
+
case "string":
|
|
272
|
+
if (schema.format === "date-time" || schema.format === "date")
|
|
273
|
+
return "string"; // Timestamps as ISO strings
|
|
274
|
+
if (schema.format === "int64")
|
|
275
|
+
return "int64";
|
|
276
|
+
return "string";
|
|
277
|
+
case "number":
|
|
278
|
+
if (schema.format === "float")
|
|
279
|
+
return "float";
|
|
280
|
+
if (schema.format === "int32" || schema.format === "integer")
|
|
281
|
+
return "int32";
|
|
282
|
+
if (schema.format === "int64")
|
|
283
|
+
return "int64";
|
|
284
|
+
return "double";
|
|
285
|
+
case "integer":
|
|
286
|
+
if (schema.format === "int64")
|
|
287
|
+
return "int64";
|
|
288
|
+
return "int32";
|
|
289
|
+
case "boolean":
|
|
290
|
+
return "bool";
|
|
291
|
+
case "object":
|
|
292
|
+
if (schema.properties) {
|
|
293
|
+
// Nested message
|
|
294
|
+
const nestedName = `${parentName}${fieldName.charAt(0).toUpperCase() + fieldName.slice(1)}`;
|
|
295
|
+
if (!messages.has(nestedName)) {
|
|
296
|
+
messages.set(nestedName, jsonSchemaToMessage(nestedName, schema, allSchemas, messages));
|
|
297
|
+
}
|
|
298
|
+
return nestedName;
|
|
299
|
+
}
|
|
300
|
+
return "google.protobuf.Struct";
|
|
301
|
+
case "array":
|
|
302
|
+
// Handled by caller
|
|
303
|
+
return jsonTypeToProto(fieldName, schema.items || {}, parentName, allSchemas, messages);
|
|
304
|
+
default:
|
|
305
|
+
return "google.protobuf.Struct";
|
|
306
|
+
}
|
|
307
|
+
// Enum support
|
|
308
|
+
if (schema.enum) {
|
|
309
|
+
return "string"; // Map enums to strings for simplicity
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
//# sourceMappingURL=proto-generator.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"proto-generator.js","sourceRoot":"","sources":["../src/proto-generator.ts"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@webda/grpc",
|
|
3
|
+
"version": "4.0.0-beta.3",
|
|
4
|
+
"description": "gRPC transport for Webda.io — exposes operations as gRPC services",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "lib/index.js",
|
|
7
|
+
"types": "lib/index.d.ts",
|
|
8
|
+
"dependencies": {
|
|
9
|
+
"@grpc/proto-loader": "^0.7.0",
|
|
10
|
+
"@webda/workout": "4.0.0-beta.3"
|
|
11
|
+
},
|
|
12
|
+
"peerDependencies": {
|
|
13
|
+
"@webda/core": "^4.0.0-beta.1"
|
|
14
|
+
},
|
|
15
|
+
"devDependencies": {
|
|
16
|
+
"@vitest/coverage-v8": "^4.1.2",
|
|
17
|
+
"vite": "^6.0.0",
|
|
18
|
+
"vitest": "^4.0.0",
|
|
19
|
+
"@webda/compiler": "^4.0.0-beta.3",
|
|
20
|
+
"@webda/core": "^4.0.0-beta.3",
|
|
21
|
+
"@webda/test": "^4.0.0-beta.3",
|
|
22
|
+
"@webda/utils": "^4.0.0-beta.3"
|
|
23
|
+
},
|
|
24
|
+
"webda": {
|
|
25
|
+
"namespace": "Webda",
|
|
26
|
+
"capabilities": {
|
|
27
|
+
"grpc": "Webda/GrpcService"
|
|
28
|
+
}
|
|
29
|
+
},
|
|
30
|
+
"license": "LGPL-3.0-only",
|
|
31
|
+
"repository": {
|
|
32
|
+
"type": "git",
|
|
33
|
+
"url": "https://github.com/loopingz/webda.io.git",
|
|
34
|
+
"directory": "packages/grpc"
|
|
35
|
+
},
|
|
36
|
+
"scripts": {
|
|
37
|
+
"build": "webdac build",
|
|
38
|
+
"test": "vitest run",
|
|
39
|
+
"lint": "eslint src/",
|
|
40
|
+
"lint:fix": "eslint --fix src/"
|
|
41
|
+
}
|
|
42
|
+
}
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
import { suite, test } from "@webda/test";
|
|
2
|
+
import * as assert from "assert";
|
|
3
|
+
import { execSync } from "node:child_process";
|
|
4
|
+
import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
5
|
+
import { tmpdir } from "node:os";
|
|
6
|
+
import { dirname, join, resolve } from "node:path";
|
|
7
|
+
import { fileURLToPath } from "node:url";
|
|
8
|
+
import { shouldDispatchBuildHooks } from "@webda/compiler/lib/shell-build-dispatch.js";
|
|
9
|
+
import { listConfiguredServiceTypes } from "@webda/utils";
|
|
10
|
+
|
|
11
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
12
|
+
const __dirname = dirname(__filename);
|
|
13
|
+
|
|
14
|
+
// Resolve key fixtures from the repo layout. These paths are stable:
|
|
15
|
+
// - packages/grpc/webda.module.json is produced by `webdac build` against this package
|
|
16
|
+
// - sample-apps/blog-system is a tracked fixture that configures Webda/GrpcService
|
|
17
|
+
const GRPC_PKG_ROOT = resolve(__dirname, "..");
|
|
18
|
+
const GRPC_MODULE_JSON = join(GRPC_PKG_ROOT, "webda.module.json");
|
|
19
|
+
const BLOG_SYSTEM_DIR = resolve(GRPC_PKG_ROOT, "..", "..", "sample-apps", "blog-system");
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* End-to-end-ish checks for the build-hook dispatch flow:
|
|
23
|
+
*
|
|
24
|
+
* webdac build
|
|
25
|
+
* → reads webda.module.json
|
|
26
|
+
* → reads webda.config.json
|
|
27
|
+
* → shouldDispatchBuildHooks(module, configuredTypes) === true
|
|
28
|
+
* → spawns `webda build`
|
|
29
|
+
* → GrpcService.build() writes .webda/app.proto
|
|
30
|
+
*
|
|
31
|
+
* The decision-helpers (listConfiguredServiceTypes, shouldDispatchBuildHooks) each
|
|
32
|
+
* have focused unit tests in their home packages. The tests below verify their
|
|
33
|
+
* *composition* against the real grpc webda.module.json — so a refactor that
|
|
34
|
+
* breaks either piece for this specific user journey fails here.
|
|
35
|
+
*/
|
|
36
|
+
@suite
|
|
37
|
+
class GrpcBuildDispatchIntegrationTest {
|
|
38
|
+
private tmpDir!: string;
|
|
39
|
+
|
|
40
|
+
beforeEach() {
|
|
41
|
+
this.tmpDir = join(tmpdir(), `grpc-build-int-${Date.now()}-${Math.random().toString(36).slice(2)}`);
|
|
42
|
+
mkdirSync(this.tmpDir, { recursive: true });
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
afterEach() {
|
|
46
|
+
try {
|
|
47
|
+
rmSync(this.tmpDir, { recursive: true, force: true });
|
|
48
|
+
} catch {
|
|
49
|
+
// Best-effort cleanup.
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Write a minimal webda.config.json and return its absolute path.
|
|
55
|
+
*/
|
|
56
|
+
private writeConfig(services: Record<string, { type: string }>): string {
|
|
57
|
+
const configPath = join(this.tmpDir, "webda.config.json");
|
|
58
|
+
writeFileSync(configPath, JSON.stringify({ services }, null, 2));
|
|
59
|
+
return configPath;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
@test
|
|
63
|
+
grpcModuleJsonDeclaresBuildCommandOnGrpcService() {
|
|
64
|
+
// Sanity check: Task 12 adopted @BuildCommand on GrpcService. If someone
|
|
65
|
+
// removes the decorator the whole dispatch chain silently breaks — catch
|
|
66
|
+
// that regression explicitly.
|
|
67
|
+
assert.ok(existsSync(GRPC_MODULE_JSON), `grpc webda.module.json missing at ${GRPC_MODULE_JSON}`);
|
|
68
|
+
const mod = JSON.parse(readFileSync(GRPC_MODULE_JSON, "utf-8"));
|
|
69
|
+
const entry = mod?.moddas?.["Webda/GrpcService"];
|
|
70
|
+
assert.ok(entry, "Webda/GrpcService missing from moddas");
|
|
71
|
+
assert.ok(entry?.commands?.build, "Webda/GrpcService is missing a build command — did the @BuildCommand decorator get removed?");
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
@test
|
|
75
|
+
dispatchesWhenConfigFullyQualifiesGrpcService() {
|
|
76
|
+
const configPath = this.writeConfig({
|
|
77
|
+
GRPCService: { type: "Webda/GrpcService" }
|
|
78
|
+
});
|
|
79
|
+
const mod = JSON.parse(readFileSync(GRPC_MODULE_JSON, "utf-8"));
|
|
80
|
+
|
|
81
|
+
const configured = listConfiguredServiceTypes(configPath, "Webda");
|
|
82
|
+
assert.ok(configured.includes("Webda/GrpcService"), `Expected Webda/GrpcService in configured types, got: ${configured.join(", ")}`);
|
|
83
|
+
|
|
84
|
+
assert.strictEqual(
|
|
85
|
+
shouldDispatchBuildHooks(mod, configured),
|
|
86
|
+
true,
|
|
87
|
+
"webdac build should dispatch when GrpcService is configured"
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
@test
|
|
92
|
+
dispatchesWhenConfigUsesUnqualifiedGrpcServiceAndMatchingNamespace() {
|
|
93
|
+
// Users often drop the namespace when the package namespace matches.
|
|
94
|
+
// listConfiguredServiceTypes prefixes unqualified names with the project
|
|
95
|
+
// namespace — so "GrpcService" in a "Webda"-namespace app should still
|
|
96
|
+
// trigger dispatch.
|
|
97
|
+
const configPath = this.writeConfig({
|
|
98
|
+
GRPCService: { type: "GrpcService" }
|
|
99
|
+
});
|
|
100
|
+
const mod = JSON.parse(readFileSync(GRPC_MODULE_JSON, "utf-8"));
|
|
101
|
+
|
|
102
|
+
const configured = listConfiguredServiceTypes(configPath, "Webda");
|
|
103
|
+
assert.deepStrictEqual(configured, ["Webda/GrpcService"]);
|
|
104
|
+
assert.strictEqual(shouldDispatchBuildHooks(mod, configured), true);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
@test
|
|
108
|
+
doesNotDispatchWhenGrpcServiceIsNotConfigured() {
|
|
109
|
+
const configPath = this.writeConfig({
|
|
110
|
+
Store: { type: "Webda/MemoryStore" }
|
|
111
|
+
});
|
|
112
|
+
const mod = JSON.parse(readFileSync(GRPC_MODULE_JSON, "utf-8"));
|
|
113
|
+
|
|
114
|
+
const configured = listConfiguredServiceTypes(configPath, "Webda");
|
|
115
|
+
assert.ok(!configured.includes("Webda/GrpcService"));
|
|
116
|
+
assert.strictEqual(
|
|
117
|
+
shouldDispatchBuildHooks(mod, configured),
|
|
118
|
+
false,
|
|
119
|
+
"Dispatch should be skipped when no configured service owns a build hook"
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
@test
|
|
124
|
+
doesNotDispatchOnEmptyConfig() {
|
|
125
|
+
const configPath = this.writeConfig({});
|
|
126
|
+
const mod = JSON.parse(readFileSync(GRPC_MODULE_JSON, "utf-8"));
|
|
127
|
+
|
|
128
|
+
const configured = listConfiguredServiceTypes(configPath, "Webda");
|
|
129
|
+
assert.deepStrictEqual(configured, []);
|
|
130
|
+
assert.strictEqual(shouldDispatchBuildHooks(mod, configured), false);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Full end-to-end: run the `webda build` binary against the blog-system
|
|
135
|
+
* fixture (which configures Webda/GrpcService) and assert that the proto
|
|
136
|
+
* file lands at the configured path.
|
|
137
|
+
*
|
|
138
|
+
* This is the only test here that spawns a subprocess. It relies on:
|
|
139
|
+
* - pnpm having linked the `webda` bin into blog-system's node_modules
|
|
140
|
+
* - blog-system's checked-in webda.module.json being up-to-date enough
|
|
141
|
+
* for `webda build` to enter the resolved phase
|
|
142
|
+
*
|
|
143
|
+
* If either precondition fails we skip rather than assert-fail, so that
|
|
144
|
+
* a partial workspace install can still run the rest of the grpc tests.
|
|
145
|
+
*/
|
|
146
|
+
@test
|
|
147
|
+
webdaBuildOnBlogSystemFixtureProducesProtoFile() {
|
|
148
|
+
if (!existsSync(BLOG_SYSTEM_DIR)) {
|
|
149
|
+
// Fixture not checked out — nothing to exercise.
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
const webdaBin = join(BLOG_SYSTEM_DIR, "node_modules", ".bin", "webda");
|
|
153
|
+
const blogModuleJson = join(BLOG_SYSTEM_DIR, "webda.module.json");
|
|
154
|
+
if (!existsSync(webdaBin) || !existsSync(blogModuleJson)) {
|
|
155
|
+
// Workspace not installed — cannot spawn `webda`. Skip cleanly.
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const protoPath = join(BLOG_SYSTEM_DIR, ".webda", "app.proto");
|
|
160
|
+
// Remove any prior artifact so we know this run produced the file.
|
|
161
|
+
if (existsSync(protoPath)) {
|
|
162
|
+
rmSync(protoPath);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
try {
|
|
166
|
+
execSync(`"${webdaBin}" build`, {
|
|
167
|
+
cwd: BLOG_SYSTEM_DIR,
|
|
168
|
+
stdio: "pipe",
|
|
169
|
+
timeout: 60_000
|
|
170
|
+
});
|
|
171
|
+
} catch (err: any) {
|
|
172
|
+
// `webda build` is supposed to succeed on the fixture. Surface the
|
|
173
|
+
// stderr to make diagnosis easier when it doesn't.
|
|
174
|
+
const stderr = err?.stderr?.toString?.() ?? "";
|
|
175
|
+
const stdout = err?.stdout?.toString?.() ?? "";
|
|
176
|
+
throw new Error(`webda build failed on blog-system fixture:\n stdout: ${stdout}\n stderr: ${stderr}`);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
assert.ok(existsSync(protoPath), `Expected ${protoPath} to be produced by 'webda build'`);
|
|
180
|
+
const proto = readFileSync(protoPath, "utf-8");
|
|
181
|
+
assert.ok(proto.includes('syntax = "proto3"'), "Output should be a proto3 file");
|
|
182
|
+
assert.ok(proto.includes("package webda;"), "Should use the default webda package name");
|
|
183
|
+
// Sanity: blog-system exposes operations over several prefixes (Post, User, Tag, …).
|
|
184
|
+
// At least one service block should appear.
|
|
185
|
+
assert.ok(/service \w+Service \{/.test(proto), "Should declare at least one proto service");
|
|
186
|
+
}
|
|
187
|
+
}
|