@reactionary/source 0.0.37 → 0.0.39

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 (93) hide show
  1. package/.env-template +10 -0
  2. package/.github/workflows/pull-request.yml +5 -3
  3. package/core/package.json +1 -2
  4. package/core/src/client/client.ts +4 -2
  5. package/core/src/index.ts +3 -9
  6. package/core/src/providers/analytics.provider.ts +6 -1
  7. package/core/src/providers/base.provider.ts +33 -3
  8. package/core/src/providers/cart.provider.ts +7 -1
  9. package/core/src/providers/category.provider.ts +91 -0
  10. package/core/src/providers/identity.provider.ts +5 -1
  11. package/core/src/providers/inventory.provider.ts +4 -0
  12. package/core/src/providers/price.provider.ts +54 -0
  13. package/core/src/providers/product.provider.ts +4 -0
  14. package/core/src/providers/search.provider.ts +6 -0
  15. package/core/src/schemas/capabilities.schema.ts +3 -2
  16. package/core/src/schemas/models/base.model.ts +42 -1
  17. package/core/src/schemas/models/cart.model.ts +27 -3
  18. package/core/src/schemas/models/category.model.ts +23 -0
  19. package/core/src/schemas/models/identifiers.model.ts +29 -1
  20. package/core/src/schemas/models/inventory.model.ts +6 -2
  21. package/core/src/schemas/models/price.model.ts +11 -3
  22. package/core/src/schemas/models/search.model.ts +4 -2
  23. package/core/src/schemas/queries/category.query.ts +32 -0
  24. package/core/src/schemas/queries/index.ts +9 -0
  25. package/core/src/schemas/queries/inventory.query.ts +18 -3
  26. package/core/src/schemas/session.schema.ts +13 -2
  27. package/examples/next/.swcrc +30 -0
  28. package/examples/next/eslint.config.mjs +21 -0
  29. package/examples/next/index.d.ts +6 -0
  30. package/examples/next/next-env.d.ts +5 -0
  31. package/examples/next/next.config.js +20 -0
  32. package/examples/next/project.json +9 -0
  33. package/examples/next/public/.gitkeep +0 -0
  34. package/examples/next/public/favicon.ico +0 -0
  35. package/examples/next/src/app/global.css +0 -0
  36. package/examples/next/src/app/layout.tsx +18 -0
  37. package/examples/next/src/app/page.module.scss +2 -0
  38. package/examples/next/src/app/page.tsx +51 -0
  39. package/examples/next/src/instrumentation.ts +9 -0
  40. package/examples/next/tsconfig.json +44 -0
  41. package/examples/node/src/basic/basic-node-provider-model-extension.spec.ts +0 -1
  42. package/examples/node/src/basic/basic-node-setup.spec.ts +0 -1
  43. package/otel/README.md +152 -172
  44. package/otel/package.json +0 -1
  45. package/otel/src/index.ts +15 -5
  46. package/otel/src/metrics.ts +3 -3
  47. package/otel/src/test/otel.spec.ts +8 -0
  48. package/otel/src/trace-decorator.ts +87 -108
  49. package/otel/src/tracer.ts +3 -3
  50. package/package.json +2 -1
  51. package/providers/commercetools/package.json +1 -0
  52. package/providers/commercetools/src/core/initialize.ts +7 -3
  53. package/providers/commercetools/src/providers/cart.provider.ts +84 -8
  54. package/providers/commercetools/src/providers/category.provider.ts +244 -0
  55. package/providers/commercetools/src/providers/index.ts +7 -0
  56. package/providers/commercetools/src/providers/inventory.provider.ts +31 -14
  57. package/providers/commercetools/src/providers/price.provider.ts +74 -18
  58. package/providers/commercetools/src/providers/product.provider.ts +19 -15
  59. package/providers/commercetools/src/providers/search.provider.ts +9 -7
  60. package/providers/commercetools/src/schema/capabilities.schema.ts +2 -1
  61. package/providers/commercetools/src/schema/configuration.schema.ts +1 -1
  62. package/providers/commercetools/src/test/cart.provider.spec.ts +119 -0
  63. package/providers/commercetools/src/test/category.provider.spec.ts +180 -0
  64. package/providers/commercetools/src/test/price.provider.spec.ts +80 -0
  65. package/providers/commercetools/src/test/product.provider.spec.ts +29 -14
  66. package/providers/commercetools/src/test/search.provider.spec.ts +51 -9
  67. package/providers/commercetools/src/test/test-utils.ts +35 -0
  68. package/providers/commercetools/tsconfig.lib.json +1 -1
  69. package/providers/fake/jest.config.ts +10 -0
  70. package/providers/fake/src/core/initialize.ts +15 -1
  71. package/providers/fake/src/index.ts +2 -9
  72. package/providers/fake/src/providers/cart.provider.ts +74 -15
  73. package/providers/fake/src/providers/category.provider.ts +152 -0
  74. package/providers/fake/src/providers/index.ts +8 -0
  75. package/providers/fake/src/providers/inventory.provider.ts +23 -9
  76. package/providers/fake/src/providers/price.provider.ts +46 -6
  77. package/providers/fake/src/providers/search.provider.ts +13 -4
  78. package/providers/fake/src/schema/capabilities.schema.ts +4 -2
  79. package/providers/fake/src/schema/configuration.schema.ts +5 -0
  80. package/providers/fake/src/test/cart.provider.spec.ts +126 -0
  81. package/providers/fake/src/test/category.provider.spec.ts +134 -0
  82. package/providers/fake/src/test/price.provider.spec.ts +80 -0
  83. package/providers/fake/src/test/test-utils.ts +42 -0
  84. package/providers/fake/tsconfig.json +4 -0
  85. package/providers/fake/tsconfig.lib.json +3 -1
  86. package/providers/fake/tsconfig.spec.json +16 -0
  87. package/trpc/package.json +1 -2
  88. package/trpc/src/client.ts +1 -3
  89. package/trpc/src/integration.spec.ts +16 -10
  90. package/trpc/src/transparent-client.spec.ts +23 -17
  91. package/tsconfig.base.json +2 -0
  92. package/core/src/decorators/trpc.decorators.ts +0 -144
  93. package/otel/src/sdk.ts +0 -57
@@ -0,0 +1,134 @@
1
+ import 'dotenv/config'
2
+
3
+
4
+ import { CategorySchema, NoOpCache, ProductSchema, Session } from '@reactionary/core';
5
+
6
+ import { getTracer, shutdownOtel } from '@reactionary/otel';
7
+ import { FakeCategoryProvider } from '../providers';
8
+ import { createAnonymousTestSession, getFakerTestConfiguration } from './test-utils';
9
+ describe('Faker Category Provider', () => {
10
+ let provider: FakeCategoryProvider;
11
+ let session: Session;
12
+
13
+
14
+
15
+ beforeAll( () => {
16
+ provider = new FakeCategoryProvider(getFakerTestConfiguration(), CategorySchema, new NoOpCache());
17
+ });
18
+
19
+ beforeEach( () => {
20
+ session = createAnonymousTestSession()
21
+ })
22
+
23
+ it('should be able to get top-categories', async () => {
24
+ const result = await provider.findTopCategories({ paginationOptions: { pageSize: 10, pageNumber: 1 }}, session);
25
+
26
+ expect(result.items.length).toBeGreaterThan(0);
27
+ expect(result.items[0].identifier.key).toBe('grocery');
28
+ expect(result.items[0].name).toBe('Grocery');
29
+
30
+ expect(result.items[1].identifier.key).toBe('sports');
31
+ expect(result.items[1].name).toBe('Sports');
32
+ });
33
+
34
+ it('should be able to get child categories for a category', async () => {
35
+ const result = await provider.findChildCategories({ parentId: { key: 'grocery' }, paginationOptions: { pageSize: 10, pageNumber: 1 }}, session);
36
+
37
+ expect(result.items.length).toBeGreaterThan(0);
38
+ expect(result.items[0].identifier.key).toBe('grocery-0');
39
+ expect(result.items[0].name).toBe('Grocery-0');
40
+
41
+ expect(result.items[1].identifier.key).toBe('grocery-1');
42
+ expect(result.items[1].name).toBe('Grocery-1');
43
+
44
+ });
45
+
46
+
47
+ it('should be able to get child categories for a category, paged', async () => {
48
+ let result = await provider.findChildCategories({ parentId: { key: 'grocery' }, paginationOptions: { pageSize: 1, pageNumber: 1 }}, session);
49
+
50
+ expect(result.items.length).toBeGreaterThan(0);
51
+ expect(result.items[0].identifier.key).toBe('grocery-0');
52
+ expect(result.items[0].name).toBe('Grocery-0');
53
+ expect(result.totalCount).toBeGreaterThan(1);
54
+ expect(result.totalPages).toEqual(result.totalCount);
55
+ expect(result.pageSize).toBe(1);
56
+ expect(result.pageNumber).toBe(1);
57
+
58
+ result = await provider.findChildCategories({ parentId: { key: 'grocery' }, paginationOptions: { pageSize: 1, pageNumber: 2 }}, session);
59
+
60
+ expect(result.items.length).toBeGreaterThan(0);
61
+ expect(result.items[0].identifier.key).toBe('grocery-1');
62
+ expect(result.items[0].name).toBe('Grocery-1');
63
+ expect(result.totalCount).toBeGreaterThan(1);
64
+ expect(result.totalPages).toEqual(result.totalCount);
65
+ expect(result.pageSize).toBe(1);
66
+ expect(result.pageNumber).toBe(2);
67
+ });
68
+
69
+
70
+ it('can load all breadcrumbs for a category', async () => {
71
+ const result = await provider.getBreadcrumbPathToCategory({ id: { key: 'grocery-0-0' } }, session);
72
+
73
+ expect(result.length).toBeGreaterThan(2);
74
+ expect(result[0].identifier.key).toBe('grocery');
75
+ expect(result[0].name).toBe('Grocery');
76
+ expect(result[0].slug).toBe('grocery-slug');
77
+
78
+ expect(result[1].identifier.key).toBe('grocery-0');
79
+ expect(result[1].name).toBe('Grocery-0');
80
+ expect(result[1].slug).toBe('grocery-0-slug');
81
+
82
+ expect(result[2].identifier.key).toBe('grocery-0-0');
83
+ expect(result[2].name).toBe('Grocery-0-0');
84
+ expect(result[2].slug).toBe('grocery-0-0-slug');
85
+
86
+ });
87
+
88
+
89
+ it('should be able to get a category by slug', async () => {
90
+ const result = await provider.getBySlug({ slug: 'grocery-slug' }, session);
91
+ expect(result).toBeTruthy();
92
+ if (result) {
93
+ expect(result.identifier.key).toBe('grocery');
94
+ expect(result.name).toBe('Grocery');
95
+ expect(result.slug).toBe('grocery-slug');
96
+ expect(result.parentCategory).toBeUndefined();
97
+ expect(result.text).not.toBe("");
98
+ expect(result.meta.placeholder).toBe(false);
99
+ }
100
+ });
101
+
102
+ it('returns null if looking for slug that does not exist', async () => {
103
+ const result = await provider.getBySlug({ slug: 'non-existent-slug' }, session);
104
+ expect(result).toBeNull();
105
+ });
106
+
107
+
108
+
109
+ it('should be able to get a category by id', async () => {
110
+ const result = await provider.getById({ id: { key: 'grocery'}}, session);
111
+
112
+ expect(result.identifier.key).toBe('grocery');
113
+ expect(result.name).toBe('Grocery');
114
+ expect(result.slug).toBe('grocery-slug');
115
+ expect(result.parentCategory).toBeUndefined();
116
+
117
+ expect(result.text).not.toBe("");
118
+ expect(result.meta.placeholder).toBe(false);
119
+
120
+ });
121
+
122
+
123
+
124
+
125
+
126
+ it('returns a placeholder if you search for a category that does not exist', async () => {
127
+ const result = await provider.getById({ id: { key: 'non-existent-category'}}, session);
128
+ expect(result.identifier.key).toBe('non-existent-category');
129
+ expect(result.meta.placeholder).toBe(true);
130
+
131
+ });
132
+
133
+
134
+ });
@@ -0,0 +1,80 @@
1
+ import 'dotenv/config';
2
+
3
+
4
+ import { NoOpCache, PriceSchema, Session } from '@reactionary/core';
5
+ import { createAnonymousTestSession, getFakerTestConfiguration } from './test-utils';
6
+
7
+ import { FakePriceProvider } from '../providers/price.provider';
8
+
9
+ const testData = {
10
+ skuWithoutTiers: 'SGB-01',
11
+ skuWithTiers: 'GMCT-01-with-tiers'
12
+ }
13
+
14
+
15
+ describe('Fake Price Provider', () => {
16
+ let provider: FakePriceProvider;
17
+ let session: Session;
18
+
19
+
20
+
21
+ beforeAll( () => {
22
+ provider = new FakePriceProvider(getFakerTestConfiguration(), PriceSchema, new NoOpCache());
23
+ });
24
+
25
+ beforeEach( () => {
26
+ session = createAnonymousTestSession()
27
+ })
28
+
29
+ it('should be able to get prices for a product without tiers', async () => {
30
+ const result = await provider.getBySKU({ sku: { key: testData.skuWithoutTiers }}, session);
31
+
32
+ expect(result).toBeTruthy();
33
+ if (result) {
34
+ expect(result.identifier.sku.key).toBe(testData.skuWithoutTiers);
35
+ expect(result.unitPrice.value).toBeGreaterThan(0);
36
+ expect(result.unitPrice.currency).toBe('USD');
37
+ expect(result.tieredPrices.length).toBe(0);
38
+ }
39
+ });
40
+
41
+ it('should be able to get prices for a product with tiers', async () => {
42
+ const result = await provider.getBySKU({ sku: { key: testData.skuWithTiers }}, session);
43
+
44
+ expect(result).toBeTruthy();
45
+ if (result) {
46
+ expect(result.identifier.sku.key).toBe(testData.skuWithTiers);
47
+ expect(result.unitPrice.value).toBeGreaterThan(0);
48
+ expect(result.unitPrice.currency).toBe('USD');
49
+ expect(result.tieredPrices.length).toBeGreaterThan(0);
50
+
51
+ expect(result.tieredPrices[0].minimumQuantity).toBeGreaterThan(0);
52
+ expect(result.tieredPrices[0].price.value).toBeLessThanOrEqual(result.unitPrice.value);
53
+ expect(result.tieredPrices[0].price.currency).toBe('USD');
54
+
55
+ }
56
+ });
57
+
58
+ it('should return a placeholder price for an unknown SKU', async () => {
59
+ const result = await provider.getBySKU({ sku: { key: 'unknown-sku' }}, session);
60
+
61
+ expect(result).toBeTruthy();
62
+ if (result) {
63
+ expect(result.identifier.sku.key).toBe('unknown-sku');
64
+ expect(result.unitPrice.value).toBe(-1);
65
+ expect(result.unitPrice.currency).toBe('USD');
66
+ expect(result.tieredPrices.length).toBe(0);
67
+ expect(result.meta?.placeholder).toBe(true);
68
+ }
69
+ });
70
+
71
+ it('can look up multiple prices at once', async () => {
72
+ const skus = [testData.skuWithTiers, testData.skuWithoutTiers, 'unknown-sku'];
73
+ const results = await Promise.all(skus.map( sku => provider.getBySKU({ sku: { key: sku }}, session)));
74
+
75
+ expect(results).toHaveLength(skus.length);
76
+ expect(results[0].identifier.sku.key).toBe(testData.skuWithTiers);
77
+ expect(results[1].identifier.sku.key).toBe(testData.skuWithoutTiers);
78
+ expect(results[2].identifier.sku.key).toBe('unknown-sku');
79
+ });
80
+ });
@@ -0,0 +1,42 @@
1
+ import { Session } from "@reactionary/core";
2
+ import { FakeConfiguration } from "../schema/configuration.schema";
3
+
4
+ export function getFakerTestConfiguration(): FakeConfiguration {
5
+ return {
6
+ jitter: {
7
+ mean: 0,
8
+ deviation: 0,
9
+ },
10
+ seeds: {
11
+ product: 1,
12
+ search: 1,
13
+ category: 1,
14
+ }
15
+ }
16
+ }
17
+
18
+
19
+ export function createAnonymousTestSession(): Session {
20
+ return {
21
+ id: 'test-session-id',
22
+ identity: {
23
+ type: 'Anonymous',
24
+ meta: {
25
+ cache: { hit: false, key: '' },
26
+ placeholder: false,
27
+ },
28
+ id: '',
29
+ token: undefined,
30
+ issued: new Date(),
31
+ expiry: new Date(new Date().getTime() + 3600 * 1000), // 1 hour from now
32
+ },
33
+ languageContext: {
34
+ locale: 'en-US',
35
+ currencyCode: 'USD',
36
+ countryCode: 'US',
37
+ },
38
+ storeIdentifier: {
39
+ key: 'the-good-store',
40
+ },
41
+ };
42
+ }
@@ -15,6 +15,10 @@
15
15
  "references": [
16
16
  {
17
17
  "path": "./tsconfig.lib.json"
18
+ },
19
+ {
20
+ "path": "./tsconfig.spec.json"
18
21
  }
22
+
19
23
  ]
20
24
  }
@@ -5,5 +5,7 @@
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", "src/**/test-utils.ts"]
10
+
9
11
  }
@@ -0,0 +1,16 @@
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/**/test-utils.ts",
14
+ "src/**/*.d.ts"
15
+ ]
16
+ }
package/trpc/package.json CHANGED
@@ -9,7 +9,6 @@
9
9
  "@reactionary/core": "0.0.1",
10
10
  "@reactionary/otel": "0.0.1",
11
11
  "superjson": "^2.2.2",
12
- "zod": "4.0.0-beta.20250430T185432",
13
- "@trpc/client": "^11.1.2"
12
+ "zod": "4.0.0-beta.20250430T185432"
14
13
  }
15
14
  }
@@ -1,7 +1,5 @@
1
- import { Client, Session, isTRPCQuery, isTRPCMutation, isTRPCMethod } from '@reactionary/core';
1
+ import { Client, Session } from '@reactionary/core';
2
2
  import type { TransparentClient } from './types';
3
- import type { inferRouterInputs, inferRouterOutputs } from '@trpc/server';
4
- import type { TRPCClientError } from '@trpc/client';
5
3
 
6
4
  /**
7
5
  * Configuration options for TRPC client creation
@@ -21,8 +21,14 @@ const serverClient = buildClient(
21
21
  withFakeCapabilities(
22
22
  {
23
23
  jitter: { mean: 0, deviation: 0 },
24
+ seeds: {
25
+ product: 12345,
26
+ category: 12345,
27
+ cart: 12345,
28
+ search: 0
29
+ },
24
30
  },
25
- { search: true, product: true, identity: false }
31
+ { search: true, product: true, identity: false, cart: true }
26
32
  ),
27
33
  ],
28
34
  { cache: new NoOpCache() }
@@ -97,14 +103,14 @@ describe('TRPC Integration Test - Real HTTP Server', () => {
97
103
  describe('Product Provider via HTTP', () => {
98
104
  it('should fetch product by slug through real HTTP calls', async () => {
99
105
  const slug = 'integration-test-product';
100
-
106
+
101
107
  // Get result from transparent client (through HTTP/TRPC)
102
108
  const trpcResult = await transparentClient.product.getBySlug(
103
109
  { slug },
104
110
  session
105
111
  );
106
112
 
107
- // Get result from server client (direct call)
113
+ // Get result from server client (direct call)
108
114
  const directResult = await serverClient.product.getBySlug(
109
115
  { slug },
110
116
  session
@@ -116,14 +122,14 @@ describe('TRPC Integration Test - Real HTTP Server', () => {
116
122
  expect(trpcResult.name).toBeDefined();
117
123
  expect(trpcResult.description).toBeDefined();
118
124
  expect(trpcResult.image).toBeDefined();
119
-
125
+
120
126
  // Both should have the same slug (faker uses seed for consistency)
121
127
  expect(trpcResult.slug).toBe(directResult.slug);
122
128
  });
123
129
 
124
130
  it('should fetch product by id through real HTTP calls', async () => {
125
131
  const productId = 'integration-test-id';
126
-
132
+
127
133
  const trpcResult = await transparentClient.product.getById(
128
134
  { id: productId },
129
135
  session
@@ -138,7 +144,7 @@ describe('TRPC Integration Test - Real HTTP Server', () => {
138
144
  expect(trpcResult.identifier.key).toBe(productId);
139
145
  expect(trpcResult.name).toBeDefined();
140
146
  expect(trpcResult.description).toBeDefined();
141
-
147
+
142
148
  // Should match direct call
143
149
  expect(trpcResult.identifier?.key).toBe(directResult.identifier?.key);
144
150
  });
@@ -147,7 +153,7 @@ describe('TRPC Integration Test - Real HTTP Server', () => {
147
153
  describe('Search Provider via HTTP', () => {
148
154
  it('should perform search through real HTTP calls', async () => {
149
155
  const searchTerm = 'integration test search';
150
-
156
+
151
157
  const trpcResult = await transparentClient.search.queryByTerm(
152
158
  {
153
159
  search: {
@@ -176,7 +182,7 @@ describe('TRPC Integration Test - Real HTTP Server', () => {
176
182
  expect(trpcResult.products).toBeDefined();
177
183
  expect(Array.isArray(trpcResult.products)).toBe(true);
178
184
  expect(trpcResult.facets).toBeDefined();
179
-
185
+
180
186
  // Should match direct call structure
181
187
  expect(trpcResult.products.length).toBe(directResult.products.length);
182
188
  });
@@ -196,7 +202,7 @@ describe('TRPC Integration Test - Real HTTP Server', () => {
196
202
  describe('API Equivalence', () => {
197
203
  it('should produce identical results for TRPC vs direct calls', async () => {
198
204
  const testId = 'equivalence-test';
199
-
205
+
200
206
  // Make same call through both paths
201
207
  const [trpcResult, directResult] = await Promise.all([
202
208
  transparentClient.product.getById(
@@ -213,4 +219,4 @@ describe('TRPC Integration Test - Real HTTP Server', () => {
213
219
  expect(trpcResult.description).toBe(directResult.description);
214
220
  });
215
221
  });
216
- });
222
+ });
@@ -18,8 +18,14 @@ const serverClient = buildClient(
18
18
  mean: 0,
19
19
  deviation: 0,
20
20
  },
21
+ seeds: {
22
+ product: 12345,
23
+ category: 12345,
24
+ cart: 12345,
25
+ search: 0
26
+ },
21
27
  },
22
- { search: true, product: true, identity: false }
28
+ { search: true, product: true, identity: false, cart: true }
23
29
  ),
24
30
  ],
25
31
  {
@@ -39,13 +45,13 @@ describe('TRPC Transparent Client Core Functionality', () => {
39
45
  describe('Client Introspection', () => {
40
46
  it('should correctly introspect client methods', () => {
41
47
  const methods = introspectClient(serverClient);
42
-
48
+
43
49
  expect(methods.length).toBeGreaterThan(0);
44
-
50
+
45
51
  // Should find product methods
46
52
  const productMethods = methods.filter(m => m.providerName === 'product');
47
53
  expect(productMethods.length).toBeGreaterThan(0);
48
-
54
+
49
55
  const getBySlugMethod = productMethods.find(m => m.name === 'getBySlug');
50
56
  expect(getBySlugMethod).toBeDefined();
51
57
  expect(getBySlugMethod!.isQuery).toBe(true);
@@ -54,7 +60,7 @@ describe('TRPC Transparent Client Core Functionality', () => {
54
60
  // Should find search methods
55
61
  const searchMethods = methods.filter(m => m.providerName === 'search');
56
62
  expect(searchMethods.length).toBeGreaterThan(0);
57
-
63
+
58
64
  const queryByTermMethod = searchMethods.find(m => m.name === 'queryByTerm');
59
65
  expect(queryByTermMethod).toBeDefined();
60
66
  expect(queryByTermMethod!.isQuery).toBe(true);
@@ -66,7 +72,7 @@ describe('TRPC Transparent Client Core Functionality', () => {
66
72
  describe('Router Generation', () => {
67
73
  it('should create TRPC router from client', () => {
68
74
  expect(router).toBeDefined();
69
-
75
+
70
76
  // Router should be an object (TRPC router)
71
77
  expect(typeof router).toBe('object');
72
78
  expect(router).toBeDefined();
@@ -75,7 +81,7 @@ describe('TRPC Transparent Client Core Functionality', () => {
75
81
  it('should handle provider methods correctly', () => {
76
82
  const methods = introspectClient(serverClient);
77
83
  const providerNames = [...new Set(methods.map(m => m.providerName))];
78
-
84
+
79
85
  // Should have enabled providers
80
86
  expect(providerNames).toContain('product');
81
87
  expect(providerNames).toContain('search');
@@ -85,24 +91,24 @@ describe('TRPC Transparent Client Core Functionality', () => {
85
91
  describe('Method Classification', () => {
86
92
  it('should correctly classify query methods', () => {
87
93
  const methods = introspectClient(serverClient);
88
-
94
+
89
95
  const queryMethods = methods.filter(m => m.isQuery);
90
96
  const queryMethodNames = queryMethods.map(m => m.name);
91
-
97
+
92
98
  // Should include get* methods from enabled providers
93
99
  expect(queryMethodNames).toContain('getById');
94
100
  expect(queryMethodNames).toContain('getBySlug');
95
-
96
- // Should include query* methods
101
+
102
+ // Should include query* methods
97
103
  expect(queryMethodNames).toContain('queryByTerm');
98
104
  });
99
105
 
100
106
  it('should correctly classify mutation methods', () => {
101
107
  const methods = introspectClient(serverClient);
102
-
108
+
103
109
  const mutationMethods = methods.filter(m => m.isMutation);
104
110
  const mutationMethodNames = mutationMethods.map(m => m.name);
105
-
111
+
106
112
  // With current setup, only search and product are enabled
107
113
  // No mutations expected from these providers
108
114
  });
@@ -114,7 +120,7 @@ describe('TRPC Transparent Client Core Functionality', () => {
114
120
  { slug: 'test-product' },
115
121
  session
116
122
  );
117
-
123
+
118
124
  expect(result).toBeDefined();
119
125
  expect(result.slug).toBe('test-product');
120
126
  expect(result.name).toBeDefined();
@@ -133,7 +139,7 @@ describe('TRPC Transparent Client Core Functionality', () => {
133
139
  },
134
140
  session
135
141
  );
136
-
142
+
137
143
  expect(result).toBeDefined();
138
144
  expect(result.products).toBeDefined();
139
145
  expect(result.facets).toBeDefined();
@@ -147,7 +153,7 @@ describe('TRPC Transparent Client Core Functionality', () => {
147
153
  // Verify the client has expected enabled provider structure
148
154
  expect(serverClient.product).toBeDefined();
149
155
  expect(serverClient.search).toBeDefined();
150
-
156
+
151
157
  // Verify methods exist
152
158
  expect(typeof serverClient.product.getById).toBe('function');
153
159
  expect(typeof serverClient.product.getBySlug).toBe('function');
@@ -157,4 +163,4 @@ describe('TRPC Transparent Client Core Functionality', () => {
157
163
  });
158
164
 
159
165
  // Export for potential use in other tests
160
- export { serverClient, router };
166
+ export { serverClient, router };
@@ -5,6 +5,8 @@
5
5
  "sourceMap": true,
6
6
  "declaration": false,
7
7
  "moduleResolution": "node",
8
+ "emitDecoratorMetadata": true,
9
+ "experimentalDecorators": true,
8
10
  "importHelpers": true,
9
11
  "target": "es2015",
10
12
  "module": "esnext",
@@ -1,144 +0,0 @@
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
- }