@mosano-product-framework/sdk 0.2.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.
- package/README.md +827 -0
- package/README.react.md +348 -0
- package/dist/auth/claims-types.d.ts +89 -0
- package/dist/auth/claims.d.ts +125 -0
- package/dist/auth/cross-tab.d.ts +114 -0
- package/dist/auth/errors.d.ts +40 -0
- package/dist/auth/index.d.ts +18 -0
- package/dist/auth/index.js +5 -0
- package/dist/auth/index.js.map +1 -0
- package/dist/auth/oauth-state.d.ts +93 -0
- package/dist/auth/session-manager.d.ts +253 -0
- package/dist/auth/storage.d.ts +36 -0
- package/dist/auth/tenant-directory.d.ts +59 -0
- package/dist/auth/tenant-selection.d.ts +92 -0
- package/dist/chunk-7WAV52EO.js +621 -0
- package/dist/chunk-7WAV52EO.js.map +1 -0
- package/dist/chunk-AJWM5MDZ.js +410 -0
- package/dist/chunk-AJWM5MDZ.js.map +1 -0
- package/dist/chunk-EXPYHNPV.js +212 -0
- package/dist/chunk-EXPYHNPV.js.map +1 -0
- package/dist/chunk-GPWGOYCA.js +85 -0
- package/dist/chunk-GPWGOYCA.js.map +1 -0
- package/dist/chunk-GQJ3QQPH.js +339 -0
- package/dist/chunk-GQJ3QQPH.js.map +1 -0
- package/dist/chunk-K2ELAI2X.js +64 -0
- package/dist/chunk-K2ELAI2X.js.map +1 -0
- package/dist/chunk-LRM6JJ63.js +616 -0
- package/dist/chunk-LRM6JJ63.js.map +1 -0
- package/dist/chunk-XAXFIIRT.js +959 -0
- package/dist/chunk-XAXFIIRT.js.map +1 -0
- package/dist/client/core/client-factory.d.ts +61 -0
- package/dist/client/core/client.d.ts +144 -0
- package/dist/client/core/errors.d.ts +105 -0
- package/dist/client/core/index.d.ts +9 -0
- package/dist/client/core/middleware.d.ts +67 -0
- package/dist/client/core/types.d.ts +99 -0
- package/dist/client/graphql/client.d.ts +66 -0
- package/dist/client/graphql/factory.d.ts +84 -0
- package/dist/client/graphql/operation.d.ts +24 -0
- package/dist/client/graphql/types.d.ts +60 -0
- package/dist/client/graphql/ws-client.d.ts +116 -0
- package/dist/client/index.d.ts +17 -0
- package/dist/client/index.js +227 -0
- package/dist/client/index.js.map +1 -0
- package/dist/client/middlewares/admin-auth.d.ts +90 -0
- package/dist/client/middlewares/auth.d.ts +81 -0
- package/dist/client/middlewares/index.d.ts +12 -0
- package/dist/client/middlewares/logging.d.ts +102 -0
- package/dist/client/middlewares/retry.d.ts +138 -0
- package/dist/client/middlewares/tenant.d.ts +60 -0
- package/dist/client/middlewares/turnstile.d.ts +41 -0
- package/dist/client/peer-free.d.ts +25 -0
- package/dist/client/utils/url.d.ts +19 -0
- package/dist/identity/index.d.ts +85 -0
- package/dist/identity/index.js +6 -0
- package/dist/identity/index.js.map +1 -0
- package/dist/identity/types.d.ts +690 -0
- package/dist/identity/v0.d.ts +594 -0
- package/dist/index.d.ts +50 -0
- package/dist/index.js +24 -0
- package/dist/index.js.map +1 -0
- package/dist/react/context.d.ts +47 -0
- package/dist/react/hooks.d.ts +120 -0
- package/dist/react/index.d.ts +19 -0
- package/dist/react/index.js +308 -0
- package/dist/react/index.js.map +1 -0
- package/dist/react/provider.d.ts +68 -0
- package/dist/react/store.d.ts +85 -0
- package/dist/storage/index.d.ts +31 -0
- package/dist/storage/index.js +5 -0
- package/dist/storage/index.js.map +1 -0
- package/dist/storage/types.d.ts +107 -0
- package/dist/storage/v0.d.ts +120 -0
- package/package.json +99 -0
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Core types for the MPF SDK
|
|
3
|
+
*/
|
|
4
|
+
/** Token provider interface for SDK consumers */
|
|
5
|
+
export interface TokenProvider {
|
|
6
|
+
getAccessToken: () => string | null | Promise<string | null>;
|
|
7
|
+
/**
|
|
8
|
+
* Called when a 401 is final — a token was attached and renewal was not
|
|
9
|
+
* possible or already failed. The "log out" hook.
|
|
10
|
+
*/
|
|
11
|
+
onTokenExpired?: () => void | Promise<void>;
|
|
12
|
+
/**
|
|
13
|
+
* Called on the first 401 of a request that carried a token. Attempt a
|
|
14
|
+
* renewal; resolve `true` to have the request retried once with a fresh
|
|
15
|
+
* token. Wire this to `SessionManager.renew()`.
|
|
16
|
+
*/
|
|
17
|
+
onUnauthorized?: () => Promise<boolean>;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Request context passed through the middleware chain
|
|
21
|
+
*/
|
|
22
|
+
export interface RequestContext {
|
|
23
|
+
/** The full URL being requested */
|
|
24
|
+
url: string;
|
|
25
|
+
/** The fetch RequestInit options */
|
|
26
|
+
init: RequestInit;
|
|
27
|
+
/** Custom metadata that middlewares can use to pass data */
|
|
28
|
+
metadata: Record<string, unknown>;
|
|
29
|
+
/** The original request options, available for middleware inspection */
|
|
30
|
+
options?: RequestOptions;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Response context passed through the middleware chain
|
|
34
|
+
*/
|
|
35
|
+
export interface ResponseContext<T = unknown> {
|
|
36
|
+
/** The raw fetch Response object */
|
|
37
|
+
response: Response;
|
|
38
|
+
/** The parsed response data */
|
|
39
|
+
data: T;
|
|
40
|
+
/** Custom metadata that middlewares can use to pass data */
|
|
41
|
+
metadata: Record<string, unknown>;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Configuration options for the MPF client
|
|
45
|
+
*/
|
|
46
|
+
export interface ClientConfig {
|
|
47
|
+
/** Base URL for all API requests (e.g., "https://api.example.com") */
|
|
48
|
+
baseUrl: string;
|
|
49
|
+
/** Default headers to include in all requests */
|
|
50
|
+
headers?: Record<string, string>;
|
|
51
|
+
/** Middlewares to apply to all requests */
|
|
52
|
+
middlewares?: import('./middleware.js').Middleware[];
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Options for individual requests
|
|
56
|
+
*/
|
|
57
|
+
export interface RequestOptions {
|
|
58
|
+
/** Additional headers for this specific request */
|
|
59
|
+
headers?: Record<string, string>;
|
|
60
|
+
/** AbortSignal for request cancellation */
|
|
61
|
+
signal?: AbortSignal;
|
|
62
|
+
/** Query parameters to append to the URL */
|
|
63
|
+
params?: Record<string, string | number | boolean | undefined>;
|
|
64
|
+
/** When true, the auth middleware will skip injecting the Authorization header */
|
|
65
|
+
skipAuth?: boolean;
|
|
66
|
+
/**
|
|
67
|
+
* Tenant scope for this request. Three meaningful states:
|
|
68
|
+
*
|
|
69
|
+
* - `undefined` — inherit the client-wide tenant selection (the default).
|
|
70
|
+
* - `null` — deliberately unscoped: send no tenant header for this request.
|
|
71
|
+
* - a string — use that tenant for this request only.
|
|
72
|
+
*
|
|
73
|
+
* The `undefined` vs `null` distinction matters: without it, "I did not say"
|
|
74
|
+
* and "I said no tenant" collapse into the same value and a deliberately
|
|
75
|
+
* unscoped call would silently pick up the ambient selection.
|
|
76
|
+
*/
|
|
77
|
+
tenant?: string | null;
|
|
78
|
+
/**
|
|
79
|
+
* Role to select within the tenant. Omit to let the server apply that
|
|
80
|
+
* tenant's default role — its `tnts[].dfr` when the token carries one, else
|
|
81
|
+
* `tnts[].rls[0]`.
|
|
82
|
+
*/
|
|
83
|
+
tenantRole?: string;
|
|
84
|
+
/** When true, the tenant middleware sends no tenant headers at all. */
|
|
85
|
+
skipTenant?: boolean;
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Standard API error response shape
|
|
89
|
+
*/
|
|
90
|
+
export interface APIErrorResponse {
|
|
91
|
+
/** Error code identifier */
|
|
92
|
+
code?: string;
|
|
93
|
+
/** Human-readable error message */
|
|
94
|
+
message?: string;
|
|
95
|
+
/** Error string from Go backend */
|
|
96
|
+
error?: string;
|
|
97
|
+
/** Additional error details */
|
|
98
|
+
details?: unknown;
|
|
99
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GraphQL client that extends the base MPF client
|
|
3
|
+
*/
|
|
4
|
+
import { MPFClient } from '../core/client.js';
|
|
5
|
+
import type { ClientConfig } from '../core/types.js';
|
|
6
|
+
import type { TypedDocumentNode, GraphQLRequestOptions } from './types.js';
|
|
7
|
+
/**
|
|
8
|
+
* Configuration options for the GraphQL client
|
|
9
|
+
*/
|
|
10
|
+
export interface GraphQLClientConfig extends ClientConfig {
|
|
11
|
+
/**
|
|
12
|
+
* GraphQL endpoint path (default: '/graphql')
|
|
13
|
+
*/
|
|
14
|
+
endpoint?: string;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* GraphQL client for MPF services
|
|
18
|
+
*
|
|
19
|
+
* Supports both raw query strings and typed document nodes for type-safe operations.
|
|
20
|
+
*
|
|
21
|
+
* @example
|
|
22
|
+
* ```typescript
|
|
23
|
+
* const client = new MPFGraphQLClient({ baseUrl: 'https://api.example.com' });
|
|
24
|
+
*
|
|
25
|
+
* // Using raw query string
|
|
26
|
+
* const result = await client.query(`
|
|
27
|
+
* query GetUser($id: ID!) {
|
|
28
|
+
* user(id: $id) { id name email }
|
|
29
|
+
* }
|
|
30
|
+
* `, { id: '123' });
|
|
31
|
+
*
|
|
32
|
+
* // Using typed document node (from codegen)
|
|
33
|
+
* const typedResult = await client.query(GetUserDocument, { id: '123' });
|
|
34
|
+
* ```
|
|
35
|
+
*/
|
|
36
|
+
export declare class MPFGraphQLClient extends MPFClient {
|
|
37
|
+
/** The GraphQL endpoint path */
|
|
38
|
+
protected graphqlEndpoint: string;
|
|
39
|
+
constructor(config: GraphQLClientConfig);
|
|
40
|
+
/**
|
|
41
|
+
* Execute a GraphQL query operation
|
|
42
|
+
*
|
|
43
|
+
* @param document - The GraphQL query (string or typed document node)
|
|
44
|
+
* @param variables - Optional variables for the query
|
|
45
|
+
* @param options - Optional request options (headers, signal)
|
|
46
|
+
* @returns The query result data
|
|
47
|
+
* @throws {MPFAPIError} When GraphQL errors occur or no data is returned
|
|
48
|
+
*/
|
|
49
|
+
query<TData, TVariables = Record<string, unknown>>(document: TypedDocumentNode<TData, TVariables> | string, variables?: TVariables, options?: GraphQLRequestOptions): Promise<TData>;
|
|
50
|
+
/**
|
|
51
|
+
* Execute a GraphQL mutation operation
|
|
52
|
+
*
|
|
53
|
+
* @param document - The GraphQL mutation (string or typed document node)
|
|
54
|
+
* @param variables - Optional variables for the mutation
|
|
55
|
+
* @param options - Optional request options (headers, signal)
|
|
56
|
+
* @returns The mutation result data
|
|
57
|
+
* @throws {MPFAPIError} When GraphQL errors occur or no data is returned
|
|
58
|
+
*/
|
|
59
|
+
mutate<TData, TVariables = Record<string, unknown>>(document: TypedDocumentNode<TData, TVariables> | string, variables?: TVariables, options?: GraphQLRequestOptions): Promise<TData>;
|
|
60
|
+
/**
|
|
61
|
+
* Execute a GraphQL operation (query or mutation)
|
|
62
|
+
*
|
|
63
|
+
* @internal
|
|
64
|
+
*/
|
|
65
|
+
private executeOperation;
|
|
66
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `createGraphQLClient` — the GraphQL counterpart to `createAuthClient` /
|
|
3
|
+
* `createStorageClient`, built on the same `attachStandardMiddlewares`
|
|
4
|
+
* wiring (see `core/client-factory.ts`) so it shares one `tokenProvider` /
|
|
5
|
+
* `tenantProvider` with every other client an app constructs.
|
|
6
|
+
*
|
|
7
|
+
* Adds one more axis specific to GraphQL: `mode`, which decides HTTP vs
|
|
8
|
+
* `graphql-ws` per operation. This is a deliberately small stand-in for an
|
|
9
|
+
* Apollo-Link-style split link — enough to route by operation kind, not a
|
|
10
|
+
* general link-chaining system. If usage grows past "route by operation
|
|
11
|
+
* kind" (retry links, batching links, etc.) that is the point to reach for
|
|
12
|
+
* an actual link abstraction; this does not try to anticipate that.
|
|
13
|
+
*/
|
|
14
|
+
import { type HttpClientOptions } from '../core/client-factory.js';
|
|
15
|
+
import { type GraphQLWsCreateClientParams, type GraphQLWsClientLike } from './ws-client.js';
|
|
16
|
+
import type { TypedDocumentNode, GraphQLRequestOptions, GraphQLResponse } from './types.js';
|
|
17
|
+
/**
|
|
18
|
+
* How a `createGraphQLClient` routes operations.
|
|
19
|
+
*
|
|
20
|
+
* - `'individual-requests'` — every operation (query/mutation) goes over
|
|
21
|
+
* plain HTTP POST. No socket is ever opened; `subscribe()` throws.
|
|
22
|
+
* - `'websocket'` — every operation, including query/mutation, goes over one
|
|
23
|
+
* `graphql-ws` connection.
|
|
24
|
+
* - `'hybrid'` (default) — starts in `'individual-requests'` behavior.
|
|
25
|
+
* Query/mutation always stay on HTTP (cheaper: no connection to keep
|
|
26
|
+
* alive for the common case). The FIRST `subscribe()` call lazily opens
|
|
27
|
+
* the `graphql-ws` connection; every subscription after that reuses it.
|
|
28
|
+
*/
|
|
29
|
+
export type GraphQLConnectionMode = 'individual-requests' | 'websocket' | 'hybrid';
|
|
30
|
+
export interface GraphQLClientOptions extends HttpClientOptions {
|
|
31
|
+
/** GraphQL HTTP endpoint path. @default '/graphql' */
|
|
32
|
+
endpoint?: string;
|
|
33
|
+
/** @default 'hybrid' */
|
|
34
|
+
mode?: GraphQLConnectionMode;
|
|
35
|
+
/**
|
|
36
|
+
* `ws://`/`wss://` endpoint for the `graphql-ws` transport. Defaults to
|
|
37
|
+
* `baseUrl` with `http(s)` swapped for `ws(s)` and `endpoint` appended —
|
|
38
|
+
* correct for Hasura, which serves GraphQL-over-HTTP and `graphql-ws` on
|
|
39
|
+
* the same path. Override for a service that splits them.
|
|
40
|
+
*/
|
|
41
|
+
wsUrl?: string;
|
|
42
|
+
/** `WebSocket` implementation for non-browser runtimes (e.g. the `ws` package in Node). */
|
|
43
|
+
webSocketImpl?: unknown;
|
|
44
|
+
/** `graphql-ws` reconnect attempts. @default 5 */
|
|
45
|
+
wsRetryAttempts?: number;
|
|
46
|
+
/**
|
|
47
|
+
* Injectable `graphql-ws` `createClient`, forwarded to
|
|
48
|
+
* {@link createGraphQLWsTransport}. Lets tests exercise `'websocket'`/
|
|
49
|
+
* `'hybrid'` routing with a fake socket instead of a real one — see
|
|
50
|
+
* `ws-client.ts`'s `GraphQLWsClientOptions.createClient` doc.
|
|
51
|
+
*/
|
|
52
|
+
wsCreateClient?: (params: GraphQLWsCreateClientParams) => GraphQLWsClientLike;
|
|
53
|
+
}
|
|
54
|
+
export interface GraphQLSubscriptionHandlers<TData> {
|
|
55
|
+
onData: (result: GraphQLResponse<TData>) => void;
|
|
56
|
+
onError?: (error: unknown) => void;
|
|
57
|
+
onComplete?: () => void;
|
|
58
|
+
}
|
|
59
|
+
export interface MPFGraphQLHybridClient {
|
|
60
|
+
query<TData, TVariables = Record<string, unknown>>(document: TypedDocumentNode<TData, TVariables> | string, variables?: TVariables, options?: GraphQLRequestOptions): Promise<TData>;
|
|
61
|
+
mutate<TData, TVariables = Record<string, unknown>>(document: TypedDocumentNode<TData, TVariables> | string, variables?: TVariables, options?: GraphQLRequestOptions): Promise<TData>;
|
|
62
|
+
/**
|
|
63
|
+
* Subscribe to a `subscription` document. Throws synchronously in
|
|
64
|
+
* `'individual-requests'` mode — that mode has no transport for it.
|
|
65
|
+
*
|
|
66
|
+
* @returns An unsubscribe function.
|
|
67
|
+
*/
|
|
68
|
+
subscribe<TData, TVariables = Record<string, unknown>>(document: TypedDocumentNode<TData, TVariables> | string, variables: TVariables | undefined, handlers: GraphQLSubscriptionHandlers<TData>): () => void;
|
|
69
|
+
/**
|
|
70
|
+
* Force the `graphql-ws` socket closed; it reopens with a fresh token and
|
|
71
|
+
* resubscribes every live subscription. Wire this to fire on every token
|
|
72
|
+
* renewal — e.g. from `SessionManagerOptions.onTokensChanged` — so a
|
|
73
|
+
* subscription never outlives the token it was opened with. A no-op if no
|
|
74
|
+
* socket has been opened yet (nothing to reconnect).
|
|
75
|
+
*/
|
|
76
|
+
reconnectWebSocket(): void;
|
|
77
|
+
/** Release the HTTP client's resources (none currently) and close any open socket. */
|
|
78
|
+
dispose(): void;
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Create a GraphQL client with the standard auth/tenant middlewares wired in
|
|
82
|
+
* and a `mode`-selected HTTP/`graphql-ws` transport.
|
|
83
|
+
*/
|
|
84
|
+
export declare function createGraphQLClient(options: GraphQLClientOptions): MPFGraphQLHybridClient;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cheap operation-kind detection — no `graphql` parser dependency.
|
|
3
|
+
*
|
|
4
|
+
* The hybrid client only needs ONE bit: "is this a subscription". A full AST
|
|
5
|
+
* parse (`graphql`'s `parse()` + `getOperationAST()`) would answer that too,
|
|
6
|
+
* but it drags `graphql` into the HTTP-only path as a hard dependency, which
|
|
7
|
+
* defeats the point of `graphql` staying an optional peer (see
|
|
8
|
+
* `graphql/ws-client.ts`). A GraphQL document's operation keyword is always
|
|
9
|
+
* the first non-trivia token — `query`/`mutation`/`subscription`, or omitted
|
|
10
|
+
* entirely for the anonymous-query shorthand (`{ ... }`) — so a regex over the
|
|
11
|
+
* leading trivia is exact, not a heuristic.
|
|
12
|
+
*/
|
|
13
|
+
/** The three GraphQL operation kinds, plus `unknown` for an unparseable document. */
|
|
14
|
+
export type OperationKind = 'query' | 'mutation' | 'subscription' | 'unknown';
|
|
15
|
+
/**
|
|
16
|
+
* Determine a GraphQL document's operation kind from its source text.
|
|
17
|
+
*
|
|
18
|
+
* `{ ... }` (no keyword) is the anonymous-query shorthand and is always a
|
|
19
|
+
* `query` — a `subscription` document is never allowed to omit its keyword,
|
|
20
|
+
* per the GraphQL spec, so this cannot misclassify a subscription as a query.
|
|
21
|
+
*/
|
|
22
|
+
export declare function getOperationKind(document: string): OperationKind;
|
|
23
|
+
/** Whether a GraphQL document is a subscription. */
|
|
24
|
+
export declare function isSubscription(document: string): boolean;
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GraphQL-specific types for the MPF SDK
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* Minimal typed document representation
|
|
6
|
+
* Compatible with graphql-request, urql, Apollo, and other GraphQL clients
|
|
7
|
+
*/
|
|
8
|
+
export interface TypedDocumentNode<TData = unknown, TVariables = unknown> {
|
|
9
|
+
/** Type brand for compile-time type inference */
|
|
10
|
+
__apiType?: (variables: TVariables) => TData;
|
|
11
|
+
/** Returns the GraphQL query string */
|
|
12
|
+
toString(): string;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Represents a single GraphQL error from the response
|
|
16
|
+
*/
|
|
17
|
+
export interface GraphQLError {
|
|
18
|
+
/** Human-readable error message */
|
|
19
|
+
message: string;
|
|
20
|
+
/** Locations in the GraphQL document where the error occurred */
|
|
21
|
+
locations?: Array<{
|
|
22
|
+
line: number;
|
|
23
|
+
column: number;
|
|
24
|
+
}>;
|
|
25
|
+
/** Path to the field that caused the error */
|
|
26
|
+
path?: Array<string | number>;
|
|
27
|
+
/** Additional error metadata */
|
|
28
|
+
extensions?: Record<string, unknown>;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Standard GraphQL response structure
|
|
32
|
+
*/
|
|
33
|
+
export interface GraphQLResponse<T = unknown> {
|
|
34
|
+
/** The data returned from the operation */
|
|
35
|
+
data?: T;
|
|
36
|
+
/** Any errors that occurred during execution */
|
|
37
|
+
errors?: GraphQLError[];
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Options for individual GraphQL requests
|
|
41
|
+
*/
|
|
42
|
+
export interface GraphQLRequestOptions {
|
|
43
|
+
/** Additional headers for this request */
|
|
44
|
+
headers?: Record<string, string>;
|
|
45
|
+
/** AbortSignal for request cancellation */
|
|
46
|
+
signal?: AbortSignal;
|
|
47
|
+
/**
|
|
48
|
+
* Tenant scope for this operation. `undefined` inherits the client-wide
|
|
49
|
+
* selection, `null` is deliberately unscoped, a string selects that tenant.
|
|
50
|
+
*
|
|
51
|
+
* @see {@link import('../core/types.js').RequestOptions.tenant}
|
|
52
|
+
*/
|
|
53
|
+
tenant?: string | null;
|
|
54
|
+
/** Role within the tenant. Omit to let the server apply the tenant default. */
|
|
55
|
+
tenantRole?: string;
|
|
56
|
+
/** When true, send no tenant headers for this operation. */
|
|
57
|
+
skipTenant?: boolean;
|
|
58
|
+
/** When true, send no Authorization header for this operation. */
|
|
59
|
+
skipAuth?: boolean;
|
|
60
|
+
}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `graphql-ws` transport, wired to the SAME `tokenProvider`/`tenantProvider`
|
|
3
|
+
* every REST and HTTP-GraphQL client uses.
|
|
4
|
+
*
|
|
5
|
+
* This is meant to be a project-wide standard, not a go-hasura special case:
|
|
6
|
+
* any microservice exposing `graphql-ws` subscriptions (Hasura's, or a Go
|
|
7
|
+
* service's own) authenticates the same way every other MPF client does —
|
|
8
|
+
* `connectionParams` carries the identical `Authorization: Bearer <jwt>` (plus
|
|
9
|
+
* `X-MPF-Tenant`/`X-MPF-Tenant-Role` when a tenant is selected) that the HTTP
|
|
10
|
+
* middlewares put on REST/GraphQL-over-HTTP requests. A service just needs to
|
|
11
|
+
* read those three keys off the `connection_init` payload at its own webhook
|
|
12
|
+
* auth boundary — no new token format.
|
|
13
|
+
*
|
|
14
|
+
* They are carried under a `headers` key of the `connection_init` payload —
|
|
15
|
+
* `{ headers: { Authorization, 'X-MPF-Tenant', ... } }` — NOT flat at the top
|
|
16
|
+
* level. That is what Hasura reads: over the websocket path it takes
|
|
17
|
+
* `payload.headers` and replays those as the request headers it hands to the
|
|
18
|
+
* auth webhook (hasura-authd). Flat top-level keys are silently ignored, so
|
|
19
|
+
* the socket connects and every subscription then fails as unauthenticated —
|
|
20
|
+
* verified end-to-end against a live stack in
|
|
21
|
+
* `e2e/tests/go-identity/graphql-subscriptions.e2e.test.ts`. It is also the
|
|
22
|
+
* shape the wider `graphql-ws` ecosystem uses for header-style auth, so a Go
|
|
23
|
+
* service reading `payload.headers` stays consistent with both.
|
|
24
|
+
*/
|
|
25
|
+
import type { TokenProvider } from '../core/types.js';
|
|
26
|
+
import type { TenantSelectionStore } from '../../auth/tenant-selection.js';
|
|
27
|
+
import type { GraphQLResponse } from './types.js';
|
|
28
|
+
/**
|
|
29
|
+
* The subset of a `graphql-ws` `Client` this module uses. Kept as our own
|
|
30
|
+
* interface (not `import type { Client } from 'graphql-ws'`) so this file
|
|
31
|
+
* never has a hard, non-dynamic dependency on the `graphql-ws` types either —
|
|
32
|
+
* matches `cross-tab.ts`'s `LockManagerLike`/`BroadcastChannelLike` pattern.
|
|
33
|
+
*/
|
|
34
|
+
export interface GraphQLWsClientLike {
|
|
35
|
+
subscribe<TData = unknown>(payload: {
|
|
36
|
+
query: string;
|
|
37
|
+
variables?: Record<string, unknown>;
|
|
38
|
+
}, sink: {
|
|
39
|
+
next: (value: {
|
|
40
|
+
data?: TData | null;
|
|
41
|
+
errors?: readonly unknown[];
|
|
42
|
+
}) => void;
|
|
43
|
+
error: (err: unknown) => void;
|
|
44
|
+
complete: () => void;
|
|
45
|
+
}): () => void;
|
|
46
|
+
terminate(): void;
|
|
47
|
+
dispose(): void | Promise<void>;
|
|
48
|
+
}
|
|
49
|
+
/** Options `createGraphQLWsTransport` forwards to `graphql-ws`'s `createClient`. */
|
|
50
|
+
export interface GraphQLWsCreateClientParams {
|
|
51
|
+
url: string;
|
|
52
|
+
lazy: boolean;
|
|
53
|
+
retryAttempts: number;
|
|
54
|
+
webSocketImpl?: unknown;
|
|
55
|
+
/**
|
|
56
|
+
* Resolved per connection attempt. Shape is `{ headers: { ... } }` — see
|
|
57
|
+
* the module doc for why the auth headers are nested rather than flat.
|
|
58
|
+
*/
|
|
59
|
+
connectionParams: () => Promise<{
|
|
60
|
+
headers: Record<string, string>;
|
|
61
|
+
}>;
|
|
62
|
+
}
|
|
63
|
+
export interface GraphQLWsClientOptions {
|
|
64
|
+
/** `ws://` or `wss://` endpoint. */
|
|
65
|
+
url: string;
|
|
66
|
+
/** Same token provider passed to the HTTP transport — see module docs. */
|
|
67
|
+
tokenProvider?: TokenProvider;
|
|
68
|
+
/** Same tenant provider passed to the HTTP transport. */
|
|
69
|
+
tenantProvider?: TenantSelectionStore;
|
|
70
|
+
/**
|
|
71
|
+
* `WebSocket` implementation for non-browser environments (Node has no
|
|
72
|
+
* global `WebSocket` before v22). Pass the `ws` package's export there.
|
|
73
|
+
*/
|
|
74
|
+
webSocketImpl?: unknown;
|
|
75
|
+
/** Reconnect attempts before giving up on a dropped socket. @default 5 */
|
|
76
|
+
retryAttempts?: number;
|
|
77
|
+
/**
|
|
78
|
+
* Open the socket only when the first subscription starts, close it when
|
|
79
|
+
* the last one ends. @default true — matches `hybrid` mode's "no socket
|
|
80
|
+
* until a subscription shows up" contract.
|
|
81
|
+
*/
|
|
82
|
+
lazy?: boolean;
|
|
83
|
+
/**
|
|
84
|
+
* Injectable `graphql-ws` `createClient`. Defaults to a dynamic
|
|
85
|
+
* `import('graphql-ws')`. Tests supply a fake here to exercise the full
|
|
86
|
+
* subscribe/reconnect/dispose contract without a real socket or the
|
|
87
|
+
* `graphql-ws` package — same reason `cross-tab.ts` takes an injectable
|
|
88
|
+
* `locks`/`channelFactory`.
|
|
89
|
+
*/
|
|
90
|
+
createClient?: (params: GraphQLWsCreateClientParams) => GraphQLWsClientLike;
|
|
91
|
+
}
|
|
92
|
+
export interface GraphQLSubscriptionSink<TData> {
|
|
93
|
+
next: (result: GraphQLResponse<TData>) => void;
|
|
94
|
+
error: (error: unknown) => void;
|
|
95
|
+
complete: () => void;
|
|
96
|
+
}
|
|
97
|
+
export interface GraphQLWsTransport {
|
|
98
|
+
/** Subscribe; returns an unsubscribe function. */
|
|
99
|
+
subscribe<TData = unknown, TVariables = Record<string, unknown>>(document: {
|
|
100
|
+
toString(): string;
|
|
101
|
+
} | string, variables: TVariables | undefined, sink: GraphQLSubscriptionSink<TData>): () => void;
|
|
102
|
+
/**
|
|
103
|
+
* Force the current socket closed. `graphql-ws`'s own retry logic reopens
|
|
104
|
+
* it with a FRESH `connectionParams` call (so a rotated token is picked up)
|
|
105
|
+
* and resubscribes every still-registered operation automatically.
|
|
106
|
+
*
|
|
107
|
+
* Call this right after a `SessionManager` renewal — see
|
|
108
|
+
* `createGraphQLClient`'s `mode: 'websocket' | 'hybrid'` wiring — so a live
|
|
109
|
+
* subscription is never carried on a token past its renewal point.
|
|
110
|
+
*/
|
|
111
|
+
reconnect(): void;
|
|
112
|
+
/** Close the socket and release it for good. Registered subscriptions end. */
|
|
113
|
+
dispose(): void;
|
|
114
|
+
}
|
|
115
|
+
/** Create a `graphql-ws`-backed subscription transport. */
|
|
116
|
+
export declare function createGraphQLWsTransport(options: GraphQLWsClientOptions): GraphQLWsTransport;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@mosano-product-framework/sdk/client` — the generic transport layer.
|
|
3
|
+
*
|
|
4
|
+
* Everything here is service-agnostic: the HTTP client, the middleware
|
|
5
|
+
* machinery and the built-in middlewares, the error types, and the GraphQL
|
|
6
|
+
* clients (HTTP, WebSocket and hybrid).
|
|
7
|
+
*
|
|
8
|
+
* The root barrel re-exports this module as a `client` namespace, but only the
|
|
9
|
+
* `./peer-free.js` half of it — `createGraphQLClient` and
|
|
10
|
+
* `createGraphQLWsTransport` are subpath-only because they reach the optional
|
|
11
|
+
* `graphql-ws` peer. See the comment at the top of `peer-free.ts`.
|
|
12
|
+
*
|
|
13
|
+
* @packageDocumentation
|
|
14
|
+
*/
|
|
15
|
+
export * from './peer-free.js';
|
|
16
|
+
export { createGraphQLClient, type GraphQLClientOptions, type GraphQLConnectionMode, type MPFGraphQLHybridClient, type GraphQLSubscriptionHandlers, } from './graphql/factory.js';
|
|
17
|
+
export { createGraphQLWsTransport, type GraphQLWsClientLike, type GraphQLWsCreateClientParams, type GraphQLWsClientOptions, type GraphQLWsTransport, type GraphQLSubscriptionSink, } from './graphql/ws-client.js';
|