@data-eden/athena
Advanced tools
Comparing version 0.7.2 to 0.8.0
@@ -438,2 +438,54 @@ /* eslint-disable @typescript-eslint/no-unsafe-member-access */ | ||
}); | ||
test('can resolve cycle with array property with multiple values', () => { | ||
cache.evict('Person:1'); | ||
cache.evict('Pet:1'); | ||
cache.storeEntity('Person:1', { | ||
...Person1, | ||
pets: [ | ||
{ | ||
__link: 'Pet:1', | ||
}, | ||
{ | ||
__link: 'Pet:2', | ||
}, | ||
], | ||
}); | ||
cache.storeEntity('Pet:1', { | ||
...Pet1, | ||
owner: { | ||
__link: 'Person:1', | ||
}, | ||
}); | ||
cache.storeEntity('Pet:2', { | ||
...Pet2, | ||
owner: { | ||
__link: 'Person:1', | ||
}, | ||
}); | ||
const pet1 = cache.resolve('Pet:1'); | ||
expect(pet1.__typename).toEqual('Pet'); | ||
expect(pet1.name).toEqual('Hitch'); | ||
expect(pet1.owner.__typename).toEqual('Person'); | ||
expect(pet1.owner.name).toEqual('Foo'); | ||
expect(pet1.owner.pets[0].name).toEqual('Hitch'); | ||
expect(pet1.owner.pets[0].owner.__typename).toEqual('Person'); | ||
const pet2 = cache.resolve('Pet:2'); | ||
expect(pet2.__typename).toEqual('Pet'); | ||
expect(pet2.name).toEqual('Dre'); | ||
expect(pet2.owner.__typename).toEqual('Person'); | ||
expect(pet2.owner.name).toEqual('Foo'); | ||
expect(pet2.owner.pets[1].name).toEqual('Dre'); | ||
expect(pet2.owner.pets[1].owner.__typename).toEqual('Person'); | ||
// Owners for both pets should be the same object | ||
expect(pet1.owner).toBe(pet2.owner); | ||
}); | ||
}); | ||
@@ -440,0 +492,0 @@ |
import type { DefaultVariables, DocumentInput, GraphQLRequest, IdFetcher, OperationResult, ReactiveAdapter } from './types.js'; | ||
export interface ClientArgs { | ||
url: string; | ||
id: IdFetcher; | ||
getCacheKey: IdFetcher; | ||
fetch?: typeof fetch; | ||
@@ -17,3 +17,3 @@ buildRequest?: BuildRequest; | ||
private cache; | ||
private getId; | ||
private getCacheKey; | ||
private signalCache; | ||
@@ -20,0 +20,0 @@ private buildRequest; |
@@ -18,6 +18,6 @@ import { buildCache } from '@data-eden/cache'; | ||
this.url = options.url; | ||
this.getId = options.id; | ||
this.fetch = options.fetch || globalThis.fetch; | ||
this.getCacheKey = options.getCacheKey; | ||
this.fetch = options.fetch || globalThis.fetch.bind(globalThis); | ||
this.buildRequest = options.buildRequest || defaultBuildRequest; | ||
this.signalCache = new SignalCache(options.adapter, options.id); | ||
this.signalCache = new SignalCache(options.adapter, options.getCacheKey); | ||
const signalCache = this.signalCache; | ||
@@ -69,3 +69,2 @@ this.cache = buildCache({ | ||
async processEntities(response) { | ||
let fakeRevisionCounter = 0; | ||
const parsedEntitiesList = parseEntities(response); | ||
@@ -94,3 +93,3 @@ // This object maps "root" entities from a graphql docment to the key used to store them in the | ||
for (const { parent, prop, entity } of parsedEntities) { | ||
const key = this.getId(entity); | ||
const key = this.getCacheKey(entity, parent); | ||
// replace the entity object with the key we're using to store it in the cache so that we can | ||
@@ -105,5 +104,3 @@ // later replace the key with the reactive entity | ||
} | ||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment | ||
// @ts-ignore | ||
await tx.merge(key, { entity, revision: fakeRevisionCounter++ }); | ||
await tx.merge(key, entity); | ||
} | ||
@@ -110,0 +107,0 @@ await tx.commit(); |
@@ -18,3 +18,3 @@ import type { DefaultRecord, DefaultVariables, Entity, IdFetcher, ReactiveAdapter, Scalar, WithSignal } from './types.js'; | ||
export declare class SignalCache { | ||
getId: IdFetcher; | ||
getCacheKey: IdFetcher; | ||
signalAdapter: ReactiveAdapter; | ||
@@ -26,6 +26,6 @@ queryLinks: Map<string, Link>; | ||
private registry; | ||
constructor(signalAdapter: ReactiveAdapter, getId?: IdFetcher); | ||
constructor(signalAdapter: ReactiveAdapter, getCacheKey?: IdFetcher); | ||
store({ query, variables, result }: Payload): void; | ||
storeEntity(key: string, entity: Entity): void; | ||
resolve(entityKey: string, visited?: Set<string>, exploring?: Set<string>): WithSignal<Entity<Record<string, any>>>; | ||
resolve(entityKey: string, visited?: Set<string>, exploring?: Set<string>): WithSignal<Entity<Record<string, any>>> | undefined; | ||
evict(entityKey: string): void; | ||
@@ -32,0 +32,0 @@ private getSignal; |
@@ -71,3 +71,3 @@ import { createSignalProxy } from './signal-proxy.js'; | ||
export class SignalCache { | ||
constructor(signalAdapter, getId = defaultIdGetter) { | ||
constructor(signalAdapter, getCacheKey = defaultIdGetter) { | ||
this.queryLinks = new Map(); | ||
@@ -77,3 +77,3 @@ this.links = new Map(); | ||
this.signals = new Map(); | ||
this.getId = getId; | ||
this.getCacheKey = getCacheKey; | ||
this.signalAdapter = signalAdapter; | ||
@@ -142,2 +142,7 @@ this.registry = new FinalizationRegistry((key) => { | ||
const signal = this.getSignal(entityKey); | ||
// If we've already visited this node in a given `resolve` computation, it means we've already | ||
// fully materialized its data and can just return it from the signal cache | ||
if (visited.has(entityKey)) { | ||
return signal; | ||
} | ||
if (signal) { | ||
@@ -176,3 +181,3 @@ root = signal; | ||
const toRemove = parentArray.filter((v) => { | ||
return !value.includes(this.getId(v)); | ||
return !value.includes(this.getCacheKey(v, parent)); | ||
}); | ||
@@ -198,7 +203,2 @@ for (let entity of toRemove) { | ||
} | ||
// If this node has already been visited, we know it has already been fully traveresed | ||
// and don't need to continue | ||
if (visited.has(value)) { | ||
return false; | ||
} | ||
// If this node is already being explored, we're in a cycle and need to bail | ||
@@ -220,2 +220,9 @@ if (exploring.has(value)) { | ||
} | ||
// If this node has already been visited, we know it has already been fully traversed | ||
// and don't need to continue | ||
if (visited.has(value)) { | ||
return false; | ||
} | ||
// If we've made it here, it means that we've fully traversed all the elements in this path | ||
// and can mark this node as having been visited so we don't traverse it again | ||
exploring.delete(value); | ||
@@ -222,0 +229,0 @@ visited.add(value); |
@@ -49,3 +49,3 @@ import type { buildCache } from '@data-eden/cache'; | ||
} | ||
export type IdFetcher<T = any> = (v: T) => string; | ||
export type IdFetcher<T = any> = (v: T, parent: T) => string; | ||
//# sourceMappingURL=types.d.ts.map |
{ | ||
"name": "@data-eden/athena", | ||
"version": "0.7.2", | ||
"version": "0.8.0", | ||
"repository": { | ||
@@ -24,6 +24,7 @@ "type": "git", | ||
"build": "tsc --build", | ||
"dev": "tsc --watch", | ||
"test": "vitest run" | ||
}, | ||
"dependencies": { | ||
"@data-eden/cache": "^0.7.2", | ||
"@data-eden/cache": "^0.8.0", | ||
"lodash-es": "^4.17.21" | ||
@@ -30,0 +31,0 @@ }, |
@@ -19,3 +19,3 @@ import { buildCache } from '@data-eden/cache'; | ||
url: string; | ||
id: IdFetcher; | ||
getCacheKey: IdFetcher; | ||
fetch?: typeof fetch; | ||
@@ -45,3 +45,3 @@ buildRequest?: BuildRequest; | ||
private cache: DataEdenCache; | ||
private getId: IdFetcher; | ||
private getCacheKey: IdFetcher; | ||
private signalCache: SignalCache; | ||
@@ -52,6 +52,6 @@ private buildRequest: BuildRequest; | ||
this.url = options.url; | ||
this.getId = options.id; | ||
this.fetch = options.fetch || globalThis.fetch; | ||
this.getCacheKey = options.getCacheKey; | ||
this.fetch = options.fetch || globalThis.fetch.bind(globalThis); | ||
this.buildRequest = options.buildRequest || defaultBuildRequest; | ||
this.signalCache = new SignalCache(options.adapter, options.id); | ||
this.signalCache = new SignalCache(options.adapter, options.getCacheKey); | ||
@@ -133,4 +133,2 @@ const signalCache = this.signalCache; | ||
): Promise<Data> { | ||
let fakeRevisionCounter = 0; | ||
const parsedEntitiesList = parseEntities(response); | ||
@@ -161,3 +159,3 @@ | ||
for (const { parent, prop, entity } of parsedEntities) { | ||
const key = this.getId(entity); | ||
const key = this.getCacheKey(entity, parent); | ||
@@ -175,5 +173,3 @@ // replace the entity object with the key we're using to store it in the cache so that we can | ||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment | ||
// @ts-ignore | ||
await tx.merge(key, { entity, revision: fakeRevisionCounter++ }); | ||
await tx.merge(key, entity); | ||
} | ||
@@ -180,0 +176,0 @@ await tx.commit(); |
@@ -126,3 +126,3 @@ import type { Entries } from 'type-fest'; | ||
export class SignalCache { | ||
getId: IdFetcher; | ||
getCacheKey: IdFetcher; | ||
signalAdapter: ReactiveAdapter; | ||
@@ -137,5 +137,5 @@ queryLinks = new Map<string, Link>(); | ||
signalAdapter: ReactiveAdapter, | ||
getId: IdFetcher = defaultIdGetter | ||
getCacheKey: IdFetcher = defaultIdGetter | ||
) { | ||
this.getId = getId; | ||
this.getCacheKey = getCacheKey; | ||
this.signalAdapter = signalAdapter; | ||
@@ -224,2 +224,8 @@ this.registry = new FinalizationRegistry((key) => { | ||
// If we've already visited this node in a given `resolve` computation, it means we've already | ||
// fully materialized its data and can just return it from the signal cache | ||
if (visited.has(entityKey)) { | ||
return signal; | ||
} | ||
if (signal) { | ||
@@ -267,3 +273,3 @@ root = signal; | ||
const toRemove = parentArray.filter((v) => { | ||
return !value.includes(this.getId(v)); | ||
return !value.includes(this.getCacheKey(v, parent)); | ||
}); | ||
@@ -294,8 +300,2 @@ | ||
// If this node has already been visited, we know it has already been fully traveresed | ||
// and don't need to continue | ||
if (visited.has(value)) { | ||
return false; | ||
} | ||
// If this node is already being explored, we're in a cycle and need to bail | ||
@@ -309,2 +309,3 @@ if (exploring.has(value)) { | ||
const resolved = this.resolve(value, visited, exploring); | ||
if (resolved) { | ||
@@ -320,2 +321,10 @@ if (parentArray) { | ||
// If this node has already been visited, we know it has already been fully traversed | ||
// and don't need to continue | ||
if (visited.has(value)) { | ||
return false; | ||
} | ||
// If we've made it here, it means that we've fully traversed all the elements in this path | ||
// and can mark this node as having been visited so we don't traverse it again | ||
exploring.delete(value); | ||
@@ -322,0 +331,0 @@ visited.add(value); |
@@ -68,2 +68,2 @@ import type { buildCache } from '@data-eden/cache'; | ||
export type IdFetcher<T = any> = (v: T) => string; | ||
export type IdFetcher<T = any> = (v: T, parent: T) => string; |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
New author
Supply chain riskA new npm collaborator published a version of the package for the first time. New collaborators are usually benign additions to a project, but do indicate a change to the security surface area of a package.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
267121
52
2680
1
+ Added@data-eden/cache@0.8.2(transitive)
- Removed@data-eden/cache@0.7.2(transitive)
Updated@data-eden/cache@^0.8.0