@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.
Files changed (119) hide show
  1. package/CLAUDE.md +11 -0
  2. package/core/package.json +1 -1
  3. package/core/src/cache/cache-evaluation.interface.ts +19 -0
  4. package/core/src/cache/cache.interface.ts +38 -0
  5. package/core/src/cache/noop-cache.ts +42 -0
  6. package/core/src/cache/redis-cache.ts +55 -22
  7. package/core/src/client/client-builder.ts +63 -0
  8. package/core/src/client/client.ts +27 -3
  9. package/core/src/decorators/trpc.decorators.ts +144 -0
  10. package/core/src/index.ts +6 -1
  11. package/core/src/providers/analytics.provider.ts +3 -6
  12. package/core/src/providers/base.provider.ts +13 -63
  13. package/core/src/providers/cart.provider.ts +10 -6
  14. package/core/src/providers/identity.provider.ts +8 -5
  15. package/core/src/providers/inventory.provider.ts +5 -6
  16. package/core/src/providers/price.provider.ts +6 -6
  17. package/core/src/providers/product.provider.ts +6 -6
  18. package/core/src/providers/search.provider.ts +6 -6
  19. package/core/src/schemas/mutations/base.mutation.ts +0 -1
  20. package/core/src/schemas/mutations/cart.mutation.ts +0 -6
  21. package/core/src/schemas/mutations/identity.mutation.ts +0 -5
  22. package/core/src/schemas/mutations/product.mutation.ts +0 -1
  23. package/core/src/schemas/queries/base.query.ts +0 -1
  24. package/core/src/schemas/queries/cart.query.ts +1 -3
  25. package/core/src/schemas/queries/identity.query.ts +1 -3
  26. package/core/src/schemas/queries/inventory.query.ts +0 -1
  27. package/core/src/schemas/queries/price.query.ts +0 -3
  28. package/core/src/schemas/queries/product.query.ts +2 -7
  29. package/core/src/schemas/queries/search.query.ts +0 -3
  30. package/examples/node/package.json +1 -5
  31. package/examples/node/src/basic/basic-node-provider-model-extension.spec.ts +97 -0
  32. package/examples/node/src/basic/basic-node-provider-query-extension.spec.ts +84 -0
  33. package/examples/node/src/basic/basic-node-setup.spec.ts +40 -0
  34. package/otel/src/index.ts +3 -0
  35. package/otel/src/trace-decorator.ts +246 -0
  36. package/package.json +2 -1
  37. package/providers/algolia/src/core/initialize.ts +11 -9
  38. package/providers/algolia/src/providers/product.provider.ts +44 -11
  39. package/providers/algolia/src/providers/search.provider.ts +47 -66
  40. package/providers/commercetools/src/core/client.ts +0 -1
  41. package/providers/commercetools/src/core/initialize.ts +28 -24
  42. package/providers/commercetools/src/providers/cart.provider.ts +58 -89
  43. package/providers/commercetools/src/providers/identity.provider.ts +34 -50
  44. package/providers/commercetools/src/providers/inventory.provider.ts +16 -38
  45. package/providers/commercetools/src/providers/price.provider.ts +30 -35
  46. package/providers/commercetools/src/providers/product.provider.ts +48 -38
  47. package/providers/commercetools/src/providers/search.provider.ts +32 -47
  48. package/providers/commercetools/src/schema/capabilities.schema.ts +1 -1
  49. package/providers/fake/package.json +1 -0
  50. package/providers/fake/src/core/initialize.ts +17 -14
  51. package/providers/fake/src/index.ts +4 -0
  52. package/providers/fake/src/providers/analytics.provider.ts +19 -0
  53. package/providers/fake/src/providers/cart.provider.ts +107 -0
  54. package/providers/fake/src/providers/identity.provider.ts +78 -67
  55. package/providers/fake/src/providers/inventory.provider.ts +54 -0
  56. package/providers/fake/src/providers/price.provider.ts +60 -0
  57. package/providers/fake/src/providers/product.provider.ts +53 -49
  58. package/providers/fake/src/providers/search.provider.ts +15 -33
  59. package/providers/posthog/src/core/initialize.ts +6 -4
  60. package/trpc/__mocks__/superjson.js +25 -0
  61. package/trpc/jest.config.ts +14 -0
  62. package/trpc/package.json +2 -1
  63. package/trpc/src/client.ts +176 -0
  64. package/trpc/src/index.ts +35 -62
  65. package/trpc/src/integration.spec.ts +216 -0
  66. package/trpc/src/server.ts +123 -0
  67. package/trpc/src/transparent-client.spec.ts +160 -0
  68. package/trpc/src/types.ts +142 -0
  69. package/trpc/tsconfig.json +3 -0
  70. package/trpc/tsconfig.lib.json +2 -1
  71. package/trpc/tsconfig.spec.json +15 -0
  72. package/tsconfig.base.json +0 -2
  73. package/core/src/cache/caching-strategy.ts +0 -25
  74. package/examples/angular/e2e/example.spec.ts +0 -9
  75. package/examples/angular/eslint.config.mjs +0 -41
  76. package/examples/angular/playwright.config.ts +0 -38
  77. package/examples/angular/project.json +0 -86
  78. package/examples/angular/public/favicon.ico +0 -0
  79. package/examples/angular/src/app/app.component.html +0 -6
  80. package/examples/angular/src/app/app.component.scss +0 -22
  81. package/examples/angular/src/app/app.component.ts +0 -14
  82. package/examples/angular/src/app/app.config.ts +0 -16
  83. package/examples/angular/src/app/app.routes.ts +0 -25
  84. package/examples/angular/src/app/cart/cart.component.html +0 -4
  85. package/examples/angular/src/app/cart/cart.component.scss +0 -14
  86. package/examples/angular/src/app/cart/cart.component.ts +0 -73
  87. package/examples/angular/src/app/identity/identity.component.html +0 -6
  88. package/examples/angular/src/app/identity/identity.component.scss +0 -18
  89. package/examples/angular/src/app/identity/identity.component.ts +0 -49
  90. package/examples/angular/src/app/product/product.component.html +0 -14
  91. package/examples/angular/src/app/product/product.component.scss +0 -11
  92. package/examples/angular/src/app/product/product.component.ts +0 -42
  93. package/examples/angular/src/app/search/search.component.html +0 -35
  94. package/examples/angular/src/app/search/search.component.scss +0 -129
  95. package/examples/angular/src/app/search/search.component.ts +0 -50
  96. package/examples/angular/src/app/services/product.service.ts +0 -35
  97. package/examples/angular/src/app/services/search.service.ts +0 -48
  98. package/examples/angular/src/app/services/trpc.client.ts +0 -27
  99. package/examples/angular/src/index.html +0 -13
  100. package/examples/angular/src/main.ts +0 -7
  101. package/examples/angular/src/styles.scss +0 -17
  102. package/examples/angular/src/test-setup.ts +0 -6
  103. package/examples/angular/tsconfig.app.json +0 -10
  104. package/examples/angular/tsconfig.editor.json +0 -6
  105. package/examples/angular/tsconfig.json +0 -32
  106. package/examples/node/src/initialize-algolia.spec.ts +0 -29
  107. package/examples/node/src/initialize-commercetools.spec.ts +0 -31
  108. package/examples/node/src/initialize-extended-providers.spec.ts +0 -38
  109. package/examples/node/src/initialize-mixed-providers.spec.ts +0 -36
  110. package/examples/node/src/providers/custom-algolia-product.provider.ts +0 -18
  111. package/examples/node/src/schemas/custom-product.schema.ts +0 -8
  112. package/examples/trpc-node/.env.example +0 -52
  113. package/examples/trpc-node/eslint.config.mjs +0 -3
  114. package/examples/trpc-node/project.json +0 -61
  115. package/examples/trpc-node/src/assets/.gitkeep +0 -0
  116. package/examples/trpc-node/src/main.ts +0 -59
  117. package/examples/trpc-node/src/router-instance.ts +0 -52
  118. package/examples/trpc-node/tsconfig.app.json +0 -9
  119. 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
@@ -4,6 +4,6 @@
4
4
  "dependencies": {
5
5
  "zod": "4.0.0-beta.20250430T185432",
6
6
  "@upstash/redis": "^1.34.9",
7
- "@reactionary/otel": "0.0.1"
7
+ "reflect-metadata": "0.2.2"
8
8
  }
9
9
  }
@@ -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 { CachingStrategy } from './caching-strategy';
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(strategy: CachingStrategy) {
13
- this.strategy = strategy;
8
+ constructor() {
14
9
  this.redis = Redis.fromEnv();
15
10
  }
16
11
 
17
- public async get<T extends BaseModel>(query: BaseQuery, session: Session, schema: z.ZodType<T>): Promise<T | null> {
18
- let result = null;
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 cacheInformation = this.strategy.get(query, session);
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
- if (cacheInformation.canCache && cacheInformation.key) {
23
- const unvalidated = await this.redis.get(cacheInformation.key);
24
- const parsed = schema.safeParse(unvalidated);
24
+ return null;
25
+ }
25
26
 
26
- if (parsed.success) {
27
- result = parsed.data;
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
- return result;
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 put(query: BaseQuery, session: Session, value: unknown): void {
35
- const cacheInformation = this.strategy.get(query, session);
53
+ public async keys(pattern: string): Promise<string[]> {
54
+ return await this.redis.keys(pattern);
55
+ }
36
56
 
37
- if (cacheInformation.canCache && cacheInformation.key) {
38
- this.redis.set(cacheInformation.key, value, { ex: cacheInformation.cacheDurationInSeconds });
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 function buildClient<T extends Partial<Client>>(providers: Array<T>): Required<T> {
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 provider of providers) {
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
- return client satisfies T;
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/caching-strategy';
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
- Q extends AnalyticsQuery = AnalyticsQuery,
9
- M extends AnalyticsMutation = AnalyticsMutation
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 { createProviderInstrumentation } from '@reactionary/otel';
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
- private instrumentation: ReturnType<typeof createProviderInstrumentation>;
12
+ protected cache: Cache;
18
13
 
19
- constructor(public readonly schema: z.ZodType<T>, public readonly querySchema: z.ZodType<Q, Q>, public readonly mutationSchema: z.ZodType<M, M>) {
20
- this.instrumentation = createProviderInstrumentation(this.constructor.name);
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
- * Retrieves a set of entities matching the list of queries. The size of
40
- * the resulting list WILL always match the size of the query list. The
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
- public async query(queries: Q[], session: Session): Promise<T[]> {
45
- return this.instrumentation.traceQuery(
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
- for (const result of results) {
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
- Q extends CartQuery = CartQuery,
9
- M extends CartMutation = CartMutation
10
- > extends BaseProvider<T, Q, M> {}
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 { IdentityQuery } from "../schemas/queries/identity.query";
3
- import { IdentityMutation } from "../schemas/mutations/identity.mutation";
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
- Q extends IdentityQuery = IdentityQuery,
9
- M extends IdentityMutation = IdentityMutation
10
- > extends BaseProvider<T, Q, M> {}
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 { InventoryMutation } from '../schemas/mutations/inventory.mutation';
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
- Q extends InventoryQuery = InventoryQuery,
10
- M extends InventoryMutation = InventoryMutation
11
- > extends BaseProvider<T, Q, M> {}
7
+ T extends Inventory = Inventory
8
+ > extends BaseProvider<T> {
9
+ public abstract getBySKU(payload: InventoryQuery, session: Session): Promise<T>;
10
+ }