buro26-strapi-graphql 0.0.26 → 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.
Files changed (57) hide show
  1. package/README.md +458 -439
  2. package/dist/server/index.d.ts +14 -8
  3. package/dist/server/index.js +1 -0
  4. package/dist/server/nexus/index.d.ts +1 -0
  5. package/dist/server/nexus/index.js +17 -0
  6. package/dist/server/nexus/plugins/field-authorize.plugin.d.ts +36 -0
  7. package/dist/server/nexus/plugins/field-authorize.plugin.js +122 -0
  8. package/dist/server/nexus/plugins/index.d.ts +2 -0
  9. package/dist/server/nexus/plugins/index.js +18 -0
  10. package/dist/server/nexus/plugins/middleware.plugin.d.ts +25 -0
  11. package/dist/server/nexus/plugins/middleware.plugin.js +63 -0
  12. package/dist/server/register.js +6 -7
  13. package/dist/server/services/file-service.d.ts +7 -6
  14. package/dist/server/services/file-service.js +36 -30
  15. package/dist/server/services/index.d.ts +1 -1
  16. package/dist/server/types.d.ts +28 -13
  17. package/dist/server/utils/collection-type.d.ts +13 -2
  18. package/dist/server/utils/collection-type.js +13 -2
  19. package/dist/server/utils/field-builder/builder.d.ts +33 -0
  20. package/dist/server/utils/field-builder/builder.js +552 -0
  21. package/dist/server/utils/field-builder/helpers.d.ts +34 -0
  22. package/dist/server/utils/field-builder/helpers.js +78 -0
  23. package/dist/server/utils/field-builder/index.d.ts +3 -0
  24. package/dist/server/utils/field-builder/index.js +19 -0
  25. package/dist/server/utils/field-builder/types.d.ts +371 -0
  26. package/dist/server/utils/field-builder/types.js +2 -0
  27. package/dist/server/utils/graphql-extension.d.ts +29 -3
  28. package/dist/server/utils/graphql-extension.js +94 -26
  29. package/dist/server/utils/index.d.ts +3 -1
  30. package/dist/server/utils/index.js +12 -1
  31. package/dist/server/utils/loaders.d.ts +94 -0
  32. package/dist/server/utils/loaders.js +166 -0
  33. package/dist/server/utils/misc.d.ts +6 -0
  34. package/dist/server/utils/misc.js +6 -0
  35. package/dist/server/utils/override.d.ts +34 -1
  36. package/dist/server/utils/override.js +54 -26
  37. package/dist/server/utils/registry/field-protection.d.ts +43 -0
  38. package/dist/server/utils/registry/field-protection.js +65 -0
  39. package/dist/server/utils/registry/global-registry.d.ts +63 -0
  40. package/dist/server/utils/registry/global-registry.js +56 -0
  41. package/dist/server/utils/registry/index.d.ts +3 -0
  42. package/dist/server/utils/registry/index.js +19 -0
  43. package/dist/server/utils/registry/resolve-field.d.ts +1 -0
  44. package/dist/server/utils/registry/resolve-field.js +67 -0
  45. package/dist/server/utils/shadow-crud.d.ts +28 -1
  46. package/dist/server/utils/shadow-crud.js +32 -1
  47. package/dist/server/utils/single-type.d.ts +30 -1
  48. package/dist/server/utils/single-type.js +25 -0
  49. package/dist/server/utils/strapi-compat.d.ts +85 -0
  50. package/dist/server/utils/strapi-compat.js +194 -0
  51. package/dist/server/utils/types.d.ts +25 -1
  52. package/dist/tsconfig.server.tsbuildinfo +1 -1
  53. package/package.json +7 -4
  54. package/dist/server/utils/field-builder.d.ts +0 -31
  55. package/dist/server/utils/field-builder.js +0 -113
  56. package/dist/server/utils/middleware.d.ts +0 -17
  57. package/dist/server/utils/middleware.js +0 -77
@@ -0,0 +1,33 @@
1
+ import type { FieldExtensionBuilder, FieldExtensionOptions, GraphQLFieldHook } from './types';
2
+ import type { ResolverFunction } from '../../types';
3
+ import type { Middleware } from '../../nexus';
4
+ /**
5
+ * Internal state for the builder.
6
+ */
7
+ type BuilderState<Type = any, TArgs = any, TOutput = any> = {
8
+ options: FieldExtensionOptions<Type, TArgs, TOutput>;
9
+ initFunctions: Array<() => void>;
10
+ typeFunctions: Array<() => unknown[]>;
11
+ middlewareFunctions: Middleware[];
12
+ authorizeFunctions: ResolverFunction<Partial<Type> | null, TArgs, boolean>[];
13
+ hooks: GraphQLFieldHook[];
14
+ built: boolean;
15
+ };
16
+ /**
17
+ * Create a field builder: the recommended way to add or replace a GraphQL field.
18
+ *
19
+ * See {@link FieldExtensionBuilder} for all methods and the execution order.
20
+ *
21
+ * @param rootType - Shorthand for `.rootType(...)`. Prefer calling `.rootType()` so presets can be applied
22
+ * before the type is known.
23
+ * @param _state - Internal; do not pass.
24
+ *
25
+ * @example
26
+ * export default createGraphQLFieldBuilder()
27
+ * .rootType('Customer')
28
+ * .fieldName('fullName')
29
+ * .outputType('String')
30
+ * .resolver((customer) => [customer?.firstName, customer?.lastName].filter(Boolean).join(' '))
31
+ */
32
+ export declare const createGraphQLFieldBuilder: <Type = any, TArgs = any, TOutput = Type>(rootType?: string, _state?: BuilderState<Type, TArgs, TOutput>) => FieldExtensionBuilder<Type, TArgs, TOutput>;
33
+ export {};
@@ -0,0 +1,552 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.resetBuiltFields = exports.createGraphQLFieldBuilder = void 0;
4
+ const nexus_1 = require("nexus");
5
+ const utils_1 = require("@strapi/utils");
6
+ const immer_1 = require("immer");
7
+ const zod_1 = require("zod");
8
+ const graphql_extension_1 = require("../graphql-extension");
9
+ const helpers_1 = require("./helpers");
10
+ const resolve_field_1 = require("../registry/resolve-field");
11
+ const field_authorize_plugin_1 = require("../../nexus/plugins/field-authorize.plugin");
12
+ const strapi_compat_1 = require("../strapi-compat");
13
+ const loaders_1 = require("../loaders");
14
+ /**
15
+ * Helper to update builder state immutably using Immer.
16
+ */
17
+ function updateState(state, updater) {
18
+ return (0, immer_1.produce)(state, updater);
19
+ }
20
+ /**
21
+ * Compose all middleware functions into a single resolver.
22
+ */
23
+ function composeMiddlewares(resolver, middlewares) {
24
+ return middlewares.reduceRight((next, mw) => mw(next), resolver);
25
+ }
26
+ /**
27
+ * Wrap resolver with Zod validation if schema is provided.
28
+ */
29
+ function withArgValidation(resolver, schema) {
30
+ if (!schema)
31
+ return resolver;
32
+ return async (parent, args, ctx, info) => {
33
+ try {
34
+ const parsedArgs = schema.parse(args);
35
+ return resolver(parent, parsedArgs, ctx, info);
36
+ }
37
+ catch (err) {
38
+ if (err instanceof zod_1.ZodError) {
39
+ throw new utils_1.errors.ValidationError('Invalid arguments', { cause: err });
40
+ }
41
+ throw err;
42
+ }
43
+ };
44
+ }
45
+ /**
46
+ * Wrap resolver with tracing handlers.
47
+ */
48
+ function withTracing(resolver, traceHandlers) {
49
+ if (!traceHandlers.length)
50
+ return resolver;
51
+ return async (parent, args, ctx, info) => {
52
+ const start = Date.now();
53
+ let result, error;
54
+ try {
55
+ result = await resolver(parent, args, ctx, info);
56
+ return result;
57
+ }
58
+ catch (err) {
59
+ error = err;
60
+ throw err;
61
+ }
62
+ finally {
63
+ const durationMs = Date.now() - start;
64
+ for (const handler of traceHandlers) {
65
+ Promise.resolve()
66
+ .then(() => {
67
+ var _a;
68
+ return handler({
69
+ type: (_a = info === null || info === void 0 ? void 0 : info.parentType) === null || _a === void 0 ? void 0 : _a.name,
70
+ fieldName: info === null || info === void 0 ? void 0 : info.fieldName,
71
+ durationMs,
72
+ args,
73
+ context: ctx,
74
+ result,
75
+ error
76
+ });
77
+ })
78
+ .catch((err) => {
79
+ // Swallow errors in trace handlers
80
+ console.error('[TRACE HANDLER ERROR]', err);
81
+ });
82
+ }
83
+ }
84
+ };
85
+ }
86
+ /**
87
+ * Run before/after resolve hooks.
88
+ */
89
+ async function runHooks(hooks, when, ...args) {
90
+ for (const hook of hooks) {
91
+ if (hook[when]) {
92
+ // @ts-ignore
93
+ await hook[when](...args);
94
+ }
95
+ }
96
+ }
97
+ /**
98
+ * Create a field builder: the recommended way to add or replace a GraphQL field.
99
+ *
100
+ * See {@link FieldExtensionBuilder} for all methods and the execution order.
101
+ *
102
+ * @param rootType - Shorthand for `.rootType(...)`. Prefer calling `.rootType()` so presets can be applied
103
+ * before the type is known.
104
+ * @param _state - Internal; do not pass.
105
+ *
106
+ * @example
107
+ * export default createGraphQLFieldBuilder()
108
+ * .rootType('Customer')
109
+ * .fieldName('fullName')
110
+ * .outputType('String')
111
+ * .resolver((customer) => [customer?.firstName, customer?.lastName].filter(Boolean).join(' '))
112
+ */
113
+ const createGraphQLFieldBuilder = (rootType, _state) => {
114
+ const initialState = _state !== null && _state !== void 0 ? _state : {
115
+ options: {
116
+ rootType: rootType !== null && rootType !== void 0 ? rootType : '',
117
+ fieldName: undefined,
118
+ resolver: undefined,
119
+ resolverConfig: { policies: [], middlewares: [] },
120
+ typeDefs: '',
121
+ types: () => [],
122
+ args: {},
123
+ outputType: undefined,
124
+ tags: [],
125
+ traceHandlers: []
126
+ },
127
+ initFunctions: [],
128
+ typeFunctions: [],
129
+ middlewareFunctions: [],
130
+ authorizeFunctions: [],
131
+ hooks: [],
132
+ built: false
133
+ };
134
+ function ensureNotBuilt(state) {
135
+ if (state.built) {
136
+ throw new utils_1.errors.ApplicationError('Builder has already been built.');
137
+ }
138
+ }
139
+ const builder = {
140
+ rootType(type) {
141
+ ensureNotBuilt(initialState);
142
+ if (initialState.options.rootType) {
143
+ throw new Error('Root type has already been set.');
144
+ }
145
+ return (0, exports.createGraphQLFieldBuilder)(undefined, updateState(initialState, (draft) => {
146
+ draft.options.rootType = type;
147
+ }));
148
+ },
149
+ fieldName(name) {
150
+ ensureNotBuilt(initialState);
151
+ if (initialState.options.fieldName) {
152
+ throw new Error('Field name has already been set.');
153
+ }
154
+ return (0, exports.createGraphQLFieldBuilder)(undefined, updateState(initialState, (draft) => {
155
+ draft.options.fieldName = name;
156
+ }));
157
+ },
158
+ relation(contentType, attribute, relationOptions = {}) {
159
+ ensureNotBuilt(initialState);
160
+ if (initialState.options.relation) {
161
+ throw new Error('Relation has already been set.');
162
+ }
163
+ if (initialState.options.fieldName && initialState.options.fieldName !== attribute) {
164
+ throw new Error(`Field name "${initialState.options.fieldName}" does not match relation attribute "${attribute}".`);
165
+ }
166
+ return (0, exports.createGraphQLFieldBuilder)(undefined, updateState(initialState, (draft) => {
167
+ draft.options.fieldName = attribute;
168
+ draft.options.relation = { contentType, attribute, batch: Boolean(relationOptions.batch) };
169
+ }));
170
+ },
171
+ description(text) {
172
+ ensureNotBuilt(initialState);
173
+ return (0, exports.createGraphQLFieldBuilder)(undefined, updateState(initialState, (draft) => {
174
+ draft.options.description = text;
175
+ }));
176
+ },
177
+ deprecate(reason) {
178
+ ensureNotBuilt(initialState);
179
+ return (0, exports.createGraphQLFieldBuilder)(undefined, updateState(initialState, (draft) => {
180
+ draft.options.deprecationReason = reason;
181
+ }));
182
+ },
183
+ init(fn) {
184
+ ensureNotBuilt(initialState);
185
+ return (0, exports.createGraphQLFieldBuilder)(undefined, updateState(initialState, (draft) => {
186
+ draft.initFunctions.push(fn);
187
+ }));
188
+ },
189
+ resolver(fn) {
190
+ ensureNotBuilt(initialState);
191
+ if (initialState.options.resolver) {
192
+ throw new Error('Resolver function has already been set.');
193
+ }
194
+ return (0, exports.createGraphQLFieldBuilder)(undefined, updateState(initialState, (draft) => {
195
+ draft.options.resolver = fn;
196
+ }));
197
+ },
198
+ middleware(middleware) {
199
+ ensureNotBuilt(initialState);
200
+ return (0, exports.createGraphQLFieldBuilder)(undefined, updateState(initialState, (draft) => {
201
+ draft.middlewareFunctions.push(middleware);
202
+ }));
203
+ },
204
+ resolverConfig(config) {
205
+ ensureNotBuilt(initialState);
206
+ const resolverConfig = typeof config === 'function' ? config() : config;
207
+ return (0, exports.createGraphQLFieldBuilder)(undefined, updateState(initialState, (draft) => {
208
+ var _a, _b;
209
+ draft.options.resolverConfig = {
210
+ policies: [
211
+ ...(((_a = draft.options.resolverConfig) === null || _a === void 0 ? void 0 : _a.policies) || []),
212
+ ...(resolverConfig.policies || [])
213
+ ],
214
+ middlewares: [
215
+ ...(((_b = draft.options.resolverConfig) === null || _b === void 0 ? void 0 : _b.middlewares) || []),
216
+ ...(resolverConfig.middlewares || [])
217
+ ],
218
+ ...(resolverConfig.auth !== undefined && {
219
+ auth: resolverConfig.auth
220
+ })
221
+ };
222
+ if (resolverConfig.auth !== undefined) {
223
+ draft.options.auth = resolverConfig.auth !== false;
224
+ }
225
+ }));
226
+ },
227
+ typeDefs(sdl) {
228
+ ensureNotBuilt(initialState);
229
+ return (0, exports.createGraphQLFieldBuilder)(undefined, updateState(initialState, (draft) => {
230
+ draft.options.typeDefs = `${draft.options.typeDefs}\n${sdl}`;
231
+ }));
232
+ },
233
+ types(typesFn) {
234
+ ensureNotBuilt(initialState);
235
+ const fn = typeof typesFn === 'function' ? typesFn : () => typesFn;
236
+ return (0, exports.createGraphQLFieldBuilder)(undefined, updateState(initialState, (draft) => {
237
+ draft.typeFunctions.push(fn);
238
+ }));
239
+ },
240
+ args(a) {
241
+ ensureNotBuilt(initialState);
242
+ return (0, exports.createGraphQLFieldBuilder)(undefined, updateState(initialState, (draft) => {
243
+ draft.options.args = a;
244
+ }));
245
+ },
246
+ validateArgs(schema) {
247
+ ensureNotBuilt(initialState);
248
+ return (0, exports.createGraphQLFieldBuilder)(undefined, updateState(initialState, (draft) => {
249
+ draft.options.validateArgsSchema = schema;
250
+ }));
251
+ },
252
+ outputType(type) {
253
+ ensureNotBuilt(initialState);
254
+ return (0, exports.createGraphQLFieldBuilder)(undefined, updateState(initialState, (draft) => {
255
+ draft.options.outputType = type;
256
+ }));
257
+ },
258
+ withAuth(auth) {
259
+ ensureNotBuilt(initialState);
260
+ return (0, exports.createGraphQLFieldBuilder)(undefined, updateState(initialState, (draft) => {
261
+ draft.options.auth = auth;
262
+ draft.options.resolverConfig = {
263
+ ...draft.options.resolverConfig,
264
+ auth
265
+ };
266
+ }));
267
+ },
268
+ authorize(authorizeFn) {
269
+ ensureNotBuilt(initialState);
270
+ return (0, exports.createGraphQLFieldBuilder)(undefined, updateState(initialState, (draft) => {
271
+ draft.authorizeFunctions.push(authorizeFn);
272
+ }));
273
+ },
274
+ tag(tag) {
275
+ ensureNotBuilt(initialState);
276
+ return (0, exports.createGraphQLFieldBuilder)(undefined, updateState(initialState, (draft) => {
277
+ draft.options.tags = [...(draft.options.tags || []), tag];
278
+ }));
279
+ },
280
+ tags(tags) {
281
+ ensureNotBuilt(initialState);
282
+ return (0, exports.createGraphQLFieldBuilder)(undefined, updateState(initialState, (draft) => {
283
+ draft.options.tags = [...(draft.options.tags || []), ...tags];
284
+ }));
285
+ },
286
+ use(preset) {
287
+ return preset(this);
288
+ },
289
+ hook(hook) {
290
+ ensureNotBuilt(initialState);
291
+ return (0, exports.createGraphQLFieldBuilder)(undefined, updateState(initialState, (draft) => {
292
+ draft.hooks.push(hook);
293
+ }));
294
+ },
295
+ validate() {
296
+ var _a, _b, _c, _d;
297
+ const { options } = initialState;
298
+ if (!options.rootType) {
299
+ throw new utils_1.errors.ApplicationError('Root type must be provided via rootType()');
300
+ }
301
+ if (!options.fieldName) {
302
+ throw new utils_1.errors.ApplicationError('Field name must be provided via fieldName()');
303
+ }
304
+ if (!options.outputType && !options.relation) {
305
+ throw new utils_1.errors.ApplicationError(`Output type must be provided for field "${options.fieldName}" via outputType()`);
306
+ }
307
+ if (!options.resolver && !options.relation) {
308
+ throw new utils_1.errors.ApplicationError(`Resolver function must be provided for field "${options.fieldName}" via resolver()`);
309
+ }
310
+ const isRootType = ['Query', 'Mutation'].includes(options.rootType);
311
+ if (!isRootType &&
312
+ (!!((_b = (_a = options.resolverConfig) === null || _a === void 0 ? void 0 : _a.middlewares) === null || _b === void 0 ? void 0 : _b.length) ||
313
+ !!((_d = (_c = options.resolverConfig) === null || _c === void 0 ? void 0 : _c.policies) === null || _d === void 0 ? void 0 : _d.length))) {
314
+ throw new utils_1.errors.ApplicationError(`Resolver config is only supported for Query and Mutation root types.`);
315
+ }
316
+ },
317
+ describe() {
318
+ // Return a snapshot of the current config for debugging/docs
319
+ const { options, initFunctions, typeFunctions, middlewareFunctions, authorizeFunctions, hooks } = initialState;
320
+ return {
321
+ options,
322
+ initFunctions: initFunctions.map(fn => fn.name || 'anonymous'),
323
+ typeFunctions: typeFunctions.length,
324
+ middlewareFunctions: middlewareFunctions.length,
325
+ authorizeFunctions: authorizeFunctions.length,
326
+ hooks: hooks.length
327
+ };
328
+ },
329
+ test({ parent, args, context, info, skipMiddleware = false, skipAuth = false } = {}) {
330
+ this.validate();
331
+ const { options } = initialState;
332
+ if (!options.resolver) {
333
+ throw new utils_1.errors.ApplicationError(`test() needs a resolver; relation fields without a custom resolver need a running Strapi instance`);
334
+ }
335
+ // Same pipeline as in production: globals, withAuth, authorize, middlewares, validation, tracing, hooks
336
+ const pipeline = (0, resolve_field_1.resolveFieldPipeline)(options.rootType, options.fieldName, fieldExtensions(initialState));
337
+ const testInfo = (info !== null && info !== void 0 ? info : {
338
+ fieldName: options.fieldName,
339
+ parentType: { name: options.rootType },
340
+ });
341
+ const resolver = composeResolver(initialState, options.resolver);
342
+ const withMiddlewares = skipMiddleware ? resolver : composeMiddlewares(resolver, pipeline.middlewares);
343
+ return (async () => {
344
+ if (!skipAuth) {
345
+ await (0, field_authorize_plugin_1.runAuthorization)(pipeline, parent, args, context, testInfo);
346
+ }
347
+ return withMiddlewares(parent, args, context, testInfo);
348
+ })();
349
+ },
350
+ trace(handler) {
351
+ ensureNotBuilt(initialState);
352
+ return (0, exports.createGraphQLFieldBuilder)(undefined, updateState(initialState, (draft) => {
353
+ if (!draft.options.traceHandlers) {
354
+ draft.options.traceHandlers = [];
355
+ }
356
+ // If no handler is provided, use the default
357
+ draft.options.traceHandlers.push(handler !== null && handler !== void 0 ? handler : defaultTraceHandler);
358
+ }));
359
+ },
360
+ build() {
361
+ ensureNotBuilt(initialState);
362
+ this.validate();
363
+ const { options, initFunctions, typeFunctions } = initialState;
364
+ registerBuiltField(options.rootType, options.fieldName);
365
+ const isRootType = ['Query', 'Mutation'].includes(options.rootType);
366
+ // Authorization and middlewares (own + global + protectFields) are applied by the package's Nexus
367
+ // plugins when the schema is built, so they are not composed into the resolver here.
368
+ const extensions = fieldExtensions(initialState);
369
+ const nexusExtension = (0, nexus_1.extendType)({
370
+ type: options.rootType,
371
+ definition(t) {
372
+ var _a, _b;
373
+ const nexusArgs = options.args ? (0, helpers_1.parseArgs)(options.args) : {};
374
+ if (options.relation) {
375
+ const { contentType, attribute } = options.relation;
376
+ const customResolver = (_a = options.resolver) !== null && _a !== void 0 ? _a : (options.relation.batch ? batchedRelationResolver(options.relation) : undefined);
377
+ for (const spec of (0, strapi_compat_1.relationFieldSpecs)(contentType, attribute, customResolver)) {
378
+ t.field(spec.name, {
379
+ type: spec.type,
380
+ args: { ...((_b = spec.args) !== null && _b !== void 0 ? _b : {}), ...nexusArgs },
381
+ description: options.description,
382
+ deprecation: options.deprecationReason,
383
+ extensions: extensions,
384
+ resolve: composeResolver(initialState, spec.resolve)
385
+ });
386
+ }
387
+ return;
388
+ }
389
+ t.field(options.fieldName, {
390
+ type: options.outputType,
391
+ args: nexusArgs,
392
+ description: options.description,
393
+ deprecation: options.deprecationReason,
394
+ extensions: extensions,
395
+ resolve: composeResolver(initialState, options.resolver)
396
+ });
397
+ }
398
+ });
399
+ return (0, graphql_extension_1.createGraphQLExtension)((_) => ({
400
+ init: () => {
401
+ for (const initFn of initFunctions) {
402
+ initFn();
403
+ }
404
+ },
405
+ typeDefs: () => options.typeDefs || '',
406
+ types: () => [
407
+ nexusExtension,
408
+ ...typeFunctions.flatMap((fn) => fn())
409
+ ],
410
+ resolversConfig: () => ({
411
+ // Keep Strapi's `<target>.find` permission scope on (re)defined relation fields
412
+ ...(options.relation
413
+ ? (0, strapi_compat_1.relationResolversConfig)(options.relation.contentType, options.relation.attribute)
414
+ : {}),
415
+ ...(isRootType
416
+ ? { [options.rootType]: { [options.fieldName]: options.resolverConfig || {} } }
417
+ : {})
418
+ })
419
+ }));
420
+ }
421
+ };
422
+ // The implementation is untyped internally; the public generics are enforced by the interface
423
+ return Object.freeze(builder);
424
+ };
425
+ exports.createGraphQLFieldBuilder = createGraphQLFieldBuilder;
426
+ const builtFields = new Set();
427
+ /**
428
+ * Warn when two builders define the same `Type.field`: only one of them ends up in the schema, which is
429
+ * almost always a mistake (e.g. a copied file). Replacing a field Strapi generates is not reported.
430
+ */
431
+ function registerBuiltField(type, fieldName) {
432
+ var _a, _b, _c;
433
+ const key = `${type}.${fieldName}`;
434
+ if (builtFields.has(key)) {
435
+ const warn = (_c = (_b = (_a = globalThis.strapi) === null || _a === void 0 ? void 0 : _a.log) === null || _b === void 0 ? void 0 : _b.warn) !== null && _c !== void 0 ? _c : console.warn;
436
+ warn(`[buro26-strapi-graphql] Field "${key}" is defined by more than one field builder; only one definition is used.`);
437
+ }
438
+ builtFields.add(key);
439
+ }
440
+ /** @internal Only for tests. */
441
+ function resetBuiltFields() {
442
+ builtFields.clear();
443
+ }
444
+ exports.resetBuiltFields = resetBuiltFields;
445
+ /**
446
+ * Field extensions read by the Nexus authorize/middleware plugins.
447
+ */
448
+ function fieldExtensions(state) {
449
+ var _a;
450
+ const { options, authorizeFunctions, middlewareFunctions } = state;
451
+ return {
452
+ ...(authorizeFunctions.length > 0 && {
453
+ authorize: async (parent, args, ctx, info) => {
454
+ for (const authorize of authorizeFunctions) {
455
+ const result = await authorize(parent, args, ctx, info);
456
+ if (result !== true)
457
+ return result instanceof Error ? result : false;
458
+ }
459
+ return true;
460
+ }
461
+ }),
462
+ middlewares: [...middlewareFunctions],
463
+ builder: {
464
+ tags: [...((_a = options.tags) !== null && _a !== void 0 ? _a : [])],
465
+ requireAuth: Boolean(options.auth)
466
+ }
467
+ };
468
+ }
469
+ /**
470
+ * The resolver as executed inside the middlewares: argument validation, hooks and tracing.
471
+ */
472
+ function composeResolver(state, baseResolver) {
473
+ var _a;
474
+ const { options, hooks } = state;
475
+ const validated = withArgValidation(baseResolver, options.validateArgsSchema);
476
+ const withHooks = async (parent, args, context, info) => {
477
+ await runHooks(hooks, 'beforeResolve', parent, args, context, info);
478
+ const result = await validated(parent, args, context, info);
479
+ await runHooks(hooks, 'afterResolve', result, parent, args, context, info);
480
+ return result;
481
+ };
482
+ return withTracing(withHooks, (_a = options.traceHandlers) !== null && _a !== void 0 ? _a : []);
483
+ }
484
+ /**
485
+ * Resolver for `.relation(..., { batch: true })`: loads the related entity of all parents in the request
486
+ * with one query, sanitizes it like Strapi does and returns the shape the relation field expects.
487
+ */
488
+ function batchedRelationResolver({ contentType, attribute }) {
489
+ var _a, _b;
490
+ const attr = (_b = (_a = strapi.getModel(contentType)) === null || _a === void 0 ? void 0 : _a.attributes) === null || _b === void 0 ? void 0 : _b[attribute];
491
+ if (!attr || attr.type !== 'relation') {
492
+ throw new utils_1.errors.ApplicationError(`"${contentType}.${attribute}" is not a relation`);
493
+ }
494
+ if (String(attr.relation).endsWith('Many')) {
495
+ throw new utils_1.errors.ApplicationError(`relation(..., { batch: true }) is only supported for to-one relations ("${contentType}.${attribute}" is to-many)`);
496
+ }
497
+ // Returns Strapi's response wrapper (`{ value, info }`), like `buildAssociationResolver`; the relation
498
+ // field spec unwraps it on Strapi v5.
499
+ const { toEntityResponse } = strapi.plugin('graphql').service('format').returnTypes;
500
+ return async (parent, _args, context) => {
501
+ const related = (parent === null || parent === void 0 ? void 0 : parent.id) ? await (0, loaders_1.loaders)(context).relation(contentType, attribute).load(parent.id) : null;
502
+ const sanitized = related ? await (0, strapi_compat_1.sanitizeOutput)(related, attr.target, context) : null;
503
+ return toEntityResponse(sanitized, { args: {}, resourceUID: attr.target });
504
+ };
505
+ }
506
+ function defaultTraceHandler({ type, fieldName, durationMs, args, error }) {
507
+ // Colors
508
+ const green = '\x1b[32m';
509
+ const yellow = '\x1b[33m';
510
+ const red = '\x1b[31m';
511
+ const cyan = '\x1b[36m';
512
+ const reset = '\x1b[0m';
513
+ // Status
514
+ const status = error ? `${red}❌ ERROR${reset}` : `${green}✅ OK${reset}`;
515
+ // Timing color
516
+ let timingColor = green;
517
+ if (durationMs > 1000)
518
+ timingColor = red;
519
+ else if (durationMs > 300)
520
+ timingColor = yellow;
521
+ // Args preview, with proper table formatting for multi-line
522
+ let argsPreview = '';
523
+ try {
524
+ argsPreview = JSON.stringify(args, null, 2);
525
+ if (argsPreview.length > 120)
526
+ argsPreview = argsPreview.slice(0, 117) + '...';
527
+ }
528
+ catch {
529
+ argsPreview = '[unserializable]';
530
+ }
531
+ const argsLines = argsPreview.split('\n');
532
+ const argsTableLines = argsLines.map((line, idx) => idx === 0
533
+ ? `${cyan}│${reset} ${'Args:'.padEnd(12)}${line}`
534
+ : `${cyan}│${reset} ${' '.repeat(12)}${line}`);
535
+ // Table-style output
536
+ const lines = [
537
+ `${cyan}┌─────────────────────────────[GraphQL Field Trace]─────────────────────────────┐${reset}`,
538
+ `${cyan}│${reset} ${'Type:'.padEnd(12)}${type}`,
539
+ `${cyan}│${reset} ${'Field:'.padEnd(12)}${fieldName}`,
540
+ `${cyan}│${reset} ${'Status:'.padEnd(12)}${status}`,
541
+ `${cyan}│${reset} ${'Duration:'.padEnd(12)}${timingColor}${durationMs}ms${reset}`,
542
+ ...argsTableLines,
543
+ `${cyan}└───────────────────────────────────────────────────────────────────────────────┘${reset}`
544
+ ];
545
+ if (error) {
546
+ lines.push(`${red}[Trace Error]${reset} ${error instanceof Error ? error.stack : error}`);
547
+ console.error(lines.join('\n'));
548
+ }
549
+ else {
550
+ console.log(lines.join('\n'));
551
+ }
552
+ }
@@ -0,0 +1,34 @@
1
+ import type { FieldExtensionBuilder, FieldGroupPreset, GraphQLFieldPreset } from './types';
2
+ import type { Middleware } from '../../nexus';
3
+ /**
4
+ * Middleware that throws a `ForbiddenError` ("Not authenticated") when there is no `ctx.state.user`.
5
+ *
6
+ * @deprecated Use `.withAuth(true)` on the field builder; it is checked before authorization runs.
7
+ */
8
+ export declare function makeAuthMiddleware(): Middleware;
9
+ /**
10
+ * Define a reusable preset for `.use()`. Identity function that exists for typing and readability.
11
+ *
12
+ * @example
13
+ * export const adminOnly = createGraphQLFieldPreset((builder) =>
14
+ * builder.withAuth(true).authorize((_p, _a, ctx) => ctx.state.user?.role?.type === 'admin')
15
+ * )
16
+ *
17
+ * createGraphQLFieldBuilder().use(adminOnly).rootType('Query') // ...
18
+ */
19
+ export declare function createGraphQLFieldPreset(preset: GraphQLFieldPreset): GraphQLFieldPreset;
20
+ /**
21
+ * Export several fields from one file. Builds each builder and returns the extensions.
22
+ *
23
+ * @example
24
+ * export default registerFields(fieldA, fieldB)
25
+ */
26
+ export declare function registerFields(...fields: FieldExtensionBuilder[]): import("../..").GraphQLExtension[];
27
+ /**
28
+ * Apply presets to every field in a group (e.g. one auth rule for all fields of a feature).
29
+ * Presets are applied in order. Returns unbuilt builders; export them with {@link registerFields}.
30
+ *
31
+ * @example
32
+ * export default registerFields(...createFieldGroup([fieldA, fieldB], requireUser))
33
+ */
34
+ export declare function createFieldGroup(fields: FieldExtensionBuilder[], ...presets: FieldGroupPreset[]): FieldExtensionBuilder[];