@reactionary/source 0.0.52 → 0.2.16
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 +19 -0
- package/.github/workflows/pull-request.yml +3 -1
- package/.github/workflows/release.yml +9 -0
- package/.vscode/extensions.json +0 -2
- package/LICENSE +21 -0
- package/README.md +175 -23
- package/core/package.json +6 -3
- package/core/src/cache/cache.interface.ts +1 -0
- package/core/src/cache/index.ts +4 -0
- package/core/src/cache/memory-cache.ts +30 -2
- package/core/src/cache/noop-cache.ts +15 -1
- package/core/src/cache/redis-cache.ts +20 -0
- package/core/src/client/client-builder.ts +71 -54
- package/core/src/client/client.ts +9 -47
- package/core/src/client/index.ts +2 -0
- package/core/src/decorators/index.ts +1 -0
- package/core/src/decorators/reactionary.decorator.ts +203 -34
- package/core/src/index.ts +6 -19
- package/core/src/initialization.ts +1 -18
- package/core/src/metrics/metrics.ts +67 -0
- package/core/src/providers/analytics.provider.ts +1 -6
- package/core/src/providers/base.provider.ts +5 -69
- package/core/src/providers/cart.provider.ts +15 -55
- package/core/src/providers/category.provider.ts +7 -11
- package/core/src/providers/checkout.provider.ts +17 -15
- package/core/src/providers/identity.provider.ts +6 -8
- package/core/src/providers/index.ts +2 -1
- package/core/src/providers/inventory.provider.ts +15 -5
- package/core/src/providers/order-search.provider.ts +29 -0
- package/core/src/providers/order.provider.ts +47 -15
- package/core/src/providers/price.provider.ts +30 -36
- package/core/src/providers/product-search.provider.ts +61 -0
- package/core/src/providers/product.provider.ts +71 -12
- package/core/src/providers/profile.provider.ts +74 -14
- package/core/src/providers/store.provider.ts +3 -5
- package/core/src/schemas/capabilities.schema.ts +10 -3
- package/core/src/schemas/errors/generic.error.ts +9 -0
- package/core/src/schemas/errors/index.ts +4 -0
- package/core/src/schemas/errors/invalid-input.error.ts +9 -0
- package/core/src/schemas/errors/invalid-output.error.ts +9 -0
- package/core/src/schemas/errors/not-found.error.ts +9 -0
- package/core/src/schemas/index.ts +7 -0
- package/core/src/schemas/models/analytics.model.ts +2 -1
- package/core/src/schemas/models/base.model.ts +6 -24
- package/core/src/schemas/models/cart.model.ts +5 -8
- package/core/src/schemas/models/category.model.ts +4 -9
- package/core/src/schemas/models/checkout.model.ts +6 -7
- package/core/src/schemas/models/cost.model.ts +4 -3
- package/core/src/schemas/models/currency.model.ts +2 -1
- package/core/src/schemas/models/identifiers.model.ts +106 -62
- package/core/src/schemas/models/identity.model.ts +10 -19
- package/core/src/schemas/models/index.ts +2 -1
- package/core/src/schemas/models/inventory.model.ts +8 -5
- package/core/src/schemas/models/order-search.model.ts +28 -0
- package/core/src/schemas/models/order.model.ts +20 -26
- package/core/src/schemas/models/payment.model.ts +14 -17
- package/core/src/schemas/models/price.model.ts +11 -11
- package/core/src/schemas/models/product-search.model.ts +42 -0
- package/core/src/schemas/models/product.model.ts +64 -22
- package/core/src/schemas/models/profile.model.ts +19 -22
- package/core/src/schemas/models/shipping-method.model.ts +24 -29
- package/core/src/schemas/models/store.model.ts +9 -5
- package/core/src/schemas/mutations/analytics.mutation.ts +8 -7
- package/core/src/schemas/mutations/base.mutation.ts +2 -1
- package/core/src/schemas/mutations/cart.mutation.ts +33 -33
- package/core/src/schemas/mutations/checkout.mutation.ts +23 -30
- package/core/src/schemas/mutations/identity.mutation.ts +4 -3
- package/core/src/schemas/mutations/profile.mutation.ts +38 -3
- package/core/src/schemas/queries/base.query.ts +2 -1
- package/core/src/schemas/queries/cart.query.ts +3 -3
- package/core/src/schemas/queries/category.query.ts +18 -18
- package/core/src/schemas/queries/checkout.query.ts +7 -9
- package/core/src/schemas/queries/identity.query.ts +2 -1
- package/core/src/schemas/queries/index.ts +2 -1
- package/core/src/schemas/queries/inventory.query.ts +5 -5
- package/core/src/schemas/queries/order-search.query.ts +10 -0
- package/core/src/schemas/queries/order.query.ts +3 -2
- package/core/src/schemas/queries/price.query.ts +10 -4
- package/core/src/schemas/queries/product-search.query.ts +16 -0
- package/core/src/schemas/queries/product.query.ts +13 -6
- package/core/src/schemas/queries/profile.query.ts +5 -2
- package/core/src/schemas/queries/store.query.ts +6 -5
- package/core/src/schemas/result.ts +107 -0
- package/core/src/schemas/session.schema.ts +4 -4
- package/core/src/test/reactionary.decorator.spec.ts +249 -0
- package/core/src/zod-utils.ts +19 -0
- package/core/tsconfig.json +1 -1
- package/core/tsconfig.spec.json +2 -26
- package/core/vitest.config.ts +14 -0
- package/documentation/1-purpose.md +114 -0
- package/documentation/2-getting-started.md +229 -0
- package/documentation/3-querying-and-changing-data.md +74 -0
- package/documentation/4-product-data.md +107 -0
- package/documentation/5-cart-and-checkout.md +211 -0
- package/documentation/6-product-search.md +143 -0
- package/documentation/7-marketing.md +3 -0
- package/eslint.config.mjs +1 -0
- package/examples/node/eslint.config.mjs +1 -4
- package/examples/node/package.json +10 -3
- package/examples/node/project.json +4 -1
- package/examples/node/src/basic/basic-node-provider-model-extension.spec.ts +22 -23
- package/examples/node/src/basic/basic-node-provider-query-extension.spec.ts +15 -11
- package/examples/node/src/basic/basic-node-setup.spec.ts +44 -28
- package/examples/node/src/basic/client-creation.spec.ts +53 -0
- package/examples/node/src/capabilities/cart.spec.ts +255 -0
- package/examples/node/src/capabilities/category.spec.ts +193 -0
- package/examples/node/src/capabilities/checkout.spec.ts +341 -0
- package/examples/node/src/capabilities/identity.spec.ts +93 -0
- package/examples/node/src/capabilities/inventory.spec.ts +66 -0
- package/examples/node/src/capabilities/order-search.spec.ts +265 -0
- package/examples/node/src/capabilities/order.spec.ts +91 -0
- package/examples/node/src/capabilities/price.spec.ts +51 -0
- package/examples/node/src/capabilities/product-search.spec.ts +293 -0
- package/examples/node/src/capabilities/product.spec.ts +122 -0
- package/examples/node/src/capabilities/profile.spec.ts +316 -0
- package/examples/node/src/capabilities/store.spec.ts +26 -0
- package/examples/node/src/utils.ts +147 -0
- package/examples/node/tsconfig.json +9 -12
- package/examples/node/tsconfig.lib.json +1 -2
- package/examples/node/tsconfig.spec.json +2 -14
- package/examples/node/vitest.config.ts +14 -0
- package/migrations.json +22 -5
- package/nx.json +8 -47
- package/package.json +24 -96
- package/providers/algolia/README.md +39 -2
- package/providers/algolia/package.json +2 -1
- package/providers/algolia/src/core/initialize.ts +7 -14
- package/providers/algolia/src/index.ts +2 -4
- package/providers/algolia/src/providers/index.ts +1 -0
- package/providers/algolia/src/providers/product-search.provider.ts +241 -0
- package/providers/algolia/src/schema/capabilities.schema.ts +2 -3
- package/providers/algolia/src/schema/index.ts +3 -0
- package/providers/algolia/src/schema/search.schema.ts +8 -8
- package/providers/algolia/tsconfig.json +1 -1
- package/providers/algolia/tsconfig.lib.json +1 -1
- package/providers/algolia/tsconfig.spec.json +2 -14
- package/providers/algolia/vitest.config.ts +14 -0
- package/providers/commercetools/README.md +30 -3
- package/providers/commercetools/package.json +2 -1
- package/providers/commercetools/src/core/client.ts +178 -99
- package/providers/commercetools/src/core/initialize.ts +130 -74
- package/providers/commercetools/src/core/token-cache.ts +45 -0
- package/providers/commercetools/src/index.ts +3 -2
- package/providers/commercetools/src/providers/cart.provider.ts +281 -341
- package/providers/commercetools/src/providers/category.provider.ts +223 -138
- package/providers/commercetools/src/providers/checkout.provider.ts +631 -449
- package/providers/commercetools/src/providers/identity.provider.ts +50 -29
- package/providers/commercetools/src/providers/index.ts +2 -2
- package/providers/commercetools/src/providers/inventory.provider.ts +76 -74
- package/providers/commercetools/src/providers/order-search.provider.ts +220 -0
- package/providers/commercetools/src/providers/order.provider.ts +96 -61
- package/providers/commercetools/src/providers/price.provider.ts +147 -117
- package/providers/commercetools/src/providers/product-search.provider.ts +528 -0
- package/providers/commercetools/src/providers/product.provider.ts +249 -74
- package/providers/commercetools/src/providers/profile.provider.ts +445 -28
- package/providers/commercetools/src/providers/store.provider.ts +54 -40
- package/providers/commercetools/src/schema/capabilities.schema.ts +3 -1
- package/providers/commercetools/src/schema/commercetools.schema.ts +17 -3
- package/providers/commercetools/src/schema/configuration.schema.ts +1 -0
- package/providers/commercetools/src/schema/session.schema.ts +7 -0
- package/providers/commercetools/src/test/caching.spec.ts +82 -0
- package/providers/commercetools/src/test/identity.spec.ts +109 -0
- package/providers/commercetools/src/test/test-utils.ts +21 -19
- package/providers/commercetools/tsconfig.json +1 -1
- package/providers/commercetools/tsconfig.lib.json +1 -1
- package/providers/commercetools/tsconfig.spec.json +2 -14
- package/providers/commercetools/vitest.config.ts +15 -0
- package/providers/fake/README.md +20 -4
- package/providers/fake/package.json +2 -1
- package/providers/fake/src/core/initialize.ts +47 -49
- package/providers/fake/src/providers/analytics.provider.ts +5 -7
- package/providers/fake/src/providers/cart.provider.ts +163 -92
- package/providers/fake/src/providers/category.provider.ts +78 -50
- package/providers/fake/src/providers/checkout.provider.ts +254 -0
- package/providers/fake/src/providers/identity.provider.ts +57 -65
- package/providers/fake/src/providers/index.ts +6 -2
- package/providers/fake/src/providers/inventory.provider.ts +40 -36
- package/providers/fake/src/providers/order-search.provider.ts +78 -0
- package/providers/fake/src/providers/order.provider.ts +106 -0
- package/providers/fake/src/providers/price.provider.ts +93 -41
- package/providers/fake/src/providers/product-search.provider.ts +206 -0
- package/providers/fake/src/providers/product.provider.ts +56 -41
- package/providers/fake/src/providers/profile.provider.ts +147 -0
- package/providers/fake/src/providers/store.provider.ts +30 -20
- package/providers/fake/src/schema/capabilities.schema.ts +5 -1
- package/providers/fake/src/test/cart.provider.spec.ts +59 -80
- package/providers/fake/src/test/category.provider.spec.ts +145 -87
- package/providers/fake/src/test/checkout.provider.spec.ts +222 -0
- package/providers/fake/src/test/order-search.provider.spec.ts +50 -0
- package/providers/fake/src/test/order.provider.spec.ts +44 -0
- package/providers/fake/src/test/price.provider.spec.ts +50 -45
- package/providers/fake/src/test/product.provider.spec.ts +15 -7
- package/providers/fake/src/test/profile.provider.spec.ts +167 -0
- package/providers/fake/tsconfig.json +1 -1
- package/providers/fake/tsconfig.lib.json +1 -1
- package/providers/fake/tsconfig.spec.json +2 -12
- package/providers/fake/vitest.config.ts +14 -0
- package/providers/medusa/README.md +30 -0
- package/providers/medusa/TESTING.md +98 -0
- package/providers/medusa/eslint.config.mjs +19 -0
- package/providers/medusa/package.json +22 -0
- package/providers/medusa/project.json +34 -0
- package/providers/medusa/src/core/client.ts +370 -0
- package/providers/medusa/src/core/initialize.ts +78 -0
- package/providers/medusa/src/index.ts +13 -0
- package/providers/medusa/src/providers/cart.provider.ts +575 -0
- package/providers/medusa/src/providers/category.provider.ts +247 -0
- package/providers/medusa/src/providers/checkout.provider.ts +636 -0
- package/providers/medusa/src/providers/identity.provider.ts +137 -0
- package/providers/medusa/src/providers/inventory.provider.ts +173 -0
- package/providers/medusa/src/providers/order-search.provider.ts +202 -0
- package/providers/medusa/src/providers/order.provider.ts +226 -0
- package/providers/medusa/src/providers/price.provider.ts +140 -0
- package/providers/medusa/src/providers/product-search.provider.ts +243 -0
- package/providers/medusa/src/providers/product.provider.ts +261 -0
- package/providers/medusa/src/providers/profile.provider.ts +392 -0
- package/providers/medusa/src/schema/capabilities.schema.ts +18 -0
- package/providers/medusa/src/schema/configuration.schema.ts +11 -0
- package/providers/medusa/src/schema/medusa.schema.ts +31 -0
- package/providers/medusa/src/test/cart.provider.spec.ts +240 -0
- package/providers/medusa/src/test/category.provider.spec.ts +231 -0
- package/providers/medusa/src/test/checkout.spec.ts +349 -0
- package/providers/medusa/src/test/identity.provider.spec.ts +122 -0
- package/providers/medusa/src/test/inventory.provider.spec.ts +88 -0
- package/providers/medusa/src/test/large-cart.provider.spec.ts +103 -0
- package/providers/medusa/src/test/price.provider.spec.ts +104 -0
- package/providers/medusa/src/test/product.provider.spec.ts +146 -0
- package/providers/medusa/src/test/search.provider.spec.ts +203 -0
- package/providers/medusa/src/test/test-utils.ts +13 -0
- package/providers/medusa/src/utils/medusa-helpers.ts +89 -0
- package/providers/medusa/tsconfig.json +21 -0
- package/providers/medusa/tsconfig.lib.json +9 -0
- package/providers/medusa/tsconfig.spec.json +4 -0
- package/providers/medusa/vitest.config.ts +15 -0
- package/providers/meilisearch/README.md +48 -0
- package/providers/meilisearch/eslint.config.mjs +22 -0
- package/providers/meilisearch/package.json +13 -0
- package/providers/meilisearch/project.json +34 -0
- package/providers/meilisearch/src/core/initialize.ts +21 -0
- package/providers/meilisearch/src/index.ts +6 -0
- package/providers/meilisearch/src/providers/index.ts +1 -0
- package/providers/meilisearch/src/providers/order-search.provider.ts +222 -0
- package/providers/meilisearch/src/providers/product-search.provider.ts +251 -0
- package/providers/meilisearch/src/schema/capabilities.schema.ts +10 -0
- package/providers/meilisearch/src/schema/configuration.schema.ts +11 -0
- package/providers/meilisearch/src/schema/index.ts +3 -0
- package/providers/meilisearch/src/schema/search.schema.ts +14 -0
- package/providers/meilisearch/tsconfig.json +24 -0
- package/providers/meilisearch/tsconfig.lib.json +10 -0
- package/providers/meilisearch/tsconfig.spec.json +4 -0
- package/providers/meilisearch/vitest.config.ts +14 -0
- package/providers/posthog/package.json +2 -1
- package/providers/posthog/tsconfig.json +1 -1
- package/tsconfig.base.json +5 -0
- package/vitest.config.ts +10 -0
- package/core/src/providers/search.provider.ts +0 -18
- package/core/src/schemas/models/search.model.ts +0 -36
- package/core/src/schemas/queries/search.query.ts +0 -9
- package/examples/next/.swcrc +0 -30
- package/examples/next/eslint.config.mjs +0 -21
- package/examples/next/index.d.ts +0 -6
- package/examples/next/next-env.d.ts +0 -5
- package/examples/next/next.config.js +0 -31
- package/examples/next/project.json +0 -9
- 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 +0 -18
- package/examples/next/src/app/page.module.scss +0 -2
- package/examples/next/src/app/page.tsx +0 -47
- package/examples/next/src/instrumentation.ts +0 -9
- package/examples/next/tsconfig.json +0 -44
- package/examples/node/jest.config.ts +0 -10
- package/jest.config.ts +0 -6
- package/jest.preset.js +0 -3
- package/providers/algolia/jest.config.ts +0 -10
- package/providers/algolia/src/providers/product.provider.ts +0 -66
- package/providers/algolia/src/providers/search.provider.ts +0 -106
- package/providers/algolia/src/test/search.provider.spec.ts +0 -91
- package/providers/commercetools/jest.config.cjs +0 -10
- package/providers/commercetools/src/providers/search.provider.ts +0 -96
- package/providers/commercetools/src/test/cart.provider.spec.ts +0 -199
- package/providers/commercetools/src/test/category.provider.spec.ts +0 -168
- package/providers/commercetools/src/test/checkout.provider.spec.ts +0 -312
- package/providers/commercetools/src/test/identity.provider.spec.ts +0 -88
- package/providers/commercetools/src/test/inventory.provider.spec.ts +0 -41
- package/providers/commercetools/src/test/price.provider.spec.ts +0 -81
- package/providers/commercetools/src/test/product.provider.spec.ts +0 -80
- package/providers/commercetools/src/test/profile.provider.spec.ts +0 -49
- package/providers/commercetools/src/test/search.provider.spec.ts +0 -61
- package/providers/commercetools/src/test/store.provider.spec.ts +0 -37
- package/providers/fake/jest.config.cjs +0 -10
- package/providers/fake/src/providers/search.provider.ts +0 -132
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
import type { AnalyticsProvider } from "../providers/analytics.provider.js";
|
|
2
2
|
import type { ProductProvider } from "../providers/product.provider.js";
|
|
3
|
-
import type {
|
|
3
|
+
import type { ProductSearchProvider } from "../providers/product-search.provider.js";
|
|
4
4
|
import type { IdentityProvider } from '../providers/identity.provider.js';
|
|
5
5
|
import type { CartProvider } from "../providers/cart.provider.js";
|
|
6
6
|
import type { PriceProvider } from "../providers/price.provider.js";
|
|
7
7
|
import type { InventoryProvider } from "../providers/inventory.provider.js";
|
|
8
8
|
import type { Cache } from "../cache/cache.interface.js";
|
|
9
|
-
import { RedisCache } from "../cache/redis-cache.js";
|
|
10
9
|
import type { CategoryProvider } from "../providers/category.provider.js";
|
|
11
|
-
import type { CheckoutProvider } from "../providers/index.js";
|
|
10
|
+
import type { CheckoutProvider, OrderProvider, ProfileProvider, StoreProvider } from "../providers/index.js";
|
|
11
|
+
import type { OrderSearchProvider } from "../providers/order-search.provider.js";
|
|
12
12
|
|
|
13
13
|
export interface Client {
|
|
14
14
|
product: ProductProvider,
|
|
15
|
-
|
|
15
|
+
productSearch: ProductSearchProvider,
|
|
16
16
|
identity: IdentityProvider,
|
|
17
17
|
cache: Cache,
|
|
18
18
|
cart: CartProvider,
|
|
@@ -20,48 +20,10 @@ export interface Client {
|
|
|
20
20
|
analytics: Array<AnalyticsProvider>,
|
|
21
21
|
price: PriceProvider,
|
|
22
22
|
inventory: InventoryProvider,
|
|
23
|
-
category: CategoryProvider
|
|
23
|
+
category: CategoryProvider,
|
|
24
|
+
profile: ProfileProvider,
|
|
25
|
+
store: StoreProvider,
|
|
26
|
+
order: OrderProvider,
|
|
27
|
+
orderSearch: OrderSearchProvider,
|
|
24
28
|
}
|
|
25
29
|
|
|
26
|
-
export interface BuildClientOptions {
|
|
27
|
-
cache?: Cache;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
export function buildClient<T extends Partial<Client>>(
|
|
31
|
-
providerFactories: Array<(cache: Cache) => T>,
|
|
32
|
-
options: BuildClientOptions = {}
|
|
33
|
-
): Required<T> {
|
|
34
|
-
let client = { } as Required<T>;
|
|
35
|
-
|
|
36
|
-
// Create shared cache instance
|
|
37
|
-
const sharedCache = options.cache || new RedisCache();
|
|
38
|
-
|
|
39
|
-
const mergedAnalytics = [];
|
|
40
|
-
|
|
41
|
-
for (const factory of providerFactories) {
|
|
42
|
-
const provider = factory(sharedCache);
|
|
43
|
-
client = {
|
|
44
|
-
...client,
|
|
45
|
-
...provider
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
if (provider.analytics) {
|
|
49
|
-
mergedAnalytics.push(...provider.analytics);
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
client.analytics = mergedAnalytics;
|
|
54
|
-
|
|
55
|
-
// Add cache to complete the client
|
|
56
|
-
const completeClient = {
|
|
57
|
-
...client,
|
|
58
|
-
cache: sharedCache
|
|
59
|
-
} as Required<T>;
|
|
60
|
-
|
|
61
|
-
return completeClient;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
// Convenience function to create a shared cache instance
|
|
65
|
-
export function createCache(): Cache {
|
|
66
|
-
return new RedisCache();
|
|
67
|
-
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './reactionary.decorator.js';
|
|
@@ -1,24 +1,20 @@
|
|
|
1
|
+
import type { Tracer } from '@opentelemetry/api';
|
|
2
|
+
import { SpanStatusCode, trace } from '@opentelemetry/api';
|
|
3
|
+
import { z } from 'zod';
|
|
1
4
|
import type { BaseProvider } from '../providers/index.js';
|
|
2
|
-
import
|
|
3
|
-
|
|
4
|
-
import {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
5
|
+
import { getReactionaryMeter } from '../metrics/metrics.js';
|
|
6
|
+
import { error, success, type Result } from '../schemas/result.js';
|
|
7
|
+
import type {
|
|
8
|
+
InvalidInputError,
|
|
9
|
+
InvalidOutputError,
|
|
10
|
+
GenericError,
|
|
11
|
+
} from '../schemas/index.js';
|
|
8
12
|
|
|
9
13
|
const TRACER_NAME = '@reactionary';
|
|
10
14
|
const TRACER_VERSION = '0.0.1';
|
|
11
15
|
|
|
12
|
-
let globalTracer: Tracer | null = null;
|
|
13
|
-
|
|
14
16
|
export function getTracer(): Tracer {
|
|
15
|
-
|
|
16
|
-
// Simply get the tracer from the API
|
|
17
|
-
// If the SDK is not initialized by the host application,
|
|
18
|
-
// this will return a ProxyTracer that produces NonRecordingSpans
|
|
19
|
-
globalTracer = trace.getTracer(TRACER_NAME, TRACER_VERSION);
|
|
20
|
-
}
|
|
21
|
-
return globalTracer;
|
|
17
|
+
return trace.getTracer(TRACER_NAME, TRACER_VERSION);
|
|
22
18
|
}
|
|
23
19
|
|
|
24
20
|
/**
|
|
@@ -51,7 +47,16 @@ export class ReactionaryDecoratorOptions {
|
|
|
51
47
|
*/
|
|
52
48
|
public cacheTimeToLiveInSeconds = 60;
|
|
53
49
|
|
|
54
|
-
|
|
50
|
+
/**
|
|
51
|
+
* The schema for the input (query or mutation) type, for validation purposes
|
|
52
|
+
*/
|
|
53
|
+
public inputSchema: z.ZodType = z.unknown();
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* The schema for the primary output type, for validation purposes
|
|
57
|
+
*/
|
|
58
|
+
public outputSchema: z.ZodType = z.unknown();
|
|
59
|
+
}
|
|
55
60
|
|
|
56
61
|
/**
|
|
57
62
|
* Decorator for provider functions to provide functionality such as caching, tracing and type-checked
|
|
@@ -67,6 +72,13 @@ export function Reactionary(options: Partial<ReactionaryDecoratorOptions>) {
|
|
|
67
72
|
const original = descriptor.value;
|
|
68
73
|
const scope = `${target.constructor.name}.${propertyKey.toString()}`;
|
|
69
74
|
const configuration = { ...new ReactionaryDecoratorOptions(), ...options };
|
|
75
|
+
const meter = getReactionaryMeter();
|
|
76
|
+
const attributes = {
|
|
77
|
+
'labels.scope': scope,
|
|
78
|
+
};
|
|
79
|
+
const startTime = performance.now();
|
|
80
|
+
let status = 'ok';
|
|
81
|
+
let cacheStatus = 'miss';
|
|
70
82
|
|
|
71
83
|
if (!original) {
|
|
72
84
|
throw new Error(
|
|
@@ -75,34 +87,191 @@ export function Reactionary(options: Partial<ReactionaryDecoratorOptions>) {
|
|
|
75
87
|
}
|
|
76
88
|
|
|
77
89
|
descriptor.value = async function (this: BaseProvider, ...args: any[]) {
|
|
78
|
-
|
|
90
|
+
return traceSpan(scope, async () => {
|
|
91
|
+
meter.requestInProgress.add(1, attributes);
|
|
92
|
+
try {
|
|
93
|
+
const input = validateInput(args[0], configuration.inputSchema);
|
|
79
94
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
async (span) => {
|
|
84
|
-
const cacheKey = this.generateCacheKeyForQuery(scope, args[0]);
|
|
95
|
+
if (!input.success) {
|
|
96
|
+
return input;
|
|
97
|
+
}
|
|
85
98
|
|
|
86
|
-
const
|
|
87
|
-
let
|
|
99
|
+
const cacheKey = this.generateCacheKeyForQuery(scope, input.value);
|
|
100
|
+
let fromCache = null;
|
|
88
101
|
|
|
89
|
-
if (
|
|
90
|
-
|
|
102
|
+
if (options.cache) {
|
|
103
|
+
fromCache = await this.cache.get(
|
|
104
|
+
cacheKey,
|
|
105
|
+
options.outputSchema as any
|
|
106
|
+
);
|
|
107
|
+
}
|
|
91
108
|
|
|
92
|
-
|
|
109
|
+
let result;
|
|
110
|
+
if (!fromCache) {
|
|
111
|
+
result = await original.apply(this, [input.value]);
|
|
93
112
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
113
|
+
const r = result as Result<unknown>;
|
|
114
|
+
|
|
115
|
+
if (r.success) {
|
|
116
|
+
const dependencyIds = this.generateDependencyIdsForModel(r.value);
|
|
117
|
+
|
|
118
|
+
if (options.cache) {
|
|
119
|
+
this.cache.put(cacheKey, r.value, {
|
|
120
|
+
ttlSeconds: configuration.cacheTimeToLiveInSeconds,
|
|
121
|
+
dependencyIds: dependencyIds,
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
} else {
|
|
126
|
+
result = success(fromCache);
|
|
127
|
+
cacheStatus = 'hit';
|
|
98
128
|
}
|
|
99
129
|
|
|
100
|
-
|
|
101
|
-
|
|
130
|
+
// TODO: Update the below after fixing the method signature.
|
|
131
|
+
// That is, making the decorator only applicable to methods with
|
|
132
|
+
// a signature returning a result.
|
|
133
|
+
const validatedResult = validateOutput(
|
|
134
|
+
result as any,
|
|
135
|
+
configuration.outputSchema
|
|
136
|
+
);
|
|
137
|
+
|
|
138
|
+
if (validatedResult.success) {
|
|
139
|
+
validatedResult.meta.cache.hit = !!fromCache;
|
|
140
|
+
validatedResult.meta.cache.key = cacheKey;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
return validatedResult;
|
|
144
|
+
} catch (err) {
|
|
145
|
+
status = 'error';
|
|
146
|
+
trace
|
|
147
|
+
.getActiveSpan()
|
|
148
|
+
?.setStatus({
|
|
149
|
+
code: SpanStatusCode.ERROR,
|
|
150
|
+
message: errorToString(err),
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
// TODO: Decide if we want to redact the error message, since the client should never rely
|
|
154
|
+
// on the internals. On the other hand, it is REALLY convenient to have it during development...
|
|
155
|
+
const result = error<GenericError>({
|
|
156
|
+
type: 'Generic',
|
|
157
|
+
message: errorToString(err),
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
return result;
|
|
161
|
+
} finally {
|
|
162
|
+
const duration = performance.now() - startTime;
|
|
163
|
+
const finalAttributes = {
|
|
164
|
+
...attributes,
|
|
165
|
+
'labels.status': status,
|
|
166
|
+
'labels.cache_status': cacheStatus,
|
|
167
|
+
};
|
|
168
|
+
meter.requestInProgress.add(-1, finalAttributes);
|
|
169
|
+
meter.requests.add(1, finalAttributes);
|
|
170
|
+
meter.requestDuration.record(duration, finalAttributes);
|
|
102
171
|
}
|
|
103
|
-
);
|
|
172
|
+
});
|
|
104
173
|
};
|
|
105
174
|
|
|
106
175
|
return descriptor;
|
|
107
176
|
};
|
|
108
177
|
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Utility function to handle input validation.
|
|
181
|
+
*/
|
|
182
|
+
export function validateInput<T>(
|
|
183
|
+
input: T,
|
|
184
|
+
schema: z.ZodType | undefined
|
|
185
|
+
): Result<T> {
|
|
186
|
+
if (!schema) {
|
|
187
|
+
return success(input);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
let validated: Result<T> = success(input);
|
|
191
|
+
const parse = schema.safeParse(input);
|
|
192
|
+
|
|
193
|
+
if (!parse.success) {
|
|
194
|
+
validated = error<InvalidInputError>({
|
|
195
|
+
type: 'InvalidInput',
|
|
196
|
+
error: parse.error,
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
return validated;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Utility function to handle output validation.
|
|
205
|
+
*/
|
|
206
|
+
export function validateOutput(
|
|
207
|
+
output: Result<unknown>,
|
|
208
|
+
schema: z.ZodType | undefined
|
|
209
|
+
): Result<unknown> {
|
|
210
|
+
if (!schema) {
|
|
211
|
+
return output;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
let validated = output;
|
|
215
|
+
if (output.success) {
|
|
216
|
+
const parse = schema.safeParse(output.value);
|
|
217
|
+
|
|
218
|
+
if (!parse.success) {
|
|
219
|
+
validated = error<InvalidOutputError>({
|
|
220
|
+
type: 'InvalidOutput',
|
|
221
|
+
error: parse.error,
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
return validated;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* Utility function to wrap entry / exit into decorated functions in a
|
|
231
|
+
* traced span for OTEL handling.
|
|
232
|
+
*/
|
|
233
|
+
export async function traceSpan<T>(
|
|
234
|
+
name: string,
|
|
235
|
+
fn: () => Promise<T> | T
|
|
236
|
+
): Promise<T> {
|
|
237
|
+
const tracer: Tracer = getTracer();
|
|
238
|
+
|
|
239
|
+
return tracer.startActiveSpan(name, async (span) => {
|
|
240
|
+
try {
|
|
241
|
+
return await fn();
|
|
242
|
+
} catch (err: unknown) {
|
|
243
|
+
if (err instanceof Error) {
|
|
244
|
+
span.recordException(err);
|
|
245
|
+
span.setStatus({ code: 2, message: err.message });
|
|
246
|
+
} else {
|
|
247
|
+
span.recordException({ message: String(err) });
|
|
248
|
+
span.setStatus({ code: 2, message: String(err) });
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// TODO: Instead of re-throwing here, we might actually want to return a distinct error type
|
|
252
|
+
// but I am unsure if that is really a task for the decorator (outside of input / output parsing)
|
|
253
|
+
// it could perhaps always handle an unknown exception as a generic error type, for consolidation
|
|
254
|
+
throw err;
|
|
255
|
+
} finally {
|
|
256
|
+
span.end();
|
|
257
|
+
}
|
|
258
|
+
});
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* Utility function for converting a caught error to a plain
|
|
263
|
+
* string for reporting back.
|
|
264
|
+
*/
|
|
265
|
+
export function errorToString(err: unknown): string {
|
|
266
|
+
if (err instanceof Error) {
|
|
267
|
+
return err.stack ?? err.message;
|
|
268
|
+
}
|
|
269
|
+
if (typeof err === 'string') {
|
|
270
|
+
return err;
|
|
271
|
+
}
|
|
272
|
+
try {
|
|
273
|
+
return JSON.stringify(err);
|
|
274
|
+
} catch {
|
|
275
|
+
return String(err);
|
|
276
|
+
}
|
|
277
|
+
}
|
package/core/src/index.ts
CHANGED
|
@@ -1,19 +1,6 @@
|
|
|
1
|
-
export * from './cache/
|
|
2
|
-
export * from './
|
|
3
|
-
export * from './
|
|
4
|
-
export * from './
|
|
5
|
-
|
|
6
|
-
export * from './
|
|
7
|
-
export * from './client/client-builder.js';
|
|
8
|
-
|
|
9
|
-
export * from './decorators/reactionary.decorator.js';
|
|
10
|
-
|
|
11
|
-
export * from './providers/index.js'
|
|
12
|
-
|
|
13
|
-
export * from './schemas/capabilities.schema.js';
|
|
14
|
-
export * from './schemas/session.schema.js';
|
|
15
|
-
|
|
16
|
-
export * from './schemas/models/index.js';
|
|
17
|
-
export * from './schemas/mutations/index.js';
|
|
18
|
-
export * from './schemas/queries/index.js';
|
|
19
|
-
export * from './initialization.js';
|
|
1
|
+
export * from './cache/index.js';
|
|
2
|
+
export * from './client/index.js';
|
|
3
|
+
export * from './decorators/index.js';
|
|
4
|
+
export * from './providers/index.js';
|
|
5
|
+
export * from './schemas/index.js';
|
|
6
|
+
export * from './initialization.js';
|
|
@@ -2,25 +2,8 @@ import type { RequestContext } from "./schemas/session.schema.js";
|
|
|
2
2
|
|
|
3
3
|
export function createInitialRequestContext(): RequestContext {
|
|
4
4
|
return {
|
|
5
|
-
id: '',
|
|
6
|
-
identity: {
|
|
7
|
-
type: 'Anonymous',
|
|
8
|
-
meta: {
|
|
9
|
-
cache: { hit: false, key: '' },
|
|
10
|
-
placeholder: false,
|
|
11
|
-
},
|
|
12
|
-
id: { userId: 'anonymous' },
|
|
13
|
-
token: undefined,
|
|
14
|
-
issued: new Date(),
|
|
15
|
-
expiry: new Date(new Date().getTime() + 3600 * 1000),
|
|
16
|
-
logonId: '',
|
|
17
|
-
createdAt: '',
|
|
18
|
-
updatedAt: '',
|
|
19
|
-
keyring: [],
|
|
20
|
-
currentService: undefined,
|
|
21
|
-
},
|
|
22
5
|
languageContext: {
|
|
23
|
-
locale: 'en
|
|
6
|
+
locale: 'en',
|
|
24
7
|
currencyCode: 'USD',
|
|
25
8
|
},
|
|
26
9
|
storeIdentifier: {
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import type { Counter, Gauge } from '@opentelemetry/api';
|
|
2
|
+
import { metrics, type Histogram, type UpDownCounter } from '@opentelemetry/api';
|
|
3
|
+
|
|
4
|
+
const METRICS_NAME = '@reactionary';
|
|
5
|
+
const TRACER_VERSION = '0.0.1';
|
|
6
|
+
|
|
7
|
+
export interface ReactionaryCacheMetrics {
|
|
8
|
+
items: Gauge;
|
|
9
|
+
hits: Counter;
|
|
10
|
+
misses: Counter;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
let globalCacheMetrics: ReactionaryCacheMetrics | null = null;
|
|
14
|
+
export function getReactionaryCacheMeter(): ReactionaryCacheMetrics {
|
|
15
|
+
if (!globalCacheMetrics) {
|
|
16
|
+
const meter = metrics.getMeter(METRICS_NAME, TRACER_VERSION);
|
|
17
|
+
globalCacheMetrics = {
|
|
18
|
+
items: meter.createGauge('reactionary_cache_items', {
|
|
19
|
+
description: 'Tracks the number of items in the cache',
|
|
20
|
+
}),
|
|
21
|
+
hits: meter.createCounter('reactionary_cache_hits', {
|
|
22
|
+
description: 'Counts the number of cache hits',
|
|
23
|
+
}),
|
|
24
|
+
misses: meter.createCounter('reactionary_cache_misses', {
|
|
25
|
+
description: 'Counts the number of cache misses',
|
|
26
|
+
}),
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
return globalCacheMetrics;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
export interface ReactionaryMetrics {
|
|
34
|
+
requests: Counter;
|
|
35
|
+
requestDuration: Histogram;
|
|
36
|
+
requestInProgress: UpDownCounter;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
let globalMetrics: ReactionaryMetrics | null = null;
|
|
40
|
+
|
|
41
|
+
export function getReactionaryMeter(): ReactionaryMetrics {
|
|
42
|
+
if (!globalMetrics) {
|
|
43
|
+
const meter = metrics.getMeter(METRICS_NAME, TRACER_VERSION);
|
|
44
|
+
globalMetrics = {
|
|
45
|
+
requests: meter.createCounter('reactionary_provider_requests', {
|
|
46
|
+
description:
|
|
47
|
+
'Counts the number of requests made to provider queries and mutations',
|
|
48
|
+
}),
|
|
49
|
+
requestDuration: meter.createHistogram(
|
|
50
|
+
'reactionary_provider_request_duration',
|
|
51
|
+
{
|
|
52
|
+
description:
|
|
53
|
+
'Records the duration of provider query and mutation requests',
|
|
54
|
+
unit: 'ms',
|
|
55
|
+
}
|
|
56
|
+
),
|
|
57
|
+
requestInProgress: meter.createUpDownCounter(
|
|
58
|
+
'reactionary_provider_requests_in_progress',
|
|
59
|
+
{
|
|
60
|
+
description:
|
|
61
|
+
'Tracks the number of in-progress requests to provider queries and mutations',
|
|
62
|
+
}
|
|
63
|
+
),
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
return globalMetrics;
|
|
67
|
+
}
|
|
@@ -1,11 +1,6 @@
|
|
|
1
|
-
import type { AnalyticsEvent } from '../schemas/models/analytics.model.js';
|
|
2
1
|
import { BaseProvider } from './base.provider.js';
|
|
3
2
|
|
|
4
|
-
export abstract class AnalyticsProvider
|
|
5
|
-
T extends AnalyticsEvent = AnalyticsEvent
|
|
6
|
-
> extends BaseProvider<T> {
|
|
7
|
-
|
|
8
|
-
|
|
3
|
+
export abstract class AnalyticsProvider extends BaseProvider {
|
|
9
4
|
protected override getResourceName(): string {
|
|
10
5
|
return 'analytics';
|
|
11
6
|
}
|
|
@@ -1,53 +1,18 @@
|
|
|
1
|
-
import type { z } from 'zod';
|
|
2
|
-
import type {
|
|
3
|
-
BaseModel} from '../schemas/models/base.model.js';
|
|
4
|
-
import {
|
|
5
|
-
createPaginatedResponseSchema,
|
|
6
|
-
} from '../schemas/models/base.model.js';
|
|
7
1
|
import type { Cache } from '../cache/cache.interface.js';
|
|
8
|
-
import type
|
|
9
|
-
import type { IdentifierType } from '../schemas/models/identifiers.model.js';
|
|
2
|
+
import { type RequestContext } from '../schemas/session.schema.js';
|
|
10
3
|
import { hasher } from "node-object-hash";
|
|
11
4
|
|
|
12
5
|
/**
|
|
13
6
|
* Base capability provider, responsible for mutations (changes) and queries (fetches)
|
|
14
7
|
* for a given business object domain.
|
|
15
8
|
*/
|
|
16
|
-
export abstract class BaseProvider
|
|
9
|
+
export abstract class BaseProvider {
|
|
17
10
|
protected cache: Cache;
|
|
11
|
+
protected context: RequestContext;
|
|
18
12
|
|
|
19
|
-
constructor(
|
|
13
|
+
constructor(cache: Cache, context: RequestContext) {
|
|
20
14
|
this.cache = cache;
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* Validates that the final domain model constructed by the provider
|
|
25
|
-
* fulfills the schema as defined. This will throw an exception.
|
|
26
|
-
*/
|
|
27
|
-
protected assert(value: T) {
|
|
28
|
-
return this.schema.parse(value);
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
/**
|
|
32
|
-
* Creates a new model entity based on the schema defaults.
|
|
33
|
-
*/
|
|
34
|
-
protected newModel(): T {
|
|
35
|
-
return this.schema.parse({});
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
/**
|
|
39
|
-
* Handler for parsing a response from a remote provider and converting it
|
|
40
|
-
* into the typed domain model.
|
|
41
|
-
*/
|
|
42
|
-
protected parseSingle(_body: unknown, reqCtx: RequestContext): T {
|
|
43
|
-
const model = this.newModel();
|
|
44
|
-
|
|
45
|
-
return this.assert(model);
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
protected parsePaginatedResult(_body: unknown, reqCtx: RequestContext): z.infer<ReturnType<typeof createPaginatedResponseSchema<typeof this.schema>>> {
|
|
50
|
-
return createPaginatedResponseSchema(this.schema).parse({});
|
|
15
|
+
this.context = context;
|
|
51
16
|
}
|
|
52
17
|
|
|
53
18
|
public generateDependencyIdsForModel(model: unknown): Array<string> {
|
|
@@ -75,35 +40,6 @@ export abstract class BaseProvider<T extends BaseModel = BaseModel> {
|
|
|
75
40
|
return `${scope}:${queryHash}`;
|
|
76
41
|
}
|
|
77
42
|
|
|
78
|
-
protected generateCacheKeyPaginatedResult(
|
|
79
|
-
resultSetName: string,
|
|
80
|
-
res: ReturnType<typeof this.parsePaginatedResult>,
|
|
81
|
-
reqCtx: RequestContext
|
|
82
|
-
): string {
|
|
83
|
-
const type = this.getResourceName();
|
|
84
|
-
const langPart = reqCtx.languageContext.locale;
|
|
85
|
-
const currencyPart = reqCtx.languageContext.currencyCode || 'default';
|
|
86
|
-
const storePart = reqCtx.storeIdentifier?.key || 'default';
|
|
87
|
-
return `${type}-${resultSetName}-paginated|pageNumber:${res.pageNumber}|pageSize:${res.pageSize}|store:${storePart}|lang:${langPart}|currency:${currencyPart}`;
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
protected generateCacheKeySingle(
|
|
91
|
-
identifier: IdentifierType,
|
|
92
|
-
reqCtx: RequestContext
|
|
93
|
-
): string {
|
|
94
|
-
const type = this.getResourceName();
|
|
95
|
-
|
|
96
|
-
const idPart = Object.entries(identifier)
|
|
97
|
-
.map(([k, v]) => `${k}:${v}`)
|
|
98
|
-
.join('#');
|
|
99
|
-
|
|
100
|
-
const langPart = reqCtx.languageContext.locale;
|
|
101
|
-
const currencyPart = reqCtx.languageContext.currencyCode || 'default';
|
|
102
|
-
const storePart = reqCtx.storeIdentifier?.key || 'default';
|
|
103
|
-
|
|
104
|
-
return `${type}-${idPart}|store:${storePart}|lang:${langPart}|currency:${currencyPart}`;
|
|
105
|
-
}
|
|
106
|
-
|
|
107
43
|
/**
|
|
108
44
|
* Returns the abstract resource name provided by the remote system.
|
|
109
45
|
*/
|