@bielu/scalar-grpc 0.1.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.
@@ -0,0 +1,19 @@
1
+ import type { SecuritySchemeModel } from './types';
2
+ /** Store Scalar's PluginAuthState (no-op on older Scalar versions that predate the API). */
3
+ export declare function setAuthState(auth: unknown): void;
4
+ /** The captured PluginAuthState, as an opaque handle for `resolveGrpcAuth`. */
5
+ export declare function getAuthState(): unknown;
6
+ export type ResolvedAuth = {
7
+ /** Metadata (HTTP headers) to send with each gRPC-Web call. */
8
+ headers: Record<string, string>;
9
+ /** Non-fatal warnings about scheme constraints (e.g. query-based keys gRPC-Web cannot send). */
10
+ warnings: string[];
11
+ };
12
+ /**
13
+ * Resolve Scalar's selected auth for the given document and map the secrets to gRPC-Web call
14
+ * metadata. gRPC-Web metadata is plain HTTP headers, so header API keys, bearer tokens and HTTP
15
+ * Basic all map directly; query-based API keys have no place on a gRPC-Web request and only
16
+ * produce a warning. Returns an empty result when auth state is absent (stock Scalar or no scheme
17
+ * selected), so callers need no guard.
18
+ */
19
+ export declare function resolveGrpcAuth(documentName: string, securitySchemes: Record<string, SecuritySchemeModel> | undefined, auth: unknown): ResolvedAuth;
@@ -0,0 +1,7 @@
1
+ import type { GrpcDocumentRef } from '../types';
2
+ type __VLS_Props = {
3
+ options?: Record<string, any>;
4
+ documents?: GrpcDocumentRef[];
5
+ };
6
+ declare const _default: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
7
+ export default _default;
@@ -0,0 +1,13 @@
1
+ import type { GrpcDocumentRef } from './types';
2
+ /** How the gRPC bundle discovers its AsyncAPI documents (see `resolveDocuments` in @bielu/scalar-core). */
3
+ export declare const GRPC_DISCOVERY: {
4
+ readonly configKey: "grpc";
5
+ readonly globalName: "__BIELU_SCALAR_GRPC__";
6
+ };
7
+ /**
8
+ * Resolves the documents to scan, in decreasing priority: an explicit override (`documents` prop);
9
+ * a `GrpcPluginConfig` embedded inline on the Scalar config as `config.grpc`; a
10
+ * `window.__BIELU_SCALAR_GRPC__` global; otherwise auto-discovery from the Scalar configuration
11
+ * passed as `options`.
12
+ */
13
+ export declare function resolveDocuments(options: Record<string, any> | undefined, override?: GrpcDocumentRef[]): GrpcDocumentRef[];
@@ -0,0 +1,11 @@
1
+ import type { GrpcDocumentRef, GrpcServiceModel } from './types';
2
+ type AnyRecord = Record<string, any>;
3
+ /** Extract every gRPC service (channel + its operations) from a parsed AsyncAPI document. */
4
+ export declare function parseGrpcServices(documentName: string, doc: AnyRecord): GrpcServiceModel[];
5
+ /**
6
+ * Resolve the configured documents (inline object or URL) and aggregate the gRPC services they
7
+ * declare. Only documents that are actually AsyncAPI are considered, so OpenAPI sources in the
8
+ * same Scalar configuration are ignored.
9
+ */
10
+ export declare function loadGrpcServices(documents: GrpcDocumentRef[]): Promise<GrpcServiceModel[]>;
11
+ export {};
@@ -0,0 +1,39 @@
1
+ import type { DescMethod, DescService, FileRegistry, JsonValue } from '@bufbuild/protobuf';
2
+ /** The default base path `MapScalarGrpcAssets()` serves the bundle and descriptors from. */
3
+ export declare const DEFAULT_ASSETS_PATH = "/bielu/scalar/grpc";
4
+ /**
5
+ * Capture the bundle's own script URL. Must be called at module-evaluation time, while
6
+ * `document.currentScript` still points at the executing `<script>` tag.
7
+ */
8
+ export declare function captureBundleScriptSrc(): void;
9
+ /**
10
+ * The URL of the protobuf descriptor endpoint for a target base URL.
11
+ *
12
+ * When the bundle was served by `MapScalarGrpcAssets()` from the same origin as the target, the
13
+ * descriptors live next to plugin.js (whatever custom path it was mapped under). When the bundle
14
+ * came from elsewhere (e.g. a CDN in the Aspire setup), fall back to the default assets path on
15
+ * the target origin.
16
+ */
17
+ export declare function descriptorsUrlFor(baseUrl: string): string;
18
+ /** Load (and cache) the protobuf descriptor registry served by the .NET package. */
19
+ export declare function loadDescriptorRegistry(url: string): Promise<FileRegistry>;
20
+ /** Resolve a service and RPC method from the descriptor registry, with actionable errors. */
21
+ export declare function resolveMethod(registry: FileRegistry, serviceName: string, methodName: string): {
22
+ service: DescService;
23
+ method: DescMethod;
24
+ };
25
+ export type GrpcCallOptions = {
26
+ /** The target origin (scheme://host) the gRPC-Web calls are sent to. */
27
+ baseUrl: string;
28
+ /** Call metadata — gRPC-Web metadata is plain HTTP headers. */
29
+ headers?: Record<string, string>;
30
+ /** Call deadline in milliseconds. */
31
+ timeoutMs?: number;
32
+ };
33
+ /** Invoke a unary RPC with a proto3-JSON request; returns the response as proto3 JSON. */
34
+ export declare function invokeUnary(registry: FileRegistry, serviceName: string, methodName: string, requestJson: JsonValue, options: GrpcCallOptions): Promise<JsonValue>;
35
+ /**
36
+ * Invoke a server-streaming RPC with a proto3-JSON request. Each response message is passed to
37
+ * `onMessage` as proto3 JSON; resolves with the total message count once the stream completes.
38
+ */
39
+ export declare function invokeServerStreaming(registry: FileRegistry, serviceName: string, methodName: string, requestJson: JsonValue, options: GrpcCallOptions, onMessage: (message: JsonValue) => void): Promise<number>;
@@ -0,0 +1,4 @@
1
+ export { createGrpcPlugin } from './plugin';
2
+ export { loadGrpcServices, parseGrpcServices } from './grpc-bindings';
3
+ export { descriptorsUrlFor, loadDescriptorRegistry } from './grpc-client';
4
+ export type { GrpcDocumentRef, GrpcMethodModel, GrpcMethodTypeName, GrpcPluginConfig, GrpcServiceModel, } from './types';
@@ -0,0 +1,32 @@
1
+ import type { GrpcDocumentRef } from './types';
2
+ /** The custom element tag the console is rendered as (registered in `index.ts`). */
3
+ export declare const GRPC_CONSOLE_TAG = "bielu-grpc-console";
4
+ /** The Scalar plugin's identifier, used to detect (and de-duplicate) an existing registration. */
5
+ export declare const GRPC_PLUGIN_NAME = "bielu-grpc";
6
+ /** How the gRPC console registers with Scalar (plugin name, element tag, sidebar label). */
7
+ export declare const GRPC_PLUGIN_SPEC: {
8
+ readonly pluginName: "bielu-grpc";
9
+ readonly elementTag: "bielu-grpc-console";
10
+ readonly sidebarLabel: "gRPC";
11
+ };
12
+ /**
13
+ * The structural shape of a Scalar API Reference plugin. Declared locally (a structural copy of
14
+ * @bielu/scalar-core's type) so the published declaration files never reference the private core
15
+ * package; Scalar validates it at runtime.
16
+ */
17
+ export type ApiReferencePlugin = () => {
18
+ name: string;
19
+ extensions: unknown[];
20
+ views?: Record<string, unknown[]>;
21
+ hooks?: Record<string, unknown>;
22
+ apiClientPlugins?: unknown[];
23
+ };
24
+ /**
25
+ * Creates the gRPC console plugin for the Scalar API Reference.
26
+ *
27
+ * The view's `component` is the custom element tag name (a string), so Scalar's own Vue renders a
28
+ * plain element — our Web Component (with its own Vue + styles) takes over from there. The AsyncAPI
29
+ * documents (discovered from the Scalar configuration at registration time) are passed to the
30
+ * element as the `documents` prop, so the console does not depend on Scalar binding its config.
31
+ */
32
+ export declare const createGrpcPlugin: (documents?: GrpcDocumentRef[]) => ApiReferencePlugin;
@@ -0,0 +1,78 @@
1
+ /** The four kinds of RPC a gRPC method can be (camelCase wire tokens from the `grpc` binding). */
2
+ export type GrpcMethodTypeName = 'unary' | 'serverStreaming' | 'clientStreaming' | 'bidirectionalStreaming';
3
+ /** A reference to an AsyncAPI document — either by URL or as an already-parsed object. */
4
+ export type GrpcDocumentRef = {
5
+ /** Logical document name (matches the AsyncAPI/source title). */
6
+ name: string;
7
+ /** URL the AsyncAPI JSON document is served from. */
8
+ url?: string;
9
+ /** An inline, already-parsed document object (from a Scalar source's `content`). */
10
+ doc?: Record<string, any>;
11
+ };
12
+ /** Configuration consumed by the gRPC console plugin. */
13
+ export type GrpcPluginConfig = {
14
+ /**
15
+ * AsyncAPI documents to scan for gRPC bindings. Normally derived automatically from the
16
+ * Scalar configuration's `sources`; only set explicitly to override that discovery.
17
+ */
18
+ documents: GrpcDocumentRef[];
19
+ };
20
+ /** A message an RPC method can carry, with a generated example payload. */
21
+ export type GrpcMessageModel = {
22
+ /** Message name/key (e.g. `helloRequest`). */
23
+ name: string;
24
+ /** Human-friendly title, when the document provides one. */
25
+ title?: string;
26
+ /**
27
+ * A ready-to-send example of the request message, as a pretty-printed JSON object string
28
+ * (proto3 JSON mapping), generated from the message payload schema.
29
+ */
30
+ example: string;
31
+ };
32
+ /** A single RPC method discovered from an AsyncAPI operation with a `grpc` binding. */
33
+ export type GrpcMethodModel = {
34
+ /** AsyncAPI operation id. */
35
+ id: string;
36
+ /** The RPC method name actually used on the wire, e.g. `SayHello`. */
37
+ method: string;
38
+ /** The kind of RPC. Missing method types are treated as unary. */
39
+ methodType?: GrpcMethodTypeName;
40
+ /** The fully-qualified protobuf type of the request message, e.g. `greet.HelloRequest`. */
41
+ requestType?: string;
42
+ /** The fully-qualified protobuf type of the response message, e.g. `greet.HelloReply`. */
43
+ responseType?: string;
44
+ /** The declared idempotency level (e.g. `noSideEffects`). */
45
+ idempotencyLevel?: string;
46
+ /** The call deadline in seconds, when the operation declares one. */
47
+ deadlineSeconds?: number;
48
+ summary?: string;
49
+ /** Messages this operation carries, each with an example payload to prefill the editor. */
50
+ messages: GrpcMessageModel[];
51
+ };
52
+ /** A minimal representation of an AsyncAPI security scheme, extracted at document-parse time. */
53
+ export type SecuritySchemeModel = {
54
+ /** e.g. `'apiKey'`, `'http'`, `'oauth2'`, `'openIdConnect'`. */
55
+ type: string;
56
+ /** For `apiKey`: `'query'`, `'header'`, or `'cookie'`. */
57
+ in?: string;
58
+ /** For `apiKey`: the query-param or header field name. */
59
+ name?: string;
60
+ /** For `http`: `'bearer'`, `'basic'`, etc. */
61
+ scheme?: string;
62
+ };
63
+ /** A gRPC service discovered from an AsyncAPI channel with a `grpc` binding. */
64
+ export type GrpcServiceModel = {
65
+ documentName: string;
66
+ channelName: string;
67
+ /** The fully-qualified service name, e.g. `greet.Greeter`. */
68
+ service: string;
69
+ /** The protobuf package the service belongs to, e.g. `greet`. */
70
+ package?: string;
71
+ /** Path or URL to the `.proto` file that defines the service. */
72
+ protoFile?: string;
73
+ /** Best-effort absolute base URL (scheme://host) resolved from the AsyncAPI servers. */
74
+ baseUrl: string;
75
+ methods: GrpcMethodModel[];
76
+ /** Security schemes declared in this service's AsyncAPI document, keyed by scheme name. */
77
+ securitySchemes?: Record<string, SecuritySchemeModel>;
78
+ };
package/package.json ADDED
@@ -0,0 +1,57 @@
1
+ {
2
+ "name": "@bielu/scalar-grpc",
3
+ "version": "0.1.0",
4
+ "description": "Interactive gRPC console plugin for the Scalar API Reference. Reads gRPC AsyncAPI bindings and lets you invoke unary and server-streaming methods over gRPC-Web.",
5
+ "license": "MIT",
6
+ "author": "Arkadiusz Biel",
7
+ "type": "module",
8
+ "main": "./dist/plugin.js",
9
+ "module": "./dist/plugin.js",
10
+ "types": "./dist/types/index.d.ts",
11
+ "files": [
12
+ "dist",
13
+ "src",
14
+ "README.md"
15
+ ],
16
+ "exports": {
17
+ ".": {
18
+ "types": "./dist/types/index.d.ts",
19
+ "default": "./dist/plugin.js"
20
+ }
21
+ },
22
+ "keywords": [
23
+ "scalar",
24
+ "grpc",
25
+ "grpc-web",
26
+ "asyncapi",
27
+ "api-reference",
28
+ "plugin"
29
+ ],
30
+ "repository": {
31
+ "type": "git",
32
+ "url": "https://github.com/bielu/Bielu.AspNetCore.AsyncApi"
33
+ },
34
+ "scripts": {
35
+ "build:core": "cd ../../Bielu.AspNetCore.AsyncApi.Scalar/assets && npm install && npm run build",
36
+ "prebuild": "npm run build:core",
37
+ "build": "vite build && npm run build:standalone && vue-tsc -p tsconfig.json",
38
+ "build:standalone": "node ../../Bielu.AspNetCore.AsyncApi.Scalar/assets/scripts/build-standalone.mjs",
39
+ "pretypecheck": "npm run build:core",
40
+ "typecheck": "vue-tsc --noEmit",
41
+ "dev": "vite build --watch"
42
+ },
43
+ "dependencies": {
44
+ "@bufbuild/protobuf": "^2.2.0",
45
+ "@connectrpc/connect": "^2.0.0",
46
+ "@connectrpc/connect-web": "^2.0.0",
47
+ "vue": "^3.5.13"
48
+ },
49
+ "devDependencies": {
50
+ "@bielu/scalar-core": "file:../../Bielu.AspNetCore.AsyncApi.Scalar/assets",
51
+ "@scalar/api-reference": "1.62.9",
52
+ "@vitejs/plugin-vue": "^5.2.1",
53
+ "typescript": "^5.6.0",
54
+ "vite": "^6.4.3",
55
+ "vue-tsc": "^2.1.10"
56
+ }
57
+ }
package/src/auth.ts ADDED
@@ -0,0 +1,93 @@
1
+ import {
2
+ getAuthState as coreGetAuthState,
3
+ resolveSelectedSchemes,
4
+ setAuthState as coreSetAuthState,
5
+ } from '@bielu/scalar-core'
6
+ import type { SecuritySchemeModel } from './types'
7
+
8
+ // Thin wrappers around @bielu/scalar-core's auth-state store. Typed as `unknown` so the published
9
+ // declaration files never reference the private core package (see the core README).
10
+
11
+ /** Store Scalar's PluginAuthState (no-op on older Scalar versions that predate the API). */
12
+ export function setAuthState(auth: unknown): void {
13
+ coreSetAuthState(auth)
14
+ }
15
+
16
+ /** The captured PluginAuthState, as an opaque handle for `resolveGrpcAuth`. */
17
+ export function getAuthState(): unknown {
18
+ return coreGetAuthState()
19
+ }
20
+
21
+ export type ResolvedAuth = {
22
+ /** Metadata (HTTP headers) to send with each gRPC-Web call. */
23
+ headers: Record<string, string>
24
+ /** Non-fatal warnings about scheme constraints (e.g. query-based keys gRPC-Web cannot send). */
25
+ warnings: string[]
26
+ }
27
+
28
+ /**
29
+ * Resolve Scalar's selected auth for the given document and map the secrets to gRPC-Web call
30
+ * metadata. gRPC-Web metadata is plain HTTP headers, so header API keys, bearer tokens and HTTP
31
+ * Basic all map directly; query-based API keys have no place on a gRPC-Web request and only
32
+ * produce a warning. Returns an empty result when auth state is absent (stock Scalar or no scheme
33
+ * selected), so callers need no guard.
34
+ */
35
+ export function resolveGrpcAuth(
36
+ documentName: string,
37
+ securitySchemes: Record<string, SecuritySchemeModel> | undefined,
38
+ auth: unknown,
39
+ ): ResolvedAuth {
40
+ const result: ResolvedAuth = { headers: {}, warnings: [] }
41
+
42
+ for (const { schemeName, type, scheme: schemeDef, secrets } of resolveSelectedSchemes(
43
+ documentName,
44
+ securitySchemes,
45
+ auth,
46
+ )) {
47
+ const token = secrets['x-scalar-secret-token']
48
+
49
+ switch (type) {
50
+ case 'apiKey':
51
+ case 'httpApiKey': {
52
+ const location = (schemeDef.in ?? 'header').toLowerCase()
53
+ const paramName = schemeDef.name ?? 'api_key'
54
+ if (location === 'cookie') {
55
+ result.warnings.push(
56
+ `API key scheme "${schemeName}" targets cookie "${paramName}", which the browser ` +
57
+ `attaches automatically — not adding a header. Ensure the cookie is set for the ` +
58
+ `target origin.`,
59
+ )
60
+ break
61
+ }
62
+ if (location === 'query') {
63
+ result.warnings.push(
64
+ `API key scheme "${schemeName}" targets query parameter "${paramName}", but gRPC-Web ` +
65
+ `requests carry no query string — sending it as a "${paramName}" header instead. ` +
66
+ `Ensure the server also accepts "${paramName}" as a header.`,
67
+ )
68
+ }
69
+ if (token) result.headers[paramName] = token
70
+ break
71
+ }
72
+ case 'http': {
73
+ const scheme = schemeDef?.scheme?.toLowerCase() ?? ''
74
+ if (scheme === 'bearer' || scheme === '') {
75
+ if (token) result.headers['Authorization'] = `Bearer ${token}`
76
+ } else if (scheme === 'basic') {
77
+ const username = secrets['x-scalar-secret-username'] ?? ''
78
+ const password = secrets['x-scalar-secret-password'] ?? ''
79
+ if (username || password) {
80
+ result.headers['Authorization'] = `Basic ${btoa(`${username}:${password}`)}`
81
+ }
82
+ }
83
+ break
84
+ }
85
+ case 'oauth2':
86
+ case 'openIdConnect':
87
+ if (token) result.headers['Authorization'] = `Bearer ${token}`
88
+ break
89
+ }
90
+ }
91
+
92
+ return result
93
+ }