@constructive-io/graphql-query 3.23.3 → 3.24.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/esm/introspect/infer-tables.js +32 -1
- package/esm/introspect/transform-schema.js +9 -0
- package/esm/runtime/index.js +4 -6
- package/introspect/infer-tables.js +32 -1
- package/introspect/transform-schema.d.ts +4 -0
- package/introspect/transform-schema.js +9 -0
- package/package.json +4 -3
- package/runtime/index.d.ts +5 -5
- package/runtime/index.js +5 -7
- package/types/schema.d.ts +8 -0
- package/esm/runtime/localhost-fetch.js +0 -108
- package/runtime/localhost-fetch.d.ts +0 -27
- package/runtime/localhost-fetch.js +0 -112
|
@@ -200,6 +200,10 @@ function buildCleanTable(entityName, entityType, typeMap, queryFields, mutationF
|
|
|
200
200
|
update: mutationOps.update,
|
|
201
201
|
delete: mutationOps.delete,
|
|
202
202
|
patchFieldName,
|
|
203
|
+
bulkInsert: mutationOps.bulkInsert,
|
|
204
|
+
bulkUpsert: mutationOps.bulkUpsert,
|
|
205
|
+
bulkUpdate: mutationOps.bulkUpdate,
|
|
206
|
+
bulkDelete: mutationOps.bulkDelete,
|
|
203
207
|
};
|
|
204
208
|
// Extract description from entity type (PostgreSQL COMMENT), strip smart comments
|
|
205
209
|
const description = commentsEnabled ? stripSmartComments(entityType.description) : undefined;
|
|
@@ -488,14 +492,28 @@ function matchQueryOperations(entityName, queryFields, entityToConnection) {
|
|
|
488
492
|
* - create{EntityName}
|
|
489
493
|
* - update{EntityName} or update{EntityName}ById
|
|
490
494
|
* - delete{EntityName} or delete{EntityName}ById
|
|
495
|
+
* - bulkCreate{PluralName} (bulk insert)
|
|
496
|
+
* - bulkUpsert{PluralName} (bulk upsert)
|
|
497
|
+
* - bulkUpdate{PluralName} (bulk update)
|
|
498
|
+
* - bulkDelete{PluralName} (bulk delete)
|
|
491
499
|
*/
|
|
492
500
|
function matchMutationOperations(entityName, mutationFields) {
|
|
493
501
|
let create = null;
|
|
494
502
|
let update = null;
|
|
495
503
|
let del = null;
|
|
504
|
+
let bulkInsert = null;
|
|
505
|
+
let bulkUpsert = null;
|
|
506
|
+
let bulkUpdate = null;
|
|
507
|
+
let bulkDelete = null;
|
|
496
508
|
const expectedCreate = `create${entityName}`;
|
|
497
509
|
const expectedUpdate = `update${entityName}`;
|
|
498
510
|
const expectedDelete = `delete${entityName}`;
|
|
511
|
+
// Bulk mutation patterns use plural form: bulkCreate{Plural}
|
|
512
|
+
const pluralName = pluralize(entityName);
|
|
513
|
+
const expectedBulkInsert = `bulkCreate${pluralName}`;
|
|
514
|
+
const expectedBulkUpsert = `bulkUpsert${pluralName}`;
|
|
515
|
+
const expectedBulkUpdate = `bulkUpdate${pluralName}`;
|
|
516
|
+
const expectedBulkDelete = `bulkDelete${pluralName}`;
|
|
499
517
|
for (const field of mutationFields) {
|
|
500
518
|
// Exact match for create
|
|
501
519
|
if (field.name === expectedCreate) {
|
|
@@ -519,8 +537,21 @@ function matchMutationOperations(entityName, mutationFields) {
|
|
|
519
537
|
field.name.startsWith(`${expectedDelete}By`))) {
|
|
520
538
|
del = field.name;
|
|
521
539
|
}
|
|
540
|
+
// Bulk mutations
|
|
541
|
+
if (field.name === expectedBulkInsert) {
|
|
542
|
+
bulkInsert = field.name;
|
|
543
|
+
}
|
|
544
|
+
if (field.name === expectedBulkUpsert) {
|
|
545
|
+
bulkUpsert = field.name;
|
|
546
|
+
}
|
|
547
|
+
if (field.name === expectedBulkUpdate) {
|
|
548
|
+
bulkUpdate = field.name;
|
|
549
|
+
}
|
|
550
|
+
if (field.name === expectedBulkDelete) {
|
|
551
|
+
bulkDelete = field.name;
|
|
552
|
+
}
|
|
522
553
|
}
|
|
523
|
-
return { create, update, delete: del };
|
|
554
|
+
return { create, update, delete: del, bulkInsert, bulkUpsert, bulkUpdate, bulkDelete };
|
|
524
555
|
}
|
|
525
556
|
// ============================================================================
|
|
526
557
|
// Constraint Inference
|
|
@@ -241,6 +241,15 @@ export function getTableOperationNames(tables) {
|
|
|
241
241
|
mutations.add(table.query.update);
|
|
242
242
|
if (table.query.delete)
|
|
243
243
|
mutations.add(table.query.delete);
|
|
244
|
+
// Add bulk mutation names
|
|
245
|
+
if (table.query.bulkInsert)
|
|
246
|
+
mutations.add(table.query.bulkInsert);
|
|
247
|
+
if (table.query.bulkUpsert)
|
|
248
|
+
mutations.add(table.query.bulkUpsert);
|
|
249
|
+
if (table.query.bulkUpdate)
|
|
250
|
+
mutations.add(table.query.bulkUpdate);
|
|
251
|
+
if (table.query.bulkDelete)
|
|
252
|
+
mutations.add(table.query.bulkDelete);
|
|
244
253
|
}
|
|
245
254
|
}
|
|
246
255
|
return { queries, mutations };
|
package/esm/runtime/index.js
CHANGED
|
@@ -3,9 +3,9 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Generated ORM clients need runtime dependencies at execution time.
|
|
5
5
|
* This module re-exports so generated code can consolidate imports:
|
|
6
|
-
* - @0no-co/graphql.web
|
|
7
|
-
* - @constructive-io/graphql-types
|
|
8
|
-
* -
|
|
6
|
+
* - @0no-co/graphql.web — parseType, print
|
|
7
|
+
* - @constructive-io/graphql-types — GraphQLAdapter, GraphQLError, QueryResult
|
|
8
|
+
* - @constructive-io/fetch — createFetch (isomorphic *.localhost-aware fetch)
|
|
9
9
|
*
|
|
10
10
|
* gql-ast is intentionally NOT re-exported here because the templates
|
|
11
11
|
* use `import * as t from 'gql-ast'` — mixing it into this namespace
|
|
@@ -16,7 +16,5 @@
|
|
|
16
16
|
* import * as t from 'gql-ast';
|
|
17
17
|
* import type { GraphQLAdapter } from '@constructive-io/graphql-query/runtime';
|
|
18
18
|
*/
|
|
19
|
-
// From @0no-co/graphql.web — GraphQL parsing/printing
|
|
20
19
|
export { parseType, print } from '@0no-co/graphql.web';
|
|
21
|
-
|
|
22
|
-
export { createFetch } from './localhost-fetch';
|
|
20
|
+
export { createFetch } from '@constructive-io/fetch';
|
|
@@ -203,6 +203,10 @@ function buildCleanTable(entityName, entityType, typeMap, queryFields, mutationF
|
|
|
203
203
|
update: mutationOps.update,
|
|
204
204
|
delete: mutationOps.delete,
|
|
205
205
|
patchFieldName,
|
|
206
|
+
bulkInsert: mutationOps.bulkInsert,
|
|
207
|
+
bulkUpsert: mutationOps.bulkUpsert,
|
|
208
|
+
bulkUpdate: mutationOps.bulkUpdate,
|
|
209
|
+
bulkDelete: mutationOps.bulkDelete,
|
|
206
210
|
};
|
|
207
211
|
// Extract description from entity type (PostgreSQL COMMENT), strip smart comments
|
|
208
212
|
const description = commentsEnabled ? (0, utils_1.stripSmartComments)(entityType.description) : undefined;
|
|
@@ -491,14 +495,28 @@ function matchQueryOperations(entityName, queryFields, entityToConnection) {
|
|
|
491
495
|
* - create{EntityName}
|
|
492
496
|
* - update{EntityName} or update{EntityName}ById
|
|
493
497
|
* - delete{EntityName} or delete{EntityName}ById
|
|
498
|
+
* - bulkCreate{PluralName} (bulk insert)
|
|
499
|
+
* - bulkUpsert{PluralName} (bulk upsert)
|
|
500
|
+
* - bulkUpdate{PluralName} (bulk update)
|
|
501
|
+
* - bulkDelete{PluralName} (bulk delete)
|
|
494
502
|
*/
|
|
495
503
|
function matchMutationOperations(entityName, mutationFields) {
|
|
496
504
|
let create = null;
|
|
497
505
|
let update = null;
|
|
498
506
|
let del = null;
|
|
507
|
+
let bulkInsert = null;
|
|
508
|
+
let bulkUpsert = null;
|
|
509
|
+
let bulkUpdate = null;
|
|
510
|
+
let bulkDelete = null;
|
|
499
511
|
const expectedCreate = `create${entityName}`;
|
|
500
512
|
const expectedUpdate = `update${entityName}`;
|
|
501
513
|
const expectedDelete = `delete${entityName}`;
|
|
514
|
+
// Bulk mutation patterns use plural form: bulkCreate{Plural}
|
|
515
|
+
const pluralName = (0, inflekt_1.pluralize)(entityName);
|
|
516
|
+
const expectedBulkInsert = `bulkCreate${pluralName}`;
|
|
517
|
+
const expectedBulkUpsert = `bulkUpsert${pluralName}`;
|
|
518
|
+
const expectedBulkUpdate = `bulkUpdate${pluralName}`;
|
|
519
|
+
const expectedBulkDelete = `bulkDelete${pluralName}`;
|
|
502
520
|
for (const field of mutationFields) {
|
|
503
521
|
// Exact match for create
|
|
504
522
|
if (field.name === expectedCreate) {
|
|
@@ -522,8 +540,21 @@ function matchMutationOperations(entityName, mutationFields) {
|
|
|
522
540
|
field.name.startsWith(`${expectedDelete}By`))) {
|
|
523
541
|
del = field.name;
|
|
524
542
|
}
|
|
543
|
+
// Bulk mutations
|
|
544
|
+
if (field.name === expectedBulkInsert) {
|
|
545
|
+
bulkInsert = field.name;
|
|
546
|
+
}
|
|
547
|
+
if (field.name === expectedBulkUpsert) {
|
|
548
|
+
bulkUpsert = field.name;
|
|
549
|
+
}
|
|
550
|
+
if (field.name === expectedBulkUpdate) {
|
|
551
|
+
bulkUpdate = field.name;
|
|
552
|
+
}
|
|
553
|
+
if (field.name === expectedBulkDelete) {
|
|
554
|
+
bulkDelete = field.name;
|
|
555
|
+
}
|
|
525
556
|
}
|
|
526
|
-
return { create, update, delete: del };
|
|
557
|
+
return { create, update, delete: del, bulkInsert, bulkUpsert, bulkUpdate, bulkDelete };
|
|
527
558
|
}
|
|
528
559
|
// ============================================================================
|
|
529
560
|
// Constraint Inference
|
|
@@ -62,6 +62,10 @@ export declare function getTableOperationNames(tables: Array<{
|
|
|
62
62
|
create: string;
|
|
63
63
|
update: string | null;
|
|
64
64
|
delete: string | null;
|
|
65
|
+
bulkInsert?: string | null;
|
|
66
|
+
bulkUpsert?: string | null;
|
|
67
|
+
bulkUpdate?: string | null;
|
|
68
|
+
bulkDelete?: string | null;
|
|
65
69
|
};
|
|
66
70
|
}>): TableOperationNames;
|
|
67
71
|
/**
|
|
@@ -253,6 +253,15 @@ function getTableOperationNames(tables) {
|
|
|
253
253
|
mutations.add(table.query.update);
|
|
254
254
|
if (table.query.delete)
|
|
255
255
|
mutations.add(table.query.delete);
|
|
256
|
+
// Add bulk mutation names
|
|
257
|
+
if (table.query.bulkInsert)
|
|
258
|
+
mutations.add(table.query.bulkInsert);
|
|
259
|
+
if (table.query.bulkUpsert)
|
|
260
|
+
mutations.add(table.query.bulkUpsert);
|
|
261
|
+
if (table.query.bulkUpdate)
|
|
262
|
+
mutations.add(table.query.bulkUpdate);
|
|
263
|
+
if (table.query.bulkDelete)
|
|
264
|
+
mutations.add(table.query.bulkDelete);
|
|
256
265
|
}
|
|
257
266
|
}
|
|
258
267
|
return { queries, mutations };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@constructive-io/graphql-query",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.24.0",
|
|
4
4
|
"description": "Constructive GraphQL Query",
|
|
5
5
|
"author": "Constructive <developers@constructive.io>",
|
|
6
6
|
"main": "index.js",
|
|
@@ -30,13 +30,14 @@
|
|
|
30
30
|
},
|
|
31
31
|
"dependencies": {
|
|
32
32
|
"@0no-co/graphql.web": "^1.1.2",
|
|
33
|
+
"@constructive-io/fetch": "^1.0.0",
|
|
33
34
|
"@constructive-io/graphql-types": "^3.9.1",
|
|
34
35
|
"ajv": "^8.18.0",
|
|
35
36
|
"gql-ast": "^3.9.1",
|
|
36
37
|
"grafast": "1.0.0",
|
|
37
38
|
"graphile-build-pg": "5.0.0",
|
|
38
39
|
"graphile-config": "1.0.0",
|
|
39
|
-
"graphile-settings": "^
|
|
40
|
+
"graphile-settings": "^5.0.0",
|
|
40
41
|
"graphql": "16.13.0",
|
|
41
42
|
"inflection": "^3.0.0",
|
|
42
43
|
"inflekt": "^0.7.1",
|
|
@@ -53,5 +54,5 @@
|
|
|
53
54
|
"devDependencies": {
|
|
54
55
|
"makage": "^0.3.0"
|
|
55
56
|
},
|
|
56
|
-
"gitHead": "
|
|
57
|
+
"gitHead": "70f9d2052fe9218c65b82e896d1660d4dc6a51c8"
|
|
57
58
|
}
|
package/runtime/index.d.ts
CHANGED
|
@@ -3,9 +3,9 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Generated ORM clients need runtime dependencies at execution time.
|
|
5
5
|
* This module re-exports so generated code can consolidate imports:
|
|
6
|
-
* - @0no-co/graphql.web
|
|
7
|
-
* - @constructive-io/graphql-types
|
|
8
|
-
* -
|
|
6
|
+
* - @0no-co/graphql.web — parseType, print
|
|
7
|
+
* - @constructive-io/graphql-types — GraphQLAdapter, GraphQLError, QueryResult
|
|
8
|
+
* - @constructive-io/fetch — createFetch (isomorphic *.localhost-aware fetch)
|
|
9
9
|
*
|
|
10
10
|
* gql-ast is intentionally NOT re-exported here because the templates
|
|
11
11
|
* use `import * as t from 'gql-ast'` — mixing it into this namespace
|
|
@@ -18,5 +18,5 @@
|
|
|
18
18
|
*/
|
|
19
19
|
export { parseType, print } from '@0no-co/graphql.web';
|
|
20
20
|
export type { GraphQLAdapter, GraphQLError, QueryResult } from '@constructive-io/graphql-types';
|
|
21
|
-
export { createFetch } from '
|
|
22
|
-
export type { FetchFunction } from '
|
|
21
|
+
export { createFetch } from '@constructive-io/fetch';
|
|
22
|
+
export type { FetchFunction } from '@constructive-io/fetch';
|
package/runtime/index.js
CHANGED
|
@@ -4,9 +4,9 @@
|
|
|
4
4
|
*
|
|
5
5
|
* Generated ORM clients need runtime dependencies at execution time.
|
|
6
6
|
* This module re-exports so generated code can consolidate imports:
|
|
7
|
-
* - @0no-co/graphql.web
|
|
8
|
-
* - @constructive-io/graphql-types
|
|
9
|
-
* -
|
|
7
|
+
* - @0no-co/graphql.web — parseType, print
|
|
8
|
+
* - @constructive-io/graphql-types — GraphQLAdapter, GraphQLError, QueryResult
|
|
9
|
+
* - @constructive-io/fetch — createFetch (isomorphic *.localhost-aware fetch)
|
|
10
10
|
*
|
|
11
11
|
* gql-ast is intentionally NOT re-exported here because the templates
|
|
12
12
|
* use `import * as t from 'gql-ast'` — mixing it into this namespace
|
|
@@ -19,10 +19,8 @@
|
|
|
19
19
|
*/
|
|
20
20
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
21
21
|
exports.createFetch = exports.parseType = void 0;
|
|
22
|
-
// From @0no-co/graphql.web — GraphQL parsing/printing
|
|
23
22
|
var graphql_web_1 = require("@0no-co/graphql.web");
|
|
24
23
|
Object.defineProperty(exports, "parseType", { enumerable: true, get: function () { return graphql_web_1.parseType; } });
|
|
25
24
|
Object.defineProperty(exports, "print", { enumerable: true, get: function () { return graphql_web_1.print; } });
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
Object.defineProperty(exports, "createFetch", { enumerable: true, get: function () { return localhost_fetch_1.createFetch; } });
|
|
25
|
+
var fetch_1 = require("@constructive-io/fetch");
|
|
26
|
+
Object.defineProperty(exports, "createFetch", { enumerable: true, get: function () { return fetch_1.createFetch; } });
|
package/types/schema.d.ts
CHANGED
|
@@ -85,6 +85,14 @@ export interface TableQueryNames {
|
|
|
85
85
|
delete: string | null;
|
|
86
86
|
/** Patch field name in update mutation input (e.g., "userPatch" for UpdateUserInput) */
|
|
87
87
|
patchFieldName?: string;
|
|
88
|
+
/** Bulk insert mutation name (e.g., "bulkCreateUsers") */
|
|
89
|
+
bulkInsert?: string | null;
|
|
90
|
+
/** Bulk upsert mutation name (e.g., "bulkUpsertUsers") */
|
|
91
|
+
bulkUpsert?: string | null;
|
|
92
|
+
/** Bulk update mutation name (e.g., "bulkUpdateUsers") */
|
|
93
|
+
bulkUpdate?: string | null;
|
|
94
|
+
/** Bulk delete mutation name (e.g., "bulkDeleteUsers") */
|
|
95
|
+
bulkDelete?: string | null;
|
|
88
96
|
}
|
|
89
97
|
/**
|
|
90
98
|
* Table constraints
|
|
@@ -1,108 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Isomorphic fetch that resolves *.localhost subdomains and preserves
|
|
3
|
-
* Host headers across Node.js and browsers.
|
|
4
|
-
*
|
|
5
|
-
* Node.js has two issues with *.localhost subdomains:
|
|
6
|
-
* 1. DNS — fetch('http://auth.localhost:3000/') throws ENOTFOUND
|
|
7
|
-
* because undici doesn't resolve *.localhost to loopback.
|
|
8
|
-
* 2. Host header — Node's fetch treats Host as forbidden and silently
|
|
9
|
-
* drops it, breaking server-side subdomain routing.
|
|
10
|
-
*
|
|
11
|
-
* In browsers *.localhost resolves natively, so createFetch() returns
|
|
12
|
-
* globalThis.fetch as-is.
|
|
13
|
-
*/
|
|
14
|
-
export function isLocalhostSubdomain(hostname) {
|
|
15
|
-
return hostname.endsWith('.localhost') && hostname !== 'localhost';
|
|
16
|
-
}
|
|
17
|
-
function buildNodeFetch(http, https) {
|
|
18
|
-
return (input, init) => {
|
|
19
|
-
const url = new URL(typeof input === 'string'
|
|
20
|
-
? input
|
|
21
|
-
: input instanceof URL
|
|
22
|
-
? input.href
|
|
23
|
-
: input.url);
|
|
24
|
-
if (!isLocalhostSubdomain(url.hostname)) {
|
|
25
|
-
return globalThis.fetch(input, init);
|
|
26
|
-
}
|
|
27
|
-
const originalHost = url.host;
|
|
28
|
-
url.hostname = 'localhost';
|
|
29
|
-
return new Promise((resolve, reject) => {
|
|
30
|
-
const headers = {
|
|
31
|
-
Host: originalHost,
|
|
32
|
-
};
|
|
33
|
-
if (init?.headers) {
|
|
34
|
-
const entries = init.headers instanceof Headers
|
|
35
|
-
? Array.from(init.headers.entries())
|
|
36
|
-
: Array.isArray(init.headers)
|
|
37
|
-
? init.headers
|
|
38
|
-
: Object.entries(init.headers);
|
|
39
|
-
for (const [key, value] of entries) {
|
|
40
|
-
headers[key] = value;
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
const protocol = url.protocol === 'https:' ? https : http;
|
|
44
|
-
const req = protocol.request(url, {
|
|
45
|
-
method: init?.method ?? 'GET',
|
|
46
|
-
headers,
|
|
47
|
-
}, (res) => {
|
|
48
|
-
const chunks = [];
|
|
49
|
-
res.on('data', (chunk) => chunks.push(chunk));
|
|
50
|
-
res.on('end', () => {
|
|
51
|
-
const body = Buffer.concat(chunks);
|
|
52
|
-
resolve(new Response(body, {
|
|
53
|
-
status: res.statusCode ?? 0,
|
|
54
|
-
statusText: res.statusMessage ?? '',
|
|
55
|
-
headers: res.headers,
|
|
56
|
-
}));
|
|
57
|
-
});
|
|
58
|
-
});
|
|
59
|
-
req.on('error', reject);
|
|
60
|
-
if (init?.signal) {
|
|
61
|
-
const onAbort = () => {
|
|
62
|
-
req.destroy(new Error('The operation was aborted'));
|
|
63
|
-
};
|
|
64
|
-
init.signal.addEventListener('abort', onAbort, { once: true });
|
|
65
|
-
req.on('close', () => {
|
|
66
|
-
init.signal.removeEventListener('abort', onAbort);
|
|
67
|
-
});
|
|
68
|
-
}
|
|
69
|
-
if (init?.body != null) {
|
|
70
|
-
req.write(typeof init.body === 'string' || init.body instanceof Uint8Array
|
|
71
|
-
? init.body
|
|
72
|
-
: String(init.body));
|
|
73
|
-
}
|
|
74
|
-
req.end();
|
|
75
|
-
});
|
|
76
|
-
};
|
|
77
|
-
}
|
|
78
|
-
let _fetch;
|
|
79
|
-
/**
|
|
80
|
-
* Create an isomorphic fetch function.
|
|
81
|
-
*
|
|
82
|
-
* - In browsers (and Deno/Bun/edge): returns globalThis.fetch as-is.
|
|
83
|
-
* - In Node.js: returns a wrapper that uses node:http/node:https for
|
|
84
|
-
* *.localhost URLs (fixing DNS + Host header) and delegates everything
|
|
85
|
-
* else to globalThis.fetch.
|
|
86
|
-
*
|
|
87
|
-
* The result is cached — calling createFetch() multiple times returns
|
|
88
|
-
* the same function instance.
|
|
89
|
-
*/
|
|
90
|
-
export function createFetch() {
|
|
91
|
-
if (_fetch)
|
|
92
|
-
return _fetch;
|
|
93
|
-
if (typeof process !== 'undefined' && process.versions?.node) {
|
|
94
|
-
try {
|
|
95
|
-
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
96
|
-
const http = require('node:http');
|
|
97
|
-
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
98
|
-
const https = require('node:https');
|
|
99
|
-
_fetch = buildNodeFetch(http, https);
|
|
100
|
-
return _fetch;
|
|
101
|
-
}
|
|
102
|
-
catch {
|
|
103
|
-
// node:http unavailable — fall through
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
_fetch = globalThis.fetch;
|
|
107
|
-
return _fetch;
|
|
108
|
-
}
|
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Isomorphic fetch that resolves *.localhost subdomains and preserves
|
|
3
|
-
* Host headers across Node.js and browsers.
|
|
4
|
-
*
|
|
5
|
-
* Node.js has two issues with *.localhost subdomains:
|
|
6
|
-
* 1. DNS — fetch('http://auth.localhost:3000/') throws ENOTFOUND
|
|
7
|
-
* because undici doesn't resolve *.localhost to loopback.
|
|
8
|
-
* 2. Host header — Node's fetch treats Host as forbidden and silently
|
|
9
|
-
* drops it, breaking server-side subdomain routing.
|
|
10
|
-
*
|
|
11
|
-
* In browsers *.localhost resolves natively, so createFetch() returns
|
|
12
|
-
* globalThis.fetch as-is.
|
|
13
|
-
*/
|
|
14
|
-
export type FetchFunction = typeof globalThis.fetch;
|
|
15
|
-
export declare function isLocalhostSubdomain(hostname: string): boolean;
|
|
16
|
-
/**
|
|
17
|
-
* Create an isomorphic fetch function.
|
|
18
|
-
*
|
|
19
|
-
* - In browsers (and Deno/Bun/edge): returns globalThis.fetch as-is.
|
|
20
|
-
* - In Node.js: returns a wrapper that uses node:http/node:https for
|
|
21
|
-
* *.localhost URLs (fixing DNS + Host header) and delegates everything
|
|
22
|
-
* else to globalThis.fetch.
|
|
23
|
-
*
|
|
24
|
-
* The result is cached — calling createFetch() multiple times returns
|
|
25
|
-
* the same function instance.
|
|
26
|
-
*/
|
|
27
|
-
export declare function createFetch(): FetchFunction;
|
|
@@ -1,112 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
/**
|
|
3
|
-
* Isomorphic fetch that resolves *.localhost subdomains and preserves
|
|
4
|
-
* Host headers across Node.js and browsers.
|
|
5
|
-
*
|
|
6
|
-
* Node.js has two issues with *.localhost subdomains:
|
|
7
|
-
* 1. DNS — fetch('http://auth.localhost:3000/') throws ENOTFOUND
|
|
8
|
-
* because undici doesn't resolve *.localhost to loopback.
|
|
9
|
-
* 2. Host header — Node's fetch treats Host as forbidden and silently
|
|
10
|
-
* drops it, breaking server-side subdomain routing.
|
|
11
|
-
*
|
|
12
|
-
* In browsers *.localhost resolves natively, so createFetch() returns
|
|
13
|
-
* globalThis.fetch as-is.
|
|
14
|
-
*/
|
|
15
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
16
|
-
exports.isLocalhostSubdomain = isLocalhostSubdomain;
|
|
17
|
-
exports.createFetch = createFetch;
|
|
18
|
-
function isLocalhostSubdomain(hostname) {
|
|
19
|
-
return hostname.endsWith('.localhost') && hostname !== 'localhost';
|
|
20
|
-
}
|
|
21
|
-
function buildNodeFetch(http, https) {
|
|
22
|
-
return (input, init) => {
|
|
23
|
-
const url = new URL(typeof input === 'string'
|
|
24
|
-
? input
|
|
25
|
-
: input instanceof URL
|
|
26
|
-
? input.href
|
|
27
|
-
: input.url);
|
|
28
|
-
if (!isLocalhostSubdomain(url.hostname)) {
|
|
29
|
-
return globalThis.fetch(input, init);
|
|
30
|
-
}
|
|
31
|
-
const originalHost = url.host;
|
|
32
|
-
url.hostname = 'localhost';
|
|
33
|
-
return new Promise((resolve, reject) => {
|
|
34
|
-
const headers = {
|
|
35
|
-
Host: originalHost,
|
|
36
|
-
};
|
|
37
|
-
if (init?.headers) {
|
|
38
|
-
const entries = init.headers instanceof Headers
|
|
39
|
-
? Array.from(init.headers.entries())
|
|
40
|
-
: Array.isArray(init.headers)
|
|
41
|
-
? init.headers
|
|
42
|
-
: Object.entries(init.headers);
|
|
43
|
-
for (const [key, value] of entries) {
|
|
44
|
-
headers[key] = value;
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
const protocol = url.protocol === 'https:' ? https : http;
|
|
48
|
-
const req = protocol.request(url, {
|
|
49
|
-
method: init?.method ?? 'GET',
|
|
50
|
-
headers,
|
|
51
|
-
}, (res) => {
|
|
52
|
-
const chunks = [];
|
|
53
|
-
res.on('data', (chunk) => chunks.push(chunk));
|
|
54
|
-
res.on('end', () => {
|
|
55
|
-
const body = Buffer.concat(chunks);
|
|
56
|
-
resolve(new Response(body, {
|
|
57
|
-
status: res.statusCode ?? 0,
|
|
58
|
-
statusText: res.statusMessage ?? '',
|
|
59
|
-
headers: res.headers,
|
|
60
|
-
}));
|
|
61
|
-
});
|
|
62
|
-
});
|
|
63
|
-
req.on('error', reject);
|
|
64
|
-
if (init?.signal) {
|
|
65
|
-
const onAbort = () => {
|
|
66
|
-
req.destroy(new Error('The operation was aborted'));
|
|
67
|
-
};
|
|
68
|
-
init.signal.addEventListener('abort', onAbort, { once: true });
|
|
69
|
-
req.on('close', () => {
|
|
70
|
-
init.signal.removeEventListener('abort', onAbort);
|
|
71
|
-
});
|
|
72
|
-
}
|
|
73
|
-
if (init?.body != null) {
|
|
74
|
-
req.write(typeof init.body === 'string' || init.body instanceof Uint8Array
|
|
75
|
-
? init.body
|
|
76
|
-
: String(init.body));
|
|
77
|
-
}
|
|
78
|
-
req.end();
|
|
79
|
-
});
|
|
80
|
-
};
|
|
81
|
-
}
|
|
82
|
-
let _fetch;
|
|
83
|
-
/**
|
|
84
|
-
* Create an isomorphic fetch function.
|
|
85
|
-
*
|
|
86
|
-
* - In browsers (and Deno/Bun/edge): returns globalThis.fetch as-is.
|
|
87
|
-
* - In Node.js: returns a wrapper that uses node:http/node:https for
|
|
88
|
-
* *.localhost URLs (fixing DNS + Host header) and delegates everything
|
|
89
|
-
* else to globalThis.fetch.
|
|
90
|
-
*
|
|
91
|
-
* The result is cached — calling createFetch() multiple times returns
|
|
92
|
-
* the same function instance.
|
|
93
|
-
*/
|
|
94
|
-
function createFetch() {
|
|
95
|
-
if (_fetch)
|
|
96
|
-
return _fetch;
|
|
97
|
-
if (typeof process !== 'undefined' && process.versions?.node) {
|
|
98
|
-
try {
|
|
99
|
-
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
100
|
-
const http = require('node:http');
|
|
101
|
-
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
102
|
-
const https = require('node:https');
|
|
103
|
-
_fetch = buildNodeFetch(http, https);
|
|
104
|
-
return _fetch;
|
|
105
|
-
}
|
|
106
|
-
catch {
|
|
107
|
-
// node:http unavailable — fall through
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
_fetch = globalThis.fetch;
|
|
111
|
-
return _fetch;
|
|
112
|
-
}
|