@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
@@ -0,0 +1,216 @@
1
+ import { buildClient, NoOpCache, SessionSchema } from '@reactionary/core';
2
+ import { withFakeCapabilities } from '@reactionary/provider-fake';
3
+ import { createTRPCServerRouter, createTRPCContext, type TRPCRouterFromClient } from './server';
4
+ import { createTRPCClient, type TRPCClientFromRouter } from './client';
5
+ import type { TransparentClient } from './types';
6
+ import { createTRPCProxyClient, httpBatchLink } from '@trpc/client';
7
+ import { createHTTPHandler } from '@trpc/server/adapters/standalone';
8
+ import * as http from 'http';
9
+
10
+ /**
11
+ * Integration test that actually starts an HTTP server and makes real network calls
12
+ * This is the real test that verifies the TRPC transparent transport works end-to-end
13
+ */
14
+
15
+ // Polyfill fetch for Node.js
16
+ global.fetch = global.fetch || require('node-fetch');
17
+
18
+ // Create the server-side client
19
+ const serverClient = buildClient(
20
+ [
21
+ withFakeCapabilities(
22
+ {
23
+ jitter: { mean: 0, deviation: 0 },
24
+ },
25
+ { search: true, product: true, identity: false }
26
+ ),
27
+ ],
28
+ { cache: new NoOpCache() }
29
+ );
30
+
31
+ // Create TRPC router from the client (do this at module level for type inference)
32
+ const router = createTRPCServerRouter(serverClient);
33
+ type AppRouter = typeof router;
34
+
35
+ describe('TRPC Integration Test - Real HTTP Server', () => {
36
+ let server: http.Server;
37
+ let serverPort: number;
38
+ let trpcProxyClient: ReturnType<typeof createTRPCProxyClient<AppRouter>>;
39
+ let transparentClient: TransparentClient<typeof serverClient>;
40
+
41
+ beforeAll(async () => {
42
+
43
+ // Create TRPC HTTP handler
44
+ const handler = createHTTPHandler({
45
+ router,
46
+ createContext: createTRPCContext,
47
+ });
48
+
49
+ // Start HTTP server with TRPC handler
50
+ server = http.createServer(handler);
51
+
52
+ // Find available port
53
+ serverPort = 3001;
54
+ await new Promise<void>((resolve, reject) => {
55
+ const tryPort = (port: number) => {
56
+ server.listen(port, (err?: Error) => {
57
+ if (err) {
58
+ if (port < 3010) {
59
+ tryPort(port + 1);
60
+ } else {
61
+ reject(err);
62
+ }
63
+ } else {
64
+ serverPort = port;
65
+ resolve();
66
+ }
67
+ });
68
+ };
69
+ tryPort(serverPort);
70
+ });
71
+
72
+ // Create real TRPC proxy client (no transformer for testing)
73
+ trpcProxyClient = createTRPCProxyClient<AppRouter>({
74
+ links: [
75
+ httpBatchLink({
76
+ url: `http://localhost:${serverPort}`,
77
+ }),
78
+ ],
79
+ });
80
+
81
+ // Create transparent client using the real implementation - now properly typed!
82
+ transparentClient = createTRPCClient<typeof serverClient>(trpcProxyClient);
83
+ });
84
+
85
+ afterAll(async () => {
86
+ if (server) {
87
+ await new Promise<void>((resolve) => {
88
+ server.close(() => resolve());
89
+ });
90
+ }
91
+ });
92
+
93
+ const session = SessionSchema.parse({
94
+ id: '1234567890',
95
+ });
96
+
97
+ describe('Product Provider via HTTP', () => {
98
+ it('should fetch product by slug through real HTTP calls', async () => {
99
+ const slug = 'integration-test-product';
100
+
101
+ // Get result from transparent client (through HTTP/TRPC)
102
+ const trpcResult = await transparentClient.product.getBySlug(
103
+ { slug },
104
+ session
105
+ );
106
+
107
+ // Get result from server client (direct call)
108
+ const directResult = await serverClient.product.getBySlug(
109
+ { slug },
110
+ session
111
+ );
112
+
113
+ // Results should have the same structure
114
+ expect(trpcResult).toBeDefined();
115
+ expect(trpcResult.slug).toBe(slug);
116
+ expect(trpcResult.name).toBeDefined();
117
+ expect(trpcResult.description).toBeDefined();
118
+ expect(trpcResult.image).toBeDefined();
119
+
120
+ // Both should have the same slug (faker uses seed for consistency)
121
+ expect(trpcResult.slug).toBe(directResult.slug);
122
+ });
123
+
124
+ it('should fetch product by id through real HTTP calls', async () => {
125
+ const productId = 'integration-test-id';
126
+
127
+ const trpcResult = await transparentClient.product.getById(
128
+ { id: productId },
129
+ session
130
+ );
131
+
132
+ const directResult = await serverClient.product.getById(
133
+ { id: productId },
134
+ session
135
+ );
136
+
137
+ expect(trpcResult).toBeDefined();
138
+ expect(trpcResult.identifier.key).toBe(productId);
139
+ expect(trpcResult.name).toBeDefined();
140
+ expect(trpcResult.description).toBeDefined();
141
+
142
+ // Should match direct call
143
+ expect(trpcResult.identifier?.key).toBe(directResult.identifier?.key);
144
+ });
145
+ });
146
+
147
+ describe('Search Provider via HTTP', () => {
148
+ it('should perform search through real HTTP calls', async () => {
149
+ const searchTerm = 'integration test search';
150
+
151
+ const trpcResult = await transparentClient.search.queryByTerm(
152
+ {
153
+ search: {
154
+ term: searchTerm,
155
+ page: 0,
156
+ pageSize: 10,
157
+ facets: []
158
+ }
159
+ },
160
+ session
161
+ );
162
+
163
+ const directResult = await serverClient.search.queryByTerm(
164
+ {
165
+ search: {
166
+ term: searchTerm,
167
+ page: 0,
168
+ pageSize: 10,
169
+ facets: []
170
+ }
171
+ },
172
+ session
173
+ );
174
+
175
+ expect(trpcResult).toBeDefined();
176
+ expect(trpcResult.products).toBeDefined();
177
+ expect(Array.isArray(trpcResult.products)).toBe(true);
178
+ expect(trpcResult.facets).toBeDefined();
179
+
180
+ // Should match direct call structure
181
+ expect(trpcResult.products.length).toBe(directResult.products.length);
182
+ });
183
+ });
184
+
185
+ describe('Network Error Handling', () => {
186
+ it('should handle HTTP errors gracefully', async () => {
187
+ // This should work normally first
188
+ const result = await transparentClient.product.getById(
189
+ { id: 'test-error-handling' },
190
+ session
191
+ );
192
+ expect(result).toBeDefined();
193
+ });
194
+ });
195
+
196
+ describe('API Equivalence', () => {
197
+ it('should produce identical results for TRPC vs direct calls', async () => {
198
+ const testId = 'equivalence-test';
199
+
200
+ // Make same call through both paths
201
+ const [trpcResult, directResult] = await Promise.all([
202
+ transparentClient.product.getById(
203
+ { id: testId },
204
+ session
205
+ ),
206
+ serverClient.product.getById({ id: testId }, session)
207
+ ]);
208
+
209
+ // Results should be structurally equivalent
210
+ expect(trpcResult.identifier.key).toBe(directResult.identifier.key);
211
+ expect(trpcResult.name).toBe(directResult.name);
212
+ expect(trpcResult.slug).toBe(directResult.slug);
213
+ expect(trpcResult.description).toBe(directResult.description);
214
+ });
215
+ });
216
+ });
@@ -0,0 +1,123 @@
1
+ import { initTRPC } from '@trpc/server';
2
+ import { Client, Session } from '@reactionary/core';
3
+ import superjson from 'superjson';
4
+ import { z } from 'zod';
5
+ import { createTRPCTracing } from '@reactionary/otel';
6
+ import {
7
+ introspectClient,
8
+ MethodInfo
9
+ } from './types';
10
+
11
+ // Initialize TRPC with context containing session (no transformer for testing)
12
+ const t = initTRPC.context<{ session?: Session }>().create();
13
+
14
+ export const router = t.router;
15
+ export const mergeRouters = t.mergeRouters;
16
+
17
+ // Apply tracing middleware
18
+ const baseProcedure = t.procedure.use(createTRPCTracing());
19
+
20
+ /**
21
+ * Create a TRPC router from a built client instance
22
+ * This function introspects the client and automatically creates TRPC procedures
23
+ * for all provider methods while maintaining type safety
24
+ *
25
+ * @example
26
+ * ```typescript
27
+ * const client = buildClient([
28
+ * withFakeCapabilities(config, { product: true, search: true })
29
+ * ]);
30
+ *
31
+ * const router = createTRPCServerRouter(client);
32
+ * ```
33
+ */
34
+ export function createTRPCServerRouter<T extends Partial<Client>>(client: T) {
35
+ const methods = introspectClient(client);
36
+
37
+ // Group methods by provider
38
+ const providerMethods = methods.reduce((acc, method) => {
39
+ if (!acc[method.providerName]) {
40
+ acc[method.providerName] = [];
41
+ }
42
+ acc[method.providerName].push(method);
43
+ return acc;
44
+ }, {} as Record<string, MethodInfo[]>);
45
+
46
+ // Build router structure
47
+ const routes: Record<string, any> = {};
48
+
49
+ for (const [providerName, providerMethodsList] of Object.entries(providerMethods)) {
50
+ const providerRoutes: Record<string, any> = {};
51
+
52
+ for (const methodInfo of providerMethodsList) {
53
+ const procedure = createProcedureForMethod(methodInfo);
54
+ providerRoutes[methodInfo.name] = procedure;
55
+ }
56
+
57
+ routes[providerName] = t.router(providerRoutes);
58
+ }
59
+
60
+ return t.router(routes);
61
+ }
62
+
63
+ /**
64
+ * Create a TRPC procedure for a specific provider method
65
+ */
66
+ function createProcedureForMethod(methodInfo: MethodInfo) {
67
+ // Create input schema - we use a flexible schema since we want to preserve
68
+ // the original method signatures without requiring schema definitions
69
+ const inputSchema = z.object({
70
+ payload: z.any(), // The actual payload from the provider method
71
+ session: z.any().optional(), // Session is optional in input since it might come from context
72
+ });
73
+
74
+ const procedureWithInput = baseProcedure.input(inputSchema);
75
+
76
+ if (methodInfo.isQuery) {
77
+ return procedureWithInput.query(async ({ input, ctx }) => {
78
+ const session = input.session || ctx.session;
79
+
80
+ // Call the original provider method
81
+ if (session) {
82
+ return await methodInfo.method(input.payload, session);
83
+ } else {
84
+ // Some methods might not require session
85
+ return await methodInfo.method(input.payload);
86
+ }
87
+ });
88
+ } else if (methodInfo.isMutation) {
89
+ return procedureWithInput.mutation(async ({ input, ctx }) => {
90
+ const session = input.session || ctx.session;
91
+
92
+ // Call the original provider method
93
+ if (session) {
94
+ return await methodInfo.method(input.payload, session);
95
+ } else {
96
+ // Some methods might not require session
97
+ return await methodInfo.method(input.payload);
98
+ }
99
+ });
100
+ } else {
101
+ throw new Error(`Method ${methodInfo.name} is neither query nor mutation`);
102
+ }
103
+ }
104
+
105
+ /**
106
+ * Type helper to extract the router type from a client
107
+ * This enables full type safety on the client side
108
+ */
109
+ export type TRPCRouterFromClient<T extends Partial<Client>> = ReturnType<typeof createTRPCServerRouter<T>>;
110
+
111
+ /**
112
+ * Context creator for TRPC server
113
+ * Override this to provide session from your authentication system
114
+ */
115
+ export function createTRPCContext(_opts: { req?: any; res?: any }) {
116
+ // Default implementation - you should override this based on your auth system
117
+ return {
118
+ session: undefined as Session | undefined,
119
+ };
120
+ }
121
+
122
+ // Create publicProcedure here to avoid circular imports
123
+ export const publicProcedure = baseProcedure;
@@ -0,0 +1,160 @@
1
+ import { buildClient, NoOpCache, SessionSchema } from '@reactionary/core';
2
+ import { withFakeCapabilities } from '@reactionary/provider-fake';
3
+ import { createTRPCServerRouter, introspectClient } from './index';
4
+
5
+ /**
6
+ * Test suite for TRPC transparent client functionality
7
+ * This tests the core introspection and router generation without HTTP setup
8
+ */
9
+
10
+ // Jest test framework is now available
11
+
12
+ // Create the server-side client using the same pattern as examples/node
13
+ const serverClient = buildClient(
14
+ [
15
+ withFakeCapabilities(
16
+ {
17
+ jitter: {
18
+ mean: 0,
19
+ deviation: 0,
20
+ },
21
+ },
22
+ { search: true, product: true, identity: false }
23
+ ),
24
+ ],
25
+ {
26
+ cache: new NoOpCache(),
27
+ }
28
+ );
29
+
30
+ // Create TRPC router from the client
31
+ const router = createTRPCServerRouter(serverClient);
32
+
33
+ describe('TRPC Transparent Client Core Functionality', () => {
34
+
35
+ const session = SessionSchema.parse({
36
+ id: '1234567890',
37
+ });
38
+
39
+ describe('Client Introspection', () => {
40
+ it('should correctly introspect client methods', () => {
41
+ const methods = introspectClient(serverClient);
42
+
43
+ expect(methods.length).toBeGreaterThan(0);
44
+
45
+ // Should find product methods
46
+ const productMethods = methods.filter(m => m.providerName === 'product');
47
+ expect(productMethods.length).toBeGreaterThan(0);
48
+
49
+ const getBySlugMethod = productMethods.find(m => m.name === 'getBySlug');
50
+ expect(getBySlugMethod).toBeDefined();
51
+ expect(getBySlugMethod!.isQuery).toBe(true);
52
+ expect(getBySlugMethod!.isMutation).toBe(false);
53
+
54
+ // Should find search methods
55
+ const searchMethods = methods.filter(m => m.providerName === 'search');
56
+ expect(searchMethods.length).toBeGreaterThan(0);
57
+
58
+ const queryByTermMethod = searchMethods.find(m => m.name === 'queryByTerm');
59
+ expect(queryByTermMethod).toBeDefined();
60
+ expect(queryByTermMethod!.isQuery).toBe(true);
61
+
62
+ // Note: Only testing enabled providers (product and search)
63
+ });
64
+ });
65
+
66
+ describe('Router Generation', () => {
67
+ it('should create TRPC router from client', () => {
68
+ expect(router).toBeDefined();
69
+
70
+ // Router should be an object (TRPC router)
71
+ expect(typeof router).toBe('object');
72
+ expect(router).toBeDefined();
73
+ });
74
+
75
+ it('should handle provider methods correctly', () => {
76
+ const methods = introspectClient(serverClient);
77
+ const providerNames = [...new Set(methods.map(m => m.providerName))];
78
+
79
+ // Should have enabled providers
80
+ expect(providerNames).toContain('product');
81
+ expect(providerNames).toContain('search');
82
+ });
83
+ });
84
+
85
+ describe('Method Classification', () => {
86
+ it('should correctly classify query methods', () => {
87
+ const methods = introspectClient(serverClient);
88
+
89
+ const queryMethods = methods.filter(m => m.isQuery);
90
+ const queryMethodNames = queryMethods.map(m => m.name);
91
+
92
+ // Should include get* methods from enabled providers
93
+ expect(queryMethodNames).toContain('getById');
94
+ expect(queryMethodNames).toContain('getBySlug');
95
+
96
+ // Should include query* methods
97
+ expect(queryMethodNames).toContain('queryByTerm');
98
+ });
99
+
100
+ it('should correctly classify mutation methods', () => {
101
+ const methods = introspectClient(serverClient);
102
+
103
+ const mutationMethods = methods.filter(m => m.isMutation);
104
+ const mutationMethodNames = mutationMethods.map(m => m.name);
105
+
106
+ // With current setup, only search and product are enabled
107
+ // No mutations expected from these providers
108
+ });
109
+ });
110
+
111
+ describe('Direct Client Functionality', () => {
112
+ it('should have working product provider', async () => {
113
+ const result = await serverClient.product.getBySlug(
114
+ { slug: 'test-product' },
115
+ session
116
+ );
117
+
118
+ expect(result).toBeDefined();
119
+ expect(result.slug).toBe('test-product');
120
+ expect(result.name).toBeDefined();
121
+ expect(result.description).toBeDefined();
122
+ });
123
+
124
+ it('should have working search provider', async () => {
125
+ const result = await serverClient.search.queryByTerm(
126
+ {
127
+ search: {
128
+ term: 'test search',
129
+ page: 0,
130
+ pageSize: 10,
131
+ facets: []
132
+ }
133
+ },
134
+ session
135
+ );
136
+
137
+ expect(result).toBeDefined();
138
+ expect(result.products).toBeDefined();
139
+ expect(result.facets).toBeDefined();
140
+ });
141
+
142
+ // Only testing enabled providers (product and search)
143
+ });
144
+
145
+ describe('Type System Validation', () => {
146
+ it('should maintain provider interface structure', () => {
147
+ // Verify the client has expected enabled provider structure
148
+ expect(serverClient.product).toBeDefined();
149
+ expect(serverClient.search).toBeDefined();
150
+
151
+ // Verify methods exist
152
+ expect(typeof serverClient.product.getById).toBe('function');
153
+ expect(typeof serverClient.product.getBySlug).toBe('function');
154
+ expect(typeof serverClient.search.queryByTerm).toBe('function');
155
+ });
156
+ });
157
+ });
158
+
159
+ // Export for potential use in other tests
160
+ export { serverClient, router };
@@ -0,0 +1,142 @@
1
+ import {
2
+ BaseProvider,
3
+ Client,
4
+ Session
5
+ } from '@reactionary/core';
6
+
7
+ /**
8
+ * Extract method names from a provider that match TRPC patterns
9
+ */
10
+ export type ProviderMethods<T> = T extends BaseProvider
11
+ ? {
12
+ [K in keyof T]: T[K] extends (...args: any[]) => any
13
+ ? K extends `get${string}` | `query${string}` | 'add' | 'remove' | 'changeQuantity' | 'login' | 'logout' | 'getSelf'
14
+ ? K
15
+ : never
16
+ : never
17
+ }[keyof T]
18
+ : never;
19
+
20
+ /**
21
+ * Extract method signature from a provider method
22
+ */
23
+ export type ProviderMethodSignature<T, K extends keyof T> =
24
+ T[K] extends (...args: infer Args) => infer Return
25
+ ? (...args: Args) => Return
26
+ : never;
27
+
28
+ /**
29
+ * Map all methods of all providers in a client
30
+ */
31
+ export type ClientMethodMap<T extends Partial<Client>> = {
32
+ [K in keyof T]: T[K] extends BaseProvider
33
+ ? {
34
+ [M in ProviderMethods<T[K]>]: ProviderMethodSignature<T[K], M>
35
+ }
36
+ : never;
37
+ };
38
+
39
+
40
+ /**
41
+ * Create transparent client that only includes methods matching TRPC patterns
42
+ */
43
+ export type TransparentClient<T extends Partial<Client>> = {
44
+ [K in keyof T]: T[K] extends BaseProvider
45
+ ? {
46
+ [M in ProviderMethods<T[K]>]: ProviderMethodSignature<T[K], M>
47
+ }
48
+ : never;
49
+ };
50
+
51
+ /**
52
+ * Extract method information for TRPC procedure creation
53
+ */
54
+ export interface MethodInfo {
55
+ name: string;
56
+ providerName: string;
57
+ isQuery: boolean;
58
+ isMutation: boolean;
59
+ method: (...args: any[]) => any;
60
+ }
61
+
62
+
63
+ /**
64
+ * Utility to determine if a method is a query or mutation based on naming convention
65
+ */
66
+ export function isQueryMethod(methodName: string): boolean {
67
+ return methodName.startsWith('get') ||
68
+ methodName.startsWith('query') ||
69
+ methodName === 'getSelf';
70
+ }
71
+
72
+ export function isMutationMethod(methodName: string): boolean {
73
+ return !isQueryMethod(methodName) && (
74
+ methodName === 'add' ||
75
+ methodName === 'remove' ||
76
+ methodName === 'changeQuantity' ||
77
+ methodName === 'login' ||
78
+ methodName === 'logout'
79
+ );
80
+ }
81
+
82
+ /**
83
+ * Introspect a client instance to extract all provider methods matching naming patterns
84
+ */
85
+ export function introspectClient<T extends Partial<Client>>(client: T): MethodInfo[] {
86
+ const methods: MethodInfo[] = [];
87
+
88
+ for (const [providerName, provider] of Object.entries(client)) {
89
+ if (provider instanceof BaseProvider) {
90
+ // Get all methods that match our naming patterns
91
+ for (const key of Object.getOwnPropertyNames(Object.getPrototypeOf(provider))) {
92
+ const method = (provider as any)[key];
93
+ if (typeof method === 'function' && key !== 'constructor') {
94
+ if (isQueryMethod(key) || isMutationMethod(key)) {
95
+ methods.push({
96
+ name: key,
97
+ providerName,
98
+ isQuery: isQueryMethod(key),
99
+ isMutation: isMutationMethod(key),
100
+ method: method.bind(provider)
101
+ });
102
+ }
103
+ }
104
+ }
105
+ }
106
+ }
107
+
108
+ return methods;
109
+ }
110
+
111
+ /**
112
+ * Type for TRPC procedure input - includes payload and session
113
+ */
114
+ export interface TRPCMethodInput<TPayload = any> {
115
+ payload: TPayload;
116
+ session?: Session;
117
+ }
118
+
119
+ /**
120
+ * Helper to extract payload type from a provider method
121
+ */
122
+ export type ExtractPayloadType<T> = T extends (payload: infer P, session: Session) => any
123
+ ? P
124
+ : T extends (payload: infer P) => any
125
+ ? P
126
+ : never;
127
+
128
+ /**
129
+ * Helper to extract return type from a provider method
130
+ */
131
+ export type ExtractReturnType<T> = T extends (...args: any[]) => infer R
132
+ ? R
133
+ : never;
134
+
135
+ /**
136
+ * Create a TRPC-compatible method signature from a provider method
137
+ */
138
+ export type TRPCMethodSignature<T> = T extends (payload: infer P, session: Session) => infer R
139
+ ? (input: TRPCMethodInput<P>) => R
140
+ : T extends (payload: infer P) => infer R
141
+ ? (input: TRPCMethodInput<P>) => R
142
+ : never;
@@ -8,6 +8,9 @@
8
8
  "references": [
9
9
  {
10
10
  "path": "./tsconfig.lib.json"
11
+ },
12
+ {
13
+ "path": "./tsconfig.spec.json"
11
14
  }
12
15
  ]
13
16
  }
@@ -5,5 +5,6 @@
5
5
  "declaration": true,
6
6
  "types": ["node"]
7
7
  },
8
- "include": ["src/**/*.ts"]
8
+ "include": ["src/**/*.ts"],
9
+ "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"]
9
10
  }
@@ -0,0 +1,15 @@
1
+ {
2
+ "extends": "./tsconfig.json",
3
+ "compilerOptions": {
4
+ "outDir": "../dist/out-tsc",
5
+ "module": "commonjs",
6
+ "moduleResolution": "node10",
7
+ "types": ["jest", "node"]
8
+ },
9
+ "include": [
10
+ "jest.config.ts",
11
+ "src/**/*.test.ts",
12
+ "src/**/*.spec.ts",
13
+ "src/**/*.d.ts"
14
+ ]
15
+ }
@@ -5,8 +5,6 @@
5
5
  "sourceMap": true,
6
6
  "declaration": false,
7
7
  "moduleResolution": "node",
8
- "emitDecoratorMetadata": true,
9
- "experimentalDecorators": true,
10
8
  "importHelpers": true,
11
9
  "target": "es2015",
12
10
  "module": "esnext",