@graphql-hive/core 0.19.0-rc-20251222110102-ca00d2ee12be6920f3247ac812aa83bf9f8e9581 → 0.19.0-rc-20260105125447-29b7291593ce66ef5f929776a3df44fc05ac0886
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/cjs/client/persisted-documents.js +95 -10
- package/cjs/client/types.js +9 -0
- package/cjs/version.js +1 -1
- package/esm/client/persisted-documents.js +95 -10
- package/esm/client/types.js +6 -0
- package/esm/version.js +1 -1
- package/package.json +1 -1
- package/typings/client/persisted-documents.d.cts +4 -2
- package/typings/client/persisted-documents.d.ts +4 -2
- package/typings/client/types.d.cts +115 -1
- package/typings/client/types.d.ts +115 -1
- package/typings/index.d.cts +1 -1
- package/typings/index.d.ts +1 -1
- package/typings/version.d.cts +1 -1
- package/typings/version.d.ts +1 -1
|
@@ -6,6 +6,7 @@ const tiny_lru_1 = tslib_1.__importDefault(require("tiny-lru"));
|
|
|
6
6
|
const circuit_js_1 = tslib_1.__importDefault(require("../circuit-breaker/circuit.js"));
|
|
7
7
|
const circuit_breaker_js_1 = require("./circuit-breaker.js");
|
|
8
8
|
const http_client_js_1 = require("./http-client.js");
|
|
9
|
+
const types_js_1 = require("./types.js");
|
|
9
10
|
function isRequestOk(response) {
|
|
10
11
|
return response.status === 200 || response.status === 404;
|
|
11
12
|
}
|
|
@@ -66,8 +67,14 @@ function createValidationError(documentId, error) {
|
|
|
66
67
|
return new PersistedDocumentValidationError(documentId, error);
|
|
67
68
|
}
|
|
68
69
|
function createPersistedDocuments(config) {
|
|
69
|
-
var _a;
|
|
70
|
+
var _a, _b, _c, _d, _e, _f;
|
|
71
|
+
// L1
|
|
70
72
|
const persistedDocumentsCache = (0, tiny_lru_1.default)((_a = config.cache) !== null && _a !== void 0 ? _a : 10000);
|
|
73
|
+
// L2
|
|
74
|
+
const layer2Cache = (_b = config.layer2Cache) === null || _b === void 0 ? void 0 : _b.cache;
|
|
75
|
+
const layer2TtlSeconds = (_c = config.layer2Cache) === null || _c === void 0 ? void 0 : _c.ttlSeconds;
|
|
76
|
+
const layer2NotFoundTtlSeconds = (_e = (_d = config.layer2Cache) === null || _d === void 0 ? void 0 : _d.notFoundTtlSeconds) !== null && _e !== void 0 ? _e : 60;
|
|
77
|
+
const layer2WaitUntil = (_f = config.layer2Cache) === null || _f === void 0 ? void 0 : _f.waitUntil;
|
|
71
78
|
let allowArbitraryDocuments;
|
|
72
79
|
if (typeof config.allowArbitraryDocuments === 'boolean') {
|
|
73
80
|
let value = config.allowArbitraryDocuments;
|
|
@@ -109,28 +116,101 @@ function createPersistedDocuments(config) {
|
|
|
109
116
|
}, Object.assign(Object.assign({}, ((_a = config.circuitBreaker) !== null && _a !== void 0 ? _a : circuit_breaker_js_1.defaultCircuitBreakerConfiguration)), { timeout: false, autoRenewAbortController: true }));
|
|
110
117
|
return circuitBreaker;
|
|
111
118
|
});
|
|
112
|
-
|
|
113
|
-
function
|
|
119
|
+
// Attempt to get document from L2 cache, returns: { hit: true, value: string | null } or { hit: false }
|
|
120
|
+
async function getFromLayer2Cache(documentId) {
|
|
121
|
+
if (!layer2Cache) {
|
|
122
|
+
return { hit: false };
|
|
123
|
+
}
|
|
124
|
+
let cached;
|
|
125
|
+
try {
|
|
126
|
+
cached = await layer2Cache.get(documentId);
|
|
127
|
+
}
|
|
128
|
+
catch (error) {
|
|
129
|
+
// L2 cache failure should not break the request
|
|
130
|
+
config.logger.warn('L2 cache get failed for document %s: %O', documentId, error);
|
|
131
|
+
return { hit: false };
|
|
132
|
+
}
|
|
133
|
+
if (cached === null) {
|
|
134
|
+
// Cache miss
|
|
135
|
+
return { hit: false };
|
|
136
|
+
}
|
|
137
|
+
if (cached === types_js_1.PERSISTED_DOCUMENT_NOT_FOUND) {
|
|
138
|
+
// Negative cache hit, document was previously not found
|
|
139
|
+
config.logger.debug('L2 cache negative hit for document %s', documentId);
|
|
140
|
+
return { hit: true, value: null };
|
|
141
|
+
}
|
|
142
|
+
// Cache hit with document
|
|
143
|
+
config.logger.debug('L2 cache hit for document %s', documentId);
|
|
144
|
+
return { hit: true, value: cached };
|
|
145
|
+
}
|
|
146
|
+
// store document in L2 cache (fire-and-forget, non-blocking)
|
|
147
|
+
function setInLayer2Cache(documentId, value, waitUntil) {
|
|
148
|
+
if (!(layer2Cache === null || layer2Cache === void 0 ? void 0 : layer2Cache.set)) {
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
// Skip negative caching if TTL is 0
|
|
152
|
+
if (value === null && layer2NotFoundTtlSeconds === 0) {
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
const cacheValue = value === null ? types_js_1.PERSISTED_DOCUMENT_NOT_FOUND : value;
|
|
156
|
+
const ttl = value === null ? layer2NotFoundTtlSeconds : layer2TtlSeconds;
|
|
157
|
+
// Fire-and-forget. don't await, don't block
|
|
158
|
+
const setPromise = layer2Cache.set(documentId, cacheValue, ttl ? { ttl } : undefined);
|
|
159
|
+
if (setPromise) {
|
|
160
|
+
const handledPromise = Promise.resolve(setPromise).then(() => {
|
|
161
|
+
config.logger.debug('L2 cache set succeeded for document %s', documentId);
|
|
162
|
+
}, error => {
|
|
163
|
+
config.logger.warn('L2 cache set failed for document %s: %O', documentId, error);
|
|
164
|
+
});
|
|
165
|
+
// Register with waitUntil for serverless environments
|
|
166
|
+
// Config waitUntil takes precedence over context waitUntil
|
|
167
|
+
const effectiveWaitUntil = layer2WaitUntil !== null && layer2WaitUntil !== void 0 ? layer2WaitUntil : waitUntil;
|
|
168
|
+
if (effectiveWaitUntil) {
|
|
169
|
+
try {
|
|
170
|
+
effectiveWaitUntil(handledPromise);
|
|
171
|
+
}
|
|
172
|
+
catch (error) {
|
|
173
|
+
config.logger.warn('Failed to register L2 cache write with waitUntil: %O', error);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
/** Load a persisted document with validation and L1 -> L2 -> CDN fallback */
|
|
179
|
+
function loadPersistedDocument(documentId, context) {
|
|
180
|
+
// Validate document ID format first
|
|
114
181
|
const validationError = validateDocumentId(documentId);
|
|
115
182
|
if (validationError) {
|
|
116
183
|
// Return a promise that will be rejected with a proper error
|
|
117
184
|
return Promise.reject(createValidationError(documentId, validationError.error));
|
|
118
185
|
}
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
186
|
+
// L1 cache check (in-memory)
|
|
187
|
+
// Note: We need to distinguish between "not in cache" (undefined) and "cached as not found" (null)
|
|
188
|
+
const cachedDocument = persistedDocumentsCache.get(documentId);
|
|
189
|
+
if (cachedDocument !== undefined) {
|
|
190
|
+
// Cache hit, return the value
|
|
191
|
+
return cachedDocument;
|
|
122
192
|
}
|
|
193
|
+
// Check in-flight requests
|
|
123
194
|
let promise = fetchCache.get(documentId);
|
|
124
195
|
if (promise) {
|
|
125
196
|
return promise;
|
|
126
197
|
}
|
|
127
198
|
promise = Promise.resolve()
|
|
128
199
|
.then(async () => {
|
|
200
|
+
// L2 cache check
|
|
201
|
+
const l2Result = await getFromLayer2Cache(documentId);
|
|
202
|
+
if (l2Result.hit) {
|
|
203
|
+
// L2 cache hit, store in L1 for faster subsequent access
|
|
204
|
+
persistedDocumentsCache.set(documentId, l2Result.value);
|
|
205
|
+
return { value: l2Result.value, fromL2: true };
|
|
206
|
+
}
|
|
207
|
+
// CDN fetch
|
|
129
208
|
const cdnDocumentId = documentId.replaceAll('~', '/');
|
|
130
209
|
let lastError = null;
|
|
131
210
|
for (const breaker of circuitBreakers) {
|
|
132
211
|
try {
|
|
133
|
-
|
|
212
|
+
const result = await breaker.fire(cdnDocumentId);
|
|
213
|
+
return { value: result, fromL2: false };
|
|
134
214
|
}
|
|
135
215
|
catch (error) {
|
|
136
216
|
config.logger.debug({ error });
|
|
@@ -142,9 +222,14 @@ function createPersistedDocuments(config) {
|
|
|
142
222
|
}
|
|
143
223
|
throw new Error('Failed to look up persisted operation.');
|
|
144
224
|
})
|
|
145
|
-
.then(
|
|
146
|
-
|
|
147
|
-
|
|
225
|
+
.then(({ value, fromL2 }) => {
|
|
226
|
+
// Store in L1 cache (in-memory), only if not already stored from L2 hit
|
|
227
|
+
if (!fromL2) {
|
|
228
|
+
persistedDocumentsCache.set(documentId, value);
|
|
229
|
+
// Store in L2 cache (async, non-blocking), only for CDN fetched data
|
|
230
|
+
setInLayer2Cache(documentId, value, context === null || context === void 0 ? void 0 : context.waitUntil);
|
|
231
|
+
}
|
|
232
|
+
return value;
|
|
148
233
|
})
|
|
149
234
|
.finally(() => {
|
|
150
235
|
fetchCache.delete(documentId);
|
package/cjs/client/types.js
CHANGED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.PERSISTED_DOCUMENT_NOT_FOUND = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* A sentinel value used to indicate a document was looked up but not found.
|
|
6
|
+
* This enables negative caching - caching the absence of a document to avoid
|
|
7
|
+
* repeated CDN lookups for documents that don't exist.
|
|
8
|
+
*/
|
|
9
|
+
exports.PERSISTED_DOCUMENT_NOT_FOUND = '__HIVE_PERSISTED_DOCUMENT_NOT_FOUND__';
|
package/cjs/version.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.version = void 0;
|
|
4
|
-
exports.version = '0.19.0-rc-
|
|
4
|
+
exports.version = '0.19.0-rc-20260105125447-29b7291593ce66ef5f929776a3df44fc05ac0886';
|
|
@@ -2,6 +2,7 @@ import LRU from 'tiny-lru';
|
|
|
2
2
|
import CircuitBreaker from '../circuit-breaker/circuit.js';
|
|
3
3
|
import { defaultCircuitBreakerConfiguration } from './circuit-breaker.js';
|
|
4
4
|
import { http } from './http-client.js';
|
|
5
|
+
import { PERSISTED_DOCUMENT_NOT_FOUND, } from './types.js';
|
|
5
6
|
function isRequestOk(response) {
|
|
6
7
|
return response.status === 200 || response.status === 404;
|
|
7
8
|
}
|
|
@@ -62,8 +63,14 @@ function createValidationError(documentId, error) {
|
|
|
62
63
|
return new PersistedDocumentValidationError(documentId, error);
|
|
63
64
|
}
|
|
64
65
|
export function createPersistedDocuments(config) {
|
|
65
|
-
var _a;
|
|
66
|
+
var _a, _b, _c, _d, _e, _f;
|
|
67
|
+
// L1
|
|
66
68
|
const persistedDocumentsCache = LRU((_a = config.cache) !== null && _a !== void 0 ? _a : 10000);
|
|
69
|
+
// L2
|
|
70
|
+
const layer2Cache = (_b = config.layer2Cache) === null || _b === void 0 ? void 0 : _b.cache;
|
|
71
|
+
const layer2TtlSeconds = (_c = config.layer2Cache) === null || _c === void 0 ? void 0 : _c.ttlSeconds;
|
|
72
|
+
const layer2NotFoundTtlSeconds = (_e = (_d = config.layer2Cache) === null || _d === void 0 ? void 0 : _d.notFoundTtlSeconds) !== null && _e !== void 0 ? _e : 60;
|
|
73
|
+
const layer2WaitUntil = (_f = config.layer2Cache) === null || _f === void 0 ? void 0 : _f.waitUntil;
|
|
67
74
|
let allowArbitraryDocuments;
|
|
68
75
|
if (typeof config.allowArbitraryDocuments === 'boolean') {
|
|
69
76
|
let value = config.allowArbitraryDocuments;
|
|
@@ -105,28 +112,101 @@ export function createPersistedDocuments(config) {
|
|
|
105
112
|
}, Object.assign(Object.assign({}, ((_a = config.circuitBreaker) !== null && _a !== void 0 ? _a : defaultCircuitBreakerConfiguration)), { timeout: false, autoRenewAbortController: true }));
|
|
106
113
|
return circuitBreaker;
|
|
107
114
|
});
|
|
108
|
-
|
|
109
|
-
function
|
|
115
|
+
// Attempt to get document from L2 cache, returns: { hit: true, value: string | null } or { hit: false }
|
|
116
|
+
async function getFromLayer2Cache(documentId) {
|
|
117
|
+
if (!layer2Cache) {
|
|
118
|
+
return { hit: false };
|
|
119
|
+
}
|
|
120
|
+
let cached;
|
|
121
|
+
try {
|
|
122
|
+
cached = await layer2Cache.get(documentId);
|
|
123
|
+
}
|
|
124
|
+
catch (error) {
|
|
125
|
+
// L2 cache failure should not break the request
|
|
126
|
+
config.logger.warn('L2 cache get failed for document %s: %O', documentId, error);
|
|
127
|
+
return { hit: false };
|
|
128
|
+
}
|
|
129
|
+
if (cached === null) {
|
|
130
|
+
// Cache miss
|
|
131
|
+
return { hit: false };
|
|
132
|
+
}
|
|
133
|
+
if (cached === PERSISTED_DOCUMENT_NOT_FOUND) {
|
|
134
|
+
// Negative cache hit, document was previously not found
|
|
135
|
+
config.logger.debug('L2 cache negative hit for document %s', documentId);
|
|
136
|
+
return { hit: true, value: null };
|
|
137
|
+
}
|
|
138
|
+
// Cache hit with document
|
|
139
|
+
config.logger.debug('L2 cache hit for document %s', documentId);
|
|
140
|
+
return { hit: true, value: cached };
|
|
141
|
+
}
|
|
142
|
+
// store document in L2 cache (fire-and-forget, non-blocking)
|
|
143
|
+
function setInLayer2Cache(documentId, value, waitUntil) {
|
|
144
|
+
if (!(layer2Cache === null || layer2Cache === void 0 ? void 0 : layer2Cache.set)) {
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
// Skip negative caching if TTL is 0
|
|
148
|
+
if (value === null && layer2NotFoundTtlSeconds === 0) {
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
const cacheValue = value === null ? PERSISTED_DOCUMENT_NOT_FOUND : value;
|
|
152
|
+
const ttl = value === null ? layer2NotFoundTtlSeconds : layer2TtlSeconds;
|
|
153
|
+
// Fire-and-forget. don't await, don't block
|
|
154
|
+
const setPromise = layer2Cache.set(documentId, cacheValue, ttl ? { ttl } : undefined);
|
|
155
|
+
if (setPromise) {
|
|
156
|
+
const handledPromise = Promise.resolve(setPromise).then(() => {
|
|
157
|
+
config.logger.debug('L2 cache set succeeded for document %s', documentId);
|
|
158
|
+
}, error => {
|
|
159
|
+
config.logger.warn('L2 cache set failed for document %s: %O', documentId, error);
|
|
160
|
+
});
|
|
161
|
+
// Register with waitUntil for serverless environments
|
|
162
|
+
// Config waitUntil takes precedence over context waitUntil
|
|
163
|
+
const effectiveWaitUntil = layer2WaitUntil !== null && layer2WaitUntil !== void 0 ? layer2WaitUntil : waitUntil;
|
|
164
|
+
if (effectiveWaitUntil) {
|
|
165
|
+
try {
|
|
166
|
+
effectiveWaitUntil(handledPromise);
|
|
167
|
+
}
|
|
168
|
+
catch (error) {
|
|
169
|
+
config.logger.warn('Failed to register L2 cache write with waitUntil: %O', error);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
/** Load a persisted document with validation and L1 -> L2 -> CDN fallback */
|
|
175
|
+
function loadPersistedDocument(documentId, context) {
|
|
176
|
+
// Validate document ID format first
|
|
110
177
|
const validationError = validateDocumentId(documentId);
|
|
111
178
|
if (validationError) {
|
|
112
179
|
// Return a promise that will be rejected with a proper error
|
|
113
180
|
return Promise.reject(createValidationError(documentId, validationError.error));
|
|
114
181
|
}
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
182
|
+
// L1 cache check (in-memory)
|
|
183
|
+
// Note: We need to distinguish between "not in cache" (undefined) and "cached as not found" (null)
|
|
184
|
+
const cachedDocument = persistedDocumentsCache.get(documentId);
|
|
185
|
+
if (cachedDocument !== undefined) {
|
|
186
|
+
// Cache hit, return the value
|
|
187
|
+
return cachedDocument;
|
|
118
188
|
}
|
|
189
|
+
// Check in-flight requests
|
|
119
190
|
let promise = fetchCache.get(documentId);
|
|
120
191
|
if (promise) {
|
|
121
192
|
return promise;
|
|
122
193
|
}
|
|
123
194
|
promise = Promise.resolve()
|
|
124
195
|
.then(async () => {
|
|
196
|
+
// L2 cache check
|
|
197
|
+
const l2Result = await getFromLayer2Cache(documentId);
|
|
198
|
+
if (l2Result.hit) {
|
|
199
|
+
// L2 cache hit, store in L1 for faster subsequent access
|
|
200
|
+
persistedDocumentsCache.set(documentId, l2Result.value);
|
|
201
|
+
return { value: l2Result.value, fromL2: true };
|
|
202
|
+
}
|
|
203
|
+
// CDN fetch
|
|
125
204
|
const cdnDocumentId = documentId.replaceAll('~', '/');
|
|
126
205
|
let lastError = null;
|
|
127
206
|
for (const breaker of circuitBreakers) {
|
|
128
207
|
try {
|
|
129
|
-
|
|
208
|
+
const result = await breaker.fire(cdnDocumentId);
|
|
209
|
+
return { value: result, fromL2: false };
|
|
130
210
|
}
|
|
131
211
|
catch (error) {
|
|
132
212
|
config.logger.debug({ error });
|
|
@@ -138,9 +218,14 @@ export function createPersistedDocuments(config) {
|
|
|
138
218
|
}
|
|
139
219
|
throw new Error('Failed to look up persisted operation.');
|
|
140
220
|
})
|
|
141
|
-
.then(
|
|
142
|
-
|
|
143
|
-
|
|
221
|
+
.then(({ value, fromL2 }) => {
|
|
222
|
+
// Store in L1 cache (in-memory), only if not already stored from L2 hit
|
|
223
|
+
if (!fromL2) {
|
|
224
|
+
persistedDocumentsCache.set(documentId, value);
|
|
225
|
+
// Store in L2 cache (async, non-blocking), only for CDN fetched data
|
|
226
|
+
setInLayer2Cache(documentId, value, context === null || context === void 0 ? void 0 : context.waitUntil);
|
|
227
|
+
}
|
|
228
|
+
return value;
|
|
144
229
|
})
|
|
145
230
|
.finally(() => {
|
|
146
231
|
fetchCache.delete(documentId);
|
package/esm/client/types.js
CHANGED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A sentinel value used to indicate a document was looked up but not found.
|
|
3
|
+
* This enables negative caching - caching the absence of a document to avoid
|
|
4
|
+
* repeated CDN lookups for documents that don't exist.
|
|
5
|
+
*/
|
|
6
|
+
export const PERSISTED_DOCUMENT_NOT_FOUND = '__HIVE_PERSISTED_DOCUMENT_NOT_FOUND__';
|
package/esm/version.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const version = '0.19.0-rc-
|
|
1
|
+
export const version = '0.19.0-rc-20260105125447-29b7291593ce66ef5f929776a3df44fc05ac0886';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@graphql-hive/core",
|
|
3
|
-
"version": "0.19.0-rc-
|
|
3
|
+
"version": "0.19.0-rc-20260105125447-29b7291593ce66ef5f929776a3df44fc05ac0886",
|
|
4
4
|
"sideEffects": false,
|
|
5
5
|
"peerDependencies": {
|
|
6
6
|
"graphql": "^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0"
|
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
import type { PromiseOrValue } from 'graphql/jsutils/PromiseOrValue.cjs';
|
|
2
2
|
import { Logger } from '@graphql-hive/logger';
|
|
3
3
|
import { HttpCallConfig } from './http-client.cjs';
|
|
4
|
-
import type
|
|
4
|
+
import { type PersistedDocumentsConfiguration } from './types.cjs';
|
|
5
5
|
type HeadersObject = {
|
|
6
6
|
get(name: string): string | null;
|
|
7
7
|
};
|
|
8
8
|
type PersistedDocuments = {
|
|
9
|
-
resolve(documentId: string
|
|
9
|
+
resolve(documentId: string, context?: {
|
|
10
|
+
waitUntil?: (promise: Promise<void> | void) => void;
|
|
11
|
+
}): PromiseOrValue<string | null>;
|
|
10
12
|
allowArbitraryDocuments(context: {
|
|
11
13
|
headers?: HeadersObject;
|
|
12
14
|
}): PromiseOrValue<boolean>;
|
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
import type { PromiseOrValue } from 'graphql/jsutils/PromiseOrValue.js';
|
|
2
2
|
import { Logger } from '@graphql-hive/logger';
|
|
3
3
|
import { HttpCallConfig } from './http-client.js';
|
|
4
|
-
import type
|
|
4
|
+
import { type PersistedDocumentsConfiguration } from './types.js';
|
|
5
5
|
type HeadersObject = {
|
|
6
6
|
get(name: string): string | null;
|
|
7
7
|
};
|
|
8
8
|
type PersistedDocuments = {
|
|
9
|
-
resolve(documentId: string
|
|
9
|
+
resolve(documentId: string, context?: {
|
|
10
|
+
waitUntil?: (promise: Promise<void> | void) => void;
|
|
11
|
+
}): PromiseOrValue<string | null>;
|
|
10
12
|
allowArbitraryDocuments(context: {
|
|
11
13
|
headers?: HeadersObject;
|
|
12
14
|
}): PromiseOrValue<boolean>;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { ExecutionArgs } from 'graphql';
|
|
2
2
|
import type { PromiseOrValue } from 'graphql/jsutils/PromiseOrValue.cjs';
|
|
3
3
|
import { LogLevel as HiveLoggerLevel, Logger } from '@graphql-hive/logger';
|
|
4
|
+
import { MaybePromise } from '@graphql-tools/utils';
|
|
4
5
|
import type { AgentOptions } from './agent.cjs';
|
|
5
6
|
import { CircuitBreakerConfiguration } from './circuit-breaker.cjs';
|
|
6
7
|
import type { autoDisposeSymbol, hiveClientSymbol } from './client.cjs';
|
|
@@ -39,7 +40,9 @@ export interface HiveClient {
|
|
|
39
40
|
createInstrumentedSubscribe(executeImpl: any): any;
|
|
40
41
|
dispose(): Promise<void>;
|
|
41
42
|
experimental__persistedDocuments: null | {
|
|
42
|
-
resolve(documentId: string
|
|
43
|
+
resolve(documentId: string, context?: {
|
|
44
|
+
waitUntil?: (promise: Promise<void> | void) => void;
|
|
45
|
+
}): PromiseOrValue<string | null>;
|
|
43
46
|
allowArbitraryDocuments(context: {
|
|
44
47
|
headers?: HeadersObject;
|
|
45
48
|
}): PromiseOrValue<boolean>;
|
|
@@ -272,6 +275,81 @@ export interface ServicesFetcherOptions {
|
|
|
272
275
|
endpoint: string;
|
|
273
276
|
key: string;
|
|
274
277
|
}
|
|
278
|
+
/**
|
|
279
|
+
* A sentinel value used to indicate a document was looked up but not found.
|
|
280
|
+
* This enables negative caching - caching the absence of a document to avoid
|
|
281
|
+
* repeated CDN lookups for documents that don't exist.
|
|
282
|
+
*/
|
|
283
|
+
export declare const PERSISTED_DOCUMENT_NOT_FOUND: "__HIVE_PERSISTED_DOCUMENT_NOT_FOUND__";
|
|
284
|
+
export type PersistedDocumentNotFound = typeof PERSISTED_DOCUMENT_NOT_FOUND;
|
|
285
|
+
/**
|
|
286
|
+
* Layer 2 cache interface for persisted documents.
|
|
287
|
+
* Implementers can use Redis, Memcached, or any other distributed cache.
|
|
288
|
+
*
|
|
289
|
+
* @example
|
|
290
|
+
* ```typescript
|
|
291
|
+
* import { createClient } from 'redis';
|
|
292
|
+
* import { createHive} from '@graphql-hive/core';
|
|
293
|
+
*
|
|
294
|
+
* const redis = createClient({ url: 'redis://localhost:6379' });
|
|
295
|
+
* await redis.connect();
|
|
296
|
+
*
|
|
297
|
+
* const cache: PersistedDocumentsCache = {
|
|
298
|
+
* async get(key) {
|
|
299
|
+
* return redis.get(`hive:pd:${key}`);
|
|
300
|
+
* },
|
|
301
|
+
* async set(key, value, options) {
|
|
302
|
+
* if (options?.ttl) {
|
|
303
|
+
* await redis.set(`hive:pd:${key}`, value, { EX: options.ttl });
|
|
304
|
+
* } else {
|
|
305
|
+
* await redis.set(`hive:pd:${key}`, value);
|
|
306
|
+
* }
|
|
307
|
+
* },
|
|
308
|
+
* };
|
|
309
|
+
* ```
|
|
310
|
+
*/
|
|
311
|
+
export type PersistedDocumentsCache = {
|
|
312
|
+
/**
|
|
313
|
+
* Get a document from the cache.
|
|
314
|
+
* @param key - The document ID (e.g., "myapp~v1.0.0~abc123")
|
|
315
|
+
* @returns The document body, PERSISTED_DOCUMENT_NOT_FOUND for negative cache hit, or null for cache miss
|
|
316
|
+
*/
|
|
317
|
+
get(key: string): Promise<string | PersistedDocumentNotFound | null>;
|
|
318
|
+
/**
|
|
319
|
+
* Store a document in the cache.
|
|
320
|
+
* Optional - if not provided, the cache is read-only.
|
|
321
|
+
* @param key - The document ID
|
|
322
|
+
* @param value - The document body or PERSISTED_DOCUMENT_NOT_FOUND for negative caching
|
|
323
|
+
* @param options - Optional TTL configuration (ttl is in seconds)
|
|
324
|
+
*/
|
|
325
|
+
set?(key: string, value: string | PersistedDocumentNotFound, options?: {
|
|
326
|
+
ttl?: number;
|
|
327
|
+
}): MaybePromise<unknown>;
|
|
328
|
+
};
|
|
329
|
+
/**
|
|
330
|
+
* Configuration for the layer 2 cache.
|
|
331
|
+
*/
|
|
332
|
+
export type Layer2CacheConfiguration = {
|
|
333
|
+
/**
|
|
334
|
+
* The cache implementation (e.g., Redis client wrapper)
|
|
335
|
+
*/
|
|
336
|
+
cache: PersistedDocumentsCache;
|
|
337
|
+
/**
|
|
338
|
+
* TTL in seconds for successfully found documents.
|
|
339
|
+
* @default undefined (no expiration, or cache implementation default)
|
|
340
|
+
*/
|
|
341
|
+
ttlSeconds?: number;
|
|
342
|
+
/**
|
|
343
|
+
* TTL in seconds for negative cache entries (document not found).
|
|
344
|
+
* Set to 0 to disable negative caching.
|
|
345
|
+
* @default 60 (1 minute)
|
|
346
|
+
*/
|
|
347
|
+
notFoundTtlSeconds?: number;
|
|
348
|
+
/**
|
|
349
|
+
* Optional function to register background work in serverless environments if not available in context.
|
|
350
|
+
*/
|
|
351
|
+
waitUntil?: (promise: Promise<void> | void) => void;
|
|
352
|
+
};
|
|
275
353
|
export type PersistedDocumentsConfiguration = {
|
|
276
354
|
/**
|
|
277
355
|
* CDN configuration for loading persisted documents.
|
|
@@ -316,6 +394,42 @@ export type PersistedDocumentsConfiguration = {
|
|
|
316
394
|
fetch?: typeof fetch;
|
|
317
395
|
/** Configuration for the circuit breaker. */
|
|
318
396
|
circuitBreaker?: CircuitBreakerConfiguration;
|
|
397
|
+
/**
|
|
398
|
+
* Optional layer 2 cache configuration.
|
|
399
|
+
* When configured, the SDK will check this cache after the in-memory cache miss
|
|
400
|
+
* and before making a CDN request.
|
|
401
|
+
*
|
|
402
|
+
* This is useful for distributed caching (e.g., Redis) in multi-instance deployments,
|
|
403
|
+
* providing:
|
|
404
|
+
* - Shared cache between gateway instances
|
|
405
|
+
* - Additional resilience layer for CDN outages
|
|
406
|
+
* - Faster response times after gateway restarts
|
|
407
|
+
*
|
|
408
|
+
* @example
|
|
409
|
+
* ```typescript
|
|
410
|
+
* import { createClient } from 'redis';
|
|
411
|
+
* import { createHive} from '@graphql-hive/core';
|
|
412
|
+
*
|
|
413
|
+
* const redis = createClient({ url: 'redis://localhost:6379' });
|
|
414
|
+
* await redis.connect();
|
|
415
|
+
*
|
|
416
|
+
* const hive = createHive({
|
|
417
|
+
* experimental__persistedDocuments: {
|
|
418
|
+
* cdn: { endpoint: '...', accessToken: '...' },
|
|
419
|
+
* layer2Cache: {
|
|
420
|
+
* cache: {
|
|
421
|
+
* get: (key) => redis.get(`hive:pd:${key}`),
|
|
422
|
+
* set: (key, value, opts) =>
|
|
423
|
+
* redis.set(`hive:pd:${key}`, value, opts?.ttl ? { EX: opts.ttl } : {}),
|
|
424
|
+
* },
|
|
425
|
+
* ttlSeconds: 3600, // 1 hour for found documents
|
|
426
|
+
* notFoundTtlSeconds: 60, // 1 minute for notfound entries
|
|
427
|
+
* },
|
|
428
|
+
* },
|
|
429
|
+
* });
|
|
430
|
+
* ```
|
|
431
|
+
*/
|
|
432
|
+
layer2Cache?: Layer2CacheConfiguration;
|
|
319
433
|
};
|
|
320
434
|
export type AllowArbitraryDocumentsFunction = (context: {
|
|
321
435
|
/** an object for accessing the request headers. */
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { ExecutionArgs } from 'graphql';
|
|
2
2
|
import type { PromiseOrValue } from 'graphql/jsutils/PromiseOrValue.js';
|
|
3
3
|
import { LogLevel as HiveLoggerLevel, Logger } from '@graphql-hive/logger';
|
|
4
|
+
import { MaybePromise } from '@graphql-tools/utils';
|
|
4
5
|
import type { AgentOptions } from './agent.js';
|
|
5
6
|
import { CircuitBreakerConfiguration } from './circuit-breaker.js';
|
|
6
7
|
import type { autoDisposeSymbol, hiveClientSymbol } from './client.js';
|
|
@@ -39,7 +40,9 @@ export interface HiveClient {
|
|
|
39
40
|
createInstrumentedSubscribe(executeImpl: any): any;
|
|
40
41
|
dispose(): Promise<void>;
|
|
41
42
|
experimental__persistedDocuments: null | {
|
|
42
|
-
resolve(documentId: string
|
|
43
|
+
resolve(documentId: string, context?: {
|
|
44
|
+
waitUntil?: (promise: Promise<void> | void) => void;
|
|
45
|
+
}): PromiseOrValue<string | null>;
|
|
43
46
|
allowArbitraryDocuments(context: {
|
|
44
47
|
headers?: HeadersObject;
|
|
45
48
|
}): PromiseOrValue<boolean>;
|
|
@@ -272,6 +275,81 @@ export interface ServicesFetcherOptions {
|
|
|
272
275
|
endpoint: string;
|
|
273
276
|
key: string;
|
|
274
277
|
}
|
|
278
|
+
/**
|
|
279
|
+
* A sentinel value used to indicate a document was looked up but not found.
|
|
280
|
+
* This enables negative caching - caching the absence of a document to avoid
|
|
281
|
+
* repeated CDN lookups for documents that don't exist.
|
|
282
|
+
*/
|
|
283
|
+
export declare const PERSISTED_DOCUMENT_NOT_FOUND: "__HIVE_PERSISTED_DOCUMENT_NOT_FOUND__";
|
|
284
|
+
export type PersistedDocumentNotFound = typeof PERSISTED_DOCUMENT_NOT_FOUND;
|
|
285
|
+
/**
|
|
286
|
+
* Layer 2 cache interface for persisted documents.
|
|
287
|
+
* Implementers can use Redis, Memcached, or any other distributed cache.
|
|
288
|
+
*
|
|
289
|
+
* @example
|
|
290
|
+
* ```typescript
|
|
291
|
+
* import { createClient } from 'redis';
|
|
292
|
+
* import { createHive} from '@graphql-hive/core';
|
|
293
|
+
*
|
|
294
|
+
* const redis = createClient({ url: 'redis://localhost:6379' });
|
|
295
|
+
* await redis.connect();
|
|
296
|
+
*
|
|
297
|
+
* const cache: PersistedDocumentsCache = {
|
|
298
|
+
* async get(key) {
|
|
299
|
+
* return redis.get(`hive:pd:${key}`);
|
|
300
|
+
* },
|
|
301
|
+
* async set(key, value, options) {
|
|
302
|
+
* if (options?.ttl) {
|
|
303
|
+
* await redis.set(`hive:pd:${key}`, value, { EX: options.ttl });
|
|
304
|
+
* } else {
|
|
305
|
+
* await redis.set(`hive:pd:${key}`, value);
|
|
306
|
+
* }
|
|
307
|
+
* },
|
|
308
|
+
* };
|
|
309
|
+
* ```
|
|
310
|
+
*/
|
|
311
|
+
export type PersistedDocumentsCache = {
|
|
312
|
+
/**
|
|
313
|
+
* Get a document from the cache.
|
|
314
|
+
* @param key - The document ID (e.g., "myapp~v1.0.0~abc123")
|
|
315
|
+
* @returns The document body, PERSISTED_DOCUMENT_NOT_FOUND for negative cache hit, or null for cache miss
|
|
316
|
+
*/
|
|
317
|
+
get(key: string): Promise<string | PersistedDocumentNotFound | null>;
|
|
318
|
+
/**
|
|
319
|
+
* Store a document in the cache.
|
|
320
|
+
* Optional - if not provided, the cache is read-only.
|
|
321
|
+
* @param key - The document ID
|
|
322
|
+
* @param value - The document body or PERSISTED_DOCUMENT_NOT_FOUND for negative caching
|
|
323
|
+
* @param options - Optional TTL configuration (ttl is in seconds)
|
|
324
|
+
*/
|
|
325
|
+
set?(key: string, value: string | PersistedDocumentNotFound, options?: {
|
|
326
|
+
ttl?: number;
|
|
327
|
+
}): MaybePromise<unknown>;
|
|
328
|
+
};
|
|
329
|
+
/**
|
|
330
|
+
* Configuration for the layer 2 cache.
|
|
331
|
+
*/
|
|
332
|
+
export type Layer2CacheConfiguration = {
|
|
333
|
+
/**
|
|
334
|
+
* The cache implementation (e.g., Redis client wrapper)
|
|
335
|
+
*/
|
|
336
|
+
cache: PersistedDocumentsCache;
|
|
337
|
+
/**
|
|
338
|
+
* TTL in seconds for successfully found documents.
|
|
339
|
+
* @default undefined (no expiration, or cache implementation default)
|
|
340
|
+
*/
|
|
341
|
+
ttlSeconds?: number;
|
|
342
|
+
/**
|
|
343
|
+
* TTL in seconds for negative cache entries (document not found).
|
|
344
|
+
* Set to 0 to disable negative caching.
|
|
345
|
+
* @default 60 (1 minute)
|
|
346
|
+
*/
|
|
347
|
+
notFoundTtlSeconds?: number;
|
|
348
|
+
/**
|
|
349
|
+
* Optional function to register background work in serverless environments if not available in context.
|
|
350
|
+
*/
|
|
351
|
+
waitUntil?: (promise: Promise<void> | void) => void;
|
|
352
|
+
};
|
|
275
353
|
export type PersistedDocumentsConfiguration = {
|
|
276
354
|
/**
|
|
277
355
|
* CDN configuration for loading persisted documents.
|
|
@@ -316,6 +394,42 @@ export type PersistedDocumentsConfiguration = {
|
|
|
316
394
|
fetch?: typeof fetch;
|
|
317
395
|
/** Configuration for the circuit breaker. */
|
|
318
396
|
circuitBreaker?: CircuitBreakerConfiguration;
|
|
397
|
+
/**
|
|
398
|
+
* Optional layer 2 cache configuration.
|
|
399
|
+
* When configured, the SDK will check this cache after the in-memory cache miss
|
|
400
|
+
* and before making a CDN request.
|
|
401
|
+
*
|
|
402
|
+
* This is useful for distributed caching (e.g., Redis) in multi-instance deployments,
|
|
403
|
+
* providing:
|
|
404
|
+
* - Shared cache between gateway instances
|
|
405
|
+
* - Additional resilience layer for CDN outages
|
|
406
|
+
* - Faster response times after gateway restarts
|
|
407
|
+
*
|
|
408
|
+
* @example
|
|
409
|
+
* ```typescript
|
|
410
|
+
* import { createClient } from 'redis';
|
|
411
|
+
* import { createHive} from '@graphql-hive/core';
|
|
412
|
+
*
|
|
413
|
+
* const redis = createClient({ url: 'redis://localhost:6379' });
|
|
414
|
+
* await redis.connect();
|
|
415
|
+
*
|
|
416
|
+
* const hive = createHive({
|
|
417
|
+
* experimental__persistedDocuments: {
|
|
418
|
+
* cdn: { endpoint: '...', accessToken: '...' },
|
|
419
|
+
* layer2Cache: {
|
|
420
|
+
* cache: {
|
|
421
|
+
* get: (key) => redis.get(`hive:pd:${key}`),
|
|
422
|
+
* set: (key, value, opts) =>
|
|
423
|
+
* redis.set(`hive:pd:${key}`, value, opts?.ttl ? { EX: opts.ttl } : {}),
|
|
424
|
+
* },
|
|
425
|
+
* ttlSeconds: 3600, // 1 hour for found documents
|
|
426
|
+
* notFoundTtlSeconds: 60, // 1 minute for notfound entries
|
|
427
|
+
* },
|
|
428
|
+
* },
|
|
429
|
+
* });
|
|
430
|
+
* ```
|
|
431
|
+
*/
|
|
432
|
+
layer2Cache?: Layer2CacheConfiguration;
|
|
319
433
|
};
|
|
320
434
|
export type AllowArbitraryDocumentsFunction = (context: {
|
|
321
435
|
/** an object for accessing the request headers. */
|
package/typings/index.d.cts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export * from './normalize/operation.cjs';
|
|
2
2
|
export { collectSchemaCoordinates } from './client/collect-schema-coordinates.cjs';
|
|
3
|
-
export type { HivePluginOptions, HiveClient, CollectUsageCallback, LegacyLogger as Logger, } from './client/types.cjs';
|
|
3
|
+
export type { HivePluginOptions, HiveClient, CollectUsageCallback, LegacyLogger as Logger, PersistedDocumentsCache, Layer2CacheConfiguration, } from './client/types.cjs';
|
|
4
4
|
export { createSchemaFetcher, createServicesFetcher } from './client/gateways.cjs';
|
|
5
5
|
export { createHive, autoDisposeSymbol } from './client/client.cjs';
|
|
6
6
|
export { atLeastOnceSampler } from './client/samplers.cjs';
|
package/typings/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export * from './normalize/operation.js';
|
|
2
2
|
export { collectSchemaCoordinates } from './client/collect-schema-coordinates.js';
|
|
3
|
-
export type { HivePluginOptions, HiveClient, CollectUsageCallback, LegacyLogger as Logger, } from './client/types.js';
|
|
3
|
+
export type { HivePluginOptions, HiveClient, CollectUsageCallback, LegacyLogger as Logger, PersistedDocumentsCache, Layer2CacheConfiguration, } from './client/types.js';
|
|
4
4
|
export { createSchemaFetcher, createServicesFetcher } from './client/gateways.js';
|
|
5
5
|
export { createHive, autoDisposeSymbol } from './client/client.js';
|
|
6
6
|
export { atLeastOnceSampler } from './client/samplers.js';
|
package/typings/version.d.cts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export declare const version = "0.19.0-rc-
|
|
1
|
+
export declare const version = "0.19.0-rc-20260105125447-29b7291593ce66ef5f929776a3df44fc05ac0886";
|
|
2
2
|
//# sourceMappingURL=version.d.ts.map
|
package/typings/version.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export declare const version = "0.19.0-rc-
|
|
1
|
+
export declare const version = "0.19.0-rc-20260105125447-29b7291593ce66ef5f929776a3df44fc05ac0886";
|
|
2
2
|
//# sourceMappingURL=version.d.ts.map
|