@glubean/graphql 0.1.6 → 0.2.1

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,90 @@
1
+ /**
2
+ * contract.graphql factory — scoped-defaults pattern.
3
+ *
4
+ * Root `contract.graphql` exposes only `.with(name, defaults)`. Calling
5
+ * `contract.graphql("id", spec)` directly is forbidden — client injection
6
+ * via scoped instances is the canonical authoring pattern (same as HTTP
7
+ * and gRPC).
8
+ *
9
+ * The factory wraps the generic dispatcher attached to `contract.graphql`
10
+ * by `contract.register("graphql", graphqlAdapter)`. Calls flow through:
11
+ * user code
12
+ * → scoped factory(id, spec)
13
+ * → merge instance defaults into spec
14
+ * → dispatcher(id, mergedSpec) [generic register() output]
15
+ * → adapter.project + per-case registerTest + Test[] with
16
+ * _projection/_spec
17
+ */
18
+ function mergeExtensions(base, override) {
19
+ if (!base && !override)
20
+ return undefined;
21
+ const merged = {
22
+ ...(base ?? {}),
23
+ ...(override ?? {}),
24
+ };
25
+ return Object.keys(merged).length > 0 ? merged : undefined;
26
+ }
27
+ function mergeGraphqlDefaults(defaults, spec) {
28
+ if (!defaults)
29
+ return spec;
30
+ const mergedTags = [...(defaults.tags ?? []), ...(spec.tags ?? [])];
31
+ const mergedExtensions = mergeExtensions(defaults.extensions, spec.extensions);
32
+ const mergedHeaders = {
33
+ ...(defaults.headers ?? {}),
34
+ ...(spec.defaultHeaders ?? {}),
35
+ };
36
+ return {
37
+ ...spec,
38
+ client: spec.client ?? defaults.client,
39
+ endpoint: spec.endpoint ?? defaults.endpoint,
40
+ feature: spec.feature ?? defaults.feature,
41
+ tags: mergedTags.length > 0 ? mergedTags : undefined,
42
+ extensions: mergedExtensions,
43
+ defaultHeaders: Object.keys(mergedHeaders).length > 0 ? mergedHeaders : undefined,
44
+ };
45
+ }
46
+ function applyInstanceMetadata(contract, instanceName) {
47
+ const proj = contract._projection;
48
+ proj.instanceName = instanceName;
49
+ }
50
+ /**
51
+ * Build a scoped GraphQL factory. `dispatch` is `contract.graphql` attached
52
+ * by the core's register() call — we wrap it to inject instance defaults.
53
+ */
54
+ export function createGraphqlFactory(dispatch, defaults) {
55
+ const factory = (id, spec) => {
56
+ if (!defaults?._name) {
57
+ throw new Error(`contract.graphql("${id}", spec) is not supported. ` +
58
+ `Use contract.graphql.with("name", { client }) first to create a scoped instance, ` +
59
+ `then call instance("${id}", spec).`);
60
+ }
61
+ const merged = mergeGraphqlDefaults(defaults, spec);
62
+ const result = dispatch(id, merged);
63
+ applyInstanceMetadata(result, defaults._name);
64
+ return result;
65
+ };
66
+ factory.with = (name, more = {}) => {
67
+ const mergedTags = [...(defaults?.tags ?? []), ...(more.tags ?? [])];
68
+ const mergedExtensions = mergeExtensions(defaults?.extensions, more.extensions);
69
+ const mergedHeaders = {
70
+ ...(defaults?.headers ?? {}),
71
+ ...(more.headers ?? {}),
72
+ };
73
+ return createGraphqlFactory(dispatch, {
74
+ ...defaults,
75
+ ...more,
76
+ tags: mergedTags.length > 0 ? mergedTags : undefined,
77
+ extensions: mergedExtensions,
78
+ headers: Object.keys(mergedHeaders).length > 0 ? mergedHeaders : undefined,
79
+ _name: name,
80
+ });
81
+ };
82
+ return factory;
83
+ }
84
+ /**
85
+ * Root factory — `.with()` only, direct call throws.
86
+ */
87
+ export function createGraphqlRoot(dispatch) {
88
+ return createGraphqlFactory(dispatch);
89
+ }
90
+ //# sourceMappingURL=factory.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"factory.js","sourceRoot":"","sources":["../../src/contract/factory.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,37 @@
1
+ /**
2
+ * GraphQL contract surface for @glubean/graphql 0.2.0.
3
+ *
4
+ * This module makes `@glubean/graphql` a single-package owner of both:
5
+ * - Transport / client layer (existing `../index.ts`, unchanged)
6
+ * - Contract adapter layer (this directory)
7
+ *
8
+ * Mirrors the same single-package design as `@glubean/grpc` 0.2.0.
9
+ * See `internal/00-product/positioning-v3.md` §12.0 resolved and
10
+ * `internal/40-discovery/proposals/contract-grpc-graphql-expansion.md` §6.1.
11
+ *
12
+ * Side-effect on import:
13
+ * 1. `contract.register("graphql", graphqlAdapter)` — registers dispatcher
14
+ * 2. Wrap dispatcher with `createGraphqlRoot` so
15
+ * `contract.graphql.with(name, defaults)` UX works
16
+ *
17
+ * After this module loads, users can:
18
+ *
19
+ * import "@glubean/graphql"; // side-effect: registers graphql contract adapter
20
+ * import { contract } from "@glubean/sdk";
21
+ *
22
+ * const api = contract.graphql.with("api", { client });
23
+ * export const getUser = api("get-user", {
24
+ * cases: {
25
+ * ok: {
26
+ * description: "success",
27
+ * query: `query GetUser($id: ID!) { user(id: $id) { name } }`,
28
+ * variables: { id: "1" },
29
+ * expect: { data: { user: { name: "Alice" } } },
30
+ * },
31
+ * },
32
+ * });
33
+ */
34
+ export { graphqlAdapter } from "./adapter.js";
35
+ export { createGraphqlFactory, createGraphqlRoot } from "./factory.js";
36
+ export type { GraphqlContractCase, GraphqlContractDefaults, GraphqlContractExample, GraphqlContractExpect, GraphqlContractFactory, GraphqlContractMeta, GraphqlContractRoot, GraphqlContractSafeMeta, GraphqlContractSpec, GraphqlCaseResult, GraphqlErrorsExpect, GraphqlFlowCaseOutput, GraphqlPayloadSchemas, GraphqlSafeSchemas, GraphqlTypeDef, GraphqlTypeDefs, InferGraphqlVariables, InferGraphqlResponse, } from "./types.js";
37
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/contract/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AA0BH,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAC9C,OAAO,EAAE,oBAAoB,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AACvE,YAAY,EACV,mBAAmB,EACnB,uBAAuB,EACvB,sBAAsB,EACtB,qBAAqB,EACrB,sBAAsB,EACtB,mBAAmB,EACnB,mBAAmB,EACnB,uBAAuB,EACvB,mBAAmB,EACnB,iBAAiB,EACjB,mBAAmB,EACnB,qBAAqB,EACrB,qBAAqB,EACrB,kBAAkB,EAClB,cAAc,EACd,eAAe,EACf,qBAAqB,EACrB,oBAAoB,GACrB,MAAM,YAAY,CAAC"}
@@ -0,0 +1,55 @@
1
+ /**
2
+ * GraphQL contract surface for @glubean/graphql 0.2.0.
3
+ *
4
+ * This module makes `@glubean/graphql` a single-package owner of both:
5
+ * - Transport / client layer (existing `../index.ts`, unchanged)
6
+ * - Contract adapter layer (this directory)
7
+ *
8
+ * Mirrors the same single-package design as `@glubean/grpc` 0.2.0.
9
+ * See `internal/00-product/positioning-v3.md` §12.0 resolved and
10
+ * `internal/40-discovery/proposals/contract-grpc-graphql-expansion.md` §6.1.
11
+ *
12
+ * Side-effect on import:
13
+ * 1. `contract.register("graphql", graphqlAdapter)` — registers dispatcher
14
+ * 2. Wrap dispatcher with `createGraphqlRoot` so
15
+ * `contract.graphql.with(name, defaults)` UX works
16
+ *
17
+ * After this module loads, users can:
18
+ *
19
+ * import "@glubean/graphql"; // side-effect: registers graphql contract adapter
20
+ * import { contract } from "@glubean/sdk";
21
+ *
22
+ * const api = contract.graphql.with("api", { client });
23
+ * export const getUser = api("get-user", {
24
+ * cases: {
25
+ * ok: {
26
+ * description: "success",
27
+ * query: `query GetUser($id: ID!) { user(id: $id) { name } }`,
28
+ * variables: { id: "1" },
29
+ * expect: { data: { user: { name: "Alice" } } },
30
+ * },
31
+ * },
32
+ * });
33
+ */
34
+ import { contract } from "@glubean/sdk";
35
+ import { graphqlAdapter } from "./adapter.js";
36
+ import { createGraphqlRoot } from "./factory.js";
37
+ import { registerGraphqlMatchers } from "./matchers.js";
38
+ // Step 1: register the adapter. After this, `contract.graphql` exists as the
39
+ // generic dispatcher attached by `contract.register()`.
40
+ contract.register("graphql", graphqlAdapter);
41
+ // Step 2: wrap dispatcher with the scoped-defaults factory so
42
+ // `contract.graphql.with(name, defaults)` UX works.
43
+ {
44
+ const dispatcher = contract.graphql;
45
+ contract.graphql = createGraphqlRoot(dispatcher);
46
+ }
47
+ // Step 3: register GraphQL custom matchers so
48
+ // `ctx.expect(res).toHaveGraphqlData({...})` / `.toHaveGraphqlNoErrors()` /
49
+ // `.toHaveHttpStatus(200)` / `.toHaveGraphqlErrorCode("UNAUTHENTICATED")`
50
+ // work out of the box for any `@glubean/graphql` user.
51
+ registerGraphqlMatchers();
52
+ // Re-exports for type consumers who import from the package directly.
53
+ export { graphqlAdapter } from "./adapter.js";
54
+ export { createGraphqlFactory, createGraphqlRoot } from "./factory.js";
55
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/contract/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AAEH,OAAO,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;AACxC,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAC9C,OAAO,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AACjD,OAAO,EAAE,uBAAuB,EAAE,MAAM,eAAe,CAAC;AAGxD,6EAA6E;AAC7E,wDAAwD;AACxD,QAAQ,CAAC,QAAQ,CAAC,SAAS,EAAE,cAAc,CAAC,CAAC;AAE7C,8DAA8D;AAC9D,oDAAoD;AACpD,CAAC;IACC,MAAM,UAAU,GAAI,QAAgB,CAAC,OAAkD,CAAC;IACvF,QAAwD,CAAC,OAAO,GAAG,iBAAiB,CAAC,UAAU,CAAC,CAAC;AACpG,CAAC;AAED,8CAA8C;AAC9C,4EAA4E;AAC5E,0EAA0E;AAC1E,uDAAuD;AACvD,uBAAuB,EAAE,CAAC;AAE1B,sEAAsE;AACtE,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAC9C,OAAO,EAAE,oBAAoB,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC"}
@@ -0,0 +1,95 @@
1
+ /**
2
+ * GraphQL custom matchers for `ctx.expect()`.
3
+ *
4
+ * Registered as a side effect of `import "@glubean/graphql"` (see
5
+ * `./index.ts`). No extra import or configure step required — the
6
+ * matchers become available on every `ctx.expect(res)` call and are
7
+ * fully typed via the `CustomMatchers<T>` declaration merging block
8
+ * below.
9
+ *
10
+ * Matchers work on any object that carries GraphQL response shape, including:
11
+ * - `GraphQLResult<T>` from `@glubean/graphql` transport
12
+ * (`gql.query(...)` / `gql.mutate(...)`)
13
+ * - `GraphqlCaseResult<T>` from the contract adapter
14
+ * (verify callback / flow `out` lens input)
15
+ *
16
+ * Both expose:
17
+ * {
18
+ * data: T | null,
19
+ * errors?: GraphQLError[],
20
+ * extensions?: Record<string, unknown>,
21
+ * httpStatus: number,
22
+ * headers: Record<string, string | string[]>,
23
+ * rawBody: string | null,
24
+ * ...
25
+ * }
26
+ *
27
+ * The HTTP `toHaveStatus` built-in matcher reads `actual.status`, which the
28
+ * GraphQL envelope does NOT expose (it's `httpStatus` — a deliberate rename
29
+ * in CG-10 to avoid shadowing the native `Response.status` semantics). The
30
+ * `toHaveHttpStatus` matcher added here reads the envelope's `httpStatus`
31
+ * field so GraphQL users have a symmetric transport-level assertion.
32
+ */
33
+ declare module "@glubean/sdk/expect" {
34
+ interface CustomMatchers<T> {
35
+ /**
36
+ * Assert the transport-level HTTP status on a `GraphQLResult` /
37
+ * `GraphqlCaseResult`. Reads `actual.httpStatus`.
38
+ *
39
+ * Use this (not `toHaveStatus`) for GraphQL responses — the envelope
40
+ * field is `httpStatus`, not `status`.
41
+ *
42
+ * @example ctx.expect(res).toHaveHttpStatus(200);
43
+ * @example ctx.expect(res).toHaveHttpStatus(401, "missing token");
44
+ */
45
+ toHaveHttpStatus(code: number, message?: string): Expectation<T>;
46
+ /**
47
+ * Partial-match the GraphQL `data` field (like `toMatchObject`).
48
+ *
49
+ * Fails if `data` is null or the subset is not contained in `data`.
50
+ *
51
+ * @example ctx.expect(res).toHaveGraphqlData({ user: { name: "Alice" } });
52
+ */
53
+ toHaveGraphqlData(subset: Record<string, unknown>, message?: string): Expectation<T>;
54
+ /**
55
+ * Assert the `errors` array is absent or empty (strict success).
56
+ *
57
+ * @example ctx.expect(res).toHaveGraphqlNoErrors();
58
+ */
59
+ toHaveGraphqlNoErrors(message?: string): Expectation<T>;
60
+ /**
61
+ * Assert at least one entry in `errors` has matching
62
+ * `extensions.code` (case-insensitive).
63
+ *
64
+ * @example ctx.expect(res).toHaveGraphqlErrorCode("UNAUTHENTICATED");
65
+ */
66
+ toHaveGraphqlErrorCode(code: string, message?: string): Expectation<T>;
67
+ /**
68
+ * Assert a key exists in `extensions` (server-side tracing / cost),
69
+ * optionally matching an exact value.
70
+ *
71
+ * @example ctx.expect(res).toHaveGraphqlExtension("tracing");
72
+ * @example ctx.expect(res).toHaveGraphqlExtension("version", "v2");
73
+ */
74
+ toHaveGraphqlExtension(key: string, value?: unknown, message?: string): Expectation<T>;
75
+ }
76
+ }
77
+ /**
78
+ * Register GraphQL matchers onto the shared `Expectation` prototype. Called
79
+ * from `./index.ts` during the same side-effect block that registers the
80
+ * contract adapter; users get matchers + adapter from a single
81
+ * `import "@glubean/graphql"`.
82
+ *
83
+ * Idempotent: swallows the "matcher already exists" error thrown by
84
+ * `Expectation.extend` on re-evaluation (duplicate imports, Vitest
85
+ * isolation boundaries, etc.).
86
+ *
87
+ * Note: `toHaveHttpStatus` is registered here even though it's
88
+ * conceptually transport-level (not GraphQL-specific) — users of
89
+ * `@glubean/graphql` are the ones who need it because the GraphQL
90
+ * envelope uses `httpStatus` instead of `status`. If a future package
91
+ * also exposes an `httpStatus` field, registration idempotency will
92
+ * catch the conflict at boot.
93
+ */
94
+ export declare function registerGraphqlMatchers(): void;
95
+ //# sourceMappingURL=matchers.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"matchers.d.ts","sourceRoot":"","sources":["../../src/contract/matchers.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AASH,OAAO,QAAQ,qBAAqB,CAAC;IACnC,UAAU,cAAc,CAAC,CAAC;QACxB;;;;;;;;;WASG;QACH,gBAAgB,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,WAAW,CAAC,CAAC,CAAC,CAAC;QAEjE;;;;;;WAMG;QACH,iBAAiB,CACf,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC/B,OAAO,CAAC,EAAE,MAAM,GACf,WAAW,CAAC,CAAC,CAAC,CAAC;QAElB;;;;WAIG;QACH,qBAAqB,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,WAAW,CAAC,CAAC,CAAC,CAAC;QAExD;;;;;WAKG;QACH,sBAAsB,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,WAAW,CAAC,CAAC,CAAC,CAAC;QAEvE;;;;;;WAMG;QACH,sBAAsB,CACpB,GAAG,EAAE,MAAM,EACX,KAAK,CAAC,EAAE,OAAO,EACf,OAAO,CAAC,EAAE,MAAM,GACf,WAAW,CAAC,CAAC,CAAC,CAAC;KACnB;CACF;AAgPD;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,uBAAuB,IAAI,IAAI,CAe9C"}
@@ -0,0 +1,246 @@
1
+ /**
2
+ * GraphQL custom matchers for `ctx.expect()`.
3
+ *
4
+ * Registered as a side effect of `import "@glubean/graphql"` (see
5
+ * `./index.ts`). No extra import or configure step required — the
6
+ * matchers become available on every `ctx.expect(res)` call and are
7
+ * fully typed via the `CustomMatchers<T>` declaration merging block
8
+ * below.
9
+ *
10
+ * Matchers work on any object that carries GraphQL response shape, including:
11
+ * - `GraphQLResult<T>` from `@glubean/graphql` transport
12
+ * (`gql.query(...)` / `gql.mutate(...)`)
13
+ * - `GraphqlCaseResult<T>` from the contract adapter
14
+ * (verify callback / flow `out` lens input)
15
+ *
16
+ * Both expose:
17
+ * {
18
+ * data: T | null,
19
+ * errors?: GraphQLError[],
20
+ * extensions?: Record<string, unknown>,
21
+ * httpStatus: number,
22
+ * headers: Record<string, string | string[]>,
23
+ * rawBody: string | null,
24
+ * ...
25
+ * }
26
+ *
27
+ * The HTTP `toHaveStatus` built-in matcher reads `actual.status`, which the
28
+ * GraphQL envelope does NOT expose (it's `httpStatus` — a deliberate rename
29
+ * in CG-10 to avoid shadowing the native `Response.status` semantics). The
30
+ * `toHaveHttpStatus` matcher added here reads the envelope's `httpStatus`
31
+ * field so GraphQL users have a symmetric transport-level assertion.
32
+ */
33
+ import { Expectation, inspect } from "@glubean/sdk/expect";
34
+ function readHttpStatus(actual) {
35
+ const s = actual?.httpStatus;
36
+ return typeof s === "number" ? s : undefined;
37
+ }
38
+ function readEnvelope(actual) {
39
+ if (!actual || typeof actual !== "object")
40
+ return undefined;
41
+ return actual;
42
+ }
43
+ /** Minimal partial-match helper — checks that every key/value in subset
44
+ * is deep-equal to the corresponding path in target. Keeps this file
45
+ * dependency-free from the sdk internal `matchesObject`. */
46
+ function matchesSubset(target, subset) {
47
+ for (const [k, expected] of Object.entries(subset)) {
48
+ const actual = target[k];
49
+ if (expected !== null &&
50
+ typeof expected === "object" &&
51
+ !Array.isArray(expected)) {
52
+ if (!actual || typeof actual !== "object" || Array.isArray(actual)) {
53
+ return false;
54
+ }
55
+ if (!matchesSubset(actual, expected)) {
56
+ return false;
57
+ }
58
+ }
59
+ else if (Array.isArray(expected)) {
60
+ if (!Array.isArray(actual) || actual.length !== expected.length) {
61
+ return false;
62
+ }
63
+ for (let i = 0; i < expected.length; i++) {
64
+ if (JSON.stringify(actual[i]) !== JSON.stringify(expected[i])) {
65
+ return false;
66
+ }
67
+ }
68
+ }
69
+ else if (actual !== expected) {
70
+ return false;
71
+ }
72
+ }
73
+ return true;
74
+ }
75
+ // =============================================================================
76
+ // Matcher implementations
77
+ // =============================================================================
78
+ const toHaveHttpStatus = (actual, ...args) => {
79
+ const code = args[0];
80
+ const actualCode = readHttpStatus(actual);
81
+ if (actualCode === undefined) {
82
+ return {
83
+ passed: false,
84
+ message: `to have HTTP status ${code} — actual has no \`.httpStatus\` (got ${inspect(actual)})`,
85
+ actual,
86
+ expected: code,
87
+ };
88
+ }
89
+ return {
90
+ passed: actualCode === code,
91
+ message: `to have HTTP status ${code}`,
92
+ actual: actualCode,
93
+ expected: code,
94
+ };
95
+ };
96
+ const toHaveGraphqlData = (actual, ...args) => {
97
+ const subset = args[0];
98
+ const env = readEnvelope(actual);
99
+ if (!env) {
100
+ return {
101
+ passed: false,
102
+ message: `to have GraphQL data matching ${inspect(subset)} — actual is not an envelope`,
103
+ actual,
104
+ expected: subset,
105
+ };
106
+ }
107
+ const data = env.data;
108
+ if (data == null) {
109
+ return {
110
+ passed: false,
111
+ message: `to have GraphQL data matching ${inspect(subset)} — \`.data\` is null`,
112
+ actual: data,
113
+ expected: subset,
114
+ };
115
+ }
116
+ if (typeof data !== "object" || Array.isArray(data)) {
117
+ return {
118
+ passed: false,
119
+ message: `to have GraphQL data matching ${inspect(subset)} — \`.data\` is not an object`,
120
+ actual: data,
121
+ expected: subset,
122
+ };
123
+ }
124
+ const passed = matchesSubset(data, subset);
125
+ return {
126
+ passed,
127
+ message: `to have GraphQL data matching ${inspect(subset)}`,
128
+ actual: data,
129
+ expected: subset,
130
+ };
131
+ };
132
+ const toHaveGraphqlNoErrors = (actual) => {
133
+ const env = readEnvelope(actual);
134
+ const errs = env?.errors;
135
+ const count = Array.isArray(errs) ? errs.length : 0;
136
+ const summary = count > 0
137
+ ? errs.map((e) => e.message).slice(0, 3).join("; ") +
138
+ (count > 3 ? ` (+${count - 3} more)` : "")
139
+ : "";
140
+ return {
141
+ passed: count === 0,
142
+ message: count > 0
143
+ ? `to have no GraphQL errors (got ${count}: ${summary})`
144
+ : `to have no GraphQL errors`,
145
+ actual: errs ?? [],
146
+ expected: [],
147
+ };
148
+ };
149
+ const toHaveGraphqlErrorCode = (actual, ...args) => {
150
+ const code = args[0];
151
+ const env = readEnvelope(actual);
152
+ const errs = env?.errors;
153
+ if (!Array.isArray(errs) || errs.length === 0) {
154
+ return {
155
+ passed: false,
156
+ message: `to have GraphQL error code ${inspect(code)} — no errors on envelope`,
157
+ actual: errs ?? [],
158
+ expected: { "extensions.code": code },
159
+ };
160
+ }
161
+ const lower = code.toUpperCase();
162
+ const match = errs.find((e) => {
163
+ const c = e.extensions?.code;
164
+ return typeof c === "string" && c.toUpperCase() === lower;
165
+ });
166
+ return {
167
+ passed: !!match,
168
+ message: `to have GraphQL error code ${inspect(code)}`,
169
+ actual: errs.map((e) => e.extensions?.code ?? null),
170
+ expected: code,
171
+ };
172
+ };
173
+ const toHaveGraphqlExtension = (actual, ...args) => {
174
+ const key = args[0];
175
+ const expectedValue = args[1];
176
+ const env = readEnvelope(actual);
177
+ const ext = env?.extensions;
178
+ if (!ext || typeof ext !== "object") {
179
+ return {
180
+ passed: false,
181
+ message: `to have GraphQL extension \`${key}\` — actual has no \`.extensions\``,
182
+ actual: ext,
183
+ expected: expectedValue !== undefined ? { [key]: expectedValue } : key,
184
+ };
185
+ }
186
+ if (!(key in ext)) {
187
+ return {
188
+ passed: false,
189
+ message: `to have GraphQL extension \`${key}\``,
190
+ actual: Object.keys(ext),
191
+ expected: expectedValue !== undefined ? { [key]: expectedValue } : key,
192
+ };
193
+ }
194
+ if (expectedValue === undefined) {
195
+ return {
196
+ passed: true,
197
+ message: `to have GraphQL extension \`${key}\``,
198
+ actual: ext[key],
199
+ expected: key,
200
+ };
201
+ }
202
+ return {
203
+ passed: JSON.stringify(ext[key]) === JSON.stringify(expectedValue),
204
+ message: `to have GraphQL extension \`${key}\` = ${inspect(expectedValue)}`,
205
+ actual: ext[key],
206
+ expected: expectedValue,
207
+ };
208
+ };
209
+ // =============================================================================
210
+ // Registration — side effect
211
+ // =============================================================================
212
+ /**
213
+ * Register GraphQL matchers onto the shared `Expectation` prototype. Called
214
+ * from `./index.ts` during the same side-effect block that registers the
215
+ * contract adapter; users get matchers + adapter from a single
216
+ * `import "@glubean/graphql"`.
217
+ *
218
+ * Idempotent: swallows the "matcher already exists" error thrown by
219
+ * `Expectation.extend` on re-evaluation (duplicate imports, Vitest
220
+ * isolation boundaries, etc.).
221
+ *
222
+ * Note: `toHaveHttpStatus` is registered here even though it's
223
+ * conceptually transport-level (not GraphQL-specific) — users of
224
+ * `@glubean/graphql` are the ones who need it because the GraphQL
225
+ * envelope uses `httpStatus` instead of `status`. If a future package
226
+ * also exposes an `httpStatus` field, registration idempotency will
227
+ * catch the conflict at boot.
228
+ */
229
+ export function registerGraphqlMatchers() {
230
+ try {
231
+ Expectation.extend({
232
+ toHaveHttpStatus,
233
+ toHaveGraphqlData,
234
+ toHaveGraphqlNoErrors,
235
+ toHaveGraphqlErrorCode,
236
+ toHaveGraphqlExtension,
237
+ });
238
+ }
239
+ catch (err) {
240
+ if (err instanceof Error && /already exists/.test(err.message)) {
241
+ return;
242
+ }
243
+ throw err;
244
+ }
245
+ }
246
+ //# sourceMappingURL=matchers.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"matchers.js","sourceRoot":"","sources":["../../src/contract/matchers.ts"],"names":[],"mappings":"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"}