@reactionary/source 0.0.30 → 0.0.32
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/CLAUDE.md +11 -0
- package/core/package.json +1 -1
- package/core/src/cache/cache-evaluation.interface.ts +19 -0
- package/core/src/cache/cache.interface.ts +38 -0
- package/core/src/cache/noop-cache.ts +42 -0
- package/core/src/cache/redis-cache.ts +55 -22
- package/core/src/client/client-builder.ts +63 -0
- package/core/src/client/client.ts +27 -3
- package/core/src/decorators/trpc.decorators.ts +144 -0
- package/core/src/index.ts +6 -1
- package/core/src/providers/analytics.provider.ts +3 -6
- package/core/src/providers/base.provider.ts +13 -63
- package/core/src/providers/cart.provider.ts +10 -6
- package/core/src/providers/identity.provider.ts +8 -5
- package/core/src/providers/inventory.provider.ts +5 -6
- package/core/src/providers/price.provider.ts +6 -6
- package/core/src/providers/product.provider.ts +6 -6
- package/core/src/providers/search.provider.ts +6 -6
- package/core/src/schemas/mutations/base.mutation.ts +0 -1
- package/core/src/schemas/mutations/cart.mutation.ts +0 -6
- package/core/src/schemas/mutations/identity.mutation.ts +0 -5
- package/core/src/schemas/mutations/product.mutation.ts +0 -1
- package/core/src/schemas/queries/base.query.ts +0 -1
- package/core/src/schemas/queries/cart.query.ts +1 -3
- package/core/src/schemas/queries/identity.query.ts +1 -3
- package/core/src/schemas/queries/inventory.query.ts +0 -1
- package/core/src/schemas/queries/price.query.ts +0 -3
- package/core/src/schemas/queries/product.query.ts +2 -7
- package/core/src/schemas/queries/search.query.ts +0 -3
- package/examples/node/package.json +1 -5
- package/examples/node/src/basic/basic-node-provider-model-extension.spec.ts +97 -0
- package/examples/node/src/basic/basic-node-provider-query-extension.spec.ts +84 -0
- package/examples/node/src/basic/basic-node-setup.spec.ts +40 -0
- package/otel/src/index.ts +3 -0
- package/otel/src/trace-decorator.ts +246 -0
- package/package.json +2 -1
- package/providers/algolia/src/core/initialize.ts +11 -9
- package/providers/algolia/src/providers/product.provider.ts +44 -11
- package/providers/algolia/src/providers/search.provider.ts +47 -66
- package/providers/commercetools/src/core/client.ts +0 -1
- package/providers/commercetools/src/core/initialize.ts +28 -24
- package/providers/commercetools/src/providers/cart.provider.ts +58 -89
- package/providers/commercetools/src/providers/identity.provider.ts +34 -50
- package/providers/commercetools/src/providers/inventory.provider.ts +16 -38
- package/providers/commercetools/src/providers/price.provider.ts +30 -35
- package/providers/commercetools/src/providers/product.provider.ts +48 -38
- package/providers/commercetools/src/providers/search.provider.ts +32 -47
- package/providers/commercetools/src/schema/capabilities.schema.ts +1 -1
- package/providers/fake/package.json +1 -0
- package/providers/fake/src/core/initialize.ts +17 -14
- package/providers/fake/src/index.ts +4 -0
- package/providers/fake/src/providers/analytics.provider.ts +19 -0
- package/providers/fake/src/providers/cart.provider.ts +107 -0
- package/providers/fake/src/providers/identity.provider.ts +78 -67
- package/providers/fake/src/providers/inventory.provider.ts +54 -0
- package/providers/fake/src/providers/price.provider.ts +60 -0
- package/providers/fake/src/providers/product.provider.ts +53 -49
- package/providers/fake/src/providers/search.provider.ts +15 -33
- package/providers/posthog/src/core/initialize.ts +6 -4
- package/trpc/__mocks__/superjson.js +25 -0
- package/trpc/jest.config.ts +14 -0
- package/trpc/package.json +2 -1
- package/trpc/src/client.ts +176 -0
- package/trpc/src/index.ts +35 -62
- package/trpc/src/integration.spec.ts +216 -0
- package/trpc/src/server.ts +123 -0
- package/trpc/src/transparent-client.spec.ts +160 -0
- package/trpc/src/types.ts +142 -0
- package/trpc/tsconfig.json +3 -0
- package/trpc/tsconfig.lib.json +2 -1
- package/trpc/tsconfig.spec.json +15 -0
- package/tsconfig.base.json +0 -2
- package/core/src/cache/caching-strategy.ts +0 -25
- package/examples/angular/e2e/example.spec.ts +0 -9
- package/examples/angular/eslint.config.mjs +0 -41
- package/examples/angular/playwright.config.ts +0 -38
- package/examples/angular/project.json +0 -86
- package/examples/angular/public/favicon.ico +0 -0
- package/examples/angular/src/app/app.component.html +0 -6
- package/examples/angular/src/app/app.component.scss +0 -22
- package/examples/angular/src/app/app.component.ts +0 -14
- package/examples/angular/src/app/app.config.ts +0 -16
- package/examples/angular/src/app/app.routes.ts +0 -25
- package/examples/angular/src/app/cart/cart.component.html +0 -4
- package/examples/angular/src/app/cart/cart.component.scss +0 -14
- package/examples/angular/src/app/cart/cart.component.ts +0 -73
- package/examples/angular/src/app/identity/identity.component.html +0 -6
- package/examples/angular/src/app/identity/identity.component.scss +0 -18
- package/examples/angular/src/app/identity/identity.component.ts +0 -49
- package/examples/angular/src/app/product/product.component.html +0 -14
- package/examples/angular/src/app/product/product.component.scss +0 -11
- package/examples/angular/src/app/product/product.component.ts +0 -42
- package/examples/angular/src/app/search/search.component.html +0 -35
- package/examples/angular/src/app/search/search.component.scss +0 -129
- package/examples/angular/src/app/search/search.component.ts +0 -50
- package/examples/angular/src/app/services/product.service.ts +0 -35
- package/examples/angular/src/app/services/search.service.ts +0 -48
- package/examples/angular/src/app/services/trpc.client.ts +0 -27
- package/examples/angular/src/index.html +0 -13
- package/examples/angular/src/main.ts +0 -7
- package/examples/angular/src/styles.scss +0 -17
- package/examples/angular/src/test-setup.ts +0 -6
- package/examples/angular/tsconfig.app.json +0 -10
- package/examples/angular/tsconfig.editor.json +0 -6
- package/examples/angular/tsconfig.json +0 -32
- package/examples/node/src/initialize-algolia.spec.ts +0 -29
- package/examples/node/src/initialize-commercetools.spec.ts +0 -31
- package/examples/node/src/initialize-extended-providers.spec.ts +0 -38
- package/examples/node/src/initialize-mixed-providers.spec.ts +0 -36
- package/examples/node/src/providers/custom-algolia-product.provider.ts +0 -18
- package/examples/node/src/schemas/custom-product.schema.ts +0 -8
- package/examples/trpc-node/.env.example +0 -52
- package/examples/trpc-node/eslint.config.mjs +0 -3
- package/examples/trpc-node/project.json +0 -61
- package/examples/trpc-node/src/assets/.gitkeep +0 -0
- package/examples/trpc-node/src/main.ts +0 -59
- package/examples/trpc-node/src/router-instance.ts +0 -52
- package/examples/trpc-node/tsconfig.app.json +0 -9
- package/examples/trpc-node/tsconfig.json +0 -13
package/CLAUDE.md
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# Verification commands
|
|
2
|
+
- `npx nx affected:build` builds the projects affected by current changes.
|
|
3
|
+
- `npx nx affected:lint` lints and verifies the projects affected by current changes.
|
|
4
|
+
|
|
5
|
+
# Code style
|
|
6
|
+
- Use ES modules (import/export) syntax, not CommonJS (require).
|
|
7
|
+
- Keep things strongly typed. Do not use `as any` or similar unchecked type casts.
|
|
8
|
+
|
|
9
|
+
# Workflow
|
|
10
|
+
- Be sure to ensure that changes lint and build.
|
|
11
|
+
- Avoid introducing changes that would be breaking for any client consumer of the library without asking for clearance.
|
package/core/package.json
CHANGED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cache evaluation result that determines how and if a query should be cached
|
|
3
|
+
*/
|
|
4
|
+
export interface CacheEvaluation {
|
|
5
|
+
/**
|
|
6
|
+
* The cache key to use for storing/retrieving the value
|
|
7
|
+
*/
|
|
8
|
+
key: string;
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* How long to cache the value in seconds
|
|
12
|
+
*/
|
|
13
|
+
cacheDurationInSeconds: number;
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Whether this query result can be cached
|
|
17
|
+
*/
|
|
18
|
+
canCache: boolean;
|
|
19
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Generic cache interface that can be implemented by different cache backends
|
|
5
|
+
* (Redis, memory, file-based, etc.)
|
|
6
|
+
*/
|
|
7
|
+
export interface Cache {
|
|
8
|
+
/**
|
|
9
|
+
* Retrieves a value from cache and validates it against the provided schema
|
|
10
|
+
*/
|
|
11
|
+
get<T>(key: string, schema: z.ZodType<T>): Promise<T | null>;
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Stores a value in cache with optional expiration time
|
|
15
|
+
*/
|
|
16
|
+
put(key: string, value: unknown, ttlSeconds?: number): Promise<void>;
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Removes one or more keys from cache
|
|
20
|
+
* Supports wildcard patterns (implementation dependent)
|
|
21
|
+
*/
|
|
22
|
+
del(keys: string | string[]): Promise<void>;
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Finds all keys matching a pattern (implementation dependent)
|
|
26
|
+
*/
|
|
27
|
+
keys(pattern: string): Promise<string[]>;
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Clears all cache entries or entries matching a pattern
|
|
31
|
+
*/
|
|
32
|
+
clear(pattern?: string): Promise<void>;
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Gets basic cache statistics (implementation dependent)
|
|
36
|
+
*/
|
|
37
|
+
getStats(): Promise<{ hits: number; misses: number; size: number }>;
|
|
38
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { Cache } from './cache.interface';
|
|
2
|
+
import z from 'zod';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* No-op cache implementation that never stores or returns data.
|
|
6
|
+
* Useful for testing or when caching should be disabled.
|
|
7
|
+
*/
|
|
8
|
+
export class NoOpCache implements Cache {
|
|
9
|
+
public async get<T>(_key: string, _schema: z.ZodType<T>): Promise<T | null> {
|
|
10
|
+
// Always return null - never a cache hit
|
|
11
|
+
return null;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
public async put(_key: string, _value: unknown, _ttlSeconds?: number): Promise<void> {
|
|
15
|
+
// No-op - silently ignore cache write requests
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
public async del(_keys: string | string[]): Promise<void> {
|
|
20
|
+
// No-op - silently ignore delete requests
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
public async keys(_pattern: string): Promise<string[]> {
|
|
25
|
+
// Always return empty array
|
|
26
|
+
return [];
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
public async clear(_pattern?: string): Promise<void> {
|
|
30
|
+
// No-op - silently ignore clear requests
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
public async getStats(): Promise<{ hits: number; misses: number; size: number }> {
|
|
35
|
+
// Always return zeros
|
|
36
|
+
return {
|
|
37
|
+
hits: 0,
|
|
38
|
+
misses: 0,
|
|
39
|
+
size: 0
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
}
|
|
@@ -1,41 +1,74 @@
|
|
|
1
1
|
import { Redis } from '@upstash/redis';
|
|
2
|
-
import {
|
|
3
|
-
import { BaseQuery } from '../schemas/queries/base.query';
|
|
4
|
-
import { Session } from '../schemas/session.schema';
|
|
5
|
-
import { BaseModel } from '../schemas/models/base.model';
|
|
2
|
+
import { Cache } from './cache.interface';
|
|
6
3
|
import z from 'zod';
|
|
7
4
|
|
|
8
|
-
export class RedisCache {
|
|
9
|
-
protected strategy: CachingStrategy;
|
|
5
|
+
export class RedisCache implements Cache {
|
|
10
6
|
protected redis: Redis;
|
|
11
7
|
|
|
12
|
-
constructor(
|
|
13
|
-
this.strategy = strategy;
|
|
8
|
+
constructor() {
|
|
14
9
|
this.redis = Redis.fromEnv();
|
|
15
10
|
}
|
|
16
11
|
|
|
17
|
-
public async get<T
|
|
18
|
-
|
|
12
|
+
public async get<T>(key: string, schema: z.ZodType<T>): Promise<T | null> {
|
|
13
|
+
if (!key) {
|
|
14
|
+
return null;
|
|
15
|
+
}
|
|
19
16
|
|
|
20
|
-
const
|
|
17
|
+
const unvalidated = await this.redis.get(key);
|
|
18
|
+
const parsed = schema.safeParse(unvalidated);
|
|
19
|
+
|
|
20
|
+
if (parsed.success) {
|
|
21
|
+
return parsed.data;
|
|
22
|
+
}
|
|
21
23
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
const parsed = schema.safeParse(unvalidated);
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
25
26
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
27
|
+
public async put(key: string, value: unknown, ttlSeconds?: number): Promise<void> {
|
|
28
|
+
if (!key) {
|
|
29
|
+
return;
|
|
29
30
|
}
|
|
31
|
+
|
|
32
|
+
const options = ttlSeconds ? { ex: ttlSeconds } : undefined;
|
|
33
|
+
await this.redis.set(key, value, options);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
public async del(keys: string | string[]): Promise<void> {
|
|
37
|
+
const keyArray = Array.isArray(keys) ? keys : [keys];
|
|
30
38
|
|
|
31
|
-
|
|
39
|
+
for (const key of keyArray) {
|
|
40
|
+
if (key.includes('*')) {
|
|
41
|
+
// Handle wildcard patterns
|
|
42
|
+
const matchingKeys = await this.redis.keys(key);
|
|
43
|
+
if (matchingKeys.length > 0) {
|
|
44
|
+
await this.redis.del(...matchingKeys);
|
|
45
|
+
}
|
|
46
|
+
} else {
|
|
47
|
+
// Delete specific key
|
|
48
|
+
await this.redis.del(key);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
32
51
|
}
|
|
33
52
|
|
|
34
|
-
public
|
|
35
|
-
|
|
53
|
+
public async keys(pattern: string): Promise<string[]> {
|
|
54
|
+
return await this.redis.keys(pattern);
|
|
55
|
+
}
|
|
36
56
|
|
|
37
|
-
|
|
38
|
-
|
|
57
|
+
public async clear(pattern?: string): Promise<void> {
|
|
58
|
+
const searchPattern = pattern || '*';
|
|
59
|
+
const keys = await this.redis.keys(searchPattern);
|
|
60
|
+
if (keys.length > 0) {
|
|
61
|
+
await this.redis.del(...keys);
|
|
39
62
|
}
|
|
40
63
|
}
|
|
64
|
+
|
|
65
|
+
public async getStats(): Promise<{ hits: number; misses: number; size: number }> {
|
|
66
|
+
// Basic cache statistics (could be enhanced with actual Redis metrics)
|
|
67
|
+
const keys = await this.redis.keys('*');
|
|
68
|
+
return {
|
|
69
|
+
hits: 0, // Would need to track this separately
|
|
70
|
+
misses: 0, // Would need to track this separately
|
|
71
|
+
size: keys.length
|
|
72
|
+
};
|
|
73
|
+
}
|
|
41
74
|
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { Cache } from "../cache/cache.interface";
|
|
2
|
+
import { NoOpCache } from "../cache/noop-cache";
|
|
3
|
+
import { Client } from "./client";
|
|
4
|
+
import { AnalyticsProvider } from "../providers/analytics.provider";
|
|
5
|
+
|
|
6
|
+
type CapabilityFactory<T> = (cache: Cache) => T;
|
|
7
|
+
|
|
8
|
+
type MergeCapabilities<Acc, New> = Omit<Acc, keyof New> & New;
|
|
9
|
+
|
|
10
|
+
export class ClientBuilder<TClient = object> {
|
|
11
|
+
private factories: Array<CapabilityFactory<Partial<Client>>> = [];
|
|
12
|
+
private cache: Cache | undefined;
|
|
13
|
+
|
|
14
|
+
withCapability<TNew extends Partial<Client>>(
|
|
15
|
+
factory: CapabilityFactory<TNew>
|
|
16
|
+
): ClientBuilder<MergeCapabilities<TClient, TNew>> {
|
|
17
|
+
const newBuilder = new ClientBuilder<MergeCapabilities<TClient, TNew>>();
|
|
18
|
+
newBuilder.factories = [...this.factories, factory];
|
|
19
|
+
newBuilder.cache = this.cache;
|
|
20
|
+
return newBuilder;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
withCache(cache: Cache): ClientBuilder<TClient> {
|
|
24
|
+
const newBuilder = new ClientBuilder<TClient>();
|
|
25
|
+
newBuilder.factories = [...this.factories];
|
|
26
|
+
newBuilder.cache = cache;
|
|
27
|
+
return newBuilder;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
build(): TClient & { cache: Cache } {
|
|
31
|
+
let client = {} as TClient;
|
|
32
|
+
|
|
33
|
+
// Use provided cache or default to NoOpCache
|
|
34
|
+
const sharedCache = this.cache || new NoOpCache();
|
|
35
|
+
|
|
36
|
+
const mergedAnalytics: AnalyticsProvider[] = [];
|
|
37
|
+
|
|
38
|
+
for (const factory of this.factories) {
|
|
39
|
+
const provider = factory(sharedCache);
|
|
40
|
+
client = {
|
|
41
|
+
...client,
|
|
42
|
+
...provider
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
if (provider.analytics) {
|
|
46
|
+
mergedAnalytics.push(...provider.analytics);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Add merged analytics if any were collected
|
|
51
|
+
if (mergedAnalytics.length > 0) {
|
|
52
|
+
(client as Record<string, unknown>)['analytics'] = mergedAnalytics;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Add cache to complete the client
|
|
56
|
+
const completeClient = {
|
|
57
|
+
...client,
|
|
58
|
+
cache: sharedCache
|
|
59
|
+
} as TClient & { cache: Cache };
|
|
60
|
+
|
|
61
|
+
return completeClient;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
@@ -5,6 +5,8 @@ import { IdentityProvider } from '../providers/identity.provider';
|
|
|
5
5
|
import { CartProvider } from "../providers/cart.provider";
|
|
6
6
|
import { PriceProvider } from "../providers/price.provider";
|
|
7
7
|
import { InventoryProvider } from "../providers/inventory.provider";
|
|
8
|
+
import { Cache } from "../cache/cache.interface";
|
|
9
|
+
import { RedisCache } from "../cache/redis-cache";
|
|
8
10
|
|
|
9
11
|
export interface Client {
|
|
10
12
|
product: ProductProvider,
|
|
@@ -17,12 +19,23 @@ export interface Client {
|
|
|
17
19
|
inventory: InventoryProvider
|
|
18
20
|
}
|
|
19
21
|
|
|
20
|
-
export
|
|
22
|
+
export interface BuildClientOptions {
|
|
23
|
+
cache?: Cache;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function buildClient<T extends Partial<Client>>(
|
|
27
|
+
providerFactories: Array<(cache: Cache) => T>,
|
|
28
|
+
options: BuildClientOptions = {}
|
|
29
|
+
): Required<T> {
|
|
21
30
|
let client = { } as Required<T>;
|
|
22
31
|
|
|
32
|
+
// Create shared cache instance
|
|
33
|
+
const sharedCache = options.cache || new RedisCache();
|
|
34
|
+
|
|
23
35
|
const mergedAnalytics = [];
|
|
24
36
|
|
|
25
|
-
for (const
|
|
37
|
+
for (const factory of providerFactories) {
|
|
38
|
+
const provider = factory(sharedCache);
|
|
26
39
|
client = {
|
|
27
40
|
...client,
|
|
28
41
|
...provider
|
|
@@ -34,6 +47,17 @@ export function buildClient<T extends Partial<Client>>(providers: Array<T>): Req
|
|
|
34
47
|
}
|
|
35
48
|
|
|
36
49
|
client.analytics = mergedAnalytics;
|
|
50
|
+
|
|
51
|
+
// Add cache to complete the client
|
|
52
|
+
const completeClient = {
|
|
53
|
+
...client,
|
|
54
|
+
cache: sharedCache
|
|
55
|
+
} as Required<T>;
|
|
56
|
+
|
|
57
|
+
return completeClient;
|
|
58
|
+
}
|
|
37
59
|
|
|
38
|
-
|
|
60
|
+
// Convenience function to create a shared cache instance
|
|
61
|
+
export function createCache(): Cache {
|
|
62
|
+
return new RedisCache();
|
|
39
63
|
}
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import 'reflect-metadata';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Metadata keys for TRPC method decorators
|
|
5
|
+
*/
|
|
6
|
+
export const TRPC_QUERY_METADATA_KEY = Symbol('trpc:query');
|
|
7
|
+
export const TRPC_MUTATION_METADATA_KEY = Symbol('trpc:mutation');
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Options for TRPC method decorators
|
|
13
|
+
*/
|
|
14
|
+
export interface TRPCMethodOptions {
|
|
15
|
+
/** Custom name for the TRPC procedure (defaults to method name) */
|
|
16
|
+
name?: string;
|
|
17
|
+
/** Description for documentation purposes */
|
|
18
|
+
description?: string;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Decorator to mark a provider method as a TRPC query procedure
|
|
23
|
+
* Query procedures are read-only operations (GET-like)
|
|
24
|
+
*
|
|
25
|
+
* @example
|
|
26
|
+
* ```typescript
|
|
27
|
+
* class ProductProvider extends BaseProvider {
|
|
28
|
+
* @trpcQuery()
|
|
29
|
+
* async getById(payload: ProductQueryById, session: Session): Promise<Product> {
|
|
30
|
+
* // implementation
|
|
31
|
+
* }
|
|
32
|
+
*
|
|
33
|
+
* @trpcQuery({ name: 'findBySlug', description: 'Find product by URL slug' })
|
|
34
|
+
* async getBySlug(payload: ProductQueryBySlug, session: Session): Promise<Product> {
|
|
35
|
+
* // implementation
|
|
36
|
+
* }
|
|
37
|
+
* }
|
|
38
|
+
* ```
|
|
39
|
+
*/
|
|
40
|
+
export function trpcQuery(options: TRPCMethodOptions = {}): MethodDecorator {
|
|
41
|
+
return function (target: any, propertyKey: string | symbol, descriptor: PropertyDescriptor) {
|
|
42
|
+
// Store metadata about this method being a TRPC query
|
|
43
|
+
const metadata = {
|
|
44
|
+
methodName: String(propertyKey),
|
|
45
|
+
isQuery: true,
|
|
46
|
+
isMutation: false,
|
|
47
|
+
options
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
// Store on the prototype so it's inherited
|
|
51
|
+
Reflect.defineMetadata(TRPC_QUERY_METADATA_KEY, metadata, target, propertyKey);
|
|
52
|
+
|
|
53
|
+
// Also store a list of all TRPC methods on the class
|
|
54
|
+
const existingMethods = Reflect.getMetadata(TRPC_QUERY_METADATA_KEY, target.constructor) || [];
|
|
55
|
+
existingMethods.push({ propertyKey, metadata });
|
|
56
|
+
Reflect.defineMetadata(TRPC_QUERY_METADATA_KEY, existingMethods, target.constructor);
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Decorator to mark a provider method as a TRPC mutation procedure
|
|
62
|
+
* Mutation procedures are write operations that modify state (POST/PUT/DELETE-like)
|
|
63
|
+
*
|
|
64
|
+
* @example
|
|
65
|
+
* ```typescript
|
|
66
|
+
* class CartProvider extends BaseProvider {
|
|
67
|
+
* @trpcMutation()
|
|
68
|
+
* async add(payload: CartAddMutation, session: Session): Promise<Cart> {
|
|
69
|
+
* // implementation
|
|
70
|
+
* }
|
|
71
|
+
*
|
|
72
|
+
* @trpcMutation({ name: 'removeItem' })
|
|
73
|
+
* async remove(payload: CartRemoveMutation, session: Session): Promise<Cart> {
|
|
74
|
+
* // implementation
|
|
75
|
+
* }
|
|
76
|
+
* }
|
|
77
|
+
* ```
|
|
78
|
+
*/
|
|
79
|
+
export function trpcMutation(options: TRPCMethodOptions = {}): MethodDecorator {
|
|
80
|
+
return function (target: any, propertyKey: string | symbol, descriptor: PropertyDescriptor) {
|
|
81
|
+
// Store metadata about this method being a TRPC mutation
|
|
82
|
+
const metadata = {
|
|
83
|
+
methodName: String(propertyKey),
|
|
84
|
+
isQuery: false,
|
|
85
|
+
isMutation: true,
|
|
86
|
+
options
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
// Store on the prototype so it's inherited
|
|
90
|
+
Reflect.defineMetadata(TRPC_MUTATION_METADATA_KEY, metadata, target, propertyKey);
|
|
91
|
+
|
|
92
|
+
// Also store a list of all TRPC methods on the class
|
|
93
|
+
const existingMethods = Reflect.getMetadata(TRPC_MUTATION_METADATA_KEY, target.constructor) || [];
|
|
94
|
+
existingMethods.push({ propertyKey, metadata });
|
|
95
|
+
Reflect.defineMetadata(TRPC_MUTATION_METADATA_KEY, existingMethods, target.constructor);
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Get all TRPC query methods from a class or instance
|
|
101
|
+
*/
|
|
102
|
+
export function getTRPCQueryMethods(target: any): Array<{ propertyKey: string | symbol; metadata: any }> {
|
|
103
|
+
const constructor = typeof target === 'function' ? target : target.constructor;
|
|
104
|
+
return Reflect.getMetadata(TRPC_QUERY_METADATA_KEY, constructor) || [];
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Get all TRPC mutation methods from a class or instance
|
|
109
|
+
*/
|
|
110
|
+
export function getTRPCMutationMethods(target: any): Array<{ propertyKey: string | symbol; metadata: any }> {
|
|
111
|
+
const constructor = typeof target === 'function' ? target : target.constructor;
|
|
112
|
+
return Reflect.getMetadata(TRPC_MUTATION_METADATA_KEY, constructor) || [];
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Get all TRPC methods (both queries and mutations) from a class or instance
|
|
117
|
+
*/
|
|
118
|
+
export function getAllTRPCMethods(target: any): Array<{ propertyKey: string | symbol; metadata: any }> {
|
|
119
|
+
return [
|
|
120
|
+
...getTRPCQueryMethods(target),
|
|
121
|
+
...getTRPCMutationMethods(target)
|
|
122
|
+
];
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Check if a method is marked as a TRPC query
|
|
127
|
+
*/
|
|
128
|
+
export function isTRPCQuery(target: any, methodName: string | symbol): boolean {
|
|
129
|
+
return !!Reflect.getMetadata(TRPC_QUERY_METADATA_KEY, target, methodName);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Check if a method is marked as a TRPC mutation
|
|
134
|
+
*/
|
|
135
|
+
export function isTRPCMutation(target: any, methodName: string | symbol): boolean {
|
|
136
|
+
return !!Reflect.getMetadata(TRPC_MUTATION_METADATA_KEY, target, methodName);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Check if a method is marked for TRPC exposure (query or mutation)
|
|
141
|
+
*/
|
|
142
|
+
export function isTRPCMethod(target: any, methodName: string | symbol): boolean {
|
|
143
|
+
return isTRPCQuery(target, methodName) || isTRPCMutation(target, methodName);
|
|
144
|
+
}
|
package/core/src/index.ts
CHANGED
|
@@ -1,7 +1,12 @@
|
|
|
1
|
-
export * from './cache/
|
|
1
|
+
export * from './cache/cache.interface';
|
|
2
|
+
export * from './cache/cache-evaluation.interface';
|
|
2
3
|
export * from './cache/redis-cache';
|
|
4
|
+
export * from './cache/noop-cache';
|
|
3
5
|
|
|
4
6
|
export * from './client/client';
|
|
7
|
+
export * from './client/client-builder';
|
|
8
|
+
|
|
9
|
+
export * from './decorators/trpc.decorators';
|
|
5
10
|
|
|
6
11
|
export * from './providers/analytics.provider';
|
|
7
12
|
export * from './providers/base.provider';
|
|
@@ -1,10 +1,7 @@
|
|
|
1
1
|
import { AnalyticsEvent } from '../schemas/models/analytics.model';
|
|
2
|
-
import { AnalyticsMutation } from '../schemas/mutations/analytics.mutation';
|
|
3
|
-
import { AnalyticsQuery } from '../schemas/queries/analytics.query';
|
|
4
2
|
import { BaseProvider } from './base.provider';
|
|
5
3
|
|
|
6
4
|
export abstract class AnalyticsProvider<
|
|
7
|
-
T extends AnalyticsEvent = AnalyticsEvent
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
> extends BaseProvider<T, Q, M> {}
|
|
5
|
+
T extends AnalyticsEvent = AnalyticsEvent
|
|
6
|
+
> extends BaseProvider<T> {
|
|
7
|
+
}
|
|
@@ -1,23 +1,21 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
-
import { Session } from '../schemas/session.schema';
|
|
3
|
-
import { BaseQuery } from '../schemas/queries/base.query';
|
|
4
|
-
import { BaseMutation } from '../schemas/mutations/base.mutation';
|
|
5
2
|
import { BaseModel } from '../schemas/models/base.model';
|
|
6
|
-
import {
|
|
3
|
+
import { Cache } from '../cache/cache.interface';
|
|
7
4
|
|
|
8
5
|
/**
|
|
9
6
|
* Base capability provider, responsible for mutations (changes) and queries (fetches)
|
|
10
7
|
* for a given business object domain.
|
|
11
8
|
*/
|
|
12
9
|
export abstract class BaseProvider<
|
|
13
|
-
T extends BaseModel = BaseModel
|
|
14
|
-
Q extends BaseQuery = BaseQuery,
|
|
15
|
-
M extends BaseMutation = BaseMutation
|
|
10
|
+
T extends BaseModel = BaseModel
|
|
16
11
|
> {
|
|
17
|
-
|
|
12
|
+
protected cache: Cache;
|
|
18
13
|
|
|
19
|
-
constructor(
|
|
20
|
-
|
|
14
|
+
constructor(
|
|
15
|
+
public readonly schema: z.ZodType<T>,
|
|
16
|
+
cache: Cache
|
|
17
|
+
) {
|
|
18
|
+
this.cache = cache;
|
|
21
19
|
}
|
|
22
20
|
|
|
23
21
|
/**
|
|
@@ -36,60 +34,12 @@ export abstract class BaseProvider<
|
|
|
36
34
|
}
|
|
37
35
|
|
|
38
36
|
/**
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
* result list will never contain nulls or undefined. The order
|
|
42
|
-
* of the results will match the order of the queries.
|
|
37
|
+
* Handler for parsing a response from a remote provider and converting it
|
|
38
|
+
* into the typed domain model.
|
|
43
39
|
*/
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
'query',
|
|
47
|
-
async (span) => {
|
|
48
|
-
span.setAttribute('provider.query.count', queries.length);
|
|
49
|
-
const results = await this.fetch(queries, session);
|
|
40
|
+
protected parseSingle(_body: unknown): T {
|
|
41
|
+
const model = this.newModel();
|
|
50
42
|
|
|
51
|
-
|
|
52
|
-
this.assert(result);
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
span.setAttribute('provider.result.count', results.length);
|
|
56
|
-
return results;
|
|
57
|
-
},
|
|
58
|
-
{ queryCount: queries.length }
|
|
59
|
-
);
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
/**
|
|
63
|
-
* Executes the listed mutations in order and returns the final state
|
|
64
|
-
* resulting from that set of operations.
|
|
65
|
-
*/
|
|
66
|
-
public async mutate(mutations: M[], session: Session): Promise<T> {
|
|
67
|
-
return this.instrumentation.traceMutation(
|
|
68
|
-
'mutate',
|
|
69
|
-
async (span) => {
|
|
70
|
-
span.setAttribute('provider.mutation.count', mutations.length);
|
|
71
|
-
const result = await this.process(mutations, session);
|
|
72
|
-
|
|
73
|
-
this.assert(result);
|
|
74
|
-
|
|
75
|
-
return result;
|
|
76
|
-
},
|
|
77
|
-
{ mutationCount: mutations.length }
|
|
78
|
-
);
|
|
43
|
+
return this.assert(model);
|
|
79
44
|
}
|
|
80
|
-
|
|
81
|
-
/**
|
|
82
|
-
* The internal extension point for providers implementating query
|
|
83
|
-
* capabilities.
|
|
84
|
-
*/
|
|
85
|
-
protected abstract fetch(queries: Q[], session: Session): Promise<T[]>;
|
|
86
|
-
|
|
87
|
-
/**
|
|
88
|
-
* The internal extension point for providers implementing mutation
|
|
89
|
-
* capabilities.
|
|
90
|
-
*/
|
|
91
|
-
protected abstract process(
|
|
92
|
-
mutations: M[],
|
|
93
|
-
session: Session
|
|
94
|
-
): Promise<T>;
|
|
95
45
|
}
|
|
@@ -1,10 +1,14 @@
|
|
|
1
|
-
import { CartQuery } from "../schemas/queries/cart.query";
|
|
2
|
-
import { CartMutation } from "../schemas/mutations/cart.mutation";
|
|
3
1
|
import { BaseProvider } from "./base.provider";
|
|
4
2
|
import { Cart } from "../schemas/models/cart.model";
|
|
3
|
+
import { CartQueryById } from "../schemas/queries/cart.query";
|
|
4
|
+
import { Session } from "../schemas/session.schema";
|
|
5
|
+
import { CartMutationItemAdd, CartMutationItemQuantityChange, CartMutationItemRemove } from "../schemas/mutations/cart.mutation";
|
|
5
6
|
|
|
6
7
|
export abstract class CartProvider<
|
|
7
|
-
T extends Cart = Cart
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
8
|
+
T extends Cart = Cart
|
|
9
|
+
> extends BaseProvider<T> {
|
|
10
|
+
public abstract getById(payload: CartQueryById, session: Session): Promise<T>;
|
|
11
|
+
public abstract add(payload: CartMutationItemAdd, session: Session): Promise<T>;
|
|
12
|
+
public abstract remove(payload: CartMutationItemRemove, session: Session): Promise<T>;
|
|
13
|
+
public abstract changeQuantity(payload: CartMutationItemQuantityChange, session: Session): Promise<T>;
|
|
14
|
+
}
|
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
import { Identity } from "../schemas/models/identity.model";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
2
|
+
import { IdentityMutationLogin, IdentityMutationLogout } from "../schemas/mutations/identity.mutation";
|
|
3
|
+
import { IdentityQuerySelf } from "../schemas/queries/identity.query";
|
|
4
|
+
import { Session } from "../schemas/session.schema";
|
|
4
5
|
import { BaseProvider } from "./base.provider";
|
|
5
6
|
|
|
6
7
|
export abstract class IdentityProvider<
|
|
7
8
|
T extends Identity = Identity,
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
9
|
+
> extends BaseProvider<T> {
|
|
10
|
+
public abstract getSelf(payload: IdentityQuerySelf, session: Session): Promise<T>;
|
|
11
|
+
public abstract login(payload: IdentityMutationLogin, session: Session): Promise<T>;
|
|
12
|
+
public abstract logout(payload: IdentityMutationLogout, session: Session): Promise<T>;
|
|
13
|
+
}
|
|
@@ -1,11 +1,10 @@
|
|
|
1
|
-
import z from 'zod';
|
|
2
1
|
import { Inventory } from '../schemas/models/inventory.model';
|
|
3
2
|
import { InventoryQuery } from '../schemas/queries/inventory.query';
|
|
4
|
-
import {
|
|
3
|
+
import { Session } from '../schemas/session.schema';
|
|
5
4
|
import { BaseProvider } from './base.provider';
|
|
6
5
|
|
|
7
6
|
export abstract class InventoryProvider<
|
|
8
|
-
T extends Inventory = Inventory
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
7
|
+
T extends Inventory = Inventory
|
|
8
|
+
> extends BaseProvider<T> {
|
|
9
|
+
public abstract getBySKU(payload: InventoryQuery, session: Session): Promise<T>;
|
|
10
|
+
}
|