@conduit-client/type-normalization 2.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/LICENSE.txt ADDED
@@ -0,0 +1,27 @@
1
+ *Attorney/Client Privileged + Confidential*
2
+
3
+ Terms of Use for Public Code (Non-OSS)
4
+
5
+ *NOTE:* Before publishing code under this license/these Terms of Use, please review https://salesforce.quip.com/WFfvAMKB18AL and confirm that you’ve completed all prerequisites described therein. *These Terms of Use may not be used or modified without input from IP and Product Legal.*
6
+
7
+ *Terms of Use*
8
+
9
+ Copyright 2022 Salesforce, Inc. All rights reserved.
10
+
11
+ These Terms of Use govern the download, installation, and/or use of this software provided by Salesforce, Inc. (“Salesforce”) (the “Software”), were last updated on April 15, 2022, ** and constitute a legally binding agreement between you and Salesforce. If you do not agree to these Terms of Use, do not install or use the Software.
12
+
13
+ Salesforce grants you a worldwide, non-exclusive, no-charge, royalty-free copyright license to reproduce, prepare derivative works of, publicly display, publicly perform, sublicense, and distribute the Software and derivative works subject to these Terms. These Terms shall be included in all copies or substantial portions of the Software.
14
+
15
+ Subject to the limited rights expressly granted hereunder, Salesforce reserves all rights, title, and interest in and to all intellectual property subsisting in the Software. No rights are granted to you hereunder other than as expressly set forth herein. Users residing in countries on the United States Office of Foreign Assets Control sanction list, or which are otherwise subject to a US export embargo, may not use the Software.
16
+
17
+ Implementation of the Software may require development work, for which you are responsible. The Software may contain bugs, errors and incompatibilities and is made available on an AS IS basis without support, updates, or service level commitments.
18
+
19
+ Salesforce reserves the right at any time to modify, suspend, or discontinue, the Software (or any part thereof) with or without notice. You agree that Salesforce shall not be liable to you or to any third party for any modification, suspension, or discontinuance.
20
+
21
+ You agree to defend Salesforce against any claim, demand, suit or proceeding made or brought against Salesforce by a third party arising out of or accruing from (a) your use of the Software, and (b) any application you develop with the Software that infringes any copyright, trademark, trade secret, trade dress, patent, or other intellectual property right of any person or defames any person or violates their rights of publicity or privacy (each a “Claim Against Salesforce”), and will indemnify Salesforce from any damages, attorney fees, and costs finally awarded against Salesforce as a result of, or for any amounts paid by Salesforce under a settlement approved by you in writing of, a Claim Against Salesforce, provided Salesforce (x) promptly gives you written notice of the Claim Against Salesforce, (y) gives you sole control of the defense and settlement of the Claim Against Salesforce (except that you may not settle any Claim Against Salesforce unless it unconditionally releases Salesforce of all liability), and (z) gives you all reasonable assistance, at your expense.
22
+
23
+ WITHOUT LIMITING THE GENERALITY OF THE FOREGOING, THE SOFTWARE IS NOT SUPPORTED AND IS PROVIDED "AS IS," WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED. IN NO EVENT SHALL SALESFORCE HAVE ANY LIABILITY FOR ANY DAMAGES, INCLUDING, BUT NOT LIMITED TO, DIRECT, INDIRECT, SPECIAL, INCIDENTAL, PUNITIVE, OR CONSEQUENTIAL DAMAGES, OR DAMAGES BASED ON LOST PROFITS, DATA, OR USE, IN CONNECTION WITH THE SOFTWARE, HOWEVER CAUSED AND WHETHER IN CONTRACT, TORT, OR UNDER ANY OTHER THEORY OF LIABILITY, WHETHER OR NOT YOU HAVE BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
24
+
25
+ These Terms of Use shall be governed exclusively by the internal laws of the State of California, without regard to its conflicts of laws rules. Each party hereby consents to the exclusive jurisdiction of the state and federal courts located in San Francisco County, California to adjudicate any dispute arising out of or relating to these Terms of Use and the download, installation, and/or use of the Software. Except as expressly stated herein, these Terms of Use constitute the entire agreement between the parties, and supersede all prior and contemporaneous agreements, proposals, or representations, written or oral, concerning their subject matter. No modification, amendment, or waiver of any provision of these Terms of Use shall be effective unless it is by an update to these Terms of Use that Salesforce makes available, or is in writing and signed by the party against whom the modification, amendment, or waiver is to be asserted.
26
+
27
+ _*Data Privacy*_: Salesforce may collect, process, and store device, system, and other information related to your use of the Software. This information includes, but is not limited to, IP address, user metrics, and other data (“Usage Data”). Salesforce may use Usage Data for analytics, product development, and marketing purposes. You acknowledge that files generated in conjunction with the Software may contain sensitive or confidential data, and you are solely responsible for anonymizing and protecting such data.
package/README.md ADDED
@@ -0,0 +1 @@
1
+ This software is provided as-is with no support provided.
@@ -0,0 +1,6 @@
1
+ /*!
2
+ * Copyright (c) 2022, Salesforce, Inc.,
3
+ * All rights reserved.
4
+ * For full license text, see the LICENSE.txt file
5
+ */
6
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;"}
File without changes
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,140 @@
1
+ import { Cache, CanonicalCacheControlMetadata, ReadonlyCache } from '@conduit-client/service-cache/v1';
2
+ import { Result, Err } from '@conduit-client/utils';
3
+ import { IdentifiableTypeRepository, FlattenedKeySelfIdentifiableTypeRepository, NormalizedLink, TypeRepository, WriteErrors, WriteInput, ReadErrors, DenormalizeDataInput, ReadInput, WriteResult, ReadResult, NormalizeDataInput } from '..';
4
+ import type { NamedCacheControllerService } from '@conduit-client/service-cache-control/v1';
5
+ import { NamedPubSubService } from '@conduit-client/service-pubsub/v1';
6
+ import { NamedInstrumentationService } from '@conduit-client/service-instrumentation/v1';
7
+ type City = {
8
+ id: string;
9
+ name: string;
10
+ };
11
+ export type AuthorBio = {
12
+ hometown: City;
13
+ publisher: string;
14
+ birthdate: string;
15
+ };
16
+ type AuthorBioNormalized = AuthorBio;
17
+ export type Author = {
18
+ id: string;
19
+ name: string;
20
+ bio: AuthorBio;
21
+ };
22
+ type AuthorNormalized = {
23
+ id: string;
24
+ name: string;
25
+ bio: AuthorBioNormalized;
26
+ };
27
+ export type Post = {
28
+ id: number;
29
+ title: string;
30
+ text: string;
31
+ author: Author;
32
+ };
33
+ type PostNormalized = {
34
+ id: number;
35
+ title: string;
36
+ text: string;
37
+ author: NormalizedLink;
38
+ };
39
+ export type PostList = {
40
+ list: Post[];
41
+ };
42
+ type PostListNormalized = {
43
+ list: NormalizedLink[];
44
+ };
45
+ export declare class ExampleModuleTypeRegistry {
46
+ protected now: number;
47
+ protected services: NamedCacheControllerService & NamedPubSubService & Partial<NamedInstrumentationService>;
48
+ protected types: {
49
+ authorBio: AuthorBioType;
50
+ author: AuthorType;
51
+ post: PostType;
52
+ postList: PostListType;
53
+ getPostListById200JsonType: GetPostListById200JsonType;
54
+ };
55
+ constructor(now: number, services: NamedCacheControllerService & NamedPubSubService & Partial<NamedInstrumentationService>);
56
+ get authorBio(): AuthorBioType;
57
+ get author(): AuthorType;
58
+ get post(): PostType;
59
+ get postList(): PostListType;
60
+ get getPostListById200JsonType(): GetPostListById200JsonType;
61
+ }
62
+ declare class AuthorBioType implements TypeRepository<AuthorBio, AuthorBioNormalized, AuthorBioNormalized, AuthorBio> {
63
+ protected services: {};
64
+ protected typeRegistry: ExampleModuleTypeRegistry;
65
+ namespace: string;
66
+ typeName: string;
67
+ constructor(services: {}, typeRegistry: ExampleModuleTypeRegistry);
68
+ write(_cache: Cache, data: AuthorBio): WriteResult<AuthorBioNormalized>;
69
+ read(_cache: ReadonlyCache, data: AuthorBioNormalized): ReadResult<AuthorBio>;
70
+ equals(x: AuthorBio, y: AuthorBio): boolean;
71
+ }
72
+ type AuthorKeyParams = {
73
+ id: string;
74
+ };
75
+ type AuthorWriteInput = WriteInput<Author>;
76
+ type AuthorNormalizeDataInput = NormalizeDataInput<Author, AuthorNormalized>;
77
+ type AuthorDenormalizeDataInput = DenormalizeDataInput<AuthorNormalized>;
78
+ declare class AuthorType extends FlattenedKeySelfIdentifiableTypeRepository<Author, AuthorNormalized, AuthorKeyParams> {
79
+ protected services: NamedCacheControllerService & NamedPubSubService & Partial<NamedInstrumentationService>;
80
+ protected typeRegistry: ExampleModuleTypeRegistry;
81
+ namespace: string;
82
+ typeName: string;
83
+ cacheControl: CanonicalCacheControlMetadata;
84
+ keySchema: string[];
85
+ constructor(services: NamedCacheControllerService & NamedPubSubService & Partial<NamedInstrumentationService>, typeRegistry: ExampleModuleTypeRegistry);
86
+ protected normalizeData(cache: Cache, input: AuthorNormalizeDataInput): Result<AuthorNormalized, WriteErrors>;
87
+ buildKeyParams(input: AuthorWriteInput): AuthorKeyParams;
88
+ protected denormalizeData(cache: ReadonlyCache, input: AuthorDenormalizeDataInput): Result<Author, ReadErrors>;
89
+ }
90
+ type PostKeyParams = {
91
+ id: number;
92
+ };
93
+ type PostWriteInput = WriteInput<Post>;
94
+ type PostNormalizeDataInput = NormalizeDataInput<Post, PostNormalized>;
95
+ type PostDenormalizeDataInput = DenormalizeDataInput<PostNormalized>;
96
+ declare class PostType extends FlattenedKeySelfIdentifiableTypeRepository<Post, PostNormalized, PostKeyParams> {
97
+ protected services: NamedCacheControllerService & NamedPubSubService & Partial<NamedInstrumentationService>;
98
+ protected typeRegistry: ExampleModuleTypeRegistry;
99
+ namespace: string;
100
+ typeName: string;
101
+ cacheControl: CanonicalCacheControlMetadata;
102
+ constructor(services: NamedCacheControllerService & NamedPubSubService & Partial<NamedInstrumentationService>, typeRegistry: ExampleModuleTypeRegistry);
103
+ keySchema: string[];
104
+ protected normalizeData(cache: Cache, input: PostNormalizeDataInput): Result<PostNormalized, WriteErrors>;
105
+ buildKeyParams(input: PostWriteInput): PostKeyParams;
106
+ protected denormalizeData(cache: ReadonlyCache, input: PostDenormalizeDataInput): Result<Post, ReadErrors>;
107
+ }
108
+ type PostListWriteInput = NormalizeDataInput<PostList, PostListNormalized>;
109
+ type PostListReadInput = DenormalizeDataInput<PostListNormalized>;
110
+ declare class PostListType implements TypeRepository<PostList, PostListReadInput, PostListNormalized, PostListWriteInput> {
111
+ protected services: NamedCacheControllerService & NamedPubSubService & Partial<NamedInstrumentationService>;
112
+ protected typeRegistry: ExampleModuleTypeRegistry;
113
+ namespace: string;
114
+ typeName: string;
115
+ constructor(services: NamedCacheControllerService & NamedPubSubService & Partial<NamedInstrumentationService>, typeRegistry: ExampleModuleTypeRegistry);
116
+ write(cache: Cache, input: PostListWriteInput): Result<PostListNormalized, WriteErrors>;
117
+ read(cache: ReadonlyCache, input: PostListReadInput): Result<PostList, ReadErrors>;
118
+ equals(x: PostList, y: PostList): boolean;
119
+ }
120
+ type GetPostListKeyParams = {
121
+ id: string;
122
+ };
123
+ type GetPostListReadInput = ReadInput;
124
+ type GetPostListWriteInput = WriteInput<PostList, {
125
+ keyParams: GetPostListKeyParams;
126
+ }>;
127
+ type GetPostListNormalizeDataInput = NormalizeDataInput<PostList, PostListNormalized>;
128
+ type GetPostListDenormalizeDataInput = DenormalizeDataInput<PostListNormalized>;
129
+ declare class GetPostListById200JsonType extends IdentifiableTypeRepository<PostList, PostListNormalized, GetPostListKeyParams, GetPostListReadInput, GetPostListWriteInput> {
130
+ protected services: {};
131
+ protected typeRegistry: ExampleModuleTypeRegistry;
132
+ namespace: string;
133
+ typeName: string;
134
+ cacheControl: CanonicalCacheControlMetadata;
135
+ constructor(services: {}, typeRegistry: ExampleModuleTypeRegistry);
136
+ buildKeyParams(input: GetPostListWriteInput): GetPostListKeyParams;
137
+ protected normalizeData(cache: Cache, input: GetPostListNormalizeDataInput): Err<never, WriteErrors> | import("@conduit-client/utils").Ok<PostListNormalized, never>;
138
+ protected denormalizeData(cache: ReadonlyCache, input: GetPostListDenormalizeDataInput): Result<PostList, ReadErrors>;
139
+ }
140
+ export {};
@@ -0,0 +1,243 @@
1
+ import { type Result } from '@conduit-client/utils';
2
+ import type { Key, CanonicalCacheControlMetadata, ReadonlyCache, CacheEntry, Cache } from '@conduit-client/service-cache/v1';
3
+ import { NamedPubSubService } from '@conduit-client/service-pubsub/v1';
4
+ import { InstrumentationAttributes, NamedInstrumentationService } from '@conduit-client/service-instrumentation/v1';
5
+ import { NamedCacheControllerService } from '@conduit-client/service-cache-control/v1';
6
+ export interface NormalizedLink {
7
+ type: 'link';
8
+ linkedKey: Key;
9
+ }
10
+ type UnknownError = {
11
+ type: 'unknown';
12
+ error: Error;
13
+ };
14
+ /**
15
+ * An error that occurs when a type tries to read from the cache, and the cache metadata does
16
+ * not match the current type
17
+ */
18
+ type IncorrectTypeError = {
19
+ type: 'incorrectType';
20
+ expectedType: string;
21
+ actualType: string;
22
+ };
23
+ /**
24
+ * An error that occurs when a type tries to read from the cache and data is missing
25
+ */
26
+ type MissingDataError = {
27
+ type: 'missingData';
28
+ namespace: string;
29
+ typeName: string;
30
+ data: unknown;
31
+ };
32
+ /**
33
+ * A union of errors that can be returned during the write process
34
+ */
35
+ export type WriteErrors = Array<UnknownError>;
36
+ /**
37
+ * A union of errors that can be returned during the read process
38
+ */
39
+ export type ReadErrors = Array<UnknownError | IncorrectTypeError | MissingDataError>;
40
+ export type ReadResult<Data> = Result<Data, ReadErrors>;
41
+ export type WriteResult<Data> = Result<Data, WriteErrors>;
42
+ /**
43
+ * A Type defines a collection of functions associated with a particular
44
+ * data type. Types are not normalized but may contain normalized data.
45
+ *
46
+ * @typeParam Data TypeScript type that corresponds to the data
47
+ * @typeParam NormalizedData TypeScript type that corresponds to the normalized data
48
+ * @typeParam NormalizeInputData TypeScript type that corresponds to the data that the normalize method expects as input. Defaults to the same as Data
49
+ */
50
+ export type TypeRepository<Data, ReadInput, WriteResponse, WriteInput> = {
51
+ readonly namespace: string;
52
+ readonly typeName: string;
53
+ equals(x: Data, y: Data): boolean;
54
+ /**
55
+ * Normalizes data for storage.
56
+ */
57
+ write(cache: Cache, input: WriteInput): WriteResult<WriteResponse>;
58
+ /**
59
+ * De-normalizes a Type's data. A type could normalize into a Link, or return Data to the caller.
60
+ * A caller must not be relying on the referenced type being normalized or not.
61
+ * If denormalization fails, it will return undefined.
62
+ */
63
+ read(cache: ReadonlyCache, input: ReadInput): ReadResult<Data>;
64
+ };
65
+ /**
66
+ * An InvalidatableType defines an invalidate function that takes a generic input,
67
+ * and returns a promise that resolves when the invalidation is complete
68
+ *
69
+ * @typeParam T is another type to extend
70
+ * @typeParam InvalidateInput is the input to run an invalidation
71
+ */
72
+ export type Invalidatable<T extends TypeRepository<any, any, any, any>, InvalidateInput> = T & {
73
+ /**
74
+ *
75
+ * @param configs Partial KeyConfigs for this Type (partial KeyConfig treats missing parts as wildcard)
76
+ */
77
+ invalidate(configs: InvalidateInput[]): Promise<void>;
78
+ };
79
+ /**
80
+ * A QueryableType defines a collection of functions associated with a particular
81
+ * data type that gets its own cache entry.
82
+ *
83
+ * @typeParam Data TypeScript type that corresponds to the data
84
+ * @typeParam KeyConfig TypeScript type for the inputs needed to construct
85
+ * a Key for the data
86
+ * @typeParam NormalizeInputData TypeScript type that corresponds to the data that the normalize method expects as input. Defaults to the same as Data
87
+ */
88
+ export type Queryable<T extends TypeRepository<any, any, any, any>, QueryInput> = T & {
89
+ /**
90
+ *
91
+ * @param cache The cache to run the query against
92
+ * @param query The query to run
93
+ */
94
+ query(cache: Cache, query: QueryInput): ReadResult<DataOf<T>>;
95
+ };
96
+ export type DataOf<T extends TypeRepository<any, any, any, any>> = T extends TypeRepository<infer Data, any, any, any> ? Data : never;
97
+ export type ReadInputOf<T extends TypeRepository<any, any, any, any>> = T extends TypeRepository<any, infer ReadInput, any, any> ? ReadInput : never;
98
+ export type WriteResponseOf<T extends TypeRepository<any, any, any, any>> = T extends TypeRepository<any, any, infer WriteResponse, any> ? WriteResponse : never;
99
+ export type WriteResultOf<T extends TypeRepository<any, any, any, any>> = WriteResult<WriteResponseOf<T>>;
100
+ export type WriteInputOf<T extends TypeRepository<any, any, any, any>> = T extends TypeRepository<any, any, any, infer WriteInput> ? WriteInput : never;
101
+ export type InvalidateInputOf<T extends TypeRepository<any, any, any, any>> = T extends Invalidatable<any, infer InvalidateInput> ? InvalidateInput : never;
102
+ /**
103
+ * A utility method for ensuring that a given entry in the cache matches a type based on namespace and name information
104
+ * @param cacheEntry the cache entry to validate against
105
+ * @param type the type to validate the cache entry against
106
+ * @returns
107
+ */
108
+ export declare function isCacheEntryForType(cacheEntry: CacheEntry<unknown>, type: TypeRepository<unknown, unknown, unknown, unknown>): boolean;
109
+ export type KeyParamsOf<T> = T extends IdentifiableTypeRepository<any, any, infer KeyParams, any, any> ? KeyParams : never;
110
+ export type WriteInput<Data, Extension = Record<string, unknown>> = {
111
+ data: Data;
112
+ } & Extension;
113
+ export type NormalizeDataInput<Data, NormalizedData, Extension extends Record<string, any> = Record<string, unknown>> = WriteInput<Data, Extension & {
114
+ existingNormalizedData?: NormalizedData;
115
+ }>;
116
+ export type ReadInput<Extension extends Record<string, any> = Record<string, unknown>> = {
117
+ normalizedData: NormalizedLink;
118
+ } & Extension;
119
+ export type ReadByKeyParamsInput<KeyParams> = {
120
+ keyParams: KeyParams;
121
+ };
122
+ export type DenormalizeInput<Extension extends Record<string, any> = Record<string, unknown>> = Extension & {
123
+ key: Key;
124
+ };
125
+ export type DenormalizeDataInput<NormalizedData, Extension extends Record<string, any> = Record<string, unknown>> = Extension & {
126
+ normalizedData: NormalizedData;
127
+ };
128
+ export type KeyParamQuery<KeyParams, ReadInputExtension> = {
129
+ type: 'keyParams';
130
+ keyParams: KeyParams;
131
+ } & ReadInputExtension;
132
+ export type QueryFor<T> = T extends Queryable<any, infer Query> ? Query : never;
133
+ /**
134
+ * An abstract base class that implements Type, defining additional abstract properties and methods
135
+ * for building a key for a given set of parameters, and defining the canonical cache control metadata
136
+ * for any data written
137
+ */
138
+ export declare abstract class IdentifiableTypeRepository<Data, NormalizedData, KeyParams, ReadInputExtension extends Record<string, any> = Record<string, unknown>, WriteInputExtension extends Record<string, any> = Record<string, unknown>, Query extends KeyParamQuery<KeyParams, ReadInputExtension> = KeyParamQuery<KeyParams, ReadInputExtension>> implements Queryable<TypeRepository<Data, ReadInput<ReadInputExtension>, NormalizedLink, WriteInput<Data, WriteInputExtension>>, Query> {
139
+ protected services: {};
140
+ constructor(services: {});
141
+ abstract readonly cacheControl: CanonicalCacheControlMetadata;
142
+ abstract readonly namespace: string;
143
+ abstract readonly typeName: string;
144
+ protected abstract denormalizeData(cache: ReadonlyCache, input: DenormalizeDataInput<NormalizedData, ReadInput<ReadInputExtension>>): Result<Data, ReadErrors>;
145
+ protected abstract normalizeData(cache: Cache, input: NormalizeDataInput<Data, NormalizedData, WriteInputExtension & {
146
+ existingNormalizedData?: NormalizedData;
147
+ }>): Result<NormalizedData, WriteErrors>;
148
+ get cacheMetadata(): {
149
+ cacheControl: {
150
+ type: "max-age";
151
+ maxAge: number;
152
+ } | {
153
+ type: "stale-while-revalidate";
154
+ maxAge: number;
155
+ staleTime: number;
156
+ } | {
157
+ type: "no-cache";
158
+ } | {
159
+ type: "no-store";
160
+ };
161
+ type: {
162
+ namespace: string;
163
+ name: string;
164
+ };
165
+ };
166
+ buildKey(params: KeyParams): Key;
167
+ abstract buildKeyParams(input: WriteInput<Data, WriteInputExtension>): KeyParams;
168
+ write(cache: Cache, input: WriteInput<Data, WriteInputExtension>): WriteResult<NormalizedLink>;
169
+ protected validateEntry(entry: CacheEntry<unknown>): Result<undefined, ReadErrors>;
170
+ query(cache: Cache, query: Query): ReadResult<Data>;
171
+ read(cache: ReadonlyCache, input: ReadInput<ReadInputExtension>): Result<Data, ReadErrors>;
172
+ denormalize(cache: ReadonlyCache, input: DenormalizeInput<ReadInput<ReadInputExtension>>): Result<Data, ReadErrors>;
173
+ equals(x: Data, y: Data): boolean;
174
+ buildMissingDataError(): MissingDataError;
175
+ }
176
+ /**
177
+ * An abstract base class that extends IdentifiableType, adding the additional constraint that the
178
+ * only data required to write to the cache is an instance of the data itself, i.e. the identity of
179
+ * the data lives inside the data
180
+ */
181
+ export declare abstract class SelfIdentifiableTypeRepository<Data, NormalizedData, KeyParams, ReadInputExtension extends Record<string, any> = Record<string, unknown>, WriteInputExtension extends Record<string, any> = Record<string, unknown>> extends IdentifiableTypeRepository<Data, NormalizedData, KeyParams, ReadInputExtension, WriteInputExtension> {
182
+ }
183
+ /**
184
+ * An abstract base class that extends SelfIdentifiableType. Adds the constraint that the structure of
185
+ * the key parameters must be a flat object, with no objects nested. This class provides invalidation and keybuilding 'for free'.
186
+ * Should be preferred over IdentifiableType and SelfIdentifiableType
187
+ */
188
+ export declare abstract class FlattenedKeySelfIdentifiableTypeRepository<Data, NormalizedData, KeyParams extends FlattenedKeyParams, ReadInputExtension extends Record<string, any> = Record<string, unknown>, WriteInputExtension extends Record<string, any> = Record<string, unknown>> extends SelfIdentifiableTypeRepository<Data, NormalizedData, KeyParams, ReadInputExtension, WriteInputExtension> implements Invalidatable<SelfIdentifiableTypeRepository<Data, NormalizedData, KeyParams, ReadInputExtension, WriteInputExtension>, Partial<KeyParams>> {
189
+ protected services: NamedCacheControllerService & NamedPubSubService & Partial<NamedInstrumentationService>;
190
+ instrumentationAttributes: InstrumentationAttributes | undefined;
191
+ constructor(services: NamedCacheControllerService & NamedPubSubService & Partial<NamedInstrumentationService>);
192
+ abstract readonly keySchema: string[];
193
+ invalidate(configs: Partial<KeyParams>[]): Promise<void>;
194
+ protected collectInstrumentation(duration: number): void;
195
+ protected runInvalidate(configs: Partial<KeyParams>[]): Promise<void>;
196
+ buildKey(config: KeyParams): Key;
197
+ }
198
+ type ScalarKeyValue = string | number | boolean | null | undefined;
199
+ type FlattenedKeyParams = Record<string, ScalarKeyValue | ScalarKeyValue[]>;
200
+ export declare function buildNamespacedTypeKey(namespace: string, typeName: string, keyConfig: FlattenedKeyParams, keySchema: string[]): string;
201
+ export declare function buildRegexForNamespacedKeys(namespace: string, typeName: string, partialKeyConfigs: Array<FlattenedKeyParams>, keySchema: string[]): RegExp;
202
+ export declare function extractReadWriteData<Data>(result: ReadResult<Data> | WriteResult<Data>, errorCollector: any[]): Data | undefined;
203
+ export declare function buildReadWriteResult<Data, Errors extends WriteErrors | ReadErrors>(data: unknown, errors: Errors): Result<Data, Errors>;
204
+ export type UnionRepresentation<Discriminator extends string, Data> = {
205
+ discriminator: Discriminator;
206
+ data: Data;
207
+ };
208
+ export type DiscriminatorMap<Discriminator extends string, Data, Normalized> = {
209
+ [k in Discriminator]: {
210
+ data: Data;
211
+ normalized: Normalized;
212
+ };
213
+ };
214
+ export type NormalizedRepresentationOf<M extends DiscriminatorMap<any, any, any>> = M extends DiscriminatorMap<infer D, any, any> ? {
215
+ [k in D]: UnionRepresentation<k, M[k]['normalized']>;
216
+ }[D] : never;
217
+ export type DenormalizeMapOf<M extends DiscriminatorMap<any, any, any>> = M extends DiscriminatorMap<infer D, any, any> ? {
218
+ [k in D]: (normalized: M[k]['normalized']) => M[k]['data'];
219
+ } : never;
220
+ export type UnionDataOf<M extends DiscriminatorMap<any, any, any>> = M extends DiscriminatorMap<any, infer D, any> ? D : never;
221
+ export type DiscriminatorOf<M extends DiscriminatorMap<any, any, any>> = M extends DiscriminatorMap<infer D, any, any> ? D : never;
222
+ export type DiscriminatorNormalizeFunctionsMap<M extends DiscriminatorMap<any, any, any>> = M extends DiscriminatorMap<infer D, any, any> ? {
223
+ [k in D]: {
224
+ test: (data: M[D]['data']) => boolean;
225
+ normalize: (data: M[k]['data']) => M[k]['normalized'];
226
+ };
227
+ } : never;
228
+ /**
229
+ *
230
+ * @param normalized The normalized data representation, including the discriminator value
231
+ * @param denormalizeMap A map of discriminator -> denormalize functions to be used based on the datas discriminator
232
+ * @returns
233
+ */
234
+ export declare function denormalizeUnionRepresentation<M extends DiscriminatorMap<string, unknown, unknown>>(normalized: NormalizedRepresentationOf<M>, denormalizeMap: DenormalizeMapOf<M>): UnionDataOf<M>;
235
+ /**
236
+ *
237
+ * @param data The canonical/denormalized data representation
238
+ * @param normalizeTestMap A map of discriminator to test method which return a boolean indicating if the data adheres to this "discriminator"
239
+ * and a normalize function to call if the data matches this discriminator
240
+ * @returns
241
+ */
242
+ export declare function normalizeUnionRepresentation<M extends DiscriminatorMap<string, unknown, unknown>>(data: UnionDataOf<M>, normalizeTestMap: DiscriminatorNormalizeFunctionsMap<M>): NormalizedRepresentationOf<M>;
243
+ export {};
@@ -0,0 +1,202 @@
1
+ /*!
2
+ * Copyright (c) 2022, Salesforce, Inc.,
3
+ * All rights reserved.
4
+ * For full license text, see the LICENSE.txt file
5
+ */
6
+ import { stableJSONStringify, err, ok, deepEquals, ArrayIsArray } from "@conduit-client/utils";
7
+ function isCacheEntryForType(cacheEntry, type) {
8
+ return cacheEntry.metadata.type.namespace === type.namespace && cacheEntry.metadata.type.name === type.typeName;
9
+ }
10
+ class IdentifiableTypeRepository {
11
+ constructor(services) {
12
+ this.services = services;
13
+ }
14
+ get cacheMetadata() {
15
+ return {
16
+ cacheControl: { ...this.cacheControl },
17
+ type: { namespace: this.namespace, name: this.typeName }
18
+ };
19
+ }
20
+ buildKey(params) {
21
+ return `${this.namespace}::${this.typeName}(${stableJSONStringify(params)})`;
22
+ }
23
+ write(cache, input) {
24
+ var _a;
25
+ const key = this.buildKey(this.buildKeyParams(input));
26
+ const existingNormalizedData = (_a = cache.get(key)) == null ? void 0 : _a.value;
27
+ const normalized = this.normalizeData(cache, { ...input, existingNormalizedData });
28
+ if (normalized.isErr()) {
29
+ return err(normalized.error);
30
+ }
31
+ cache.set(key, {
32
+ value: normalized.value,
33
+ metadata: this.cacheMetadata
34
+ });
35
+ return ok({
36
+ type: "link",
37
+ linkedKey: key
38
+ });
39
+ }
40
+ validateEntry(entry) {
41
+ if (!isCacheEntryForType(entry, this)) {
42
+ return err([
43
+ {
44
+ type: "incorrectType",
45
+ expectedType: this.typeName,
46
+ actualType: entry.metadata.type.name
47
+ }
48
+ ]);
49
+ }
50
+ return ok(void 0);
51
+ }
52
+ query(cache, query) {
53
+ return this.read(cache, {
54
+ ...query,
55
+ normalizedData: { type: "link", linkedKey: this.buildKey(query.keyParams) }
56
+ });
57
+ }
58
+ read(cache, input) {
59
+ return this.denormalize(cache, { ...input, key: input.normalizedData.linkedKey });
60
+ }
61
+ denormalize(cache, input) {
62
+ const cacheEntry = cache.get(input.key, {
63
+ copy: false
64
+ });
65
+ if (cacheEntry) {
66
+ const validationResult = this.validateEntry(cacheEntry);
67
+ if (validationResult.isErr()) {
68
+ return err([...validationResult.error]);
69
+ }
70
+ const normalizedData = cacheEntry.value;
71
+ return this.denormalizeData(cache, { ...input, normalizedData });
72
+ }
73
+ return err([this.buildMissingDataError()]);
74
+ }
75
+ equals(x, y) {
76
+ return deepEquals(x, y);
77
+ }
78
+ buildMissingDataError() {
79
+ return {
80
+ type: "missingData",
81
+ typeName: this.typeName,
82
+ namespace: this.namespace,
83
+ data: void 0
84
+ };
85
+ }
86
+ }
87
+ class SelfIdentifiableTypeRepository extends IdentifiableTypeRepository {
88
+ }
89
+ class FlattenedKeySelfIdentifiableTypeRepository extends SelfIdentifiableTypeRepository {
90
+ constructor(services) {
91
+ super(services);
92
+ this.services = services;
93
+ }
94
+ async invalidate(configs) {
95
+ const startTime = this.services.instrumentation ? this.services.instrumentation.currentTimeMs() : 0;
96
+ const result = this.runInvalidate(configs).then(() => {
97
+ if (this.services.instrumentation) {
98
+ const duration = this.services.instrumentation.currentTimeMs() - startTime;
99
+ this.collectInstrumentation(duration);
100
+ }
101
+ });
102
+ return result;
103
+ }
104
+ collectInstrumentation(duration) {
105
+ if (this.services.instrumentation) {
106
+ const meter = this.services.instrumentation.metrics.getMeter("onestore");
107
+ const attributes = {
108
+ ...this.instrumentationAttributes,
109
+ namespace: this.namespace,
110
+ typeName: this.typeName
111
+ };
112
+ meter.createCounter(`type.invalidate.count`).add(1, attributes);
113
+ meter.createHistogram(`type.invalidate.duration`).record(duration, attributes);
114
+ }
115
+ }
116
+ async runInvalidate(configs) {
117
+ const regex = buildRegexForNamespacedKeys(
118
+ this.namespace,
119
+ this.typeName,
120
+ configs,
121
+ this.keySchema
122
+ );
123
+ const results = /* @__PURE__ */ new Set();
124
+ for await (const entry of this.services.cacheController.findAndModify(
125
+ { key: { $regex: regex } },
126
+ { type: "invalidate" }
127
+ )) {
128
+ results.add(entry);
129
+ }
130
+ if (results.size > 0) {
131
+ await this.services.pubSub.publish({
132
+ type: "cacheInvalidation",
133
+ data: results
134
+ });
135
+ }
136
+ }
137
+ buildKey(config) {
138
+ return buildNamespacedTypeKey(this.namespace, this.typeName, config, this.keySchema);
139
+ }
140
+ }
141
+ function buildNamespacedTypeKey(namespace, typeName, keyConfig, keySchema) {
142
+ const keyParts = keySchema.map((key) => {
143
+ const value = keyConfig[key];
144
+ return `${ArrayIsArray(value) ? value.join() : value}`;
145
+ }).join("|");
146
+ return `${namespace}::${typeName}(${keyParts})`;
147
+ }
148
+ function buildRegexForNamespacedKeys(namespace, typeName, partialKeyConfigs, keySchema) {
149
+ const patterns = partialKeyConfigs.map((partial) => {
150
+ const requiredParts = keySchema.map((key) => {
151
+ if (!(key in partial)) {
152
+ return `[^|]*?`;
153
+ }
154
+ const value = partial[key];
155
+ return `(?:${(ArrayIsArray(value) ? value.join() : String(value)).replace(/[.*+?^${}()|[\]\\]/g, "\\$&")})`;
156
+ }).join("\\|");
157
+ return `^${namespace}::${typeName}\\((?:${requiredParts})\\)$`;
158
+ });
159
+ return new RegExp(`^(?:${patterns.join("|")})$`);
160
+ }
161
+ function extractReadWriteData(result, errorCollector) {
162
+ if (result.isOk()) {
163
+ return result.value;
164
+ }
165
+ errorCollector.push(...result.error);
166
+ return void 0;
167
+ }
168
+ function buildReadWriteResult(data, errors) {
169
+ if (errors.length > 0) {
170
+ return err(errors);
171
+ }
172
+ return ok(data);
173
+ }
174
+ function denormalizeUnionRepresentation(normalized, denormalizeMap) {
175
+ const discriminator = normalized.discriminator;
176
+ return denormalizeMap[discriminator](normalized.data);
177
+ }
178
+ function normalizeUnionRepresentation(data, normalizeTestMap) {
179
+ for (const discriminator of Object.keys(normalizeTestMap)) {
180
+ const discriminatorLogic = normalizeTestMap[discriminator];
181
+ if (discriminatorLogic.test(data)) {
182
+ return {
183
+ discriminator,
184
+ data: discriminatorLogic.normalize(data)
185
+ };
186
+ }
187
+ }
188
+ throw new Error("No matching union member found");
189
+ }
190
+ export {
191
+ FlattenedKeySelfIdentifiableTypeRepository,
192
+ IdentifiableTypeRepository,
193
+ SelfIdentifiableTypeRepository,
194
+ buildNamespacedTypeKey,
195
+ buildReadWriteResult,
196
+ buildRegexForNamespacedKeys,
197
+ denormalizeUnionRepresentation,
198
+ extractReadWriteData,
199
+ isCacheEntryForType,
200
+ normalizeUnionRepresentation
201
+ };
202
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sources":["../../src/v1/index.ts"],"sourcesContent":["import { type Result, ok, err, deepEquals, stableJSONStringify } from '@conduit-client/utils';\nimport type {\n Key,\n CanonicalCacheControlMetadata,\n ReadonlyCache,\n CacheEntry,\n Cache,\n} from '@conduit-client/service-cache/v1';\n\nimport { ArrayIsArray } from '@conduit-client/utils';\nimport { NamedPubSubService } from '@conduit-client/service-pubsub/v1';\nimport {\n InstrumentationAttributes,\n NamedInstrumentationService,\n} from '@conduit-client/service-instrumentation/v1';\nimport { NamedCacheControllerService } from '@conduit-client/service-cache-control/v1';\n\nexport interface NormalizedLink {\n type: 'link';\n linkedKey: Key;\n}\n\ntype UnknownError = {\n type: 'unknown';\n error: Error;\n};\n\n/**\n * An error that occurs when a type tries to read from the cache, and the cache metadata does\n * not match the current type\n */\ntype IncorrectTypeError = {\n type: 'incorrectType';\n expectedType: string;\n actualType: string;\n};\n\n/**\n * An error that occurs when a type tries to read from the cache and data is missing\n */\ntype MissingDataError = {\n type: 'missingData';\n namespace: string;\n typeName: string;\n data: unknown;\n};\n\n/**\n * A union of errors that can be returned during the write process\n */\nexport type WriteErrors = Array<UnknownError>;\n\n/**\n * A union of errors that can be returned during the read process\n */\nexport type ReadErrors = Array<UnknownError | IncorrectTypeError | MissingDataError>;\n\nexport type ReadResult<Data> = Result<Data, ReadErrors>;\nexport type WriteResult<Data> = Result<Data, WriteErrors>;\n\n/**\n * A Type defines a collection of functions associated with a particular\n * data type. Types are not normalized but may contain normalized data.\n *\n * @typeParam Data TypeScript type that corresponds to the data\n * @typeParam NormalizedData TypeScript type that corresponds to the normalized data\n * @typeParam NormalizeInputData TypeScript type that corresponds to the data that the normalize method expects as input. Defaults to the same as Data\n */\nexport type TypeRepository<Data, ReadInput, WriteResponse, WriteInput> = {\n readonly namespace: string;\n readonly typeName: string;\n\n equals(x: Data, y: Data): boolean;\n\n /**\n * Normalizes data for storage.\n */\n write(cache: Cache, input: WriteInput): WriteResult<WriteResponse>;\n\n /**\n * De-normalizes a Type's data. A type could normalize into a Link, or return Data to the caller.\n * A caller must not be relying on the referenced type being normalized or not.\n * If denormalization fails, it will return undefined.\n */\n read(cache: ReadonlyCache, input: ReadInput): ReadResult<Data>;\n};\n\n/**\n * An InvalidatableType defines an invalidate function that takes a generic input,\n * and returns a promise that resolves when the invalidation is complete\n *\n * @typeParam T is another type to extend\n * @typeParam InvalidateInput is the input to run an invalidation\n */\nexport type Invalidatable<T extends TypeRepository<any, any, any, any>, InvalidateInput> = T & {\n /**\n *\n * @param configs Partial KeyConfigs for this Type (partial KeyConfig treats missing parts as wildcard)\n */\n invalidate(configs: InvalidateInput[]): Promise<void>;\n};\n\n/**\n * A QueryableType defines a collection of functions associated with a particular\n * data type that gets its own cache entry.\n *\n * @typeParam Data TypeScript type that corresponds to the data\n * @typeParam KeyConfig TypeScript type for the inputs needed to construct\n * a Key for the data\n * @typeParam NormalizeInputData TypeScript type that corresponds to the data that the normalize method expects as input. Defaults to the same as Data\n */\nexport type Queryable<T extends TypeRepository<any, any, any, any>, QueryInput> = T & {\n /**\n *\n * @param cache The cache to run the query against\n * @param query The query to run\n */\n query(cache: Cache, query: QueryInput): ReadResult<DataOf<T>>;\n};\n\nexport type DataOf<T extends TypeRepository<any, any, any, any>> =\n T extends TypeRepository<infer Data, any, any, any> ? Data : never;\nexport type ReadInputOf<T extends TypeRepository<any, any, any, any>> =\n T extends TypeRepository<any, infer ReadInput, any, any> ? ReadInput : never;\nexport type WriteResponseOf<T extends TypeRepository<any, any, any, any>> =\n T extends TypeRepository<any, any, infer WriteResponse, any> ? WriteResponse : never;\nexport type WriteResultOf<T extends TypeRepository<any, any, any, any>> = WriteResult<\n WriteResponseOf<T>\n>;\nexport type WriteInputOf<T extends TypeRepository<any, any, any, any>> =\n T extends TypeRepository<any, any, any, infer WriteInput> ? WriteInput : never;\nexport type InvalidateInputOf<T extends TypeRepository<any, any, any, any>> =\n T extends Invalidatable<any, infer InvalidateInput> ? InvalidateInput : never;\n\n/**\n * A utility method for ensuring that a given entry in the cache matches a type based on namespace and name information\n * @param cacheEntry the cache entry to validate against\n * @param type the type to validate the cache entry against\n * @returns\n */\nexport function isCacheEntryForType(\n cacheEntry: CacheEntry<unknown>,\n type: TypeRepository<unknown, unknown, unknown, unknown>\n): boolean {\n return (\n cacheEntry.metadata.type.namespace === type.namespace &&\n cacheEntry.metadata.type.name === type.typeName\n );\n}\n\nexport type KeyParamsOf<T> =\n T extends IdentifiableTypeRepository<any, any, infer KeyParams, any, any> ? KeyParams : never;\n\nexport type WriteInput<Data, Extension = Record<string, unknown>> = { data: Data } & Extension;\nexport type NormalizeDataInput<\n Data,\n NormalizedData,\n Extension extends Record<string, any> = Record<string, unknown>,\n> = WriteInput<Data, Extension & { existingNormalizedData?: NormalizedData }>;\n\nexport type ReadInput<Extension extends Record<string, any> = Record<string, unknown>> = {\n normalizedData: NormalizedLink;\n} & Extension;\nexport type ReadByKeyParamsInput<KeyParams> = {\n keyParams: KeyParams;\n};\nexport type DenormalizeInput<Extension extends Record<string, any> = Record<string, unknown>> =\n Extension & {\n key: Key;\n };\nexport type DenormalizeDataInput<\n NormalizedData,\n Extension extends Record<string, any> = Record<string, unknown>,\n> = Extension & { normalizedData: NormalizedData };\n\nexport type KeyParamQuery<KeyParams, ReadInputExtension> = {\n type: 'keyParams';\n keyParams: KeyParams;\n} & ReadInputExtension;\nexport type QueryFor<T> = T extends Queryable<any, infer Query> ? Query : never;\n\n/**\n * An abstract base class that implements Type, defining additional abstract properties and methods\n * for building a key for a given set of parameters, and defining the canonical cache control metadata\n * for any data written\n */\nexport abstract class IdentifiableTypeRepository<\n Data,\n NormalizedData,\n KeyParams,\n ReadInputExtension extends Record<string, any> = Record<string, unknown>,\n WriteInputExtension extends Record<string, any> = Record<string, unknown>,\n Query extends KeyParamQuery<KeyParams, ReadInputExtension> = KeyParamQuery<\n KeyParams,\n ReadInputExtension\n >,\n> implements\n Queryable<\n TypeRepository<\n Data,\n ReadInput<ReadInputExtension>,\n NormalizedLink,\n WriteInput<Data, WriteInputExtension>\n >,\n Query\n >\n{\n constructor(protected services: {}) {}\n abstract readonly cacheControl: CanonicalCacheControlMetadata;\n abstract readonly namespace: string;\n abstract readonly typeName: string;\n protected abstract denormalizeData(\n cache: ReadonlyCache,\n input: DenormalizeDataInput<NormalizedData, ReadInput<ReadInputExtension>>\n ): Result<Data, ReadErrors>;\n protected abstract normalizeData(\n cache: Cache,\n input: NormalizeDataInput<\n Data,\n NormalizedData,\n WriteInputExtension & { existingNormalizedData?: NormalizedData }\n >\n ): Result<NormalizedData, WriteErrors>;\n get cacheMetadata() {\n return {\n cacheControl: { ...this.cacheControl },\n type: { namespace: this.namespace, name: this.typeName },\n };\n }\n\n buildKey(params: KeyParams): Key {\n return `${this.namespace}::${this.typeName}(${stableJSONStringify(params)})`;\n }\n abstract buildKeyParams(input: WriteInput<Data, WriteInputExtension>): KeyParams;\n\n write(cache: Cache, input: WriteInput<Data, WriteInputExtension>): WriteResult<NormalizedLink> {\n // Identifiable types can have a key that is not the same as the input normalized data\n // so we need to build the key from the input, and then get the existing normalized data from the cache\n // and pass it to the normalizeData method. This should ALWAYS be done for identifiable types.\n const key = this.buildKey(this.buildKeyParams(input));\n const existingNormalizedData: NormalizedData | undefined = cache.get<NormalizedData>(key)\n ?.value as any;\n const normalized = this.normalizeData(cache, { ...input, existingNormalizedData });\n if (normalized.isErr()) {\n return err(normalized.error);\n }\n\n cache.set<NormalizedData>(key, {\n value: normalized.value,\n metadata: this.cacheMetadata,\n });\n\n return ok({\n type: 'link',\n linkedKey: key,\n } as const);\n }\n\n protected validateEntry(entry: CacheEntry<unknown>): Result<undefined, ReadErrors> {\n if (!isCacheEntryForType(entry, this)) {\n return err([\n {\n type: 'incorrectType',\n expectedType: this.typeName,\n actualType: entry.metadata.type.name,\n },\n ]);\n }\n return ok(undefined);\n }\n\n query(cache: Cache, query: Query): ReadResult<Data> {\n return this.read(cache, {\n ...query,\n normalizedData: { type: 'link', linkedKey: this.buildKey(query.keyParams) },\n });\n }\n\n read(cache: ReadonlyCache, input: ReadInput<ReadInputExtension>): Result<Data, ReadErrors> {\n return this.denormalize(cache, { ...input, key: input.normalizedData.linkedKey });\n }\n\n denormalize(cache: ReadonlyCache, input: DenormalizeInput<ReadInput<ReadInputExtension>>) {\n const cacheEntry = cache.get<NormalizedData>(input.key, {\n copy: false,\n }) as CacheEntry<NormalizedData>;\n\n if (cacheEntry) {\n const validationResult = this.validateEntry(cacheEntry);\n if (validationResult.isErr()) {\n return err([...validationResult.error]);\n }\n const normalizedData = cacheEntry.value;\n return this.denormalizeData(cache, { ...input, normalizedData });\n }\n\n return err([this.buildMissingDataError()]);\n }\n\n equals(x: Data, y: Data) {\n return deepEquals(x, y);\n }\n\n buildMissingDataError(): MissingDataError {\n return {\n type: 'missingData',\n typeName: this.typeName,\n namespace: this.namespace,\n data: undefined,\n };\n }\n}\n\n/**\n * An abstract base class that extends IdentifiableType, adding the additional constraint that the\n * only data required to write to the cache is an instance of the data itself, i.e. the identity of\n * the data lives inside the data\n */\nexport abstract class SelfIdentifiableTypeRepository<\n Data,\n NormalizedData,\n KeyParams,\n ReadInputExtension extends Record<string, any> = Record<string, unknown>,\n WriteInputExtension extends Record<string, any> = Record<string, unknown>,\n> extends IdentifiableTypeRepository<\n Data,\n NormalizedData,\n KeyParams,\n ReadInputExtension,\n WriteInputExtension\n> {}\n\n/**\n * An abstract base class that extends SelfIdentifiableType. Adds the constraint that the structure of\n * the key parameters must be a flat object, with no objects nested. This class provides invalidation and keybuilding 'for free'.\n * Should be preferred over IdentifiableType and SelfIdentifiableType\n */\nexport abstract class FlattenedKeySelfIdentifiableTypeRepository<\n Data,\n NormalizedData,\n KeyParams extends FlattenedKeyParams,\n ReadInputExtension extends Record<string, any> = Record<string, unknown>,\n WriteInputExtension extends Record<string, any> = Record<string, unknown>,\n >\n extends SelfIdentifiableTypeRepository<\n Data,\n NormalizedData,\n KeyParams,\n ReadInputExtension,\n WriteInputExtension\n >\n implements\n Invalidatable<\n SelfIdentifiableTypeRepository<\n Data,\n NormalizedData,\n KeyParams,\n ReadInputExtension,\n WriteInputExtension\n >,\n Partial<KeyParams>\n >\n{\n instrumentationAttributes: InstrumentationAttributes | undefined;\n constructor(\n protected services: NamedCacheControllerService &\n NamedPubSubService &\n Partial<NamedInstrumentationService>\n ) {\n super(services);\n }\n\n abstract readonly keySchema: string[];\n\n async invalidate(configs: Partial<KeyParams>[]): Promise<void> {\n const startTime = this.services.instrumentation\n ? this.services.instrumentation.currentTimeMs()\n : 0;\n const result = this.runInvalidate(configs).then(() => {\n if (this.services.instrumentation) {\n const duration = this.services.instrumentation.currentTimeMs() - startTime;\n this.collectInstrumentation(duration);\n }\n });\n return result;\n }\n\n protected collectInstrumentation(duration: number) {\n if (this.services.instrumentation) {\n const meter = this.services.instrumentation.metrics.getMeter('onestore');\n const attributes = {\n ...this.instrumentationAttributes,\n namespace: this.namespace,\n typeName: this.typeName,\n };\n meter\n .createCounter<InstrumentationAttributes>(`type.invalidate.count`)\n .add(1, attributes);\n meter\n .createHistogram<InstrumentationAttributes>(`type.invalidate.duration`)\n .record(duration, attributes);\n }\n }\n\n protected async runInvalidate(configs: Partial<KeyParams>[]): Promise<void> {\n const regex = buildRegexForNamespacedKeys(\n this.namespace,\n this.typeName,\n configs,\n this.keySchema\n );\n\n const results: Set<Key> = new Set();\n for await (const entry of this.services.cacheController.findAndModify(\n { key: { $regex: regex } },\n { type: 'invalidate' }\n )) {\n results.add(entry);\n }\n if (results.size > 0) {\n await this.services.pubSub.publish({\n type: 'cacheInvalidation',\n data: results,\n });\n }\n }\n\n buildKey(config: KeyParams): Key {\n return buildNamespacedTypeKey(this.namespace, this.typeName, config, this.keySchema);\n }\n}\ntype ScalarKeyValue = string | number | boolean | null | undefined;\ntype FlattenedKeyParams = Record<string, ScalarKeyValue | ScalarKeyValue[]>;\nexport function buildNamespacedTypeKey(\n namespace: string,\n typeName: string,\n keyConfig: FlattenedKeyParams,\n keySchema: string[]\n): string {\n const keyParts = keySchema\n .map((key) => {\n const value = keyConfig[key];\n return `${ArrayIsArray(value) ? value.join() : value}`;\n })\n .join('|');\n return `${namespace}::${typeName}(${keyParts})`;\n}\n\nexport function buildRegexForNamespacedKeys(\n namespace: string,\n typeName: string,\n partialKeyConfigs: Array<FlattenedKeyParams>,\n keySchema: string[]\n): RegExp {\n const patterns = partialKeyConfigs.map((partial) => {\n // Build a regex pattern for a single KeyConfig\n const requiredParts = keySchema\n .map((key) => {\n if (!(key in partial)) {\n return `[^|]*?`; // Allow any value or absence of value (optional match)\n }\n const value = partial[key];\n return `(?:${(ArrayIsArray(value) ? value.join() : String(value)).replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&')})`;\n })\n .join('\\\\|'); // Ensure properties are matched in order with literal separator\n\n return `^${namespace}::${typeName}\\\\((?:${requiredParts})\\\\)$`;\n });\n\n return new RegExp(`^(?:${patterns.join('|')})$`); // Apply OR logic across separate partial configs\n}\n\nexport function extractReadWriteData<Data>(\n result: ReadResult<Data> | WriteResult<Data>,\n errorCollector: any[]\n): Data | undefined {\n if (result.isOk()) {\n return result.value;\n }\n\n errorCollector.push(...result.error);\n return undefined;\n}\n\nexport function buildReadWriteResult<Data, Errors extends WriteErrors | ReadErrors>(\n data: unknown,\n errors: Errors\n): Result<Data, Errors> {\n if (errors.length > 0) {\n return err(errors);\n }\n\n return ok(data as Data);\n}\n\n// This type is the normalized representation of union data (either a pure union, or a discriminated union)\n// This data shape will live in the cache, and contains the discriminator (i.e. an identity of what type of data this entry represents)\n// and the normalized data for that entry\nexport type UnionRepresentation<Discriminator extends string, Data> = {\n discriminator: Discriminator;\n data: Data;\n};\n\n// A utility type that represents all the information required for normalizing and\n// denormalizing types into a cache, including the discriminator value, the\n// canonical representation (Data) and the normalized representation (Normalized)\nexport type DiscriminatorMap<Discriminator extends string, Data, Normalized> = {\n [k in Discriminator]: {\n data: Data;\n normalized: Normalized;\n };\n};\n\n// A utility type for inferring the union of normalized representations\n// possible based on a given DiscriminatorType\nexport type NormalizedRepresentationOf<M extends DiscriminatorMap<any, any, any>> =\n M extends DiscriminatorMap<infer D, any, any>\n ? { [k in D]: UnionRepresentation<k, M[k]['normalized']> }[D]\n : never;\n\n// A utility type for inferring the discriminator -> denormalize function map\n// required by the `denormalizeUnionRepresentation` function\nexport type DenormalizeMapOf<M extends DiscriminatorMap<any, any, any>> =\n M extends DiscriminatorMap<infer D, any, any>\n ? { [k in D]: (normalized: M[k]['normalized']) => M[k]['data'] }\n : never;\n\n// A utility type for inferring the canonical data type union based on\n// a DiscriminatorMap type\nexport type UnionDataOf<M extends DiscriminatorMap<any, any, any>> =\n M extends DiscriminatorMap<any, infer D, any> ? D : never;\n\n// A utility type for inferring the discriminator string union from a\n// given discriminator map type\nexport type DiscriminatorOf<M extends DiscriminatorMap<any, any, any>> =\n M extends DiscriminatorMap<infer D, any, any> ? D : never;\n\n// A utilty type for inferring the test/normalize map type from a\n// discriminator map type to be used in the `normalizeUnionRepresentation` function\nexport type DiscriminatorNormalizeFunctionsMap<M extends DiscriminatorMap<any, any, any>> =\n M extends DiscriminatorMap<infer D, any, any>\n ? {\n [k in D]: {\n test: (data: M[D]['data']) => boolean;\n normalize: (data: M[k]['data']) => M[k]['normalized'];\n };\n }\n : never;\n\n/**\n *\n * @param normalized The normalized data representation, including the discriminator value\n * @param denormalizeMap A map of discriminator -> denormalize functions to be used based on the datas discriminator\n * @returns\n */\nexport function denormalizeUnionRepresentation<\n M extends DiscriminatorMap<string, unknown, unknown>,\n>(normalized: NormalizedRepresentationOf<M>, denormalizeMap: DenormalizeMapOf<M>): UnionDataOf<M> {\n const discriminator = normalized.discriminator as DiscriminatorOf<M>;\n return denormalizeMap[discriminator](normalized.data) as UnionDataOf<M>;\n}\n\n/**\n *\n * @param data The canonical/denormalized data representation\n * @param normalizeTestMap A map of discriminator to test method which return a boolean indicating if the data adheres to this \"discriminator\"\n * and a normalize function to call if the data matches this discriminator\n * @returns\n */\nexport function normalizeUnionRepresentation<M extends DiscriminatorMap<string, unknown, unknown>>(\n data: UnionDataOf<M>,\n normalizeTestMap: DiscriminatorNormalizeFunctionsMap<M>\n): NormalizedRepresentationOf<M> {\n for (const discriminator of Object.keys(normalizeTestMap) as DiscriminatorOf<M>[]) {\n const discriminatorLogic = normalizeTestMap[discriminator];\n if (discriminatorLogic.test(data)) {\n return {\n discriminator,\n data: discriminatorLogic.normalize(data),\n } as unknown as NormalizedRepresentationOf<M>;\n }\n }\n\n throw new Error('No matching union member found');\n}\n"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,48 @@
1
+ {
2
+ "name": "@conduit-client/type-normalization",
3
+ "version": "2.0.0",
4
+ "private": false,
5
+ "description": "Luvio Normalized Cache Control Command",
6
+ "type": "module",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "https://github.com/salesforce-experience-platform-emu/luvio-next.git",
10
+ "directory": "packages/@conduit-client/type-normalization"
11
+ },
12
+ "license": "SEE LICENSE IN LICENSE.txt",
13
+ "exports": {
14
+ "./v1": {
15
+ "import": "./dist/v1/index.js",
16
+ "types": "./dist/types/v1/index.d.ts",
17
+ "require": "./dist/v1/index.js"
18
+ }
19
+ },
20
+ "main": "./dist/main/index.js",
21
+ "module": "./dist/main/index.js",
22
+ "types": "./dist/main/index.d.ts",
23
+ "files": [
24
+ "dist/"
25
+ ],
26
+ "scripts": {
27
+ "build": "vite build && tsc --build --emitDeclarationOnly",
28
+ "clean": "rm -rf dist",
29
+ "test": "vitest run",
30
+ "test:perf": "vitest bench",
31
+ "test:size": "size-limit",
32
+ "watch": "npm run build --watch"
33
+ },
34
+ "dependencies": {
35
+ "@conduit-client/service-cache": "2.0.0",
36
+ "@conduit-client/service-cache-control": "2.0.0",
37
+ "@conduit-client/utils": "2.0.0"
38
+ },
39
+ "volta": {
40
+ "extends": "../../../package.json"
41
+ },
42
+ "size-limit": [
43
+ {
44
+ "path": "./dist/v1/index.js",
45
+ "limit": "2.5 kB"
46
+ }
47
+ ]
48
+ }