@effect-gql/persisted-queries 0.1.0 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,100 @@
1
+ # Effect GraphQL
2
+
3
+ A GraphQL framework for Effect-TS that brings full type safety, composability, and functional programming to your GraphQL servers.
4
+
5
+ > **Note:** This is an experimental prototype exploring the integration between Effect Schema, Effect's service system, and GraphQL.
6
+
7
+ ## Features
8
+
9
+ - **Type-Safe End-to-End** - Define schemas once with Effect Schema, get TypeScript types and GraphQL types automatically
10
+ - **Effect-Powered Resolvers** - Resolvers are Effect programs with built-in error handling and service injection
11
+ - **Immutable Builder** - Fluent, pipe-able API for composing schemas from reusable parts
12
+ - **Service Integration** - Use Effect's Layer system for dependency injection
13
+
14
+ ## Installation
15
+
16
+ ```bash
17
+ npm install @effect-gql/core effect graphql
18
+ ```
19
+
20
+ ## Quick Start
21
+
22
+ ```typescript
23
+ import { Effect, Layer } from "effect"
24
+ import * as S from "effect/Schema"
25
+ import { GraphQLSchemaBuilder, execute } from "@effect-gql/core"
26
+
27
+ // Define your schema with Effect Schema
28
+ const UserSchema = S.Struct({
29
+ id: S.String,
30
+ name: S.String,
31
+ email: S.String,
32
+ })
33
+
34
+ // Build your GraphQL schema
35
+ const schema = GraphQLSchemaBuilder.empty
36
+ .objectType({ name: "User", schema: UserSchema })
37
+ .query("users", {
38
+ type: S.Array(UserSchema),
39
+ resolve: () => Effect.succeed([
40
+ { id: "1", name: "Alice", email: "alice@example.com" },
41
+ { id: "2", name: "Bob", email: "bob@example.com" },
42
+ ]),
43
+ })
44
+ .query("user", {
45
+ type: UserSchema,
46
+ args: S.Struct({ id: S.String }),
47
+ resolve: (args) => Effect.succeed({
48
+ id: args.id,
49
+ name: "Alice",
50
+ email: "alice@example.com",
51
+ }),
52
+ })
53
+ .buildSchema()
54
+
55
+ // Execute a query
56
+ const result = await Effect.runPromise(
57
+ execute(schema, Layer.empty)(`
58
+ query {
59
+ users { id name email }
60
+ }
61
+ `)
62
+ )
63
+ ```
64
+
65
+ ## Documentation
66
+
67
+ For full documentation, guides, and API reference, visit the [documentation site](https://nrf110.github.io/effect-gql/).
68
+
69
+ ## Development
70
+
71
+ ```bash
72
+ # Install dependencies
73
+ npm install
74
+
75
+ # Build the project
76
+ npm run build
77
+
78
+ # Run tests
79
+ npm test
80
+
81
+ # Development mode with watch
82
+ npm run dev
83
+ ```
84
+
85
+ ## Contributing
86
+
87
+ Contributions are welcome! Here's how you can help:
88
+
89
+ 1. **Report bugs** - Open an issue describing the problem and steps to reproduce
90
+ 2. **Suggest features** - Open an issue describing your idea
91
+ 3. **Submit PRs** - Fork the repo, make your changes, and open a pull request
92
+
93
+ Please ensure your code:
94
+ - Passes all existing tests (`npm test`)
95
+ - Includes tests for new functionality
96
+ - Follows the existing code style
97
+
98
+ ## License
99
+
100
+ MIT
package/index.cjs ADDED
@@ -0,0 +1,385 @@
1
+ 'use strict';
2
+
3
+ var platform = require('@effect/platform');
4
+ var effect = require('effect');
5
+ var graphql = require('graphql');
6
+ var server = require('@effect-gql/core/server');
7
+ var core = require('@effect-gql/core');
8
+ var crypto = require('crypto');
9
+
10
+ // src/persisted-queries-router.ts
11
+ var PersistedQueryStore = effect.Context.GenericTag(
12
+ "@effect-gql/persisted-queries/PersistedQueryStore"
13
+ );
14
+ var makeMemoryStore = (config = {}) => {
15
+ const maxSize = config.maxSize ?? 1e3;
16
+ const cache = /* @__PURE__ */ new Map();
17
+ const touch = (hash, query) => {
18
+ cache.delete(hash);
19
+ cache.set(hash, query);
20
+ };
21
+ const evictIfNeeded = () => {
22
+ if (cache.size <= maxSize) return;
23
+ const oldestKey = cache.keys().next().value;
24
+ if (oldestKey !== void 0) {
25
+ cache.delete(oldestKey);
26
+ }
27
+ };
28
+ return effect.Layer.succeed(
29
+ PersistedQueryStore,
30
+ PersistedQueryStore.of({
31
+ get: (hash) => effect.Effect.sync(() => {
32
+ const query = cache.get(hash);
33
+ if (query === void 0) {
34
+ return effect.Option.none();
35
+ }
36
+ touch(hash, query);
37
+ return effect.Option.some(query);
38
+ }),
39
+ set: (hash, query) => effect.Effect.sync(() => {
40
+ cache.delete(hash);
41
+ cache.set(hash, query);
42
+ evictIfNeeded();
43
+ }),
44
+ has: (hash) => effect.Effect.sync(() => cache.has(hash))
45
+ })
46
+ );
47
+ };
48
+ var makeSafelistStore = (queries) => effect.Layer.succeed(
49
+ PersistedQueryStore,
50
+ PersistedQueryStore.of({
51
+ get: (hash) => effect.Effect.succeed(queries[hash] !== void 0 ? effect.Option.some(queries[hash]) : effect.Option.none()),
52
+ // No-op for safelist mode - queries cannot be added at runtime
53
+ set: () => effect.Effect.void,
54
+ has: (hash) => effect.Effect.succeed(queries[hash] !== void 0)
55
+ })
56
+ );
57
+ var PersistedQueryNotFoundError = class extends effect.Data.TaggedError("PersistedQueryNotFoundError") {
58
+ /**
59
+ * Convert to GraphQL error format compatible with Apollo protocol.
60
+ */
61
+ toGraphQLError() {
62
+ return {
63
+ message: "PersistedQueryNotFound",
64
+ extensions: {
65
+ code: "PERSISTED_QUERY_NOT_FOUND"
66
+ }
67
+ };
68
+ }
69
+ };
70
+ var PersistedQueryVersionError = class extends effect.Data.TaggedError("PersistedQueryVersionError") {
71
+ toGraphQLError() {
72
+ return {
73
+ message: `Unsupported persisted query version: ${this.version}`,
74
+ extensions: {
75
+ code: "PERSISTED_QUERY_VERSION_NOT_SUPPORTED",
76
+ version: this.version
77
+ }
78
+ };
79
+ }
80
+ };
81
+ var PersistedQueryHashMismatchError = class extends effect.Data.TaggedError(
82
+ "PersistedQueryHashMismatchError"
83
+ ) {
84
+ toGraphQLError() {
85
+ return {
86
+ message: "Query hash does not match provided hash",
87
+ extensions: {
88
+ code: "PERSISTED_QUERY_HASH_MISMATCH"
89
+ }
90
+ };
91
+ }
92
+ };
93
+ var PersistedQueryNotAllowedError = class extends effect.Data.TaggedError(
94
+ "PersistedQueryNotAllowedError"
95
+ ) {
96
+ toGraphQLError() {
97
+ return {
98
+ message: "Query not in safelist",
99
+ extensions: {
100
+ code: "PERSISTED_QUERY_NOT_ALLOWED"
101
+ }
102
+ };
103
+ }
104
+ };
105
+ var computeHash = (query, algorithm = "sha256") => effect.Effect.sync(() => crypto.createHash(algorithm).update(query).digest("hex"));
106
+ var parsePersistedQueryExtension = (extensions) => {
107
+ if (typeof extensions !== "object" || extensions === null || !("persistedQuery" in extensions)) {
108
+ return null;
109
+ }
110
+ const pq = extensions.persistedQuery;
111
+ if (typeof pq !== "object" || pq === null || !("version" in pq) || !("sha256Hash" in pq)) {
112
+ return null;
113
+ }
114
+ const version = pq.version;
115
+ const sha256Hash = pq.sha256Hash;
116
+ if (typeof version !== "number" || typeof sha256Hash !== "string") {
117
+ return null;
118
+ }
119
+ return { version, sha256Hash };
120
+ };
121
+ var parseGetRequestBody = (searchParams) => effect.Effect.try({
122
+ try: () => {
123
+ const extensionsRaw = searchParams.get("extensions");
124
+ const variablesRaw = searchParams.get("variables");
125
+ const result = {};
126
+ const query = searchParams.get("query");
127
+ if (query) {
128
+ result.query = query;
129
+ }
130
+ const operationName = searchParams.get("operationName");
131
+ if (operationName) {
132
+ result.operationName = operationName;
133
+ }
134
+ if (variablesRaw) {
135
+ result.variables = JSON.parse(variablesRaw);
136
+ }
137
+ if (extensionsRaw) {
138
+ result.extensions = JSON.parse(extensionsRaw);
139
+ }
140
+ return result;
141
+ },
142
+ catch: (e) => new Error(`Failed to parse query parameters: ${e}`)
143
+ });
144
+
145
+ // src/persisted-queries-router.ts
146
+ var makePersistedQueriesRouter = (schema, layer, options = {}) => {
147
+ const mode = options.mode ?? "apq";
148
+ const enableGet = options.enableGet ?? true;
149
+ const validateHashOption = options.validateHash ?? true;
150
+ const hashAlgorithm = options.hashAlgorithm ?? "sha256";
151
+ const resolvedConfig = server.normalizeConfig(options);
152
+ const fieldComplexities = options.fieldComplexities ?? /* @__PURE__ */ new Map();
153
+ const extensions = options.extensions ?? [];
154
+ const errorHandler = options.errorHandler ?? server.defaultErrorHandler;
155
+ const baseStoreLayer = options.store ?? makeMemoryStore();
156
+ const resolvePersistedQuery = (body) => effect.Effect.gen(function* () {
157
+ const persistedQuery = parsePersistedQueryExtension(body.extensions);
158
+ if (!persistedQuery) {
159
+ return body;
160
+ }
161
+ if (persistedQuery.version !== 1) {
162
+ return yield* effect.Effect.fail(
163
+ new PersistedQueryVersionError({ version: persistedQuery.version })
164
+ );
165
+ }
166
+ const hash = persistedQuery.sha256Hash;
167
+ const store = yield* PersistedQueryStore;
168
+ const storedQuery = yield* store.get(hash);
169
+ if (effect.Option.isSome(storedQuery)) {
170
+ return {
171
+ ...body,
172
+ query: storedQuery.value
173
+ };
174
+ }
175
+ if (!body.query) {
176
+ return yield* effect.Effect.fail(new PersistedQueryNotFoundError({ hash }));
177
+ }
178
+ if (mode === "safelist") {
179
+ return yield* effect.Effect.fail(new PersistedQueryNotAllowedError({ hash }));
180
+ }
181
+ if (validateHashOption) {
182
+ const computed = yield* computeHash(body.query, hashAlgorithm);
183
+ if (computed !== hash) {
184
+ return yield* effect.Effect.fail(
185
+ new PersistedQueryHashMismatchError({
186
+ providedHash: hash,
187
+ computedHash: computed
188
+ })
189
+ );
190
+ }
191
+ }
192
+ yield* store.set(hash, body.query);
193
+ return body;
194
+ });
195
+ const createHandler = (parseBody) => effect.Effect.gen(function* () {
196
+ const rawBody = yield* parseBody.pipe(
197
+ effect.Effect.catchAll(
198
+ (error) => effect.Effect.succeed({
199
+ _parseError: true,
200
+ message: error.message
201
+ })
202
+ )
203
+ );
204
+ if ("_parseError" in rawBody) {
205
+ return yield* platform.HttpServerResponse.json(
206
+ { errors: [{ message: rawBody.message }] },
207
+ { status: 400 }
208
+ );
209
+ }
210
+ const bodyResult = yield* resolvePersistedQuery(rawBody).pipe(
211
+ effect.Effect.provide(baseStoreLayer),
212
+ effect.Effect.either
213
+ );
214
+ if (bodyResult._tag === "Left") {
215
+ const error = bodyResult.left;
216
+ return yield* platform.HttpServerResponse.json({
217
+ errors: [error.toGraphQLError()]
218
+ });
219
+ }
220
+ const body = bodyResult.right;
221
+ if (!body.query) {
222
+ return yield* platform.HttpServerResponse.json(
223
+ { errors: [{ message: "No query provided" }] },
224
+ { status: 400 }
225
+ );
226
+ }
227
+ const extensionsService = yield* core.makeExtensionsService();
228
+ const runtime = yield* effect.Effect.runtime();
229
+ let document;
230
+ try {
231
+ document = graphql.parse(body.query);
232
+ } catch (parseError) {
233
+ const extensionData2 = yield* extensionsService.get();
234
+ return yield* platform.HttpServerResponse.json({
235
+ errors: [{ message: String(parseError) }],
236
+ extensions: Object.keys(extensionData2).length > 0 ? extensionData2 : void 0
237
+ });
238
+ }
239
+ yield* core.runParseHooks(extensions, body.query, document).pipe(
240
+ effect.Effect.provideService(core.ExtensionsService, extensionsService)
241
+ );
242
+ const validationRules = resolvedConfig.introspection ? void 0 : [...graphql.specifiedRules, graphql.NoSchemaIntrospectionCustomRule];
243
+ const validationErrors = graphql.validate(schema, document, validationRules);
244
+ yield* core.runValidateHooks(extensions, document, validationErrors).pipe(
245
+ effect.Effect.provideService(core.ExtensionsService, extensionsService)
246
+ );
247
+ if (validationErrors.length > 0) {
248
+ const extensionData2 = yield* extensionsService.get();
249
+ return yield* platform.HttpServerResponse.json(
250
+ {
251
+ errors: validationErrors.map((e) => ({
252
+ message: e.message,
253
+ locations: e.locations,
254
+ path: e.path
255
+ })),
256
+ extensions: Object.keys(extensionData2).length > 0 ? extensionData2 : void 0
257
+ },
258
+ { status: 400 }
259
+ );
260
+ }
261
+ if (resolvedConfig.complexity) {
262
+ yield* server.validateComplexity(
263
+ body.query,
264
+ body.operationName,
265
+ body.variables,
266
+ schema,
267
+ fieldComplexities,
268
+ resolvedConfig.complexity
269
+ ).pipe(
270
+ effect.Effect.catchTag("ComplexityLimitExceededError", (error) => effect.Effect.fail(error)),
271
+ effect.Effect.catchTag(
272
+ "ComplexityAnalysisError",
273
+ (error) => effect.Effect.logWarning("Complexity analysis failed", error)
274
+ )
275
+ );
276
+ }
277
+ const executionArgs = {
278
+ source: body.query,
279
+ document,
280
+ variableValues: body.variables,
281
+ operationName: body.operationName,
282
+ schema,
283
+ fieldComplexities
284
+ };
285
+ yield* core.runExecuteStartHooks(extensions, executionArgs).pipe(
286
+ effect.Effect.provideService(core.ExtensionsService, extensionsService)
287
+ );
288
+ const executeResult = yield* effect.Effect.try({
289
+ try: () => graphql.execute({
290
+ schema,
291
+ document,
292
+ variableValues: body.variables,
293
+ operationName: body.operationName,
294
+ contextValue: { runtime }
295
+ }),
296
+ catch: (error) => new Error(String(error))
297
+ });
298
+ const resolvedResult = executeResult && typeof executeResult === "object" && "then" in executeResult ? yield* effect.Effect.promise(() => executeResult) : executeResult;
299
+ yield* core.runExecuteEndHooks(extensions, resolvedResult).pipe(
300
+ effect.Effect.provideService(core.ExtensionsService, extensionsService)
301
+ );
302
+ const extensionData = yield* extensionsService.get();
303
+ const finalResult = Object.keys(extensionData).length > 0 ? {
304
+ ...resolvedResult,
305
+ extensions: {
306
+ ...resolvedResult.extensions,
307
+ ...extensionData
308
+ }
309
+ } : resolvedResult;
310
+ return yield* platform.HttpServerResponse.json(finalResult);
311
+ }).pipe(
312
+ effect.Effect.provide(layer),
313
+ effect.Effect.catchAll((error) => {
314
+ if (error instanceof server.ComplexityLimitExceededError) {
315
+ return platform.HttpServerResponse.json(
316
+ {
317
+ errors: [
318
+ {
319
+ message: error.message,
320
+ extensions: {
321
+ code: "COMPLEXITY_LIMIT_EXCEEDED",
322
+ limitType: error.limitType,
323
+ limit: error.limit,
324
+ actual: error.actual
325
+ }
326
+ }
327
+ ]
328
+ },
329
+ { status: 400 }
330
+ ).pipe(effect.Effect.orDie);
331
+ }
332
+ return effect.Effect.fail(error);
333
+ }),
334
+ effect.Effect.catchAllCause(errorHandler)
335
+ );
336
+ const postHandler = createHandler(
337
+ effect.Effect.gen(function* () {
338
+ const request = yield* platform.HttpServerRequest.HttpServerRequest;
339
+ return yield* request.json;
340
+ })
341
+ );
342
+ const getHandler = createHandler(
343
+ effect.Effect.gen(function* () {
344
+ const request = yield* platform.HttpServerRequest.HttpServerRequest;
345
+ const url = new URL(request.url, "http://localhost");
346
+ return yield* parseGetRequestBody(url.searchParams);
347
+ })
348
+ );
349
+ let router = platform.HttpRouter.empty.pipe(
350
+ platform.HttpRouter.post(resolvedConfig.path, postHandler)
351
+ );
352
+ if (enableGet) {
353
+ router = router.pipe(platform.HttpRouter.get(resolvedConfig.path, getHandler));
354
+ }
355
+ if (resolvedConfig.graphiql) {
356
+ const { path, endpoint } = resolvedConfig.graphiql;
357
+ router = router.pipe(
358
+ platform.HttpRouter.get(path, platform.HttpServerResponse.html(server.graphiqlHtml(endpoint)))
359
+ );
360
+ }
361
+ return router;
362
+ };
363
+ var PersistedQueriesConfigFromEnv = effect.Config.all({
364
+ mode: effect.Config.literal(
365
+ "apq",
366
+ "safelist"
367
+ )("PERSISTED_QUERIES_MODE").pipe(effect.Config.withDefault("apq")),
368
+ enableGet: effect.Config.boolean("PERSISTED_QUERIES_ENABLE_GET").pipe(effect.Config.withDefault(true)),
369
+ validateHash: effect.Config.boolean("PERSISTED_QUERIES_VALIDATE_HASH").pipe(effect.Config.withDefault(true))
370
+ });
371
+
372
+ exports.PersistedQueriesConfigFromEnv = PersistedQueriesConfigFromEnv;
373
+ exports.PersistedQueryHashMismatchError = PersistedQueryHashMismatchError;
374
+ exports.PersistedQueryNotAllowedError = PersistedQueryNotAllowedError;
375
+ exports.PersistedQueryNotFoundError = PersistedQueryNotFoundError;
376
+ exports.PersistedQueryStore = PersistedQueryStore;
377
+ exports.PersistedQueryVersionError = PersistedQueryVersionError;
378
+ exports.computeHash = computeHash;
379
+ exports.makeMemoryStore = makeMemoryStore;
380
+ exports.makePersistedQueriesRouter = makePersistedQueriesRouter;
381
+ exports.makeSafelistStore = makeSafelistStore;
382
+ exports.parseGetRequestBody = parseGetRequestBody;
383
+ exports.parsePersistedQueryExtension = parsePersistedQueryExtension;
384
+ //# sourceMappingURL=index.cjs.map
385
+ //# sourceMappingURL=index.cjs.map
package/index.cjs.map ADDED
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/store.ts","../src/memory-store.ts","../src/errors.ts","../src/utils.ts","../src/persisted-queries-router.ts","../src/config.ts"],"names":["Context","Layer","Effect","Option","Data","createHash","normalizeConfig","defaultErrorHandler","HttpServerResponse","makeExtensionsService","parse","extensionData","runParseHooks","ExtensionsService","specifiedRules","NoSchemaIntrospectionCustomRule","validate","runValidateHooks","validateComplexity","runExecuteStartHooks","graphqlExecute","runExecuteEndHooks","ComplexityLimitExceededError","HttpServerRequest","HttpRouter","graphiqlHtml","Config"],"mappings":"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","file":"index.cjs","sourcesContent":["import { Context, Effect, Option } from \"effect\"\n\n/**\n * Interface for persisted query storage.\n *\n * Implementations can be in-memory LRU cache, Redis, database, etc.\n * The interface uses Effect for consistency with the rest of the framework.\n */\nexport interface PersistedQueryStore {\n /**\n * Get a query by its hash.\n * Returns Option.none() if not found.\n */\n readonly get: (hash: string) => Effect.Effect<Option.Option<string>>\n\n /**\n * Store a query with its hash.\n * In safelist mode, this may be a no-op.\n */\n readonly set: (hash: string, query: string) => Effect.Effect<void>\n\n /**\n * Check if a hash exists in the store without retrieving the query.\n * More efficient for large queries when you only need existence check.\n */\n readonly has: (hash: string) => Effect.Effect<boolean>\n}\n\n/**\n * Service tag for the PersistedQueryStore.\n *\n * Use this tag to provide a store implementation via Effect's dependency injection.\n *\n * @example\n * ```typescript\n * import { PersistedQueryStore, makeMemoryStore } from \"@effect-gql/persisted-queries\"\n *\n * // Create a layer with the memory store\n * const storeLayer = makeMemoryStore({ maxSize: 1000 })\n *\n * // Use in an Effect\n * const program = Effect.gen(function* () {\n * const store = yield* PersistedQueryStore\n * yield* store.set(\"abc123\", \"query { hello }\")\n * const query = yield* store.get(\"abc123\")\n * // query: Option.some(\"query { hello }\")\n * })\n *\n * Effect.runPromise(Effect.provide(program, storeLayer))\n * ```\n */\nexport const PersistedQueryStore = Context.GenericTag<PersistedQueryStore>(\n \"@effect-gql/persisted-queries/PersistedQueryStore\"\n)\n","import { Effect, Layer, Option } from \"effect\"\nimport { PersistedQueryStore } from \"./store\"\n\n/**\n * Configuration for the in-memory LRU store\n */\nexport interface MemoryStoreConfig {\n /**\n * Maximum number of queries to cache.\n * When exceeded, least recently used queries are evicted.\n * Default: 1000\n */\n readonly maxSize?: number\n}\n\n/**\n * Create an in-memory LRU (Least Recently Used) store for persisted queries.\n *\n * This implementation uses Map's natural insertion order for O(1) LRU operations:\n * - get: O(1) - delete and re-insert to move to end (most recently used)\n * - set: O(1) - insert at end, evict from front if needed\n * - eviction: O(1) - delete first entry (least recently used)\n *\n * This is the default store implementation suitable for single-instance servers.\n * For multi-instance deployments, consider using a shared store like Redis.\n *\n * @param config - Optional configuration for cache size\n * @returns A Layer providing the PersistedQueryStore service\n *\n * @example\n * ```typescript\n * import { makeMemoryStore, makePersistedQueriesRouter } from \"@effect-gql/persisted-queries\"\n *\n * // Default store with 1000 entry limit\n * const router1 = makePersistedQueriesRouter(schema, serviceLayer)\n *\n * // Custom store with larger cache\n * const router2 = makePersistedQueriesRouter(schema, serviceLayer, {\n * store: makeMemoryStore({ maxSize: 5000 })\n * })\n * ```\n */\nexport const makeMemoryStore = (\n config: MemoryStoreConfig = {}\n): Layer.Layer<PersistedQueryStore> => {\n const maxSize = config.maxSize ?? 1000\n\n // Map maintains insertion order - we use this for O(1) LRU\n // First entry = least recently used, last entry = most recently used\n const cache = new Map<string, string>()\n\n // Move entry to end (most recently used) by deleting and re-inserting\n const touch = (hash: string, query: string): void => {\n cache.delete(hash)\n cache.set(hash, query)\n }\n\n // Evict oldest entry (first in Map) if over capacity - O(1)\n const evictIfNeeded = (): void => {\n if (cache.size <= maxSize) return\n // Map.keys().next() gives us the first (oldest) key in O(1)\n const oldestKey = cache.keys().next().value\n if (oldestKey !== undefined) {\n cache.delete(oldestKey)\n }\n }\n\n return Layer.succeed(\n PersistedQueryStore,\n PersistedQueryStore.of({\n get: (hash) =>\n Effect.sync(() => {\n const query = cache.get(hash)\n if (query === undefined) {\n return Option.none<string>()\n }\n // Move to end (most recently used)\n touch(hash, query)\n return Option.some(query)\n }),\n\n set: (hash, query) =>\n Effect.sync(() => {\n // If key exists, delete first to ensure it moves to end\n cache.delete(hash)\n cache.set(hash, query)\n evictIfNeeded()\n }),\n\n has: (hash) => Effect.sync(() => cache.has(hash)),\n })\n )\n}\n\n/**\n * Create a pre-populated safelist store.\n *\n * This store only allows queries that were provided at creation time.\n * Any attempt to store new queries is silently ignored.\n * Use this for production security where you want to allowlist specific operations.\n *\n * @param queries - Record mapping SHA-256 hashes to query strings\n * @returns A Layer providing the PersistedQueryStore service\n *\n * @example\n * ```typescript\n * import { makeSafelistStore, makePersistedQueriesRouter } from \"@effect-gql/persisted-queries\"\n *\n * // Pre-register allowed queries\n * const router = makePersistedQueriesRouter(schema, serviceLayer, {\n * mode: \"safelist\",\n * store: makeSafelistStore({\n * \"ecf4edb46db40b5132295c0291d62fb65d6759a9eedfa4d5d612dd5ec54a6b38\": \"query GetUser($id: ID!) { user(id: $id) { name email } }\",\n * \"a1b2c3d4...\": \"query GetPosts { posts { title } }\",\n * }),\n * })\n * ```\n */\nexport const makeSafelistStore = (\n queries: Record<string, string>\n): Layer.Layer<PersistedQueryStore> =>\n Layer.succeed(\n PersistedQueryStore,\n PersistedQueryStore.of({\n get: (hash) =>\n Effect.succeed(queries[hash] !== undefined ? Option.some(queries[hash]) : Option.none()),\n\n // No-op for safelist mode - queries cannot be added at runtime\n set: () => Effect.void,\n\n has: (hash) => Effect.succeed(queries[hash] !== undefined),\n })\n )\n","import { Data } from \"effect\"\n\n/**\n * GraphQL error format for APQ responses.\n * Compatible with Apollo Client's error handling.\n */\nexport interface PersistedQueryGraphQLError {\n readonly message: string\n readonly extensions: {\n readonly code: string\n readonly [key: string]: unknown\n }\n}\n\n/**\n * Error returned when a persisted query hash is not found in the store\n * and no query body was provided.\n *\n * Apollo clients recognize this error and automatically retry with the full query.\n * This is the expected flow for Automatic Persisted Queries (APQ).\n */\nexport class PersistedQueryNotFoundError extends Data.TaggedError(\"PersistedQueryNotFoundError\")<{\n readonly hash: string\n}> {\n /**\n * Convert to GraphQL error format compatible with Apollo protocol.\n */\n toGraphQLError(): PersistedQueryGraphQLError {\n return {\n message: \"PersistedQueryNotFound\",\n extensions: {\n code: \"PERSISTED_QUERY_NOT_FOUND\",\n },\n }\n }\n}\n\n/**\n * Error returned when the persisted query protocol version is not supported.\n *\n * Currently only version 1 is supported, which uses SHA-256 hashing.\n */\nexport class PersistedQueryVersionError extends Data.TaggedError(\"PersistedQueryVersionError\")<{\n readonly version: number\n}> {\n toGraphQLError(): PersistedQueryGraphQLError {\n return {\n message: `Unsupported persisted query version: ${this.version}`,\n extensions: {\n code: \"PERSISTED_QUERY_VERSION_NOT_SUPPORTED\",\n version: this.version,\n },\n }\n }\n}\n\n/**\n * Error returned when the provided query doesn't match its hash.\n *\n * This can indicate a client bug or a potential hash collision attack.\n * Hash validation is enabled by default and can be disabled if needed.\n */\nexport class PersistedQueryHashMismatchError extends Data.TaggedError(\n \"PersistedQueryHashMismatchError\"\n)<{\n readonly providedHash: string\n readonly computedHash: string\n}> {\n toGraphQLError(): PersistedQueryGraphQLError {\n return {\n message: \"Query hash does not match provided hash\",\n extensions: {\n code: \"PERSISTED_QUERY_HASH_MISMATCH\",\n },\n }\n }\n}\n\n/**\n * Error returned when trying to execute a query that is not in the safelist.\n *\n * In safelist mode, only pre-registered queries are allowed.\n * This error is returned when a client tries to register a new query.\n */\nexport class PersistedQueryNotAllowedError extends Data.TaggedError(\n \"PersistedQueryNotAllowedError\"\n)<{\n readonly hash: string\n}> {\n toGraphQLError(): PersistedQueryGraphQLError {\n return {\n message: \"Query not in safelist\",\n extensions: {\n code: \"PERSISTED_QUERY_NOT_ALLOWED\",\n },\n }\n }\n}\n\n/**\n * Union type of all APQ-related errors\n */\nexport type PersistedQueryError =\n | PersistedQueryNotFoundError\n | PersistedQueryVersionError\n | PersistedQueryHashMismatchError\n | PersistedQueryNotAllowedError\n","import { Effect } from \"effect\"\nimport { createHash } from \"crypto\"\nimport type { HashAlgorithm } from \"./config\"\n\n/**\n * Compute the hash of a query string.\n *\n * @param query - The GraphQL query string to hash\n * @param algorithm - Hash algorithm to use (default: sha256)\n * @returns Effect that resolves to the hex-encoded hash\n */\nexport const computeHash = (\n query: string,\n algorithm: HashAlgorithm = \"sha256\"\n): Effect.Effect<string> => Effect.sync(() => createHash(algorithm).update(query).digest(\"hex\"))\n\n/**\n * Structure of the persisted query extension in GraphQL requests.\n * This follows the Apollo APQ protocol.\n */\nexport interface PersistedQueryExtension {\n readonly version: number\n readonly sha256Hash: string\n}\n\n/**\n * Parse and validate the persisted query extension from request extensions.\n *\n * @param extensions - The extensions object from the GraphQL request\n * @returns The parsed persisted query extension, or null if not present/invalid\n */\nexport const parsePersistedQueryExtension = (\n extensions: unknown\n): PersistedQueryExtension | null => {\n if (typeof extensions !== \"object\" || extensions === null || !(\"persistedQuery\" in extensions)) {\n return null\n }\n\n const pq = (extensions as Record<string, unknown>).persistedQuery\n\n if (typeof pq !== \"object\" || pq === null || !(\"version\" in pq) || !(\"sha256Hash\" in pq)) {\n return null\n }\n\n const version = (pq as Record<string, unknown>).version\n const sha256Hash = (pq as Record<string, unknown>).sha256Hash\n\n if (typeof version !== \"number\" || typeof sha256Hash !== \"string\") {\n return null\n }\n\n return { version, sha256Hash }\n}\n\n/**\n * GraphQL request body structure with optional persisted query extension.\n */\nexport interface GraphQLRequestBody {\n query?: string\n variables?: Record<string, unknown>\n operationName?: string\n extensions?: {\n persistedQuery?: PersistedQueryExtension\n [key: string]: unknown\n }\n}\n\n/**\n * Parse a GET request's query parameters into a GraphQL request body.\n *\n * Supports the following query parameters:\n * - `query`: The GraphQL query string (optional with persisted queries)\n * - `variables`: JSON-encoded variables object\n * - `operationName`: Name of the operation to execute\n * - `extensions`: JSON-encoded extensions object containing persistedQuery\n *\n * @param searchParams - URLSearchParams from the request URL\n * @returns Effect that resolves to the parsed request body or fails with parse error\n */\nexport const parseGetRequestBody = (\n searchParams: URLSearchParams\n): Effect.Effect<GraphQLRequestBody, Error> =>\n Effect.try({\n try: () => {\n const extensionsRaw = searchParams.get(\"extensions\")\n const variablesRaw = searchParams.get(\"variables\")\n\n const result: GraphQLRequestBody = {}\n\n const query = searchParams.get(\"query\")\n if (query) {\n result.query = query\n }\n\n const operationName = searchParams.get(\"operationName\")\n if (operationName) {\n result.operationName = operationName\n }\n\n if (variablesRaw) {\n result.variables = JSON.parse(variablesRaw)\n }\n\n if (extensionsRaw) {\n result.extensions = JSON.parse(extensionsRaw)\n }\n\n return result\n },\n catch: (e) => new Error(`Failed to parse query parameters: ${e}`),\n })\n","import { HttpRouter, HttpServerRequest, HttpServerResponse } from \"@effect/platform\"\nimport { Effect, Layer, Option } from \"effect\"\nimport {\n GraphQLSchema,\n parse,\n validate,\n specifiedRules,\n NoSchemaIntrospectionCustomRule,\n execute as graphqlExecute,\n type DocumentNode,\n} from \"graphql\"\nimport {\n normalizeConfig,\n graphiqlHtml,\n validateComplexity,\n ComplexityLimitExceededError,\n type FieldComplexityMap,\n type ErrorHandler,\n defaultErrorHandler,\n} from \"@effect-gql/core/server\"\nimport type { GraphQLEffectContext } from \"@effect-gql/core\"\nimport {\n ExtensionsService,\n makeExtensionsService,\n runParseHooks,\n runValidateHooks,\n runExecuteStartHooks,\n runExecuteEndHooks,\n type GraphQLExtension,\n} from \"@effect-gql/core\"\nimport { PersistedQueryStore } from \"./store\"\nimport { makeMemoryStore } from \"./memory-store\"\nimport type { PersistedQueriesRouterOptions, PersistedQueryMode, HashAlgorithm } from \"./config\"\nimport {\n PersistedQueryNotFoundError,\n PersistedQueryVersionError,\n PersistedQueryHashMismatchError,\n PersistedQueryNotAllowedError,\n} from \"./errors\"\nimport {\n computeHash,\n parsePersistedQueryExtension,\n parseGetRequestBody,\n type GraphQLRequestBody,\n} from \"./utils\"\n\n/**\n * Create a GraphQL router with Apollo Persisted Queries support.\n *\n * This creates a complete GraphQL router that includes:\n * - Apollo Persisted Queries (APQ) support\n * - GET request support for CDN caching\n * - All standard GraphQL router features (validation, execution, extensions)\n *\n * ## Apollo APQ Protocol\n *\n * 1. Client sends request with `extensions.persistedQuery.sha256Hash`\n * 2. If hash found in store, execute the stored query\n * 3. If hash NOT found and query provided (APQ mode), store it and execute\n * 4. If hash NOT found and NO query, return `PERSISTED_QUERY_NOT_FOUND`\n * 5. Client retries with both hash and query\n *\n * ## Modes\n *\n * - **APQ mode** (`mode: \"apq\"`): Automatic runtime registration.\n * Unknown queries trigger NOT_FOUND, prompting client retry with full query.\n *\n * - **Safelist mode** (`mode: \"safelist\"`): Pre-registered queries only.\n * Unknown queries return NOT_ALLOWED error. Use with `makeSafelistStore()`.\n *\n * @example APQ Mode (default)\n * ```typescript\n * import { makePersistedQueriesRouter } from \"@effect-gql/persisted-queries\"\n *\n * const router = makePersistedQueriesRouter(schema, serviceLayer, {\n * mode: \"apq\",\n * enableGet: true,\n * graphiql: { path: \"/graphiql\" },\n * })\n * ```\n *\n * @example Safelist Mode\n * ```typescript\n * import { makePersistedQueriesRouter, makeSafelistStore } from \"@effect-gql/persisted-queries\"\n *\n * const router = makePersistedQueriesRouter(schema, serviceLayer, {\n * mode: \"safelist\",\n * store: makeSafelistStore({\n * \"abc123...\": \"query GetUser($id: ID!) { user(id: $id) { name } }\",\n * }),\n * })\n * ```\n *\n * @param schema - The GraphQL schema\n * @param layer - Effect layer providing services required by resolvers\n * @param options - Router and persisted query configuration\n * @returns An HttpRouter with persisted query support\n */\nexport const makePersistedQueriesRouter = <R>(\n schema: GraphQLSchema,\n layer: Layer.Layer<R>,\n options: PersistedQueriesRouterOptions = {}\n): HttpRouter.HttpRouter<never, never> => {\n const mode: PersistedQueryMode = options.mode ?? \"apq\"\n const enableGet = options.enableGet ?? true\n const validateHashOption = options.validateHash ?? true\n const hashAlgorithm: HashAlgorithm = options.hashAlgorithm ?? \"sha256\"\n\n // Core router config\n const resolvedConfig = normalizeConfig(options)\n const fieldComplexities: FieldComplexityMap = options.fieldComplexities ?? new Map()\n const extensions: readonly GraphQLExtension<R>[] = options.extensions ?? []\n const errorHandler: ErrorHandler = options.errorHandler ?? defaultErrorHandler\n\n // Create the store layer (default to in-memory)\n const baseStoreLayer = options.store ?? makeMemoryStore()\n\n /**\n * Resolve a persisted query from the store or register it.\n * Returns the resolved request body with the query filled in.\n */\n const resolvePersistedQuery = (\n body: GraphQLRequestBody\n ): Effect.Effect<\n GraphQLRequestBody,\n | PersistedQueryNotFoundError\n | PersistedQueryVersionError\n | PersistedQueryHashMismatchError\n | PersistedQueryNotAllowedError,\n PersistedQueryStore\n > =>\n Effect.gen(function* () {\n const persistedQuery = parsePersistedQueryExtension(body.extensions)\n\n if (!persistedQuery) {\n // No persisted query extension - pass through unchanged\n return body\n }\n\n // Validate version\n if (persistedQuery.version !== 1) {\n return yield* Effect.fail(\n new PersistedQueryVersionError({ version: persistedQuery.version })\n )\n }\n\n const hash = persistedQuery.sha256Hash\n const store = yield* PersistedQueryStore\n\n // Check if we have the query stored\n const storedQuery = yield* store.get(hash)\n\n if (Option.isSome(storedQuery)) {\n // Query found - use it\n return {\n ...body,\n query: storedQuery.value,\n }\n }\n\n // Query not found in store\n if (!body.query) {\n // No query provided - client needs to send it\n return yield* Effect.fail(new PersistedQueryNotFoundError({ hash }))\n }\n\n // Query provided - check mode\n if (mode === \"safelist\") {\n // Safelist mode: reject unknown queries\n return yield* Effect.fail(new PersistedQueryNotAllowedError({ hash }))\n }\n\n // APQ mode: validate hash and store\n if (validateHashOption) {\n const computed = yield* computeHash(body.query, hashAlgorithm)\n if (computed !== hash) {\n return yield* Effect.fail(\n new PersistedQueryHashMismatchError({\n providedHash: hash,\n computedHash: computed,\n })\n )\n }\n }\n\n // Store the query for future requests\n yield* store.set(hash, body.query)\n\n return body\n })\n\n /**\n * Main GraphQL handler with APQ support\n */\n const createHandler = <RE>(parseBody: Effect.Effect<GraphQLRequestBody, Error, RE>) =>\n Effect.gen(function* () {\n // Parse request body\n const rawBody = yield* parseBody.pipe(\n Effect.catchAll((error) =>\n Effect.succeed({\n _parseError: true,\n message: error.message,\n } as any)\n )\n )\n\n if (\"_parseError\" in rawBody) {\n return yield* HttpServerResponse.json(\n { errors: [{ message: rawBody.message }] },\n { status: 400 }\n )\n }\n\n // Resolve persisted query\n const bodyResult = yield* resolvePersistedQuery(rawBody).pipe(\n Effect.provide(baseStoreLayer),\n Effect.either\n )\n\n if (bodyResult._tag === \"Left\") {\n // APQ error - return appropriate GraphQL error response\n const error = bodyResult.left\n return yield* HttpServerResponse.json({\n errors: [error.toGraphQLError()],\n })\n }\n\n const body = bodyResult.right\n\n // Check if we have a query to execute\n if (!body.query) {\n return yield* HttpServerResponse.json(\n { errors: [{ message: \"No query provided\" }] },\n { status: 400 }\n )\n }\n\n // Create the ExtensionsService for this request\n const extensionsService = yield* makeExtensionsService()\n\n // Get the runtime from the layer\n const runtime = yield* Effect.runtime<R>()\n\n // Phase 1: Parse\n let document: DocumentNode\n try {\n document = parse(body.query)\n } catch (parseError) {\n const extensionData = yield* extensionsService.get()\n return yield* HttpServerResponse.json({\n errors: [{ message: String(parseError) }],\n extensions: Object.keys(extensionData).length > 0 ? extensionData : undefined,\n })\n }\n\n // Run onParse hooks\n yield* runParseHooks(extensions, body.query, document).pipe(\n Effect.provideService(ExtensionsService, extensionsService)\n )\n\n // Phase 2: Validate\n // Add NoSchemaIntrospectionCustomRule if introspection is disabled\n const validationRules = resolvedConfig.introspection\n ? undefined\n : [...specifiedRules, NoSchemaIntrospectionCustomRule]\n const validationErrors = validate(schema, document, validationRules)\n\n // Run onValidate hooks\n yield* runValidateHooks(extensions, document, validationErrors).pipe(\n Effect.provideService(ExtensionsService, extensionsService)\n )\n\n // If validation failed, return errors without executing\n if (validationErrors.length > 0) {\n const extensionData = yield* extensionsService.get()\n return yield* HttpServerResponse.json(\n {\n errors: validationErrors.map((e) => ({\n message: e.message,\n locations: e.locations,\n path: e.path,\n })),\n extensions: Object.keys(extensionData).length > 0 ? extensionData : undefined,\n },\n { status: 400 }\n )\n }\n\n // Validate query complexity if configured\n if (resolvedConfig.complexity) {\n yield* validateComplexity(\n body.query,\n body.operationName,\n body.variables,\n schema,\n fieldComplexities,\n resolvedConfig.complexity\n ).pipe(\n Effect.catchTag(\"ComplexityLimitExceededError\", (error) => Effect.fail(error)),\n Effect.catchTag(\"ComplexityAnalysisError\", (error) =>\n Effect.logWarning(\"Complexity analysis failed\", error)\n )\n )\n }\n\n // Phase 3: Execute\n const executionArgs = {\n source: body.query,\n document,\n variableValues: body.variables,\n operationName: body.operationName,\n schema,\n fieldComplexities,\n }\n\n // Run onExecuteStart hooks\n yield* runExecuteStartHooks(extensions, executionArgs).pipe(\n Effect.provideService(ExtensionsService, extensionsService)\n )\n\n // Execute GraphQL query\n const executeResult = yield* Effect.try({\n try: () =>\n graphqlExecute({\n schema,\n document,\n variableValues: body.variables,\n operationName: body.operationName,\n contextValue: { runtime } satisfies GraphQLEffectContext<R>,\n }),\n catch: (error) => new Error(String(error)),\n })\n\n // Await result if it's a promise\n const resolvedResult: Awaited<typeof executeResult> =\n executeResult && typeof executeResult === \"object\" && \"then\" in executeResult\n ? yield* Effect.promise(() => executeResult)\n : executeResult\n\n // Run onExecuteEnd hooks\n yield* runExecuteEndHooks(extensions, resolvedResult).pipe(\n Effect.provideService(ExtensionsService, extensionsService)\n )\n\n // Merge extension data into result\n const extensionData = yield* extensionsService.get()\n const finalResult =\n Object.keys(extensionData).length > 0\n ? {\n ...resolvedResult,\n extensions: {\n ...resolvedResult.extensions,\n ...extensionData,\n },\n }\n : resolvedResult\n\n return yield* HttpServerResponse.json(finalResult)\n }).pipe(\n Effect.provide(layer),\n Effect.catchAll((error) => {\n if (error instanceof ComplexityLimitExceededError) {\n return HttpServerResponse.json(\n {\n errors: [\n {\n message: error.message,\n extensions: {\n code: \"COMPLEXITY_LIMIT_EXCEEDED\",\n limitType: error.limitType,\n limit: error.limit,\n actual: error.actual,\n },\n },\n ],\n },\n { status: 400 }\n ).pipe(Effect.orDie)\n }\n return Effect.fail(error)\n }),\n Effect.catchAllCause(errorHandler)\n )\n\n // POST handler\n const postHandler = createHandler(\n Effect.gen(function* () {\n const request = yield* HttpServerRequest.HttpServerRequest\n return yield* request.json as Effect.Effect<GraphQLRequestBody, Error>\n })\n )\n\n // GET handler for CDN-cacheable persisted queries\n const getHandler = createHandler(\n Effect.gen(function* () {\n const request = yield* HttpServerRequest.HttpServerRequest\n const url = new URL(request.url, \"http://localhost\")\n return yield* parseGetRequestBody(url.searchParams)\n })\n )\n\n // Build router\n let router = HttpRouter.empty.pipe(\n HttpRouter.post(resolvedConfig.path as HttpRouter.PathInput, postHandler)\n )\n\n // Add GET handler if enabled\n if (enableGet) {\n router = router.pipe(HttpRouter.get(resolvedConfig.path as HttpRouter.PathInput, getHandler))\n }\n\n // Add GraphiQL route if enabled\n if (resolvedConfig.graphiql) {\n const { path, endpoint } = resolvedConfig.graphiql\n router = router.pipe(\n HttpRouter.get(path as HttpRouter.PathInput, HttpServerResponse.html(graphiqlHtml(endpoint)))\n )\n }\n\n return router\n}\n","import { Config, Layer } from \"effect\"\nimport type { PersistedQueryStore } from \"./store\"\nimport type { MakeGraphQLRouterOptions } from \"@effect-gql/core/server\"\n\n/**\n * Operating mode for persisted queries.\n *\n * - `\"apq\"`: Automatic Persisted Queries - clients can register queries at runtime\n * - `\"safelist\"`: Only pre-registered queries are allowed (security mode)\n */\nexport type PersistedQueryMode = \"apq\" | \"safelist\"\n\n/**\n * Hash algorithm used for query hashing.\n * Must match what clients use - Apollo clients use SHA-256.\n */\nexport type HashAlgorithm = \"sha256\" | \"sha512\"\n\n/**\n * Configuration for the persisted queries feature.\n */\nexport interface PersistedQueriesConfig {\n /**\n * Operating mode.\n *\n * - `\"apq\"`: Automatic Persisted Queries - clients can register queries at runtime.\n * Unknown hashes trigger PERSISTED_QUERY_NOT_FOUND, prompting clients to retry with query.\n *\n * - `\"safelist\"`: Only pre-registered queries are allowed.\n * Unknown hashes return PERSISTED_QUERY_NOT_ALLOWED error.\n * Use with `makeSafelistStore()` for production security.\n *\n * Default: `\"apq\"`\n */\n readonly mode?: PersistedQueryMode\n\n /**\n * Layer providing the PersistedQueryStore service.\n *\n * Defaults to in-memory LRU store with 1000 entries.\n * Use `makeMemoryStore()` for custom size or `makeSafelistStore()` for safelist mode.\n *\n * @example\n * ```typescript\n * // Custom memory store\n * store: makeMemoryStore({ maxSize: 5000 })\n *\n * // Safelist store\n * store: makeSafelistStore({ \"hash1\": \"query {...}\", \"hash2\": \"query {...}\" })\n * ```\n */\n readonly store?: Layer.Layer<PersistedQueryStore>\n\n /**\n * Whether to support GET requests with query parameters.\n *\n * When enabled, the router accepts:\n * ```\n * GET /graphql?extensions={\"persistedQuery\":{\"version\":1,\"sha256Hash\":\"...\"}}&variables={...}&operationName=...\n * ```\n *\n * This enables CDN caching since the same hash always maps to the same URL.\n *\n * Default: `true`\n */\n readonly enableGet?: boolean\n\n /**\n * Validate that the provided query matches its hash when storing.\n *\n * This prevents hash collision attacks where a malicious client could\n * register a different query under someone else's hash.\n *\n * Has a slight performance overhead for computing the hash.\n *\n * Default: `true`\n */\n readonly validateHash?: boolean\n\n /**\n * Hash algorithm to use for validation.\n * Must match what clients use - Apollo clients use SHA-256.\n *\n * Default: `\"sha256\"`\n */\n readonly hashAlgorithm?: HashAlgorithm\n}\n\n/**\n * Options for the persisted queries router.\n *\n * Extends the standard GraphQL router options with persisted query configuration.\n */\nexport interface PersistedQueriesRouterOptions\n extends MakeGraphQLRouterOptions, PersistedQueriesConfig {}\n\n/**\n * Effect Config for loading persisted queries settings from environment variables.\n *\n * Environment variables:\n * - `PERSISTED_QUERIES_MODE`: `\"apq\"` | `\"safelist\"` (default: `\"apq\"`)\n * - `PERSISTED_QUERIES_ENABLE_GET`: boolean (default: `true`)\n * - `PERSISTED_QUERIES_VALIDATE_HASH`: boolean (default: `true`)\n *\n * Note: The store must still be provided programmatically.\n *\n * @example\n * ```typescript\n * import { PersistedQueriesConfigFromEnv } from \"@effect-gql/persisted-queries\"\n *\n * const config = yield* Config.unwrap(PersistedQueriesConfigFromEnv)\n * ```\n */\nexport const PersistedQueriesConfigFromEnv: Config.Config<Omit<PersistedQueriesConfig, \"store\">> =\n Config.all({\n mode: Config.literal(\n \"apq\",\n \"safelist\"\n )(\"PERSISTED_QUERIES_MODE\").pipe(Config.withDefault(\"apq\" as const)),\n enableGet: Config.boolean(\"PERSISTED_QUERIES_ENABLE_GET\").pipe(Config.withDefault(true)),\n validateHash: Config.boolean(\"PERSISTED_QUERIES_VALIDATE_HASH\").pipe(Config.withDefault(true)),\n })\n"]}