urql-computed-exchange-plus 1.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. package/.editorconfig +9 -0
  2. package/.eslintrc.js +58 -0
  3. package/.importsortrc +6 -0
  4. package/.prettierrc +12 -0
  5. package/CHANGELOG.md +50 -0
  6. package/LICENSE +24 -0
  7. package/README.md +367 -0
  8. package/jest.config.js +19 -0
  9. package/jest.integration.config.js +14 -0
  10. package/jest.performance.config.js +19 -0
  11. package/lib/async-computed.d.ts +16 -0
  12. package/lib/async-computed.js +68 -0
  13. package/lib/async-computed.js.map +1 -0
  14. package/lib/computed-exchange.d.ts +5 -0
  15. package/lib/computed-exchange.js +24 -0
  16. package/lib/computed-exchange.js.map +1 -0
  17. package/lib/create-entity.d.ts +2 -0
  18. package/lib/create-entity.js +7 -0
  19. package/lib/create-entity.js.map +1 -0
  20. package/lib/create-modern-entity.d.ts +2 -0
  21. package/lib/create-modern-entity.js +10 -0
  22. package/lib/create-modern-entity.js.map +1 -0
  23. package/lib/directive-utils.d.ts +6 -0
  24. package/lib/directive-utils.js +125 -0
  25. package/lib/directive-utils.js.map +1 -0
  26. package/lib/index.d.ts +6 -0
  27. package/lib/index.js +23 -0
  28. package/lib/index.js.map +1 -0
  29. package/lib/merge-entities.d.ts +2 -0
  30. package/lib/merge-entities.js +35 -0
  31. package/lib/merge-entities.js.map +1 -0
  32. package/lib/resolve-data.d.ts +2 -0
  33. package/lib/resolve-data.js +152 -0
  34. package/lib/resolve-data.js.map +1 -0
  35. package/lib/set-utils.d.ts +5 -0
  36. package/lib/set-utils.js +27 -0
  37. package/lib/set-utils.js.map +1 -0
  38. package/lib/tsconfig.tsbuildinfo +1 -0
  39. package/lib/types/augmented-operation-result.d.ts +6 -0
  40. package/lib/types/augmented-operation-result.js +3 -0
  41. package/lib/types/augmented-operation-result.js.map +1 -0
  42. package/lib/types/augmented-operation.d.ts +6 -0
  43. package/lib/types/augmented-operation.js +3 -0
  44. package/lib/types/augmented-operation.js.map +1 -0
  45. package/lib/types/entity.d.ts +13 -0
  46. package/lib/types/entity.js +3 -0
  47. package/lib/types/entity.js.map +1 -0
  48. package/lib/types/index.d.ts +4 -0
  49. package/lib/types/index.js +21 -0
  50. package/lib/types/index.js.map +1 -0
  51. package/lib/types/node-with-directives.d.ts +2 -0
  52. package/lib/types/node-with-directives.js +3 -0
  53. package/lib/types/node-with-directives.js.map +1 -0
  54. package/lib/types.d.ts +68 -0
  55. package/lib/types.js +18 -0
  56. package/lib/types.js.map +1 -0
  57. package/package.json +77 -0
  58. package/test/integration/computed-exchange.test.ts +541 -0
  59. package/test/performance/large-dataset.test.ts +66 -0
  60. package/test/utils/index.ts +2 -0
  61. package/test/utils/run-query.ts +15 -0
  62. package/test/utils/simple-mock-fetch.ts +75 -0
  63. package/tsconfig.build.json +4 -0
  64. package/tsconfig.json +29 -0
package/.editorconfig ADDED
@@ -0,0 +1,9 @@
1
+ root = true
2
+
3
+ [*]
4
+ end_of_line = lf
5
+ charset = utf-8
6
+ trim_trailing_whitespace = true
7
+ insert_final_newline = true
8
+ indent_style = space
9
+ indent_size = 2
package/.eslintrc.js ADDED
@@ -0,0 +1,58 @@
1
+ module.exports = {
2
+ parser: '@typescript-eslint/parser',
3
+ plugins: ['@typescript-eslint', 'jest'],
4
+ extends: [
5
+ 'eslint:recommended',
6
+ '@typescript-eslint/recommended',
7
+ 'plugin:jest/recommended'
8
+ ],
9
+ parserOptions: {
10
+ ecmaVersion: 2020,
11
+ sourceType: 'module',
12
+ project: './tsconfig.json',
13
+ },
14
+ env: {
15
+ node: true,
16
+ commonjs: true,
17
+ browser: true,
18
+ es6: true,
19
+ jest: true,
20
+ },
21
+ rules: {
22
+ // General
23
+ 'array-callback-return': ['warn'],
24
+ 'eqeqeq': ['warn', 'always', { null: 'ignore' }],
25
+ 'new-parens': ['warn'],
26
+ 'no-array-constructor': ['warn'],
27
+ 'no-caller': ['warn'],
28
+ 'no-cond-assign': ['warn', 'always'],
29
+ 'no-console': ['warn', { allow: ['warn', 'error'] }],
30
+ 'no-eval': ['warn'],
31
+ 'no-extend-native': ['warn'],
32
+ 'no-extra-bind': ['warn'],
33
+ 'no-implied-eval': ['warn'],
34
+ 'no-iterator': ['warn'],
35
+ 'no-lone-blocks': ['warn'],
36
+ 'no-loop-func': ['warn'],
37
+ 'no-multi-str': ['warn'],
38
+ 'no-new-wrappers': ['warn'],
39
+ 'no-script-url': ['warn'],
40
+ 'no-self-compare': ['warn'],
41
+ 'no-shadow-restricted-names': ['warn'],
42
+ 'no-template-curly-in-string': ['warn'],
43
+ 'no-throw-literal': ['warn'],
44
+ 'no-useless-computed-key': ['warn'],
45
+ 'no-useless-concat': ['warn'],
46
+ 'no-useless-rename': ['warn'],
47
+ 'no-whitespace-before-property': ['warn'],
48
+
49
+ // TypeScript
50
+ 'no-unused-vars': 'off',
51
+ '@typescript-eslint/no-unused-vars': ['warn', { ignoreRestSiblings: true }],
52
+ 'no-useless-constructor': 'off',
53
+ '@typescript-eslint/no-useless-constructor': ['warn'],
54
+ '@typescript-eslint/explicit-function-return-type': 'off',
55
+ '@typescript-eslint/explicit-module-boundary-types': 'off',
56
+ '@typescript-eslint/no-explicit-any': 'warn',
57
+ },
58
+ };
package/.importsortrc ADDED
@@ -0,0 +1,6 @@
1
+ {
2
+ ".ts, .tsx": {
3
+ "parser": "typescript",
4
+ "style": "module"
5
+ }
6
+ }
package/.prettierrc ADDED
@@ -0,0 +1,12 @@
1
+ {
2
+ "printWidth": 100,
3
+ "tabWidth": 2,
4
+ "useTabs": false,
5
+ "semi": true,
6
+ "singleQuote": true,
7
+ "trailingComma": "all",
8
+ "bracketSpacing": true,
9
+ "jsxBracketSameLine": true,
10
+ "fluid": false,
11
+ "arrowParens": "always"
12
+ }
package/CHANGELOG.md ADDED
@@ -0,0 +1,50 @@
1
+ # Changelog
2
+
3
+ ## [1.0.3] - 2025-01-02
4
+
5
+ ### Fixed
6
+ - **CRITICAL**: Fixed infinite loop in dependency resolution caused by incorrect set difference implementation
7
+ - Fixed circular dependency detection in GraphQL fragment processing
8
+ - Improved error handling for resolver failures (now fails gracefully instead of crashing)
9
+ - Updated integration with latest urql version (removed deprecated `dedupExchange`)
10
+
11
+ ### Added
12
+ - **Async Computed Properties**: Support for async resolvers with caching and TTL
13
+ - **Enhanced TypeScript Types**: Better type safety with generic entity definitions
14
+ - **Circular Dependency Detection**: Proactive detection with detailed error messages
15
+ - **Performance Optimizations**: Handles 1000+ items efficiently with proper caching
16
+ - **Modern Entity Creator**: `createModernEntity` with enhanced type safety
17
+ - **Comprehensive Documentation**:
18
+ - Integration examples with different urql exchanges
19
+ - Dependency resolution behavior explanation
20
+ - Troubleshooting guide for common issues
21
+ - Error handling examples
22
+ - **Enhanced Testing**:
23
+ - Comprehensive integration tests with urql's cache exchange
24
+ - Performance tests for large datasets
25
+ - Tests for complex computed property chains
26
+ - Tests for error handling scenarios
27
+ - Tests for array handling with computed properties
28
+ - Async computed property tests
29
+ - **CI/CD Pipeline**: GitHub Actions workflow for automated testing and publishing
30
+
31
+ ### Improved
32
+ - **Better Error Messages**: Detailed error messages for circular dependencies and resolution failures
33
+ - **More Robust Handling**: Better handling of missing dependencies and edge cases
34
+ - **Enhanced TypeScript Support**: Improved type definitions and generic constraints
35
+ - **Documentation**: Comprehensive README with examples and troubleshooting
36
+ - **Test Coverage**: 45+ tests covering unit, integration, and performance scenarios
37
+
38
+ ### Technical Improvements
39
+ - Maximum iteration limit to prevent infinite loops (100 iterations)
40
+ - Proactive circular dependency detection using graph analysis
41
+ - Enhanced resolver error handling with detailed logging
42
+ - Support for complex dependency chains with proper resolution order
43
+ - Async computed properties with caching and TTL support
44
+ - Modern TypeScript features and better type safety
45
+
46
+ ## [1.0.2] - Previous
47
+ - Maintained fork with updated dependencies
48
+
49
+ ## [1.0.1] - Previous
50
+ - Initial fork from original urql-computed-exchange
package/LICENSE ADDED
@@ -0,0 +1,24 @@
1
+ This is free and unencumbered software released into the public domain.
2
+
3
+ Anyone is free to copy, modify, publish, use, compile, sell, or
4
+ distribute this software, either in source code form or as a compiled
5
+ binary, for any purpose, commercial or non-commercial, and by any
6
+ means.
7
+
8
+ In jurisdictions that recognize copyright laws, the author or authors
9
+ of this software dedicate any and all copyright interest in the
10
+ software to the public domain. We make this dedication for the benefit
11
+ of the public at large and to the detriment of our heirs and
12
+ successors. We intend this dedication to be an overt act of
13
+ relinquishment in perpetuity of all present and future rights to this
14
+ software under copyright law.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
19
+ IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
20
+ OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
21
+ ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
22
+ OTHER DEALINGS IN THE SOFTWARE.
23
+
24
+ For more information, please refer to <https://unlicense.org>
package/README.md ADDED
@@ -0,0 +1,367 @@
1
+ # URQL Computed Exchange Plus
2
+
3
+ An [URQL](https://github.com/FormidableLabs/urql) exchange to compute data using resolvers and entities.
4
+
5
+ **This is a maintained fork of the original `urql-computed-exchange` with updated dependencies and modern tooling.**
6
+
7
+ ## Installation
8
+
9
+ ```bash
10
+ $ npm i urql-computed-exchange-plus
11
+ ```
12
+
13
+ ## Usage
14
+
15
+ First, create your entities and their resolvers:
16
+
17
+ ```javascript
18
+ // entities.js
19
+ import { createEntity, mergeEntities } from 'urql-computed-exchange-plus';
20
+
21
+ const Pokemon = createEntity('Pokemon', {
22
+ numberOfEvolutions: {
23
+ dependencies: gql`
24
+ fragment _ on Pokemon {
25
+ evolutions {
26
+ id
27
+ }
28
+ }
29
+ `,
30
+ resolver: (pokemon) => {
31
+ return (pokemon.evolutions && pokemon.evolutions.length) ?? 0;
32
+ },
33
+ },
34
+ });
35
+
36
+ export default mergeEntities(Pokemon);
37
+ ```
38
+
39
+ Then, add it to the list of exchanges in URQL when setting up the client:
40
+
41
+ ```javascript
42
+ // client.js
43
+
44
+ import { computedExchange } from 'urql-computed-exchange-plus';
45
+ import {
46
+ createClient,
47
+ cacheExchange,
48
+ fetchExchange,
49
+ } from 'urql';
50
+
51
+ import entities from './entities';
52
+
53
+
54
+ const client = createClient({
55
+ url: 'https://graphql-pokemon.now.sh/',
56
+ exchanges: [
57
+ cacheExchange,
58
+ computedExchange({ entities }),
59
+ fetchExchange,
60
+ ],
61
+ });
62
+
63
+ export default client;
64
+ ```
65
+
66
+ Finally, use the `@computed` directive when declaring your GraphQL queries. Don't forget to indicate the corresponding `type`:
67
+
68
+ ```javascript
69
+ // App.js
70
+
71
+ import React from 'react';
72
+ import { useQuery } from 'urql';
73
+ import gql from 'graphql-tag';
74
+
75
+ const PokemonQuery = gql`
76
+ query PokemonQuery {
77
+ pokemon(name: "charmander") {
78
+ id
79
+ name
80
+ numberOfEvolutions @computed(type: Pokemon)
81
+ }
82
+ }
83
+ `;
84
+
85
+ const App = () => {
86
+ const [ res ] = useQuery({
87
+ query: PokemonQuery,
88
+ });
89
+
90
+ if (res.fetching) {
91
+ return 'Loading...';
92
+ }
93
+
94
+ return (
95
+ <pre>
96
+ {JSON.stringify(res.data, null, 2)}
97
+ </pre>
98
+ );
99
+ };
100
+
101
+ export default App;
102
+ ```
103
+ ## Error Handling
104
+
105
+ The exchange handles various error scenarios gracefully:
106
+
107
+ ### Circular Dependencies
108
+ ```typescript
109
+ // This will throw an error instead of hanging
110
+ const entities = {
111
+ User: createEntity('User', {
112
+ fieldA: {
113
+ dependencies: gql`fragment _ on User { fieldB @computed(type: User) }`,
114
+ resolver: (user) => `A-${user.fieldB}`,
115
+ },
116
+ fieldB: {
117
+ dependencies: gql`fragment _ on User { fieldA @computed(type: User) }`,
118
+ resolver: (user) => `B-${user.fieldA}`,
119
+ },
120
+ }),
121
+ };
122
+ ```
123
+
124
+ ### Resolver Errors
125
+ ```typescript
126
+ const entities = {
127
+ User: createEntity('User', {
128
+ riskyField: {
129
+ dependencies: gql`fragment _ on User { someField }`,
130
+ resolver: (user) => {
131
+ if (!user.someField) {
132
+ throw new Error('Missing required field');
133
+ }
134
+ return user.someField.toUpperCase();
135
+ },
136
+ },
137
+ }),
138
+ };
139
+ // If resolver throws, the field will be undefined instead of crashing the query
140
+ ```
141
+
142
+ ### Missing Dependencies
143
+ ```typescript
144
+ const entities = {
145
+ User: createEntity('User', {
146
+ safeField: {
147
+ dependencies: gql`fragment _ on User { nonExistentField }`,
148
+ resolver: (user) => user.nonExistentField || 'fallback value',
149
+ },
150
+ }),
151
+ };
152
+ ```
153
+
154
+ ## Integration with urql Exchanges
155
+
156
+ The computed exchange works seamlessly with urql's built-in exchanges:
157
+
158
+ ### Basic Setup
159
+ ```typescript
160
+ import { createClient, cacheExchange, fetchExchange } from 'urql';
161
+ import { computedExchange } from 'urql-computed-exchange-plus';
162
+
163
+ const client = createClient({
164
+ url: 'https://api.example.com/graphql',
165
+ exchanges: [
166
+ cacheExchange, // Works with caching
167
+ computedExchange({ entities }),
168
+ fetchExchange,
169
+ ],
170
+ });
171
+ ```
172
+
173
+ ### With Authentication Exchange
174
+ ```typescript
175
+ import { authExchange } from '@urql/exchange-auth';
176
+
177
+ const client = createClient({
178
+ url: 'https://api.example.com/graphql',
179
+ exchanges: [
180
+ cacheExchange,
181
+ authExchange({
182
+ // auth config
183
+ }),
184
+ computedExchange({ entities }),
185
+ fetchExchange,
186
+ ],
187
+ });
188
+ ```
189
+
190
+ ### With Retry Exchange
191
+ ```typescript
192
+ import { retryExchange } from '@urql/exchange-retry';
193
+
194
+ const client = createClient({
195
+ url: 'https://api.example.com/graphql',
196
+ exchanges: [
197
+ cacheExchange,
198
+ retryExchange({
199
+ initialDelayMs: 1000,
200
+ maxDelayMs: 15000,
201
+ randomDelay: true,
202
+ maxNumberAttempts: 2,
203
+ }),
204
+ computedExchange({ entities }),
205
+ fetchExchange,
206
+ ],
207
+ });
208
+ ```
209
+
210
+ ## Dependency Resolution Behavior
211
+
212
+ The computed exchange resolves dependencies in the following order:
213
+
214
+ 1. **Parse Query**: Identifies all `@computed` directives and their types
215
+ 2. **Collect Dependencies**: Gathers all required fields from entity definitions
216
+ 3. **Resolve Chain**: Processes computed properties in dependency order
217
+ 4. **Circular Detection**: Prevents infinite loops with maximum iteration limits
218
+ 5. **Error Handling**: Gracefully handles resolver failures
219
+
220
+ ### Dependency Chain Example
221
+ ```typescript
222
+ const entities = {
223
+ User: createEntity('User', {
224
+ // Level 1: Direct field access
225
+ fullName: {
226
+ dependencies: gql`fragment _ on User { firstName lastName }`,
227
+ resolver: (user) => `${user.firstName} ${user.lastName}`,
228
+ },
229
+ // Level 2: Depends on Level 1
230
+ displayName: {
231
+ dependencies: gql`fragment _ on User { fullName @computed(type: User) title }`,
232
+ resolver: (user) => `${user.title} ${user.fullName}`,
233
+ },
234
+ // Level 3: Depends on Level 2
235
+ greeting: {
236
+ dependencies: gql`fragment _ on User { displayName @computed(type: User) }`,
237
+ resolver: (user) => `Hello, ${user.displayName}!`,
238
+ },
239
+ }),
240
+ };
241
+ ```
242
+
243
+ ## Troubleshooting
244
+
245
+ ### Common Issues
246
+
247
+ #### Issue: "Maximum iterations reached" Error
248
+ **Cause**: Circular dependencies in computed properties
249
+ **Solution**: Check your entity definitions for circular references
250
+ ```typescript
251
+ // ❌ Bad: Circular dependency
252
+ const entities = {
253
+ User: createEntity('User', {
254
+ fieldA: {
255
+ dependencies: gql`fragment _ on User { fieldB @computed(type: User) }`,
256
+ resolver: (user) => user.fieldB,
257
+ },
258
+ fieldB: {
259
+ dependencies: gql`fragment _ on User { fieldA @computed(type: User) }`,
260
+ resolver: (user) => user.fieldA,
261
+ },
262
+ }),
263
+ };
264
+
265
+ // ✅ Good: Linear dependency chain
266
+ const entities = {
267
+ User: createEntity('User', {
268
+ fullName: {
269
+ dependencies: gql`fragment _ on User { firstName lastName }`,
270
+ resolver: (user) => `${user.firstName} ${user.lastName}`,
271
+ },
272
+ displayName: {
273
+ dependencies: gql`fragment _ on User { fullName @computed(type: User) title }`,
274
+ resolver: (user) => `${user.title} ${user.fullName}`,
275
+ },
276
+ }),
277
+ };
278
+ ```
279
+
280
+ #### Issue: Computed Fields Return `undefined`
281
+ **Cause**: Missing dependencies or resolver errors
282
+ **Solution**: Check that all required fields are available and resolvers handle edge cases
283
+ ```typescript
284
+ // ✅ Good: Handle missing data gracefully
285
+ const entities = {
286
+ User: createEntity('User', {
287
+ safeField: {
288
+ dependencies: gql`fragment _ on User { optionalField }`,
289
+ resolver: (user) => {
290
+ if (!user.optionalField) {
291
+ return 'Default Value';
292
+ }
293
+ return user.optionalField.toUpperCase();
294
+ },
295
+ },
296
+ }),
297
+ };
298
+ ```
299
+
300
+ #### Issue: Performance Issues with Large Datasets
301
+ **Cause**: Complex computed property chains on many items
302
+ **Solution**: Optimize resolvers and consider caching
303
+ ```typescript
304
+ // ✅ Good: Efficient resolver
305
+ const entities = {
306
+ User: createEntity('User', {
307
+ expensiveComputation: {
308
+ dependencies: gql`fragment _ on User { data }`,
309
+ resolver: (user) => {
310
+ // Cache expensive operations
311
+ if (user._cachedResult) return user._cachedResult;
312
+ const result = performExpensiveOperation(user.data);
313
+ user._cachedResult = result;
314
+ return result;
315
+ },
316
+ },
317
+ }),
318
+ };
319
+ ```
320
+
321
+ #### Issue: TypeScript Type Errors
322
+ **Cause**: Incorrect type annotations or missing type definitions
323
+ **Solution**: Ensure proper typing for entities and resolvers
324
+ ```typescript
325
+ interface User {
326
+ id: string;
327
+ firstName: string;
328
+ lastName: string;
329
+ }
330
+
331
+ const entities = {
332
+ User: createEntity('User', {
333
+ fullName: {
334
+ dependencies: gql`fragment _ on User { firstName lastName }`,
335
+ resolver: (user: User) => `${user.firstName} ${user.lastName}`,
336
+ },
337
+ }),
338
+ };
339
+ ```
340
+
341
+ ## Testing
342
+
343
+ The library includes comprehensive test coverage:
344
+ - Unit tests for core functionality
345
+ - Integration tests with urql's cache exchange
346
+ - Error handling and edge case testing
347
+ - Performance and circular dependency protection
348
+
349
+ Run tests:
350
+ ```bash
351
+ npm test # Unit tests
352
+ npm run test:integration # Integration tests
353
+ npm run test:performance # Performance tests
354
+ npm run test:all # All tests
355
+ ```
356
+
357
+ ## Changelog
358
+
359
+ See [CHANGELOG.md](./CHANGELOG.md) for version history and breaking changes.
360
+
361
+ ## Contributing
362
+
363
+ This is a maintained fork. Contributions are welcome! Please ensure tests pass and add tests for new features.
364
+
365
+ ## License
366
+
367
+ MIT
package/jest.config.js ADDED
@@ -0,0 +1,19 @@
1
+ const path = require('path');
2
+
3
+ module.exports = {
4
+ preset: 'ts-jest',
5
+ verbose: false,
6
+ testEnvironment: 'node',
7
+ rootDir: path.resolve(__dirname, 'src'),
8
+ coverageDirectory: './coverage',
9
+ moduleFileExtensions: ['js', 'json', 'ts'],
10
+ testRegex: '\\.test\\.ts$',
11
+ transform: {
12
+ '^.+\\.(t|j)s$': 'ts-jest',
13
+ },
14
+ collectCoverageFrom: [
15
+ '**/*.(t|j)s',
16
+ '!**/*.test.(t|j)s',
17
+ '!**/node_modules/**',
18
+ ],
19
+ };
@@ -0,0 +1,14 @@
1
+ const path = require('path');
2
+
3
+ module.exports = {
4
+ preset: 'ts-jest',
5
+ verbose: false,
6
+ testEnvironment: 'node',
7
+ rootDir: path.resolve(__dirname, 'test'),
8
+ coverageDirectory: './coverage',
9
+ moduleFileExtensions: ['js', 'json', 'ts'],
10
+ testRegex: '\\.test\\.ts$',
11
+ transform: {
12
+ '^.+\\.(t|j)s$': 'ts-jest',
13
+ },
14
+ };
@@ -0,0 +1,19 @@
1
+ const path = require('path');
2
+
3
+ module.exports = {
4
+ preset: 'ts-jest',
5
+ verbose: false,
6
+ testEnvironment: 'node',
7
+ rootDir: path.resolve(__dirname),
8
+ coverageDirectory: './coverage',
9
+ moduleFileExtensions: ['js', 'json', 'ts'],
10
+ testRegex: 'test/performance/.*\\.test\\.ts$',
11
+ transform: {
12
+ '^.+\\.(t|j)s$': 'ts-jest',
13
+ },
14
+ collectCoverageFrom: [
15
+ 'src/**/*.(t|j)s',
16
+ '!src/**/*.test.(t|j)s',
17
+ '!**/node_modules/**',
18
+ ],
19
+ };
@@ -0,0 +1,16 @@
1
+ import { DocumentNode } from 'graphql';
2
+ import { GraphQLObject } from './types';
3
+ export type AsyncComputedResolver<T extends GraphQLObject = GraphQLObject, R = any> = (data: T) => Promise<R>;
4
+ export interface AsyncComputedProperty<T extends GraphQLObject = GraphQLObject, R = any> {
5
+ dependencies: DocumentNode;
6
+ resolver: AsyncComputedResolver<T, R>;
7
+ cacheKey?: (data: T) => string;
8
+ ttl?: number;
9
+ }
10
+ export declare function resolveAsyncComputedProperties<T extends GraphQLObject>(data: T, properties: Record<string, AsyncComputedProperty<T>>): Promise<Partial<T>>;
11
+ export declare function createAsyncEntity<T extends GraphQLObject = GraphQLObject>(typeName: string, properties: Record<string, AsyncComputedProperty<T>>): {
12
+ __typename: string;
13
+ asyncComputedProperties: Record<string, AsyncComputedProperty<T, any>>;
14
+ resolve: (data: T) => Promise<Partial<T>>;
15
+ };
16
+ export declare function clearAsyncComputedCache(): void;
@@ -0,0 +1,68 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.resolveAsyncComputedProperties = resolveAsyncComputedProperties;
4
+ exports.createAsyncEntity = createAsyncEntity;
5
+ exports.clearAsyncComputedCache = clearAsyncComputedCache;
6
+ class AsyncComputedCache {
7
+ constructor() {
8
+ this.cache = new Map();
9
+ }
10
+ set(key, value, ttl = 5 * 60 * 1000) {
11
+ this.cache.set(key, {
12
+ value,
13
+ timestamp: Date.now(),
14
+ ttl,
15
+ });
16
+ }
17
+ get(key) {
18
+ const entry = this.cache.get(key);
19
+ if (!entry)
20
+ return undefined;
21
+ if (Date.now() - entry.timestamp > entry.ttl) {
22
+ this.cache.delete(key);
23
+ return undefined;
24
+ }
25
+ return entry.value;
26
+ }
27
+ clear() {
28
+ this.cache.clear();
29
+ }
30
+ }
31
+ const asyncCache = new AsyncComputedCache();
32
+ async function resolveAsyncComputedProperties(data, properties) {
33
+ const results = {};
34
+ const promises = Object.entries(properties).map(async ([key, property]) => {
35
+ try {
36
+ const cacheKey = property.cacheKey
37
+ ? `${data.__typename}:${key}:${property.cacheKey(data)}`
38
+ : `${data.__typename}:${key}:${JSON.stringify(data)}`;
39
+ const cached = asyncCache.get(cacheKey);
40
+ if (cached !== undefined) {
41
+ return [key, cached];
42
+ }
43
+ const result = await property.resolver(data);
44
+ asyncCache.set(cacheKey, result, property.ttl);
45
+ return [key, result];
46
+ }
47
+ catch (error) {
48
+ console.warn(`Async computed property "${key}" failed:`, error);
49
+ return [key, undefined];
50
+ }
51
+ });
52
+ const resolvedEntries = await Promise.all(promises);
53
+ for (const [key, value] of resolvedEntries) {
54
+ results[key] = value;
55
+ }
56
+ return results;
57
+ }
58
+ function createAsyncEntity(typeName, properties) {
59
+ return {
60
+ __typename: typeName,
61
+ asyncComputedProperties: properties,
62
+ resolve: (data) => resolveAsyncComputedProperties(data, properties),
63
+ };
64
+ }
65
+ function clearAsyncComputedCache() {
66
+ asyncCache.clear();
67
+ }
68
+ //# sourceMappingURL=async-computed.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"async-computed.js","sourceRoot":"","sources":["../src/async-computed.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,5 @@
1
+ import { Exchange } from 'urql';
2
+ import { Entities } from './types';
3
+ export declare function computedExchange({ entities }: {
4
+ entities: Entities;
5
+ }): Exchange;