@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.
- package/.env-template +10 -0
- package/.github/workflows/pull-request.yml +5 -3
- package/core/package.json +1 -2
- package/core/src/client/client.ts +4 -2
- package/core/src/index.ts +3 -9
- package/core/src/providers/analytics.provider.ts +6 -1
- package/core/src/providers/base.provider.ts +33 -3
- package/core/src/providers/cart.provider.ts +7 -1
- package/core/src/providers/category.provider.ts +91 -0
- package/core/src/providers/identity.provider.ts +5 -1
- package/core/src/providers/inventory.provider.ts +4 -0
- package/core/src/providers/price.provider.ts +54 -0
- package/core/src/providers/product.provider.ts +4 -0
- package/core/src/providers/search.provider.ts +6 -0
- package/core/src/schemas/capabilities.schema.ts +3 -2
- package/core/src/schemas/models/base.model.ts +42 -1
- package/core/src/schemas/models/cart.model.ts +27 -3
- package/core/src/schemas/models/category.model.ts +23 -0
- package/core/src/schemas/models/identifiers.model.ts +29 -1
- package/core/src/schemas/models/inventory.model.ts +6 -2
- package/core/src/schemas/models/price.model.ts +11 -3
- package/core/src/schemas/models/search.model.ts +4 -2
- package/core/src/schemas/queries/category.query.ts +32 -0
- package/core/src/schemas/queries/index.ts +9 -0
- package/core/src/schemas/queries/inventory.query.ts +18 -3
- package/core/src/schemas/session.schema.ts +13 -2
- package/examples/next/.swcrc +30 -0
- package/examples/next/eslint.config.mjs +21 -0
- package/examples/next/index.d.ts +6 -0
- package/examples/next/next-env.d.ts +5 -0
- package/examples/next/next.config.js +20 -0
- package/examples/next/project.json +9 -0
- package/examples/next/public/.gitkeep +0 -0
- package/examples/next/public/favicon.ico +0 -0
- package/examples/next/src/app/global.css +0 -0
- package/examples/next/src/app/layout.tsx +18 -0
- package/examples/next/src/app/page.module.scss +2 -0
- package/examples/next/src/app/page.tsx +51 -0
- package/examples/next/src/instrumentation.ts +9 -0
- package/examples/next/tsconfig.json +44 -0
- package/examples/node/src/basic/basic-node-provider-model-extension.spec.ts +0 -1
- package/examples/node/src/basic/basic-node-setup.spec.ts +0 -1
- package/otel/README.md +152 -172
- package/otel/package.json +0 -1
- package/otel/src/index.ts +15 -5
- package/otel/src/metrics.ts +3 -3
- package/otel/src/test/otel.spec.ts +8 -0
- package/otel/src/trace-decorator.ts +87 -108
- package/otel/src/tracer.ts +3 -3
- package/package.json +2 -1
- package/providers/commercetools/package.json +1 -0
- package/providers/commercetools/src/core/initialize.ts +7 -3
- package/providers/commercetools/src/providers/cart.provider.ts +84 -8
- package/providers/commercetools/src/providers/category.provider.ts +244 -0
- package/providers/commercetools/src/providers/index.ts +7 -0
- package/providers/commercetools/src/providers/inventory.provider.ts +31 -14
- package/providers/commercetools/src/providers/price.provider.ts +74 -18
- package/providers/commercetools/src/providers/product.provider.ts +19 -15
- package/providers/commercetools/src/providers/search.provider.ts +9 -7
- package/providers/commercetools/src/schema/capabilities.schema.ts +2 -1
- package/providers/commercetools/src/schema/configuration.schema.ts +1 -1
- package/providers/commercetools/src/test/cart.provider.spec.ts +119 -0
- package/providers/commercetools/src/test/category.provider.spec.ts +180 -0
- package/providers/commercetools/src/test/price.provider.spec.ts +80 -0
- package/providers/commercetools/src/test/product.provider.spec.ts +29 -14
- package/providers/commercetools/src/test/search.provider.spec.ts +51 -9
- package/providers/commercetools/src/test/test-utils.ts +35 -0
- package/providers/commercetools/tsconfig.lib.json +1 -1
- package/providers/fake/jest.config.ts +10 -0
- package/providers/fake/src/core/initialize.ts +15 -1
- package/providers/fake/src/index.ts +2 -9
- package/providers/fake/src/providers/cart.provider.ts +74 -15
- package/providers/fake/src/providers/category.provider.ts +152 -0
- package/providers/fake/src/providers/index.ts +8 -0
- package/providers/fake/src/providers/inventory.provider.ts +23 -9
- package/providers/fake/src/providers/price.provider.ts +46 -6
- package/providers/fake/src/providers/search.provider.ts +13 -4
- package/providers/fake/src/schema/capabilities.schema.ts +4 -2
- package/providers/fake/src/schema/configuration.schema.ts +5 -0
- package/providers/fake/src/test/cart.provider.spec.ts +126 -0
- package/providers/fake/src/test/category.provider.spec.ts +134 -0
- package/providers/fake/src/test/price.provider.spec.ts +80 -0
- package/providers/fake/src/test/test-utils.ts +42 -0
- package/providers/fake/tsconfig.json +4 -0
- package/providers/fake/tsconfig.lib.json +3 -1
- package/providers/fake/tsconfig.spec.json +16 -0
- package/trpc/package.json +1 -2
- package/trpc/src/client.ts +1 -3
- package/trpc/src/integration.spec.ts +16 -10
- package/trpc/src/transparent-client.spec.ts +23 -17
- package/tsconfig.base.json +2 -0
- package/core/src/decorators/trpc.decorators.ts +0 -144
- 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
|
+
}
|
|
@@ -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
package/trpc/src/client.ts
CHANGED
|
@@ -1,7 +1,5 @@
|
|
|
1
|
-
import { Client, Session
|
|
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 };
|
package/tsconfig.base.json
CHANGED
|
@@ -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
|
-
}
|