@reactionary/source 0.0.31 → 0.0.32
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/core/package.json +1 -1
- package/core/src/client/client-builder.ts +63 -0
- package/core/src/decorators/trpc.decorators.ts +144 -0
- package/core/src/index.ts +3 -0
- package/core/src/providers/analytics.provider.ts +2 -28
- package/core/src/providers/base.provider.ts +7 -135
- package/core/src/providers/cart.provider.ts +9 -22
- package/core/src/providers/identity.provider.ts +7 -21
- package/core/src/providers/inventory.provider.ts +4 -18
- package/core/src/providers/price.provider.ts +5 -22
- package/core/src/providers/product.provider.ts +5 -28
- package/core/src/providers/search.provider.ts +5 -22
- package/core/src/schemas/mutations/base.mutation.ts +0 -1
- package/core/src/schemas/mutations/cart.mutation.ts +0 -6
- package/core/src/schemas/mutations/identity.mutation.ts +0 -5
- package/core/src/schemas/queries/base.query.ts +0 -1
- package/core/src/schemas/queries/cart.query.ts +1 -3
- package/core/src/schemas/queries/identity.query.ts +1 -3
- package/core/src/schemas/queries/inventory.query.ts +0 -1
- package/core/src/schemas/queries/price.query.ts +0 -3
- package/core/src/schemas/queries/product.query.ts +2 -7
- package/core/src/schemas/queries/search.query.ts +0 -3
- package/examples/node/package.json +1 -5
- package/examples/node/src/basic/basic-node-provider-model-extension.spec.ts +97 -0
- package/examples/node/src/basic/basic-node-provider-query-extension.spec.ts +84 -0
- package/examples/node/src/basic/basic-node-setup.spec.ts +40 -0
- package/otel/src/index.ts +3 -0
- package/otel/src/trace-decorator.ts +246 -0
- package/package.json +2 -1
- package/providers/algolia/src/core/initialize.ts +3 -3
- package/providers/algolia/src/providers/product.provider.ts +44 -11
- package/providers/algolia/src/providers/search.provider.ts +47 -66
- package/providers/commercetools/src/core/client.ts +0 -1
- package/providers/commercetools/src/core/initialize.ts +8 -9
- package/providers/commercetools/src/providers/cart.provider.ts +58 -90
- package/providers/commercetools/src/providers/identity.provider.ts +34 -51
- package/providers/commercetools/src/providers/inventory.provider.ts +16 -29
- package/providers/commercetools/src/providers/price.provider.ts +30 -35
- package/providers/commercetools/src/providers/product.provider.ts +48 -38
- package/providers/commercetools/src/providers/search.provider.ts +32 -47
- package/providers/commercetools/src/schema/capabilities.schema.ts +1 -1
- package/providers/fake/package.json +1 -0
- package/providers/fake/src/core/initialize.ts +12 -11
- package/providers/fake/src/index.ts +4 -0
- package/providers/fake/src/providers/analytics.provider.ts +19 -0
- package/providers/fake/src/providers/cart.provider.ts +107 -0
- package/providers/fake/src/providers/identity.provider.ts +78 -68
- package/providers/fake/src/providers/inventory.provider.ts +54 -0
- package/providers/fake/src/providers/price.provider.ts +60 -0
- package/providers/fake/src/providers/product.provider.ts +53 -49
- package/providers/fake/src/providers/search.provider.ts +15 -33
- package/trpc/__mocks__/superjson.js +25 -0
- package/trpc/jest.config.ts +14 -0
- package/trpc/package.json +2 -1
- package/trpc/src/client.ts +176 -0
- package/trpc/src/index.ts +35 -62
- package/trpc/src/integration.spec.ts +216 -0
- package/trpc/src/server.ts +123 -0
- package/trpc/src/transparent-client.spec.ts +160 -0
- package/trpc/src/types.ts +142 -0
- package/trpc/tsconfig.json +3 -0
- package/trpc/tsconfig.lib.json +2 -1
- package/trpc/tsconfig.spec.json +15 -0
- package/tsconfig.base.json +0 -2
- package/examples/angular/e2e/example.spec.ts +0 -9
- package/examples/angular/eslint.config.mjs +0 -41
- package/examples/angular/playwright.config.ts +0 -38
- package/examples/angular/project.json +0 -86
- package/examples/angular/public/favicon.ico +0 -0
- package/examples/angular/src/app/app.component.html +0 -6
- package/examples/angular/src/app/app.component.scss +0 -22
- package/examples/angular/src/app/app.component.ts +0 -14
- package/examples/angular/src/app/app.config.ts +0 -16
- package/examples/angular/src/app/app.routes.ts +0 -25
- package/examples/angular/src/app/cart/cart.component.html +0 -4
- package/examples/angular/src/app/cart/cart.component.scss +0 -14
- package/examples/angular/src/app/cart/cart.component.ts +0 -73
- package/examples/angular/src/app/identity/identity.component.html +0 -6
- package/examples/angular/src/app/identity/identity.component.scss +0 -18
- package/examples/angular/src/app/identity/identity.component.ts +0 -49
- package/examples/angular/src/app/product/product.component.html +0 -14
- package/examples/angular/src/app/product/product.component.scss +0 -11
- package/examples/angular/src/app/product/product.component.ts +0 -42
- package/examples/angular/src/app/search/search.component.html +0 -35
- package/examples/angular/src/app/search/search.component.scss +0 -129
- package/examples/angular/src/app/search/search.component.ts +0 -50
- package/examples/angular/src/app/services/product.service.ts +0 -35
- package/examples/angular/src/app/services/search.service.ts +0 -48
- package/examples/angular/src/app/services/trpc.client.ts +0 -27
- package/examples/angular/src/index.html +0 -13
- package/examples/angular/src/main.ts +0 -7
- package/examples/angular/src/styles.scss +0 -17
- package/examples/angular/src/test-setup.ts +0 -6
- package/examples/angular/tsconfig.app.json +0 -10
- package/examples/angular/tsconfig.editor.json +0 -6
- package/examples/angular/tsconfig.json +0 -32
- package/examples/node/src/initialize-algolia.spec.ts +0 -29
- package/examples/node/src/initialize-commercetools.spec.ts +0 -31
- package/examples/node/src/initialize-extended-providers.spec.ts +0 -38
- package/examples/node/src/initialize-mixed-providers.spec.ts +0 -36
- package/examples/node/src/providers/custom-algolia-product.provider.ts +0 -18
- package/examples/node/src/schemas/custom-product.schema.ts +0 -8
- package/examples/trpc-node/.env.example +0 -52
- package/examples/trpc-node/eslint.config.mjs +0 -3
- package/examples/trpc-node/project.json +0 -61
- package/examples/trpc-node/src/assets/.gitkeep +0 -0
- package/examples/trpc-node/src/main.ts +0 -59
- package/examples/trpc-node/src/router-instance.ts +0 -54
- package/examples/trpc-node/tsconfig.app.json +0 -9
- package/examples/trpc-node/tsconfig.json +0 -13
|
@@ -3,28 +3,22 @@ import { BaseMutationSchema } from './base.mutation';
|
|
|
3
3
|
import { CartIdentifierSchema, CartItemIdentifierSchema, ProductIdentifierSchema } from '../models/identifiers.model';
|
|
4
4
|
|
|
5
5
|
export const CartMutationItemAddSchema = BaseMutationSchema.extend({
|
|
6
|
-
mutation: z.literal('add'),
|
|
7
6
|
cart: CartIdentifierSchema.required(),
|
|
8
7
|
product: ProductIdentifierSchema.required(),
|
|
9
8
|
quantity: z.number()
|
|
10
9
|
});
|
|
11
10
|
|
|
12
11
|
export const CartMutationItemRemoveSchema = BaseMutationSchema.extend({
|
|
13
|
-
mutation: z.literal('remove'),
|
|
14
12
|
cart: CartIdentifierSchema.required(),
|
|
15
13
|
item: CartItemIdentifierSchema.required()
|
|
16
14
|
});
|
|
17
15
|
|
|
18
16
|
export const CartMutationItemQuantityChangeSchema = BaseMutationSchema.extend({
|
|
19
|
-
mutation: z.literal('adjustQuantity'),
|
|
20
17
|
cart: CartIdentifierSchema.required(),
|
|
21
18
|
item: CartItemIdentifierSchema.required(),
|
|
22
19
|
quantity: z.number()
|
|
23
20
|
});
|
|
24
21
|
|
|
25
|
-
export const CartMutationSchema = z.union([CartMutationItemAddSchema, CartMutationItemRemoveSchema, CartMutationItemQuantityChangeSchema]);
|
|
26
|
-
|
|
27
|
-
export type CartMutation = z.infer<typeof CartMutationSchema>;
|
|
28
22
|
export type CartMutationItemAdd = z.infer<typeof CartMutationItemAddSchema>;
|
|
29
23
|
export type CartMutationItemRemove = z.infer<typeof CartMutationItemRemoveSchema>;
|
|
30
24
|
export type CartMutationItemQuantityChange = z.infer<typeof CartMutationItemQuantityChangeSchema>;
|
|
@@ -2,17 +2,12 @@ import { z } from 'zod';
|
|
|
2
2
|
import { BaseMutationSchema } from './base.mutation';
|
|
3
3
|
|
|
4
4
|
export const IdentityMutationLoginSchema = BaseMutationSchema.extend({
|
|
5
|
-
mutation: z.literal('login'),
|
|
6
5
|
username: z.string(),
|
|
7
6
|
password: z.string()
|
|
8
7
|
});
|
|
9
8
|
|
|
10
9
|
export const IdentityMutationLogoutSchema = BaseMutationSchema.extend({
|
|
11
|
-
mutation: z.literal('logout')
|
|
12
10
|
});
|
|
13
11
|
|
|
14
|
-
export const IdentityMutationSchema = z.union([IdentityMutationLoginSchema, IdentityMutationLogoutSchema]);
|
|
15
|
-
|
|
16
|
-
export type IdentityMutation = z.infer<typeof IdentityMutationSchema>;
|
|
17
12
|
export type IdentityMutationLogin = z.infer<typeof IdentityMutationLoginSchema>;
|
|
18
13
|
export type IdentityMutationLogout = z.infer<typeof IdentityMutationLogoutSchema>;
|
|
@@ -3,10 +3,8 @@ import { BaseQuerySchema } from './base.query';
|
|
|
3
3
|
import { CartIdentifierSchema } from '../models/identifiers.model';
|
|
4
4
|
|
|
5
5
|
export const CartQueryByIdSchema = BaseQuerySchema.extend({
|
|
6
|
-
query: z.literal('id'),
|
|
7
6
|
cart: CartIdentifierSchema.required()
|
|
8
7
|
});
|
|
9
8
|
export const CartQuerySchema = z.union([CartQueryByIdSchema]);
|
|
10
9
|
|
|
11
|
-
export type CartQueryById = z.infer<typeof CartQueryByIdSchema>;
|
|
12
|
-
export type CartQuery = z.infer<typeof CartQuerySchema>;
|
|
10
|
+
export type CartQueryById = z.infer<typeof CartQueryByIdSchema>;
|
|
@@ -2,9 +2,7 @@ import { z } from 'zod';
|
|
|
2
2
|
import { BaseQuerySchema } from './base.query';
|
|
3
3
|
|
|
4
4
|
export const IdentityQuerySelfSchema = BaseQuerySchema.extend({
|
|
5
|
-
|
|
5
|
+
|
|
6
6
|
});
|
|
7
|
-
export const IdentityQuerySchema = z.union([IdentityQuerySelfSchema]);
|
|
8
7
|
|
|
9
|
-
export type IdentityQuery = z.infer<typeof IdentityQuerySchema>;
|
|
10
8
|
export type IdentityQuerySelf = z.infer<typeof IdentityQuerySelfSchema>;
|
|
@@ -3,10 +3,7 @@ import { BaseQuerySchema } from './base.query';
|
|
|
3
3
|
import { SKUIdentifierSchema } from '../models/identifiers.model';
|
|
4
4
|
|
|
5
5
|
export const PriceQueryBySkuSchema = BaseQuerySchema.extend({
|
|
6
|
-
query: z.literal('sku'),
|
|
7
6
|
sku: SKUIdentifierSchema.required(),
|
|
8
7
|
});
|
|
9
|
-
export const PriceQuerySchema = z.union([PriceQueryBySkuSchema]);
|
|
10
8
|
|
|
11
9
|
export type PriceQueryBySku = z.infer<typeof PriceQueryBySkuSchema>;
|
|
12
|
-
export type PriceQuery = z.infer<typeof PriceQuerySchema>;
|
|
@@ -2,17 +2,12 @@ import { z } from 'zod';
|
|
|
2
2
|
import { BaseQuerySchema } from './base.query';
|
|
3
3
|
|
|
4
4
|
export const ProductQueryBySlugSchema = BaseQuerySchema.extend({
|
|
5
|
-
query: z.literal('slug'),
|
|
6
5
|
slug: z.string()
|
|
7
6
|
});
|
|
8
7
|
|
|
9
8
|
export const ProductQueryByIdSchema = BaseQuerySchema.extend({
|
|
10
|
-
|
|
11
|
-
id: z.string()
|
|
9
|
+
id: z.string()
|
|
12
10
|
});
|
|
13
11
|
|
|
14
|
-
export const ProductQuerySchema = z.union([ProductQueryBySlugSchema, ProductQueryByIdSchema]);
|
|
15
|
-
|
|
16
12
|
export type ProductQueryBySlug = z.infer<typeof ProductQueryBySlugSchema>;
|
|
17
|
-
export type ProductQueryById = z.infer<typeof ProductQueryByIdSchema>;
|
|
18
|
-
export type ProductQuery = z.infer<typeof ProductQuerySchema>;
|
|
13
|
+
export type ProductQueryById = z.infer<typeof ProductQueryByIdSchema>;
|
|
@@ -3,10 +3,7 @@ import { BaseQuerySchema } from './base.query';
|
|
|
3
3
|
import { SearchIdentifierSchema } from '../models/identifiers.model';
|
|
4
4
|
|
|
5
5
|
export const SearchQueryByTermSchema = BaseQuerySchema.extend({
|
|
6
|
-
query: z.literal('term'),
|
|
7
6
|
search: SearchIdentifierSchema.required()
|
|
8
7
|
});
|
|
9
|
-
export const SearchQuerySchema = z.union([SearchQueryByTermSchema]);
|
|
10
8
|
|
|
11
|
-
export type SearchQuery = z.infer<typeof SearchQuerySchema>;
|
|
12
9
|
export type SearchQueryByTerm = z.infer<typeof SearchQueryByTermSchema>;
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ClientBuilder,
|
|
3
|
+
Cache,
|
|
4
|
+
NoOpCache,
|
|
5
|
+
ProductSchema,
|
|
6
|
+
SessionSchema,
|
|
7
|
+
ProductQueryById,
|
|
8
|
+
ProductQueryBySlug,
|
|
9
|
+
} from '@reactionary/core';
|
|
10
|
+
import {
|
|
11
|
+
FakeProductProvider,
|
|
12
|
+
withFakeCapabilities,
|
|
13
|
+
} from '@reactionary/provider-fake';
|
|
14
|
+
import z from 'zod';
|
|
15
|
+
|
|
16
|
+
describe('basic node provider extension (models)', () => {
|
|
17
|
+
const session = SessionSchema.parse({
|
|
18
|
+
id: '1234567890',
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
const ExtendedProductModel = ProductSchema.extend({
|
|
22
|
+
gtin: z.string().default('gtin-default'),
|
|
23
|
+
});
|
|
24
|
+
type ExtendedProduct = z.infer<typeof ExtendedProductModel>;
|
|
25
|
+
|
|
26
|
+
class ExtendedProductProvider extends FakeProductProvider<ExtendedProduct> {
|
|
27
|
+
protected override parseSingle(body: ProductQueryById | ProductQueryBySlug): ExtendedProduct {
|
|
28
|
+
const model = super.parseSingle(body);
|
|
29
|
+
|
|
30
|
+
if (body.id) {
|
|
31
|
+
model.gtin = 'gtin-1234';
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return this.assert(model);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function withExtendedCapabilities() {
|
|
39
|
+
return (cache: Cache) => {
|
|
40
|
+
const client = {
|
|
41
|
+
product: new ExtendedProductProvider(
|
|
42
|
+
{ jitter: { mean: 0, deviation: 0 } },
|
|
43
|
+
ExtendedProductModel,
|
|
44
|
+
cache
|
|
45
|
+
),
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
return client;
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const client = new ClientBuilder()
|
|
53
|
+
.withCapability(
|
|
54
|
+
withFakeCapabilities(
|
|
55
|
+
{
|
|
56
|
+
jitter: {
|
|
57
|
+
mean: 0,
|
|
58
|
+
deviation: 0,
|
|
59
|
+
},
|
|
60
|
+
},
|
|
61
|
+
{ search: true, product: false, identity: false }
|
|
62
|
+
)
|
|
63
|
+
)
|
|
64
|
+
.withCapability(withExtendedCapabilities())
|
|
65
|
+
.withCache(new NoOpCache())
|
|
66
|
+
.build();
|
|
67
|
+
|
|
68
|
+
it('should get the enabled set of capabilities across providers', async () => {
|
|
69
|
+
expect(client.product).toBeDefined();
|
|
70
|
+
expect(client.search).toBeDefined();
|
|
71
|
+
expect(client.identity).toBeUndefined();
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it('should be able to call the regular methods and get the default value', async () => {
|
|
75
|
+
const product = await client.product.getBySlug(
|
|
76
|
+
{
|
|
77
|
+
slug: '1234',
|
|
78
|
+
},
|
|
79
|
+
session
|
|
80
|
+
);
|
|
81
|
+
|
|
82
|
+
expect(product).toBeDefined();
|
|
83
|
+
expect(product.gtin).toBe('gtin-default');
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it('should be able to get serialized value from the extended provider', async () => {
|
|
87
|
+
const product = await client.product.getById(
|
|
88
|
+
{
|
|
89
|
+
id: '1234',
|
|
90
|
+
},
|
|
91
|
+
session
|
|
92
|
+
);
|
|
93
|
+
|
|
94
|
+
expect(product).toBeDefined();
|
|
95
|
+
expect(product.gtin).toBe('gtin-1234');
|
|
96
|
+
});
|
|
97
|
+
});
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ClientBuilder,
|
|
3
|
+
Cache,
|
|
4
|
+
NoOpCache,
|
|
5
|
+
ProductSchema,
|
|
6
|
+
Product,
|
|
7
|
+
} from '@reactionary/core';
|
|
8
|
+
import {
|
|
9
|
+
FakeProductProvider,
|
|
10
|
+
withFakeCapabilities,
|
|
11
|
+
} from '@reactionary/provider-fake';
|
|
12
|
+
import z from 'zod';
|
|
13
|
+
|
|
14
|
+
describe('basic node provider extension (models)', () => {
|
|
15
|
+
const ExtendedProductModel = ProductSchema.extend({
|
|
16
|
+
gtin: z.string().default('gtin-default'),
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
class ExtendedProductProvider extends FakeProductProvider {
|
|
20
|
+
|
|
21
|
+
public async getByCustom(_custom: string): Promise<Product> {
|
|
22
|
+
// Lets say we called somewhere...
|
|
23
|
+
const data = {
|
|
24
|
+
id: 'foo',
|
|
25
|
+
name: 'bar'
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
const model = this.parseItem(data);
|
|
29
|
+
|
|
30
|
+
return model;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
protected parseItem(data: unknown): Product {
|
|
34
|
+
// In the real world, call super
|
|
35
|
+
// super.parseItem(data);
|
|
36
|
+
// Which would start by doing
|
|
37
|
+
const item = this.newModel();
|
|
38
|
+
|
|
39
|
+
if (data) {
|
|
40
|
+
item.name = (data as { name: string }).name;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
return this.assert(item);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function withExtendedCapabilities() {
|
|
49
|
+
return (cache: Cache) => {
|
|
50
|
+
const client = {
|
|
51
|
+
product: new ExtendedProductProvider(
|
|
52
|
+
{ jitter: { mean: 0, deviation: 0 } },
|
|
53
|
+
ExtendedProductModel,
|
|
54
|
+
cache
|
|
55
|
+
),
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
return client;
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const client = new ClientBuilder()
|
|
63
|
+
.withCapability(
|
|
64
|
+
withFakeCapabilities(
|
|
65
|
+
{
|
|
66
|
+
jitter: {
|
|
67
|
+
mean: 0,
|
|
68
|
+
deviation: 0,
|
|
69
|
+
},
|
|
70
|
+
},
|
|
71
|
+
{ search: true, product: false, identity: false }
|
|
72
|
+
)
|
|
73
|
+
)
|
|
74
|
+
.withCapability(withExtendedCapabilities())
|
|
75
|
+
.withCache(new NoOpCache())
|
|
76
|
+
.build();
|
|
77
|
+
|
|
78
|
+
it('should be able to call a custom query method on the provider', async () => {
|
|
79
|
+
const product = await client.product.getByCustom('1234');
|
|
80
|
+
|
|
81
|
+
expect(product.name).toBe('bar');
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
});
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { buildClient, NoOpCache, SessionSchema } from '@reactionary/core';
|
|
2
|
+
import { withFakeCapabilities } from '@reactionary/provider-fake';
|
|
3
|
+
|
|
4
|
+
describe('basic node setup', () => {
|
|
5
|
+
const client = buildClient(
|
|
6
|
+
[
|
|
7
|
+
withFakeCapabilities(
|
|
8
|
+
{
|
|
9
|
+
jitter: {
|
|
10
|
+
mean: 0,
|
|
11
|
+
deviation: 0,
|
|
12
|
+
},
|
|
13
|
+
},
|
|
14
|
+
{ search: true, product: true, identity: false }
|
|
15
|
+
),
|
|
16
|
+
],
|
|
17
|
+
{
|
|
18
|
+
cache: new NoOpCache(),
|
|
19
|
+
}
|
|
20
|
+
);
|
|
21
|
+
|
|
22
|
+
const session = SessionSchema.parse({
|
|
23
|
+
id: '1234567890'
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it('should only get back the enabled capabilities', async () => {
|
|
27
|
+
expect(client.product).toBeDefined();
|
|
28
|
+
expect(client.search).toBeDefined();
|
|
29
|
+
expect(client.identity).toBeUndefined();
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it('should be able to call the enabled capabilities', async () => {
|
|
33
|
+
const product = await client.product.getBySlug({
|
|
34
|
+
slug: '1234'
|
|
35
|
+
}, session);
|
|
36
|
+
|
|
37
|
+
expect(product).toBeDefined();
|
|
38
|
+
expect(product.slug).toBe('1234');
|
|
39
|
+
});
|
|
40
|
+
});
|
package/otel/src/index.ts
CHANGED
|
@@ -5,5 +5,8 @@
|
|
|
5
5
|
export { createTRPCTracing } from './trpc-middleware';
|
|
6
6
|
export { createProviderInstrumentation } from './provider-instrumentation';
|
|
7
7
|
|
|
8
|
+
// Decorator for tracing functions
|
|
9
|
+
export { traced, TracedOptions } from './trace-decorator';
|
|
10
|
+
|
|
8
11
|
// Graceful shutdown for process termination
|
|
9
12
|
export { shutdownOtel } from './sdk';
|
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
import { SpanKind, SpanStatusCode } from '@opentelemetry/api';
|
|
2
|
+
import { getTracer } from './tracer';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Options for the @traced decorator
|
|
6
|
+
*/
|
|
7
|
+
export interface TracedOptions {
|
|
8
|
+
/** Whether to capture function arguments as span attributes (default: true) */
|
|
9
|
+
captureArgs?: boolean;
|
|
10
|
+
/** Whether to capture the return value as a span attribute (default: true) */
|
|
11
|
+
captureResult?: boolean;
|
|
12
|
+
/** Custom span name to use instead of the function name */
|
|
13
|
+
spanName?: string;
|
|
14
|
+
/** OpenTelemetry SpanKind (default: INTERNAL) */
|
|
15
|
+
spanKind?: SpanKind;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Safely serializes a value for use as a span attribute
|
|
20
|
+
* Handles circular references and large objects
|
|
21
|
+
*/
|
|
22
|
+
function safeSerialize(value: unknown, maxDepth = 3, currentDepth = 0): string {
|
|
23
|
+
if (currentDepth >= maxDepth) {
|
|
24
|
+
return '[Max depth reached]';
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
if (value === null) return 'null';
|
|
28
|
+
if (value === undefined) return 'undefined';
|
|
29
|
+
|
|
30
|
+
const type = typeof value;
|
|
31
|
+
|
|
32
|
+
if (type === 'string' || type === 'number' || type === 'boolean') {
|
|
33
|
+
return String(value);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if (type === 'function') {
|
|
37
|
+
return `[Function: ${(value as { name?: string }).name || 'anonymous'}]`;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
if (value instanceof Date) {
|
|
41
|
+
return value.toISOString();
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (value instanceof Error) {
|
|
45
|
+
return `[Error: ${value.message}]`;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (Array.isArray(value)) {
|
|
49
|
+
if (value.length > 10) {
|
|
50
|
+
return `[Array(${value.length})]`;
|
|
51
|
+
}
|
|
52
|
+
try {
|
|
53
|
+
return JSON.stringify(value.map(item =>
|
|
54
|
+
safeSerialize(item, maxDepth, currentDepth + 1)
|
|
55
|
+
));
|
|
56
|
+
} catch {
|
|
57
|
+
return '[Array - circular reference]';
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (type === 'object') {
|
|
62
|
+
try {
|
|
63
|
+
const keys = Object.keys(value as object);
|
|
64
|
+
if (keys.length > 20) {
|
|
65
|
+
return `[Object with ${keys.length} keys]`;
|
|
66
|
+
}
|
|
67
|
+
const simplified: Record<string, unknown> = {};
|
|
68
|
+
for (const key of keys.slice(0, 10)) {
|
|
69
|
+
simplified[key] = safeSerialize(
|
|
70
|
+
(value as Record<string, unknown>)[key],
|
|
71
|
+
maxDepth,
|
|
72
|
+
currentDepth + 1
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
return JSON.stringify(simplified);
|
|
76
|
+
} catch {
|
|
77
|
+
return '[Object - circular reference]';
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return String(value);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* TypeScript decorator for tracing function execution
|
|
86
|
+
* Automatically creates OpenTelemetry spans for decorated methods
|
|
87
|
+
* Supports both Stage 2 (legacy) and Stage 3 decorator syntax
|
|
88
|
+
*
|
|
89
|
+
* @example
|
|
90
|
+
* ```typescript
|
|
91
|
+
* class MyService {
|
|
92
|
+
* @traced()
|
|
93
|
+
* async fetchData(id: string): Promise<Data> {
|
|
94
|
+
* // method implementation
|
|
95
|
+
* }
|
|
96
|
+
*
|
|
97
|
+
* @traced({ spanName: 'custom-operation', captureResult: false })
|
|
98
|
+
* processData(data: Data): void {
|
|
99
|
+
* // method implementation
|
|
100
|
+
* }
|
|
101
|
+
* }
|
|
102
|
+
* ```
|
|
103
|
+
*/
|
|
104
|
+
export function traced(options: TracedOptions = {}): any {
|
|
105
|
+
const {
|
|
106
|
+
captureArgs = true,
|
|
107
|
+
captureResult = true,
|
|
108
|
+
spanName,
|
|
109
|
+
spanKind = SpanKind.INTERNAL
|
|
110
|
+
} = options;
|
|
111
|
+
|
|
112
|
+
// Stage 2 (legacy) decorator
|
|
113
|
+
return function (
|
|
114
|
+
target: any,
|
|
115
|
+
propertyKey?: string | symbol,
|
|
116
|
+
descriptor?: PropertyDescriptor
|
|
117
|
+
): any {
|
|
118
|
+
// Handle Stage 3 decorator (when called with context)
|
|
119
|
+
if (typeof propertyKey === 'object' && propertyKey && 'kind' in propertyKey) {
|
|
120
|
+
const context = propertyKey as any;
|
|
121
|
+
const originalMethod = target;
|
|
122
|
+
const methodName = String(context.name);
|
|
123
|
+
|
|
124
|
+
return createTracedMethod(originalMethod, methodName, {
|
|
125
|
+
captureArgs,
|
|
126
|
+
captureResult,
|
|
127
|
+
spanName,
|
|
128
|
+
spanKind
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Handle Stage 2 decorator
|
|
133
|
+
if (descriptor && typeof descriptor.value === 'function') {
|
|
134
|
+
const originalMethod = descriptor.value;
|
|
135
|
+
const methodName = String(propertyKey);
|
|
136
|
+
|
|
137
|
+
descriptor.value = createTracedMethod(originalMethod, methodName, {
|
|
138
|
+
captureArgs,
|
|
139
|
+
captureResult,
|
|
140
|
+
spanName,
|
|
141
|
+
spanKind
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
return descriptor;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
return target;
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function createTracedMethod(
|
|
152
|
+
originalMethod: (...args: any[]) => any,
|
|
153
|
+
methodName: string,
|
|
154
|
+
options: {
|
|
155
|
+
captureArgs: boolean;
|
|
156
|
+
captureResult: boolean;
|
|
157
|
+
spanName?: string;
|
|
158
|
+
spanKind: SpanKind;
|
|
159
|
+
}
|
|
160
|
+
): any {
|
|
161
|
+
const { captureArgs, captureResult, spanName, spanKind } = options;
|
|
162
|
+
|
|
163
|
+
function tracedMethod(this: any, ...args: any[]): any {
|
|
164
|
+
const tracer = getTracer();
|
|
165
|
+
const className = this?.constructor?.name || 'Unknown';
|
|
166
|
+
const effectiveSpanName = spanName || `${className}.${methodName}`;
|
|
167
|
+
|
|
168
|
+
// Start the span
|
|
169
|
+
const span = tracer.startSpan(effectiveSpanName, {
|
|
170
|
+
kind: spanKind,
|
|
171
|
+
attributes: {
|
|
172
|
+
'function.name': methodName,
|
|
173
|
+
'function.class': className,
|
|
174
|
+
}
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
// Capture arguments if enabled
|
|
178
|
+
if (captureArgs && args.length > 0) {
|
|
179
|
+
args.forEach((arg, index) => {
|
|
180
|
+
try {
|
|
181
|
+
span.setAttribute(`function.args.${index}`, safeSerialize(arg));
|
|
182
|
+
} catch {
|
|
183
|
+
span.setAttribute(`function.args.${index}`, '[Serialization error]');
|
|
184
|
+
}
|
|
185
|
+
});
|
|
186
|
+
span.setAttribute('function.args.count', args.length);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// Helper function to finalize span with result
|
|
190
|
+
const finalizeSpan = (result: unknown, isError = false) => {
|
|
191
|
+
if (!isError && captureResult && result !== undefined) {
|
|
192
|
+
try {
|
|
193
|
+
span.setAttribute('function.result', safeSerialize(result));
|
|
194
|
+
} catch {
|
|
195
|
+
span.setAttribute('function.result', '[Serialization error]');
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
if (isError) {
|
|
200
|
+
span.setStatus({
|
|
201
|
+
code: SpanStatusCode.ERROR,
|
|
202
|
+
message: result instanceof Error ? result.message : String(result)
|
|
203
|
+
});
|
|
204
|
+
if (result instanceof Error) {
|
|
205
|
+
span.recordException(result);
|
|
206
|
+
}
|
|
207
|
+
} else {
|
|
208
|
+
span.setStatus({ code: SpanStatusCode.OK });
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
span.end();
|
|
212
|
+
};
|
|
213
|
+
|
|
214
|
+
try {
|
|
215
|
+
const result = originalMethod.apply(this, args);
|
|
216
|
+
|
|
217
|
+
// Handle async functions
|
|
218
|
+
if (result instanceof Promise) {
|
|
219
|
+
return result
|
|
220
|
+
.then((value) => {
|
|
221
|
+
finalizeSpan(value);
|
|
222
|
+
return value;
|
|
223
|
+
})
|
|
224
|
+
.catch((error) => {
|
|
225
|
+
finalizeSpan(error, true);
|
|
226
|
+
throw error;
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// Handle sync functions
|
|
231
|
+
finalizeSpan(result);
|
|
232
|
+
return result;
|
|
233
|
+
} catch (error) {
|
|
234
|
+
finalizeSpan(error, true);
|
|
235
|
+
throw error;
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// Preserve the original function's name and properties
|
|
240
|
+
Object.defineProperty(tracedMethod, 'name', {
|
|
241
|
+
value: methodName,
|
|
242
|
+
configurable: true
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
return tracedMethod;
|
|
246
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@reactionary/source",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.32",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"private": false,
|
|
6
6
|
"dependencies": {
|
|
@@ -40,6 +40,7 @@
|
|
|
40
40
|
"posthog-node": "^4.18.0",
|
|
41
41
|
"react": "19.0.0",
|
|
42
42
|
"react-dom": "19.0.0",
|
|
43
|
+
"reflect-metadata": "^0.2.2",
|
|
43
44
|
"rxjs": "~7.8.0",
|
|
44
45
|
"search-insights": "^2.17.3",
|
|
45
46
|
"superjson": "^2.2.2",
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Client,
|
|
1
|
+
import { Client, ProductSchema, Cache } from "@reactionary/core";
|
|
2
2
|
import { AlgoliaProductProvider } from "../providers/product.provider";
|
|
3
3
|
import { AlgoliaSearchProvider } from "../providers/search.provider";
|
|
4
4
|
import { AlgoliaCapabilities } from "../schema/capabilities.schema";
|
|
@@ -10,11 +10,11 @@ export function withAlgoliaCapabilities(configuration: AlgoliaConfiguration, cap
|
|
|
10
10
|
const client: Partial<Client> = {};
|
|
11
11
|
|
|
12
12
|
if (capabilities.product) {
|
|
13
|
-
client.product = new AlgoliaProductProvider(configuration, ProductSchema,
|
|
13
|
+
client.product = new AlgoliaProductProvider(configuration, ProductSchema, cache);
|
|
14
14
|
}
|
|
15
15
|
|
|
16
16
|
if (capabilities.search) {
|
|
17
|
-
client.search = new AlgoliaSearchProvider(configuration, AlgoliaSearchResultSchema,
|
|
17
|
+
client.search = new AlgoliaSearchProvider(configuration, AlgoliaSearchResultSchema, cache);
|
|
18
18
|
}
|
|
19
19
|
|
|
20
20
|
return client;
|