@data-eden/athena
Advanced tools
Comparing version 0.14.0 to 0.15.0
@@ -65,4 +65,4 @@ import { | ||
adapter: adapter, | ||
getCacheKey: (v: Entity, parent: Entity) => { | ||
if (v.id) { | ||
getCacheKey: (v: Entity) => { | ||
if (v?.id) { | ||
return `${v.__typename}:${v?.id}`; | ||
@@ -370,5 +370,7 @@ } | ||
type: 'APPRECIATION', | ||
__typename: 'Reaction', | ||
}, | ||
commentSummary: { | ||
count: 0, | ||
__typename: 'CommentSummary', | ||
}, | ||
@@ -441,3 +443,3 @@ __typename: 'SocialMetadata', | ||
commentSummary: { | ||
count: 0, | ||
count: 1, | ||
__typename: 'CommentSummary', | ||
@@ -460,3 +462,89 @@ }, | ||
).toEqual('INTEREST'); | ||
await client.processEntities({ | ||
toggleSocialReaction: { | ||
socialMetadata: { | ||
reactionSummaries: [], | ||
id: 'urn:li:comment:(activity:7070125034782027776,7071631601935249410)', | ||
commentSummary: { | ||
count: 1, | ||
__typename: 'CommentSummary', | ||
}, | ||
__typename: 'SocialMetadata', | ||
}, | ||
__typename: 'ToggleSocialReactionResult', | ||
responseCode: 'OK_200', | ||
}, | ||
}); | ||
expect( | ||
result.paginatedCommentsPage.comments[0].socialMetadata | ||
.reactionSummaries | ||
).toEqual([]); | ||
}); | ||
test('should be able to handle nested entites that are managed on entites that are not managed', async () => { | ||
const result = await client.processEntities({ | ||
reactionsPage: { | ||
id: 'urn:li:reactions:12345678954', | ||
reactions: [ | ||
{ | ||
actor: { | ||
firstName: 'Bob', | ||
lastName: 'Bobberson', | ||
profilePicture: { | ||
url: 'https://media.licdn.com/dms/image/C5603AQGjVp-oZT1bnw/profile-displayphoto-shrink_400_400/0/1561741260600?e=1693440000&v=beta&t=YAiJc0lInvPXvlUuohhyC0oZJ9trFc3hfE8F21CIMkI', | ||
__typename: 'Asset', | ||
}, | ||
id: 'urn:li:member:655184127', | ||
profileCanonicalUrl: { | ||
url: 'https://www.linkedin.com/in/bob-bobberson', | ||
__typename: 'ProfileCanonicalUrl', | ||
}, | ||
__typename: 'Profile', | ||
}, | ||
type: 'EMPATHY', | ||
__typename: 'Reaction', | ||
}, | ||
{ | ||
actor: { | ||
firstName: 'Chris', | ||
lastName: 'Freeman', | ||
profilePicture: { | ||
url: 'https://media.licdn.com/dms/image/D5603AQE1vYzjTXjiRA/profile-displayphoto-shrink_400_400/0/1664897400696?e=1693440000&v=beta&t=Lj5VkKd3h-6AQQSNeIPtPDfELpEMUE8QEdxT4D0ll8c', | ||
__typename: 'Asset', | ||
}, | ||
id: 'urn:li:member:96146350', | ||
profileCanonicalUrl: { | ||
url: 'https://www.linkedin.com/in/chris-freeman-2ba24828', | ||
__typename: 'ProfileCanonicalUrl', | ||
}, | ||
__typename: 'Profile', | ||
}, | ||
type: 'LIKE', | ||
__typename: 'Reaction', | ||
}, | ||
], | ||
socialMetadata: { | ||
reactionSummaries: [ | ||
{ | ||
count: 1, | ||
type: 'LIKE', | ||
__typename: 'ReactionSummary', | ||
}, | ||
{ | ||
count: 1, | ||
type: 'EMPATHY', | ||
__typename: 'ReactionSummary', | ||
}, | ||
], | ||
}, | ||
__typename: 'ReactionsPage', | ||
}, | ||
}); | ||
expect(result.reactionsPage.reactions[0].actor).not.toEqual({ | ||
__link: 'Profile:urn:li:member:655184127', | ||
}); | ||
}); | ||
}); | ||
@@ -463,0 +551,0 @@ |
@@ -129,2 +129,3 @@ /* eslint-disable @typescript-eslint/no-unsafe-member-access */ | ||
undefined, | ||
undefined, | ||
250 | ||
@@ -131,0 +132,0 @@ ); |
import { type MergeResolvers } from './signal-cache.js'; | ||
import type { DefaultVariables, DocumentInput, GraphQLOperation, IdFetcher, OperationResult, ReactiveAdapter } from './types.js'; | ||
import type { DefaultVariables, DocumentInput, GraphQLOperation, IdFetcher, SyntheticIdFetcher, OperationResult, ReactiveAdapter } from './types.js'; | ||
export interface ClientArgs { | ||
url: string; | ||
getCacheKey: IdFetcher; | ||
getSyntheticKey?: SyntheticIdFetcher; | ||
fetch?: typeof fetch; | ||
@@ -25,2 +26,3 @@ buildRequest?: BuildRequest; | ||
private getCacheKey; | ||
private getSyntheticKey; | ||
private signalCache; | ||
@@ -27,0 +29,0 @@ private buildRequest; |
import type { CacheKey, PropertyPath } from './client.js'; | ||
import type { DefaultVariables, Entity, GraphQLOperation, IdFetcher, ReactiveAdapter, Scalar, WithSignal } from './types.js'; | ||
import type { DefaultVariables, Entity, GraphQLOperation, IdFetcher, ReactiveAdapter, Scalar, SyntheticIdFetcher, WithSignal } from './types.js'; | ||
export type Link = Record<string, string | Array<string>>; | ||
@@ -16,2 +16,3 @@ interface LinkNode { | ||
getCacheKey: IdFetcher; | ||
getSyntheticKey: SyntheticIdFetcher; | ||
mergeResolvers?: MergeResolvers; | ||
@@ -22,6 +23,6 @@ queryLifetimes: Map<string, number>; | ||
links: Map<string, Link>; | ||
records: Map<string, Record<string, Scalar> | WithSignal<Entity>>; | ||
records: Map<string, Record<string, Scalar>>; | ||
signals: Map<string, WeakRef<WithSignal<Entity>>>; | ||
private registry; | ||
constructor(signalAdapter: ReactiveAdapter, getCacheKey?: IdFetcher, mergeResolvers?: MergeResolvers, queryTTL?: number); | ||
constructor(signalAdapter: ReactiveAdapter, getCacheKey?: IdFetcher, getSyntheticKey?: SyntheticIdFetcher, mergeResolvers?: MergeResolvers, queryTTL?: number); | ||
storeOperation(operation: GraphQLOperation, links?: Map<PropertyPath, CacheKey>): void; | ||
@@ -28,0 +29,0 @@ readOperation<Data extends object = object, Variables extends DefaultVariables = DefaultVariables>(operation: GraphQLOperation<Data, Variables>): WithSignal<Entity> | undefined; |
@@ -50,3 +50,4 @@ import type { buildCache } from '@data-eden/cache'; | ||
} | ||
export type IdFetcher<T = any> = (v: T, parent: T) => string | undefined; | ||
export type IdFetcher<T = any> = (v: T) => string | undefined; | ||
export type SyntheticIdFetcher = (parsedEntity: ParsedEntity, getCacheKey: IdFetcher) => string; | ||
//# sourceMappingURL=types.d.ts.map |
import type { DocumentNode } from 'graphql'; | ||
import type { DefaultVariables, DocumentInput, GraphQLOperation } from './types.js'; | ||
import type { DefaultVariables, DocumentInput, GraphQLOperation, IdFetcher, ParsedEntity } from './types.js'; | ||
import type { TypedDocumentNode } from '@graphql-typed-document-node/core'; | ||
export declare function addTypenameToDocument<Data extends object = object, Variables extends DefaultVariables = DefaultVariables>(query: DocumentNode | TypedDocumentNode<Data, Variables>): TypedDocumentNode<Data, Variables>; | ||
export declare function prepareOperation<Data extends object = object, Variables extends DefaultVariables = DefaultVariables>(operation: DocumentInput<Data, Variables>, variables?: Variables, fetchMore?: boolean): GraphQLOperation<Data, Variables>; | ||
export declare function defaultSyntheticKey(parsedEntity: ParsedEntity, getCacheKey: IdFetcher): string; | ||
//# sourceMappingURL=utils.d.ts.map |
{ | ||
"name": "@data-eden/athena", | ||
"version": "0.14.0", | ||
"version": "0.15.0", | ||
"repository": { | ||
@@ -27,3 +27,3 @@ "type": "git", | ||
"dependencies": { | ||
"@data-eden/cache": "^0.14.0", | ||
"@data-eden/cache": "^0.15.0", | ||
"date-fns": "^2.30.0", | ||
@@ -33,5 +33,5 @@ "lodash-es": "^4.17.21" | ||
"devDependencies": { | ||
"@data-eden/codegen": "0.14.0", | ||
"@data-eden/mocker": "0.14.0", | ||
"@data-eden/shared-test-utilities": "0.14.0", | ||
"@data-eden/codegen": "0.15.0", | ||
"@data-eden/mocker": "0.15.0", | ||
"@data-eden/shared-test-utilities": "0.15.0", | ||
"@graphql-typed-document-node/core": "^3.2.0", | ||
@@ -38,0 +38,0 @@ "@signalis/core": "^0.1.0", |
@@ -16,6 +16,7 @@ import type { Cache } from '@data-eden/cache'; | ||
IdFetcher, | ||
SyntheticIdFetcher, | ||
OperationResult, | ||
ReactiveAdapter, | ||
} from './types.js'; | ||
import { prepareOperation } from './utils.js'; | ||
import { defaultSyntheticKey, prepareOperation } from './utils.js'; | ||
@@ -25,2 +26,3 @@ export interface ClientArgs { | ||
getCacheKey: IdFetcher; | ||
getSyntheticKey?: SyntheticIdFetcher; | ||
fetch?: typeof fetch; | ||
@@ -62,2 +64,3 @@ buildRequest?: BuildRequest; | ||
private getCacheKey: IdFetcher; | ||
private getSyntheticKey: SyntheticIdFetcher; | ||
private signalCache: SignalCache; | ||
@@ -69,2 +72,3 @@ private buildRequest: BuildRequest; | ||
this.getCacheKey = options.getCacheKey; | ||
this.getSyntheticKey = options.getSyntheticKey || defaultSyntheticKey; | ||
this.fetch = options.fetch || globalThis.fetch.bind(globalThis); | ||
@@ -75,2 +79,3 @@ this.buildRequest = options.buildRequest || defaultBuildRequest; | ||
options.getCacheKey, | ||
this.getSyntheticKey, | ||
options.mergeResolvers, | ||
@@ -205,7 +210,10 @@ options.queryTTL | ||
for (const { parent, prop, entity } of parsedEntities) { | ||
const key = this.getCacheKey(entity, parent); | ||
let key = this.getCacheKey(entity); | ||
if (!key) { | ||
// if we don't have a key it is not cacheable and must be cached based on the parent | ||
continue; | ||
// set a synthetic shallow cache key | ||
key = this.getSyntheticKey( | ||
{ parent, prop, entity }, | ||
this.getCacheKey | ||
); | ||
} | ||
@@ -212,0 +220,0 @@ |
@@ -13,4 +13,6 @@ import { addMilliseconds, getTime } from 'date-fns'; | ||
Scalar, | ||
SyntheticIdFetcher, | ||
WithSignal, | ||
} from './types.js'; | ||
import { defaultSyntheticKey } from './utils.js'; | ||
@@ -48,2 +50,3 @@ export type Link = Record<string, string | Array<string>>; | ||
getCacheKey: IdFetcher; | ||
getSyntheticKey: SyntheticIdFetcher; | ||
mergeResolvers?: MergeResolvers; | ||
@@ -55,3 +58,3 @@ queryLifetimes = new Map<string, number>(); | ||
links = new Map<string, Link>(); | ||
records = new Map<string, Record<string, Scalar> | WithSignal<Entity>>(); | ||
records = new Map<string, Record<string, Scalar>>(); | ||
signals = new Map<string, WeakRef<WithSignal<Entity>>>(); | ||
@@ -63,2 +66,3 @@ private registry: FinalizationRegistry<string>; | ||
getCacheKey: IdFetcher = defaultIdGetter, | ||
getSyntheticKey: SyntheticIdFetcher = defaultSyntheticKey, | ||
mergeResolvers?: MergeResolvers, | ||
@@ -69,2 +73,3 @@ queryTTL = 60_000 | ||
this.getCacheKey = getCacheKey; | ||
this.getSyntheticKey = getSyntheticKey; | ||
this.mergeResolvers = mergeResolvers; | ||
@@ -158,10 +163,2 @@ this.queryTTL = queryTTL; | ||
}); | ||
// We only want to update the array value on the entity if the record has an array value or if the record has a length | ||
// This is to ensure we update the record with an empty array if had an original value | ||
if ( | ||
recordArray.length > 0 || | ||
(record[entityKey] && Array.isArray(record[entityKey])) | ||
) { | ||
record[entityKey] = recordArray; | ||
} | ||
links[entityKey] = arrayLink; | ||
@@ -214,3 +211,10 @@ } else if (isLinkNode(value)) { | ||
exploring.delete(entityKey); | ||
root = createSignalProxy(this.signalAdapter({ id: '', __typename: '' })); | ||
root = createSignalProxy( | ||
// the id is the source of the truth for the signal's cache key | ||
// it is import that this is set so that given a signal we know the cache key | ||
this.signalAdapter({ | ||
id: entityKey, | ||
__typename: '', | ||
}) | ||
); | ||
this.signals.set(entityKey, new WeakRef(root)); | ||
@@ -252,8 +256,20 @@ this.registry.register(root, entityKey); | ||
// as well | ||
const toRemove = parentArray.filter((v) => { | ||
const key = this.getCacheKey(v, parent); | ||
const toRemove = parentArray.filter((v, i) => { | ||
let key = this.getCacheKey(v); | ||
// if there is no key we can't consistency manage it. | ||
// it will get overriden when new values are returned on the parent object | ||
return key && !value.includes(key); | ||
if (!key) { | ||
key = this.getSyntheticKey( | ||
// we know the prop is the key and value in the array as the second value | ||
// i is a number and needs to be a string to match the same value as provided in the first getSyntheticKey in process entities | ||
// TODO: props should be a function we can normalize to avoid this | ||
{ | ||
entity: v, | ||
parent: root, | ||
prop: [parentKey as string, i.toString()], | ||
}, | ||
this.getCacheKey | ||
); | ||
} | ||
return !value.includes(key); | ||
}); | ||
@@ -282,2 +298,3 @@ | ||
parentKey = null; | ||
parentArray = null; | ||
} | ||
@@ -321,6 +338,2 @@ | ||
// in order to get consistency updates on non managed field updates we set the signal if we have it | ||
// so we can update it in storeEntity | ||
this.records.set(entityKey, root); | ||
return root; | ||
@@ -327,0 +340,0 @@ } |
@@ -69,2 +69,6 @@ import type { buildCache } from '@data-eden/cache'; | ||
export type IdFetcher<T = any> = (v: T, parent: T) => string | undefined; | ||
export type IdFetcher<T = any> = (v: T) => string | undefined; | ||
export type SyntheticIdFetcher = ( | ||
parsedEntity: ParsedEntity, | ||
getCacheKey: IdFetcher | ||
) => string; |
@@ -13,2 +13,4 @@ import type { | ||
GraphQLOperation, | ||
IdFetcher, | ||
ParsedEntity, | ||
} from './types.js'; | ||
@@ -129,1 +131,29 @@ import type { TypedDocumentNode } from '@graphql-typed-document-node/core'; | ||
} | ||
function hashCode(str: string) { | ||
let hash = 0; | ||
for (let i = 0, len = str.length; i < len; i++) { | ||
let chr = str.charCodeAt(i); | ||
// eslint-disable-next-line no-bitwise | ||
hash = (hash << 5) - hash + chr; | ||
// eslint-disable-next-line no-bitwise | ||
hash |= 0; // Convert to 32bit integer | ||
} | ||
return hash.toString(); | ||
} | ||
// The purpose of this function is to generate a shallow key that you know only the fields that will not change if data is updated | ||
export function defaultSyntheticKey( | ||
parsedEntity: ParsedEntity, | ||
getCacheKey: IdFetcher | ||
): string { | ||
const { entity, parent, prop } = parsedEntity; | ||
return `${entity.__typename}:${hashCode( | ||
JSON.stringify({ | ||
prop, | ||
parentId: getCacheKey(parent), | ||
entityType: entity.__typename, | ||
}) | ||
)}`; | ||
} |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is too big to display
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
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
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
1032649
6455
+ Added@data-eden/cache@0.15.0(transitive)
- Removed@data-eden/cache@0.14.0(transitive)
Updated@data-eden/cache@^0.15.0