@burdenoff/be-sdk 2026.510.79
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/README.md +173 -0
- package/configs/eslint/index.js +492 -0
- package/configs/tsconfig/tsconfig.json +53 -0
- package/dist/EventQueueFactory-6NMSGECR.js +8 -0
- package/dist/EventQueueFactory-6NMSGECR.js.map +1 -0
- package/dist/auth/index.d.ts +66 -0
- package/dist/auth/index.js +8 -0
- package/dist/auth/index.js.map +1 -0
- package/dist/cache/index.d.ts +2 -0
- package/dist/cache/index.js +6 -0
- package/dist/cache/index.js.map +1 -0
- package/dist/captcha/index.d.ts +1 -0
- package/dist/captcha/index.js +4 -0
- package/dist/captcha/index.js.map +1 -0
- package/dist/chunk-2HANKZZM.js +245 -0
- package/dist/chunk-2HANKZZM.js.map +1 -0
- package/dist/chunk-2SD5L2F2.js +816 -0
- package/dist/chunk-2SD5L2F2.js.map +1 -0
- package/dist/chunk-3O2RQOPV.js +1068 -0
- package/dist/chunk-3O2RQOPV.js.map +1 -0
- package/dist/chunk-4B43DZIS.js +560 -0
- package/dist/chunk-4B43DZIS.js.map +1 -0
- package/dist/chunk-4XAEES25.js +2444 -0
- package/dist/chunk-4XAEES25.js.map +1 -0
- package/dist/chunk-4XGGWW36.js +1405 -0
- package/dist/chunk-4XGGWW36.js.map +1 -0
- package/dist/chunk-5LXDPGDH.js +245 -0
- package/dist/chunk-5LXDPGDH.js.map +1 -0
- package/dist/chunk-AI7L22ZJ.js +472 -0
- package/dist/chunk-AI7L22ZJ.js.map +1 -0
- package/dist/chunk-CBZNP6RF.js +286 -0
- package/dist/chunk-CBZNP6RF.js.map +1 -0
- package/dist/chunk-ELLX3K54.js +613 -0
- package/dist/chunk-ELLX3K54.js.map +1 -0
- package/dist/chunk-F5XEAGHQ.js +1875 -0
- package/dist/chunk-F5XEAGHQ.js.map +1 -0
- package/dist/chunk-FNZEQOJY.js +628 -0
- package/dist/chunk-FNZEQOJY.js.map +1 -0
- package/dist/chunk-GTEMQXCA.js +516 -0
- package/dist/chunk-GTEMQXCA.js.map +1 -0
- package/dist/chunk-HIWTWIGQ.js +226 -0
- package/dist/chunk-HIWTWIGQ.js.map +1 -0
- package/dist/chunk-IYE2NTRX.js +764 -0
- package/dist/chunk-IYE2NTRX.js.map +1 -0
- package/dist/chunk-J26YGR2U.js +1484 -0
- package/dist/chunk-J26YGR2U.js.map +1 -0
- package/dist/chunk-JG2FTZPI.js +509 -0
- package/dist/chunk-JG2FTZPI.js.map +1 -0
- package/dist/chunk-JVBTNSPG.js +1551 -0
- package/dist/chunk-JVBTNSPG.js.map +1 -0
- package/dist/chunk-L7ZMN2FJ.js +12 -0
- package/dist/chunk-L7ZMN2FJ.js.map +1 -0
- package/dist/chunk-M5IMU3US.js +473 -0
- package/dist/chunk-M5IMU3US.js.map +1 -0
- package/dist/chunk-MLW2HPS5.js +1267 -0
- package/dist/chunk-MLW2HPS5.js.map +1 -0
- package/dist/chunk-MNCGUYLJ.js +269 -0
- package/dist/chunk-MNCGUYLJ.js.map +1 -0
- package/dist/chunk-MOCUBSTW.js +373 -0
- package/dist/chunk-MOCUBSTW.js.map +1 -0
- package/dist/chunk-NGMEI42S.js +103 -0
- package/dist/chunk-NGMEI42S.js.map +1 -0
- package/dist/chunk-NUB5GQTU.js +270 -0
- package/dist/chunk-NUB5GQTU.js.map +1 -0
- package/dist/chunk-OVSYMNCI.js +145 -0
- package/dist/chunk-OVSYMNCI.js.map +1 -0
- package/dist/chunk-QLVKMWFA.js +94 -0
- package/dist/chunk-QLVKMWFA.js.map +1 -0
- package/dist/chunk-SAG6ZYWQ.js +89 -0
- package/dist/chunk-SAG6ZYWQ.js.map +1 -0
- package/dist/chunk-SAK2JCA7.js +198 -0
- package/dist/chunk-SAK2JCA7.js.map +1 -0
- package/dist/chunk-SBS4S2PJ.js +100 -0
- package/dist/chunk-SBS4S2PJ.js.map +1 -0
- package/dist/chunk-SNEOLWXT.js +148 -0
- package/dist/chunk-SNEOLWXT.js.map +1 -0
- package/dist/chunk-TKBD2ONF.js +564 -0
- package/dist/chunk-TKBD2ONF.js.map +1 -0
- package/dist/chunk-UBXFIIAW.js +127 -0
- package/dist/chunk-UBXFIIAW.js.map +1 -0
- package/dist/chunk-VI4VSNTI.js +57 -0
- package/dist/chunk-VI4VSNTI.js.map +1 -0
- package/dist/chunk-VWCN5PMD.js +268 -0
- package/dist/chunk-VWCN5PMD.js.map +1 -0
- package/dist/chunk-XC2YD6CF.js +913 -0
- package/dist/chunk-XC2YD6CF.js.map +1 -0
- package/dist/chunk-XE4OXN2W.js +10 -0
- package/dist/chunk-XE4OXN2W.js.map +1 -0
- package/dist/chunk-YMZEUTD2.js +110 -0
- package/dist/chunk-YMZEUTD2.js.map +1 -0
- package/dist/chunk-Z4IRLBWO.js +758 -0
- package/dist/chunk-Z4IRLBWO.js.map +1 -0
- package/dist/client/internal-client.d.ts +7 -0
- package/dist/client/internal-client.js +5 -0
- package/dist/client/internal-client.js.map +1 -0
- package/dist/client-CSjIrYKS.d.ts +77 -0
- package/dist/config-UKE2E3BW.js +4 -0
- package/dist/config-UKE2E3BW.js.map +1 -0
- package/dist/crypto/index.d.ts +1 -0
- package/dist/crypto/index.js +4 -0
- package/dist/crypto/index.js.map +1 -0
- package/dist/database/index.d.ts +117 -0
- package/dist/database/index.js +7 -0
- package/dist/database/index.js.map +1 -0
- package/dist/dataloader/index.d.ts +1 -0
- package/dist/dataloader/index.js +4 -0
- package/dist/dataloader/index.js.map +1 -0
- package/dist/esm-K6H7EIWK.js +1450 -0
- package/dist/esm-K6H7EIWK.js.map +1 -0
- package/dist/event-queue/index.d.ts +6 -0
- package/dist/event-queue/index.js +10 -0
- package/dist/event-queue/index.js.map +1 -0
- package/dist/export/index.d.ts +2 -0
- package/dist/export/index.js +9 -0
- package/dist/export/index.js.map +1 -0
- package/dist/feature-flags/index.d.ts +1 -0
- package/dist/feature-flags/index.js +4 -0
- package/dist/feature-flags/index.js.map +1 -0
- package/dist/gateway/index.d.ts +4 -0
- package/dist/gateway/index.js +8 -0
- package/dist/gateway/index.js.map +1 -0
- package/dist/gateway/security/index.d.ts +2 -0
- package/dist/gateway/security/index.js +5 -0
- package/dist/gateway/security/index.js.map +1 -0
- package/dist/graph/index.d.ts +9 -0
- package/dist/graph/index.js +13 -0
- package/dist/graph/index.js.map +1 -0
- package/dist/graph-emitter-Bk0FqD53.d.ts +47 -0
- package/dist/graphql/index.d.ts +2 -0
- package/dist/graphql/index.js +7 -0
- package/dist/graphql/index.js.map +1 -0
- package/dist/graphql/services/index.d.ts +24 -0
- package/dist/graphql/services/index.js +5 -0
- package/dist/graphql/services/index.js.map +1 -0
- package/dist/graphql-BAVrUgBF.d.ts +22 -0
- package/dist/i18n/index.d.ts +2 -0
- package/dist/i18n/index.js +5 -0
- package/dist/i18n/index.js.map +1 -0
- package/dist/i18n-DA6IPnYy.d.ts +101 -0
- package/dist/index-4gKQKZ2B.d.ts +71 -0
- package/dist/index-B18rrFru.d.ts +42 -0
- package/dist/index-B70b5mYS.d.ts +33 -0
- package/dist/index-BMq3lFjj.d.ts +247 -0
- package/dist/index-BODgvRtL.d.ts +316 -0
- package/dist/index-BS2idbnr.d.ts +874 -0
- package/dist/index-BV3G48cH.d.ts +124 -0
- package/dist/index-CHbVw5M7.d.ts +73 -0
- package/dist/index-Cq_xNjcv.d.ts +602 -0
- package/dist/index-CrQgidpx.d.ts +77 -0
- package/dist/index-CyVEg86m.d.ts +309 -0
- package/dist/index-DOcBnmMe.d.ts +166 -0
- package/dist/index-DSOpj8DI.d.ts +17 -0
- package/dist/index-DW9q22GH.d.ts +75 -0
- package/dist/index-DlFDFyWD.d.ts +55 -0
- package/dist/index-DoK0bjhH.d.ts +607 -0
- package/dist/index-Dtf6FBaD.d.ts +199 -0
- package/dist/index-KVWGHMbS.d.ts +415 -0
- package/dist/index-PaojfSdd.d.ts +71 -0
- package/dist/index-RU_M5c74.d.ts +163 -0
- package/dist/index-UhkH2eaB.d.ts +192 -0
- package/dist/index-e_18DzAg.d.ts +18 -0
- package/dist/index-jOuh_TKn.d.ts +59 -0
- package/dist/index.d.ts +584 -0
- package/dist/index.js +2626 -0
- package/dist/index.js.map +1 -0
- package/dist/init/index.d.ts +2 -0
- package/dist/init/index.js +12 -0
- package/dist/init/index.js.map +1 -0
- package/dist/instrumentation/index.d.ts +3 -0
- package/dist/instrumentation/index.js +5 -0
- package/dist/instrumentation/index.js.map +1 -0
- package/dist/layer/index.d.ts +2 -0
- package/dist/layer/index.js +6 -0
- package/dist/layer/index.js.map +1 -0
- package/dist/layer-DDip18KY.d.ts +67 -0
- package/dist/logger/index.d.ts +2 -0
- package/dist/logger/index.js +4 -0
- package/dist/logger/index.js.map +1 -0
- package/dist/middleware/index.d.ts +3 -0
- package/dist/middleware/index.js +5 -0
- package/dist/middleware/index.js.map +1 -0
- package/dist/notifications/index.d.ts +4 -0
- package/dist/notifications/index.js +7 -0
- package/dist/notifications/index.js.map +1 -0
- package/dist/observability/index.d.ts +3 -0
- package/dist/observability/index.js +5 -0
- package/dist/observability/index.js.map +1 -0
- package/dist/pino-logger-RYLGXPHZ.js +5 -0
- package/dist/pino-logger-RYLGXPHZ.js.map +1 -0
- package/dist/prisma/generator/index.d.ts +1 -0
- package/dist/prisma/generator/index.js +228 -0
- package/dist/prisma/generator/index.js.map +1 -0
- package/dist/prisma/index.d.ts +5 -0
- package/dist/prisma/index.js +12 -0
- package/dist/prisma/index.js.map +1 -0
- package/dist/prisma-extension-krXnYnrN.d.ts +30 -0
- package/dist/rbac/index.d.ts +116 -0
- package/dist/rbac/index.js +7 -0
- package/dist/rbac/index.js.map +1 -0
- package/dist/sdk-THO3IWEE.js +136 -0
- package/dist/sdk-THO3IWEE.js.map +1 -0
- package/dist/server/index.d.ts +359 -0
- package/dist/server/index.js +884 -0
- package/dist/server/index.js.map +1 -0
- package/dist/telemetry/index.d.ts +83 -0
- package/dist/telemetry/index.js +209 -0
- package/dist/telemetry/index.js.map +1 -0
- package/dist/temporal/index.d.ts +148 -0
- package/dist/temporal/index.js +6 -0
- package/dist/temporal/index.js.map +1 -0
- package/dist/tracker/index.d.ts +5 -0
- package/dist/tracker/index.js +9 -0
- package/dist/tracker/index.js.map +1 -0
- package/dist/types-Bo2wkjSP.d.ts +35 -0
- package/dist/utils/index.d.ts +1 -0
- package/dist/utils/index.js +4 -0
- package/dist/utils/index.js.map +1 -0
- package/dist/workspace-guard-LHBNK4TG.js +5 -0
- package/dist/workspace-guard-LHBNK4TG.js.map +1 -0
- package/package.json +241 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,2626 @@
|
|
|
1
|
+
export { TemporalClient, TemporalClientFactory, TemporalModule, TemporalWorker, TemporalWorkerFactory, TemporalWorkerManager } from './chunk-Z4IRLBWO.js';
|
|
2
|
+
export { instrumentation_exports as Instrumentation } from './chunk-4XAEES25.js';
|
|
3
|
+
export { middleware_exports as Middleware } from './chunk-2HANKZZM.js';
|
|
4
|
+
export { observability_exports as Observability } from './chunk-5LXDPGDH.js';
|
|
5
|
+
export { prisma_exports as Prisma } from './chunk-2SD5L2F2.js';
|
|
6
|
+
import './chunk-GTEMQXCA.js';
|
|
7
|
+
import { ActivityModule, UsageModule, TrackerModule } from './chunk-JVBTNSPG.js';
|
|
8
|
+
export { ActivityModule, TrackerModule, UsageModule } from './chunk-JVBTNSPG.js';
|
|
9
|
+
import { RBACModule } from './chunk-J26YGR2U.js';
|
|
10
|
+
export { RBACModule, createRBACDirective, createRBACDirectiveForLayer, createRBACDirectiveFromEnv } from './chunk-J26YGR2U.js';
|
|
11
|
+
import { TenantNotificationsModule, NotificationsModule } from './chunk-XC2YD6CF.js';
|
|
12
|
+
export { tenant_notifications_exports as Notifications, NotificationsModule } from './chunk-XC2YD6CF.js';
|
|
13
|
+
export { crypto_exports as Crypto } from './chunk-SBS4S2PJ.js';
|
|
14
|
+
export { createInternalClient, createInternalClientForTenant, createInternalClientForWorkspaces } from './chunk-VI4VSNTI.js';
|
|
15
|
+
export { feature_flags_exports as FeatureFlags } from './chunk-VWCN5PMD.js';
|
|
16
|
+
import { GraphModule } from './chunk-F5XEAGHQ.js';
|
|
17
|
+
export { graph_exports as Graph, GraphModule } from './chunk-F5XEAGHQ.js';
|
|
18
|
+
import './chunk-TKBD2ONF.js';
|
|
19
|
+
export { graphql_exports as GraphQL } from './chunk-MNCGUYLJ.js';
|
|
20
|
+
import './chunk-QLVKMWFA.js';
|
|
21
|
+
export { i18n_exports as I18n, createTranslator, getLanguage, getLocale, hasI18n, t } from './chunk-IYE2NTRX.js';
|
|
22
|
+
export { init_exports as Init } from './chunk-AI7L22ZJ.js';
|
|
23
|
+
import { AuthModule } from './chunk-SAK2JCA7.js';
|
|
24
|
+
export { AuthModule } from './chunk-SAK2JCA7.js';
|
|
25
|
+
export { utils_exports as Utils } from './chunk-MOCUBSTW.js';
|
|
26
|
+
export { cache_exports as Cache } from './chunk-4XGGWW36.js';
|
|
27
|
+
export { captcha_exports as Captcha, isCaptchaEnabled, verifyCaptcha, verifyCaptchaIfEnabled } from './chunk-YMZEUTD2.js';
|
|
28
|
+
export { export_exports as Export } from './chunk-FNZEQOJY.js';
|
|
29
|
+
export { gateway_exports as Gateway } from './chunk-4B43DZIS.js';
|
|
30
|
+
import './chunk-MLW2HPS5.js';
|
|
31
|
+
export { layer_exports as Layer } from './chunk-HIWTWIGQ.js';
|
|
32
|
+
import { EventsModule } from './chunk-M5IMU3US.js';
|
|
33
|
+
export { event_queue_exports as EventQueue, EventsModule } from './chunk-M5IMU3US.js';
|
|
34
|
+
import './chunk-ELLX3K54.js';
|
|
35
|
+
import { EventQueueFactory } from './chunk-3O2RQOPV.js';
|
|
36
|
+
import './chunk-L7ZMN2FJ.js';
|
|
37
|
+
import './chunk-OVSYMNCI.js';
|
|
38
|
+
export { dataloader_exports as DataLoader } from './chunk-NUB5GQTU.js';
|
|
39
|
+
import { DatabaseModule } from './chunk-JG2FTZPI.js';
|
|
40
|
+
export { DatabaseModule } from './chunk-JG2FTZPI.js';
|
|
41
|
+
import { APIError, NetworkError, TimeoutError, ValidationError } from './chunk-SAG6ZYWQ.js';
|
|
42
|
+
export { DEFAULT_CIRCUIT_BREAKER_CONFIG, DEFAULT_GATEWAY_RETRY_CONFIG, DEFAULT_GATEWAY_ROUTING, DEFAULT_LAYER_RESOURCES, DEFAULT_LAYER_STREAMS } from './chunk-NGMEI42S.js';
|
|
43
|
+
import { logger } from './chunk-CBZNP6RF.js';
|
|
44
|
+
export { logger_exports as Logger } from './chunk-CBZNP6RF.js';
|
|
45
|
+
import { __name } from './chunk-XE4OXN2W.js';
|
|
46
|
+
import { GraphQLClient } from 'graphql-request';
|
|
47
|
+
import { AsyncLocalStorage } from 'async_hooks';
|
|
48
|
+
|
|
49
|
+
var BurdenoffGraphQLClient = class {
|
|
50
|
+
static {
|
|
51
|
+
__name(this, "BurdenoffGraphQLClient");
|
|
52
|
+
}
|
|
53
|
+
workspacesClient;
|
|
54
|
+
tenantClient;
|
|
55
|
+
config;
|
|
56
|
+
defaultContext;
|
|
57
|
+
constructor(config, defaultContext = {}) {
|
|
58
|
+
this.config = config;
|
|
59
|
+
this.defaultContext = defaultContext;
|
|
60
|
+
this.workspacesClient = new GraphQLClient(config.workspacesApiUrl, {
|
|
61
|
+
headers: this.getHeaders(defaultContext)
|
|
62
|
+
});
|
|
63
|
+
if (config.tenantApiUrl) {
|
|
64
|
+
this.tenantClient = new GraphQLClient(config.tenantApiUrl, {
|
|
65
|
+
headers: this.getHeaders(defaultContext)
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Get headers for GraphQL request
|
|
71
|
+
* Includes actor context headers for RBAC authorization
|
|
72
|
+
*/
|
|
73
|
+
getHeaders(context) {
|
|
74
|
+
const headers = {
|
|
75
|
+
"Content-Type": "application/json"
|
|
76
|
+
};
|
|
77
|
+
if (context.token) {
|
|
78
|
+
headers["Authorization"] = `Bearer ${context.token}`;
|
|
79
|
+
}
|
|
80
|
+
if (context.actorId) {
|
|
81
|
+
headers["x-actor-id"] = context.actorId;
|
|
82
|
+
}
|
|
83
|
+
if (context.actorType) {
|
|
84
|
+
headers["x-actor-type"] = context.actorType;
|
|
85
|
+
}
|
|
86
|
+
if (context.workspaceId) {
|
|
87
|
+
headers["x-workspace-id"] = context.workspaceId;
|
|
88
|
+
}
|
|
89
|
+
if (context.productId) {
|
|
90
|
+
headers["x-product-id"] = context.productId;
|
|
91
|
+
}
|
|
92
|
+
return headers;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Update context for future requests
|
|
96
|
+
*/
|
|
97
|
+
updateContext(context) {
|
|
98
|
+
this.defaultContext = {
|
|
99
|
+
...this.defaultContext,
|
|
100
|
+
...context
|
|
101
|
+
};
|
|
102
|
+
this.workspacesClient.setHeaders(this.getHeaders(this.defaultContext));
|
|
103
|
+
if (this.tenantClient) {
|
|
104
|
+
this.tenantClient.setHeaders(this.getHeaders(this.defaultContext));
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Execute GraphQL query/mutation against workspaces API
|
|
109
|
+
*/
|
|
110
|
+
async query(query, variables, context) {
|
|
111
|
+
return this.executeWithRetry(() => this.executeQuery(this.workspacesClient, query, variables, context), "workspaces");
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Execute GraphQL query/mutation against tenant API
|
|
115
|
+
*/
|
|
116
|
+
async tenantQuery(query, variables, context) {
|
|
117
|
+
if (!this.tenantClient) {
|
|
118
|
+
throw new APIError("Tenant API URL not configured", {
|
|
119
|
+
operation: "tenantQuery"
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
return this.executeWithRetry(() => this.executeQuery(this.tenantClient, query, variables, context), "tenant");
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Execute GraphQL request
|
|
126
|
+
*/
|
|
127
|
+
async executeQuery(client, query, variables, context) {
|
|
128
|
+
try {
|
|
129
|
+
const mergedContext = {
|
|
130
|
+
...this.defaultContext,
|
|
131
|
+
...context
|
|
132
|
+
};
|
|
133
|
+
if (context) {
|
|
134
|
+
const headers = this.getHeaders(mergedContext);
|
|
135
|
+
client.setHeaders(headers);
|
|
136
|
+
}
|
|
137
|
+
const response = await client.request(query, variables);
|
|
138
|
+
return response;
|
|
139
|
+
} catch (error) {
|
|
140
|
+
throw this.handleError(error);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Execute with retry logic
|
|
145
|
+
*/
|
|
146
|
+
async executeWithRetry(operation, api) {
|
|
147
|
+
const maxAttempts = this.config.retry?.maxAttempts || 3;
|
|
148
|
+
const initialDelay = this.config.retry?.initialDelay || 1e3;
|
|
149
|
+
const maxDelay = this.config.retry?.maxDelay || 1e4;
|
|
150
|
+
const backoffMultiplier = this.config.retry?.backoffMultiplier || 2;
|
|
151
|
+
let lastError;
|
|
152
|
+
let delay = initialDelay;
|
|
153
|
+
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
154
|
+
try {
|
|
155
|
+
return await operation();
|
|
156
|
+
} catch (error) {
|
|
157
|
+
lastError = error;
|
|
158
|
+
if (error instanceof APIError && !this.shouldRetry(error)) {
|
|
159
|
+
throw error;
|
|
160
|
+
}
|
|
161
|
+
if (attempt === maxAttempts) {
|
|
162
|
+
break;
|
|
163
|
+
}
|
|
164
|
+
await this.sleep(Math.min(delay, maxDelay));
|
|
165
|
+
delay *= backoffMultiplier;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
throw new APIError(`GraphQL request failed after ${maxAttempts} attempts`, {
|
|
169
|
+
api,
|
|
170
|
+
lastError: lastError?.message
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* Determine if error should be retried
|
|
175
|
+
*/
|
|
176
|
+
shouldRetry(error) {
|
|
177
|
+
if (error instanceof NetworkError || error instanceof TimeoutError) {
|
|
178
|
+
return true;
|
|
179
|
+
}
|
|
180
|
+
if (error.code === "SERVER_ERROR") {
|
|
181
|
+
return true;
|
|
182
|
+
}
|
|
183
|
+
return false;
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* Handle GraphQL errors
|
|
187
|
+
*/
|
|
188
|
+
handleError(error) {
|
|
189
|
+
if (error instanceof Error) {
|
|
190
|
+
if (error.message.includes("fetch failed") || error.message.includes("ECONNREFUSED")) {
|
|
191
|
+
return new NetworkError("Network error while connecting to API", {
|
|
192
|
+
originalError: error.message
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
if (error.message.includes("timeout") || error.message.includes("ETIMEDOUT")) {
|
|
196
|
+
return new TimeoutError("Request timed out", {
|
|
197
|
+
originalError: error.message
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
if ("response" in error && error.response) {
|
|
201
|
+
const response = error.response;
|
|
202
|
+
if (response.errors && response.errors.length > 0) {
|
|
203
|
+
const firstError = response.errors[0];
|
|
204
|
+
if (firstError) {
|
|
205
|
+
return new APIError(firstError.message, {
|
|
206
|
+
graphqlErrors: response.errors,
|
|
207
|
+
extensions: firstError.extensions
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
return new APIError(error.message, {
|
|
213
|
+
originalError: error.message
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
return new APIError("Unknown error occurred", {
|
|
217
|
+
error: String(error)
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
/**
|
|
221
|
+
* Sleep utility for retry delays
|
|
222
|
+
*/
|
|
223
|
+
sleep(ms) {
|
|
224
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
225
|
+
}
|
|
226
|
+
/**
|
|
227
|
+
* Get current context
|
|
228
|
+
*/
|
|
229
|
+
getContext() {
|
|
230
|
+
return {
|
|
231
|
+
...this.defaultContext
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
};
|
|
235
|
+
var asyncLocalStorage = new AsyncLocalStorage();
|
|
236
|
+
var ContextManager = class {
|
|
237
|
+
static {
|
|
238
|
+
__name(this, "ContextManager");
|
|
239
|
+
}
|
|
240
|
+
/**
|
|
241
|
+
* Get current context from async local storage
|
|
242
|
+
*/
|
|
243
|
+
static getContext() {
|
|
244
|
+
return asyncLocalStorage.getStore();
|
|
245
|
+
}
|
|
246
|
+
/**
|
|
247
|
+
* Set context for current async scope
|
|
248
|
+
* Merges with existing context if present
|
|
249
|
+
*/
|
|
250
|
+
static setContext(context) {
|
|
251
|
+
const currentContext = this.getContext() || {};
|
|
252
|
+
asyncLocalStorage.enterWith({
|
|
253
|
+
...currentContext,
|
|
254
|
+
...context
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
/**
|
|
258
|
+
* Run function with specific context
|
|
259
|
+
* Context is isolated to this execution and its children
|
|
260
|
+
*/
|
|
261
|
+
static async withContext(context, fn) {
|
|
262
|
+
return asyncLocalStorage.run(context, fn);
|
|
263
|
+
}
|
|
264
|
+
/**
|
|
265
|
+
* Run synchronous function with specific context
|
|
266
|
+
*/
|
|
267
|
+
static withContextSync(context, fn) {
|
|
268
|
+
return asyncLocalStorage.run(context, fn);
|
|
269
|
+
}
|
|
270
|
+
/**
|
|
271
|
+
* Clear current context
|
|
272
|
+
*/
|
|
273
|
+
static clearContext() {
|
|
274
|
+
asyncLocalStorage.disable();
|
|
275
|
+
}
|
|
276
|
+
/**
|
|
277
|
+
* Get actor ID from context
|
|
278
|
+
*/
|
|
279
|
+
static getActorId() {
|
|
280
|
+
return this.getContext()?.actorId;
|
|
281
|
+
}
|
|
282
|
+
/**
|
|
283
|
+
* Get legacy user ID from context.
|
|
284
|
+
*/
|
|
285
|
+
static getUser() {
|
|
286
|
+
const context = this.getContext();
|
|
287
|
+
const userId = context?.["userId"];
|
|
288
|
+
return typeof userId === "string" ? userId : context?.actorId;
|
|
289
|
+
}
|
|
290
|
+
/**
|
|
291
|
+
* Get workspace ID from context
|
|
292
|
+
*/
|
|
293
|
+
static getWorkspace() {
|
|
294
|
+
return this.getContext()?.workspaceId;
|
|
295
|
+
}
|
|
296
|
+
/**
|
|
297
|
+
* Get tenant ID from context
|
|
298
|
+
*/
|
|
299
|
+
static getTenant() {
|
|
300
|
+
return this.getContext()?.tenantId;
|
|
301
|
+
}
|
|
302
|
+
/**
|
|
303
|
+
* Get legacy organization ID from context.
|
|
304
|
+
*/
|
|
305
|
+
static getOrganization() {
|
|
306
|
+
const organizationId = this.getContext()?.["organizationId"];
|
|
307
|
+
return typeof organizationId === "string" ? organizationId : void 0;
|
|
308
|
+
}
|
|
309
|
+
/**
|
|
310
|
+
* Get project ID from context
|
|
311
|
+
*/
|
|
312
|
+
static getProject() {
|
|
313
|
+
return this.getContext()?.projectId;
|
|
314
|
+
}
|
|
315
|
+
/**
|
|
316
|
+
* Get app ID from context
|
|
317
|
+
*/
|
|
318
|
+
static getApp() {
|
|
319
|
+
return this.getContext()?.appId;
|
|
320
|
+
}
|
|
321
|
+
/**
|
|
322
|
+
* Get JWT token from context
|
|
323
|
+
*/
|
|
324
|
+
static getToken() {
|
|
325
|
+
return this.getContext()?.token;
|
|
326
|
+
}
|
|
327
|
+
/**
|
|
328
|
+
* Get product ID from context
|
|
329
|
+
*/
|
|
330
|
+
static getProduct() {
|
|
331
|
+
return this.getContext()?.productId;
|
|
332
|
+
}
|
|
333
|
+
/**
|
|
334
|
+
* Update specific context fields
|
|
335
|
+
*/
|
|
336
|
+
static updateContext(updates) {
|
|
337
|
+
const current = this.getContext();
|
|
338
|
+
if (current) {
|
|
339
|
+
this.setContext({
|
|
340
|
+
...current,
|
|
341
|
+
...updates
|
|
342
|
+
});
|
|
343
|
+
} else {
|
|
344
|
+
this.setContext(updates);
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
/**
|
|
348
|
+
* Check if context exists
|
|
349
|
+
*/
|
|
350
|
+
static hasContext() {
|
|
351
|
+
return this.getContext() !== void 0;
|
|
352
|
+
}
|
|
353
|
+
/**
|
|
354
|
+
* Get custom field from context
|
|
355
|
+
*/
|
|
356
|
+
static getCustomField(key) {
|
|
357
|
+
return this.getContext()?.[key];
|
|
358
|
+
}
|
|
359
|
+
/**
|
|
360
|
+
* Set custom field in context
|
|
361
|
+
*/
|
|
362
|
+
static setCustomField(key, value) {
|
|
363
|
+
const current = this.getContext() || {};
|
|
364
|
+
this.setContext({
|
|
365
|
+
...current,
|
|
366
|
+
[key]: value
|
|
367
|
+
});
|
|
368
|
+
}
|
|
369
|
+
};
|
|
370
|
+
|
|
371
|
+
// src/search/index.ts
|
|
372
|
+
var SearchModule = class {
|
|
373
|
+
static {
|
|
374
|
+
__name(this, "SearchModule");
|
|
375
|
+
}
|
|
376
|
+
client;
|
|
377
|
+
context;
|
|
378
|
+
constructor(client, context) {
|
|
379
|
+
this.client = client;
|
|
380
|
+
this.context = context;
|
|
381
|
+
}
|
|
382
|
+
/**
|
|
383
|
+
* Search across all resources
|
|
384
|
+
*/
|
|
385
|
+
async search(options) {
|
|
386
|
+
if (!options.query || options.query.trim().length === 0) {
|
|
387
|
+
throw new ValidationError("Search query is required");
|
|
388
|
+
}
|
|
389
|
+
logger.debug("[Search] Searching", {
|
|
390
|
+
query: options.query,
|
|
391
|
+
resourceTypes: options.resourceTypes
|
|
392
|
+
});
|
|
393
|
+
const query = `
|
|
394
|
+
query Search($input: SearchInput!) {
|
|
395
|
+
search(input: $input) {
|
|
396
|
+
results {
|
|
397
|
+
id
|
|
398
|
+
type
|
|
399
|
+
title
|
|
400
|
+
description
|
|
401
|
+
data
|
|
402
|
+
workspaceId
|
|
403
|
+
organizationId
|
|
404
|
+
projectId
|
|
405
|
+
score
|
|
406
|
+
highlights
|
|
407
|
+
createdAt
|
|
408
|
+
updatedAt
|
|
409
|
+
}
|
|
410
|
+
total
|
|
411
|
+
took
|
|
412
|
+
aggregations
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
`;
|
|
416
|
+
const variables = {
|
|
417
|
+
input: {
|
|
418
|
+
query: options.query,
|
|
419
|
+
resourceTypes: options.resourceTypes,
|
|
420
|
+
workspaceId: options.workspaceId || this.context.workspaceId,
|
|
421
|
+
organizationId: options.organizationId,
|
|
422
|
+
projectId: options.projectId || this.context.projectId,
|
|
423
|
+
fields: options.fields,
|
|
424
|
+
fuzziness: options.fuzziness || 0.8,
|
|
425
|
+
boosts: options.boosts,
|
|
426
|
+
filters: options.filters,
|
|
427
|
+
sort: options.sort,
|
|
428
|
+
limit: options.limit || 20,
|
|
429
|
+
offset: options.offset || 0,
|
|
430
|
+
highlight: options.highlight !== false
|
|
431
|
+
}
|
|
432
|
+
};
|
|
433
|
+
const response = await this.client.query(query, variables, this.context);
|
|
434
|
+
logger.info("[Search] Search completed", {
|
|
435
|
+
total: response.search.total,
|
|
436
|
+
took: response.search.took
|
|
437
|
+
});
|
|
438
|
+
return response.search;
|
|
439
|
+
}
|
|
440
|
+
/**
|
|
441
|
+
* Index document for search
|
|
442
|
+
*/
|
|
443
|
+
async indexDocument(options) {
|
|
444
|
+
if (!options.id) {
|
|
445
|
+
throw new ValidationError("Document ID is required");
|
|
446
|
+
}
|
|
447
|
+
if (!options.type) {
|
|
448
|
+
throw new ValidationError("Document type is required");
|
|
449
|
+
}
|
|
450
|
+
if (!options.title) {
|
|
451
|
+
throw new ValidationError("Document title is required");
|
|
452
|
+
}
|
|
453
|
+
logger.debug("[Search] Indexing document", {
|
|
454
|
+
id: options.id,
|
|
455
|
+
type: options.type
|
|
456
|
+
});
|
|
457
|
+
const mutation = `
|
|
458
|
+
mutation IndexDocument($input: IndexDocumentInput!) {
|
|
459
|
+
indexDocument(input: $input) {
|
|
460
|
+
id
|
|
461
|
+
indexed
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
`;
|
|
465
|
+
const variables = {
|
|
466
|
+
input: {
|
|
467
|
+
id: options.id,
|
|
468
|
+
type: options.type,
|
|
469
|
+
title: options.title,
|
|
470
|
+
description: options.description,
|
|
471
|
+
content: options.content,
|
|
472
|
+
data: options.data,
|
|
473
|
+
workspaceId: options.workspaceId || this.context.workspaceId,
|
|
474
|
+
organizationId: options.organizationId,
|
|
475
|
+
projectId: options.projectId || this.context.projectId,
|
|
476
|
+
tags: options.tags,
|
|
477
|
+
metadata: options.metadata
|
|
478
|
+
}
|
|
479
|
+
};
|
|
480
|
+
const response = await this.client.query(mutation, variables, this.context);
|
|
481
|
+
logger.info("[Search] Document indexed", {
|
|
482
|
+
id: response.indexDocument.id
|
|
483
|
+
});
|
|
484
|
+
return response.indexDocument;
|
|
485
|
+
}
|
|
486
|
+
/**
|
|
487
|
+
* Remove document from search index
|
|
488
|
+
*/
|
|
489
|
+
async removeDocument(id, type) {
|
|
490
|
+
logger.debug("[Search] Removing document", {
|
|
491
|
+
id,
|
|
492
|
+
type
|
|
493
|
+
});
|
|
494
|
+
const mutation = `
|
|
495
|
+
mutation RemoveDocument($id: ID!, $type: String!) {
|
|
496
|
+
removeDocument(id: $id, type: $type) {
|
|
497
|
+
success
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
`;
|
|
501
|
+
await this.client.query(mutation, {
|
|
502
|
+
id,
|
|
503
|
+
type
|
|
504
|
+
}, this.context);
|
|
505
|
+
logger.info("[Search] Document removed", {
|
|
506
|
+
id
|
|
507
|
+
});
|
|
508
|
+
return true;
|
|
509
|
+
}
|
|
510
|
+
/**
|
|
511
|
+
* Update document in search index
|
|
512
|
+
*/
|
|
513
|
+
async updateDocument(id, type, updates) {
|
|
514
|
+
logger.debug("[Search] Updating document", {
|
|
515
|
+
id,
|
|
516
|
+
type
|
|
517
|
+
});
|
|
518
|
+
const mutation = `
|
|
519
|
+
mutation UpdateDocument($id: ID!, $type: String!, $updates: JSON!) {
|
|
520
|
+
updateDocument(id: $id, type: $type, updates: $updates) {
|
|
521
|
+
id
|
|
522
|
+
updated
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
`;
|
|
526
|
+
const response = await this.client.query(mutation, {
|
|
527
|
+
id,
|
|
528
|
+
type,
|
|
529
|
+
updates
|
|
530
|
+
}, this.context);
|
|
531
|
+
logger.info("[Search] Document updated", {
|
|
532
|
+
id
|
|
533
|
+
});
|
|
534
|
+
return response.updateDocument;
|
|
535
|
+
}
|
|
536
|
+
/**
|
|
537
|
+
* Bulk index documents
|
|
538
|
+
*/
|
|
539
|
+
async indexDocumentsBulk(documents) {
|
|
540
|
+
logger.debug("[Search] Bulk indexing documents", {
|
|
541
|
+
count: documents.length
|
|
542
|
+
});
|
|
543
|
+
const mutation = `
|
|
544
|
+
mutation IndexDocumentsBulk($documents: [IndexDocumentInput!]!) {
|
|
545
|
+
indexDocumentsBulk(documents: $documents) {
|
|
546
|
+
indexed
|
|
547
|
+
failed
|
|
548
|
+
errors
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
`;
|
|
552
|
+
const response = await this.client.query(mutation, {
|
|
553
|
+
documents
|
|
554
|
+
}, this.context);
|
|
555
|
+
logger.info("[Search] Bulk indexing completed", {
|
|
556
|
+
indexed: response.indexDocumentsBulk.indexed,
|
|
557
|
+
failed: response.indexDocumentsBulk.failed
|
|
558
|
+
});
|
|
559
|
+
return response.indexDocumentsBulk;
|
|
560
|
+
}
|
|
561
|
+
/**
|
|
562
|
+
* Get search suggestions (autocomplete)
|
|
563
|
+
*/
|
|
564
|
+
async suggest(prefix, options) {
|
|
565
|
+
if (!prefix || prefix.trim().length === 0) {
|
|
566
|
+
throw new ValidationError("Prefix is required for suggestions");
|
|
567
|
+
}
|
|
568
|
+
logger.debug("[Search] Getting suggestions", {
|
|
569
|
+
prefix
|
|
570
|
+
});
|
|
571
|
+
const query = `
|
|
572
|
+
query GetSuggestions($prefix: String!, $resourceTypes: [String!], $limit: Int) {
|
|
573
|
+
getSuggestions(prefix: $prefix, resourceTypes: $resourceTypes, limit: $limit) {
|
|
574
|
+
text
|
|
575
|
+
score
|
|
576
|
+
type
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
`;
|
|
580
|
+
const response = await this.client.query(query, {
|
|
581
|
+
prefix,
|
|
582
|
+
resourceTypes: options?.resourceTypes,
|
|
583
|
+
limit: options?.limit || 10
|
|
584
|
+
}, this.context);
|
|
585
|
+
return response.getSuggestions;
|
|
586
|
+
}
|
|
587
|
+
/**
|
|
588
|
+
* Get related documents
|
|
589
|
+
*/
|
|
590
|
+
async getRelated(documentId, options) {
|
|
591
|
+
logger.debug("[Search] Getting related documents", {
|
|
592
|
+
documentId
|
|
593
|
+
});
|
|
594
|
+
const query = `
|
|
595
|
+
query GetRelatedDocuments($documentId: ID!, $limit: Int, $minScore: Float) {
|
|
596
|
+
getRelatedDocuments(documentId: $documentId, limit: $limit, minScore: $minScore) {
|
|
597
|
+
id
|
|
598
|
+
type
|
|
599
|
+
title
|
|
600
|
+
description
|
|
601
|
+
data
|
|
602
|
+
score
|
|
603
|
+
createdAt
|
|
604
|
+
updatedAt
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
`;
|
|
608
|
+
const response = await this.client.query(query, {
|
|
609
|
+
documentId,
|
|
610
|
+
limit: options?.limit || 10,
|
|
611
|
+
minScore: options?.minScore || 0.5
|
|
612
|
+
}, this.context);
|
|
613
|
+
return response.getRelatedDocuments;
|
|
614
|
+
}
|
|
615
|
+
/**
|
|
616
|
+
* Get search aggregations
|
|
617
|
+
*/
|
|
618
|
+
async aggregate(query, aggregations) {
|
|
619
|
+
logger.debug("[Search] Getting aggregations", {
|
|
620
|
+
query
|
|
621
|
+
});
|
|
622
|
+
const gqlQuery = `
|
|
623
|
+
query GetAggregations($query: String!, $aggregations: [AggregationInput!]!) {
|
|
624
|
+
getAggregations(query: $query, aggregations: $aggregations) {
|
|
625
|
+
aggregations
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
`;
|
|
629
|
+
const response = await this.client.query(gqlQuery, {
|
|
630
|
+
query,
|
|
631
|
+
aggregations
|
|
632
|
+
}, this.context);
|
|
633
|
+
return response.getAggregations.aggregations;
|
|
634
|
+
}
|
|
635
|
+
/**
|
|
636
|
+
* Clear search index for workspace
|
|
637
|
+
*/
|
|
638
|
+
async clearIndex(workspaceId) {
|
|
639
|
+
logger.debug("[Search] Clearing index", {
|
|
640
|
+
workspaceId
|
|
641
|
+
});
|
|
642
|
+
const mutation = `
|
|
643
|
+
mutation ClearSearchIndex($workspaceId: ID) {
|
|
644
|
+
clearSearchIndex(workspaceId: $workspaceId) {
|
|
645
|
+
deletedCount
|
|
646
|
+
}
|
|
647
|
+
}
|
|
648
|
+
`;
|
|
649
|
+
const response = await this.client.query(mutation, {
|
|
650
|
+
workspaceId: workspaceId || this.context.workspaceId
|
|
651
|
+
}, this.context);
|
|
652
|
+
logger.info("[Search] Index cleared", {
|
|
653
|
+
deletedCount: response.clearSearchIndex.deletedCount
|
|
654
|
+
});
|
|
655
|
+
return response.clearSearchIndex;
|
|
656
|
+
}
|
|
657
|
+
/**
|
|
658
|
+
* Reindex all documents
|
|
659
|
+
*/
|
|
660
|
+
async reindex(options) {
|
|
661
|
+
logger.debug("[Search] Reindexing", options);
|
|
662
|
+
const mutation = `
|
|
663
|
+
mutation ReindexDocuments($workspaceId: ID, $resourceTypes: [String!]) {
|
|
664
|
+
reindexDocuments(workspaceId: $workspaceId, resourceTypes: $resourceTypes) {
|
|
665
|
+
indexed
|
|
666
|
+
failed
|
|
667
|
+
}
|
|
668
|
+
}
|
|
669
|
+
`;
|
|
670
|
+
const response = await this.client.query(mutation, {
|
|
671
|
+
workspaceId: options?.workspaceId || this.context.workspaceId,
|
|
672
|
+
resourceTypes: options?.resourceTypes
|
|
673
|
+
}, this.context);
|
|
674
|
+
logger.info("[Search] Reindexing completed", response.reindexDocuments);
|
|
675
|
+
return response.reindexDocuments;
|
|
676
|
+
}
|
|
677
|
+
};
|
|
678
|
+
|
|
679
|
+
// src/translation/index.ts
|
|
680
|
+
var TranslationModule = class {
|
|
681
|
+
static {
|
|
682
|
+
__name(this, "TranslationModule");
|
|
683
|
+
}
|
|
684
|
+
client;
|
|
685
|
+
context;
|
|
686
|
+
constructor(client, context) {
|
|
687
|
+
this.client = client;
|
|
688
|
+
this.context = context;
|
|
689
|
+
}
|
|
690
|
+
/**
|
|
691
|
+
* Translate text
|
|
692
|
+
*/
|
|
693
|
+
async translate(options) {
|
|
694
|
+
if (!options.text || options.text.trim().length === 0) {
|
|
695
|
+
throw new ValidationError("Text to translate is required");
|
|
696
|
+
}
|
|
697
|
+
if (!options.targetLanguage) {
|
|
698
|
+
throw new ValidationError("Target language is required");
|
|
699
|
+
}
|
|
700
|
+
logger.debug("[Translation] Translating text", {
|
|
701
|
+
sourceLanguage: options.sourceLanguage || "auto",
|
|
702
|
+
targetLanguage: options.targetLanguage,
|
|
703
|
+
textLength: options.text.length
|
|
704
|
+
});
|
|
705
|
+
const query = `
|
|
706
|
+
query Translate($input: TranslateInput!) {
|
|
707
|
+
translate(input: $input) {
|
|
708
|
+
original
|
|
709
|
+
translated
|
|
710
|
+
sourceLanguage
|
|
711
|
+
targetLanguage
|
|
712
|
+
confidence
|
|
713
|
+
}
|
|
714
|
+
}
|
|
715
|
+
`;
|
|
716
|
+
const variables = {
|
|
717
|
+
input: {
|
|
718
|
+
text: options.text,
|
|
719
|
+
sourceLanguage: options.sourceLanguage,
|
|
720
|
+
targetLanguage: options.targetLanguage,
|
|
721
|
+
context: options.context
|
|
722
|
+
}
|
|
723
|
+
};
|
|
724
|
+
const response = await this.client.query(query, variables, this.context);
|
|
725
|
+
let translated = response.translate.translated;
|
|
726
|
+
if (options.variables) {
|
|
727
|
+
translated = this.interpolate(translated, options.variables);
|
|
728
|
+
}
|
|
729
|
+
logger.info("[Translation] Text translated", {
|
|
730
|
+
sourceLanguage: response.translate.sourceLanguage,
|
|
731
|
+
targetLanguage: response.translate.targetLanguage
|
|
732
|
+
});
|
|
733
|
+
return {
|
|
734
|
+
...response.translate,
|
|
735
|
+
translated
|
|
736
|
+
};
|
|
737
|
+
}
|
|
738
|
+
/**
|
|
739
|
+
* Get translations for keys
|
|
740
|
+
*/
|
|
741
|
+
async getTranslations(options) {
|
|
742
|
+
if (!options.keys || options.keys.length === 0) {
|
|
743
|
+
throw new ValidationError("Translation keys are required");
|
|
744
|
+
}
|
|
745
|
+
if (!options.language) {
|
|
746
|
+
throw new ValidationError("Language is required");
|
|
747
|
+
}
|
|
748
|
+
logger.debug("[Translation] Getting translations", {
|
|
749
|
+
language: options.language,
|
|
750
|
+
count: options.keys.length
|
|
751
|
+
});
|
|
752
|
+
const query = `
|
|
753
|
+
query GetTranslations(
|
|
754
|
+
$keys: [String!]!
|
|
755
|
+
$language: String!
|
|
756
|
+
$namespace: String
|
|
757
|
+
$fallbackLanguage: String
|
|
758
|
+
) {
|
|
759
|
+
getTranslations(
|
|
760
|
+
keys: $keys
|
|
761
|
+
language: $language
|
|
762
|
+
namespace: $namespace
|
|
763
|
+
fallbackLanguage: $fallbackLanguage
|
|
764
|
+
) {
|
|
765
|
+
translations
|
|
766
|
+
}
|
|
767
|
+
}
|
|
768
|
+
`;
|
|
769
|
+
const response = await this.client.query(query, {
|
|
770
|
+
keys: options.keys,
|
|
771
|
+
language: options.language,
|
|
772
|
+
namespace: options.namespace,
|
|
773
|
+
fallbackLanguage: options.fallbackLanguage || "en"
|
|
774
|
+
}, this.context);
|
|
775
|
+
return response.getTranslations.translations;
|
|
776
|
+
}
|
|
777
|
+
/**
|
|
778
|
+
* Get single translation
|
|
779
|
+
*/
|
|
780
|
+
async get(key, language, variables) {
|
|
781
|
+
const translations = await this.getTranslations({
|
|
782
|
+
keys: [
|
|
783
|
+
key
|
|
784
|
+
],
|
|
785
|
+
language
|
|
786
|
+
});
|
|
787
|
+
let translation = translations[key] || key;
|
|
788
|
+
if (variables) {
|
|
789
|
+
translation = this.interpolate(translation, variables);
|
|
790
|
+
}
|
|
791
|
+
return translation;
|
|
792
|
+
}
|
|
793
|
+
/**
|
|
794
|
+
* Set translation
|
|
795
|
+
*/
|
|
796
|
+
async setTranslation(options) {
|
|
797
|
+
if (!options.key) {
|
|
798
|
+
throw new ValidationError("Translation key is required");
|
|
799
|
+
}
|
|
800
|
+
if (!options.value) {
|
|
801
|
+
throw new ValidationError("Translation value is required");
|
|
802
|
+
}
|
|
803
|
+
if (!options.language) {
|
|
804
|
+
throw new ValidationError("Language is required");
|
|
805
|
+
}
|
|
806
|
+
logger.debug("[Translation] Setting translation", {
|
|
807
|
+
key: options.key,
|
|
808
|
+
language: options.language
|
|
809
|
+
});
|
|
810
|
+
const mutation = `
|
|
811
|
+
mutation SetTranslation($input: SetTranslationInput!) {
|
|
812
|
+
setTranslation(input: $input) {
|
|
813
|
+
key
|
|
814
|
+
value
|
|
815
|
+
language
|
|
816
|
+
namespace
|
|
817
|
+
description
|
|
818
|
+
variables
|
|
819
|
+
}
|
|
820
|
+
}
|
|
821
|
+
`;
|
|
822
|
+
const variables = {
|
|
823
|
+
input: {
|
|
824
|
+
key: options.key,
|
|
825
|
+
value: options.value,
|
|
826
|
+
language: options.language,
|
|
827
|
+
namespace: options.namespace,
|
|
828
|
+
description: options.description,
|
|
829
|
+
variables: options.variables
|
|
830
|
+
}
|
|
831
|
+
};
|
|
832
|
+
const response = await this.client.query(mutation, variables, this.context);
|
|
833
|
+
logger.info("[Translation] Translation set", {
|
|
834
|
+
key: options.key,
|
|
835
|
+
language: options.language
|
|
836
|
+
});
|
|
837
|
+
return response.setTranslation;
|
|
838
|
+
}
|
|
839
|
+
/**
|
|
840
|
+
* Delete translation
|
|
841
|
+
*/
|
|
842
|
+
async deleteTranslation(key, language, namespace) {
|
|
843
|
+
logger.debug("[Translation] Deleting translation", {
|
|
844
|
+
key,
|
|
845
|
+
language
|
|
846
|
+
});
|
|
847
|
+
const mutation = `
|
|
848
|
+
mutation DeleteTranslation($key: String!, $language: String!, $namespace: String) {
|
|
849
|
+
deleteTranslation(key: $key, language: $language, namespace: $namespace) {
|
|
850
|
+
success
|
|
851
|
+
}
|
|
852
|
+
}
|
|
853
|
+
`;
|
|
854
|
+
await this.client.query(mutation, {
|
|
855
|
+
key,
|
|
856
|
+
language,
|
|
857
|
+
namespace
|
|
858
|
+
}, this.context);
|
|
859
|
+
logger.info("[Translation] Translation deleted", {
|
|
860
|
+
key,
|
|
861
|
+
language
|
|
862
|
+
});
|
|
863
|
+
return true;
|
|
864
|
+
}
|
|
865
|
+
/**
|
|
866
|
+
* Batch set translations
|
|
867
|
+
*/
|
|
868
|
+
async setTranslationsBatch(translations) {
|
|
869
|
+
logger.debug("[Translation] Batch setting translations", {
|
|
870
|
+
count: translations.length
|
|
871
|
+
});
|
|
872
|
+
const mutation = `
|
|
873
|
+
mutation SetTranslationsBatch($translations: [SetTranslationInput!]!) {
|
|
874
|
+
setTranslationsBatch(translations: $translations) {
|
|
875
|
+
key
|
|
876
|
+
value
|
|
877
|
+
language
|
|
878
|
+
namespace
|
|
879
|
+
description
|
|
880
|
+
variables
|
|
881
|
+
}
|
|
882
|
+
}
|
|
883
|
+
`;
|
|
884
|
+
const response = await this.client.query(mutation, {
|
|
885
|
+
translations
|
|
886
|
+
}, this.context);
|
|
887
|
+
logger.info("[Translation] Batch translations set", {
|
|
888
|
+
count: response.setTranslationsBatch.length
|
|
889
|
+
});
|
|
890
|
+
return response.setTranslationsBatch;
|
|
891
|
+
}
|
|
892
|
+
/**
|
|
893
|
+
* Get all translations for language
|
|
894
|
+
*/
|
|
895
|
+
async getAllTranslations(language, namespace) {
|
|
896
|
+
logger.debug("[Translation] Getting all translations", {
|
|
897
|
+
language,
|
|
898
|
+
namespace
|
|
899
|
+
});
|
|
900
|
+
const query = `
|
|
901
|
+
query GetAllTranslations($language: String!, $namespace: String) {
|
|
902
|
+
getAllTranslations(language: $language, namespace: $namespace) {
|
|
903
|
+
key
|
|
904
|
+
value
|
|
905
|
+
language
|
|
906
|
+
namespace
|
|
907
|
+
description
|
|
908
|
+
variables
|
|
909
|
+
}
|
|
910
|
+
}
|
|
911
|
+
`;
|
|
912
|
+
const response = await this.client.query(query, {
|
|
913
|
+
language,
|
|
914
|
+
namespace
|
|
915
|
+
}, this.context);
|
|
916
|
+
return response.getAllTranslations;
|
|
917
|
+
}
|
|
918
|
+
/**
|
|
919
|
+
* Detect language
|
|
920
|
+
*/
|
|
921
|
+
async detectLanguage(text) {
|
|
922
|
+
if (!text || text.trim().length === 0) {
|
|
923
|
+
throw new ValidationError("Text is required for language detection");
|
|
924
|
+
}
|
|
925
|
+
logger.debug("[Translation] Detecting language");
|
|
926
|
+
const query = `
|
|
927
|
+
query DetectLanguage($text: String!) {
|
|
928
|
+
detectLanguage(text: $text) {
|
|
929
|
+
language
|
|
930
|
+
confidence
|
|
931
|
+
}
|
|
932
|
+
}
|
|
933
|
+
`;
|
|
934
|
+
const response = await this.client.query(query, {
|
|
935
|
+
text
|
|
936
|
+
}, this.context);
|
|
937
|
+
return response.detectLanguage;
|
|
938
|
+
}
|
|
939
|
+
/**
|
|
940
|
+
* Get supported languages
|
|
941
|
+
*/
|
|
942
|
+
async getSupportedLanguages() {
|
|
943
|
+
logger.debug("[Translation] Getting supported languages");
|
|
944
|
+
const query = `
|
|
945
|
+
query GetSupportedLanguages {
|
|
946
|
+
getSupportedLanguages {
|
|
947
|
+
code
|
|
948
|
+
name
|
|
949
|
+
nativeName
|
|
950
|
+
}
|
|
951
|
+
}
|
|
952
|
+
`;
|
|
953
|
+
const response = await this.client.query(query, {}, this.context);
|
|
954
|
+
return response.getSupportedLanguages;
|
|
955
|
+
}
|
|
956
|
+
/**
|
|
957
|
+
* Import translations from file
|
|
958
|
+
*/
|
|
959
|
+
async importTranslations(language, translations, namespace) {
|
|
960
|
+
logger.debug("[Translation] Importing translations", {
|
|
961
|
+
language,
|
|
962
|
+
count: Object.keys(translations).length
|
|
963
|
+
});
|
|
964
|
+
const mutation = `
|
|
965
|
+
mutation ImportTranslations($language: String!, $translations: JSON!, $namespace: String) {
|
|
966
|
+
importTranslations(language: $language, translations: $translations, namespace: $namespace) {
|
|
967
|
+
imported
|
|
968
|
+
failed
|
|
969
|
+
}
|
|
970
|
+
}
|
|
971
|
+
`;
|
|
972
|
+
const response = await this.client.query(mutation, {
|
|
973
|
+
language,
|
|
974
|
+
translations,
|
|
975
|
+
namespace
|
|
976
|
+
}, this.context);
|
|
977
|
+
logger.info("[Translation] Translations imported", response.importTranslations);
|
|
978
|
+
return response.importTranslations;
|
|
979
|
+
}
|
|
980
|
+
/**
|
|
981
|
+
* Export translations to JSON
|
|
982
|
+
*/
|
|
983
|
+
async exportTranslations(language, namespace) {
|
|
984
|
+
logger.debug("[Translation] Exporting translations", {
|
|
985
|
+
language,
|
|
986
|
+
namespace
|
|
987
|
+
});
|
|
988
|
+
const query = `
|
|
989
|
+
query ExportTranslations($language: String!, $namespace: String) {
|
|
990
|
+
exportTranslations(language: $language, namespace: $namespace) {
|
|
991
|
+
translations
|
|
992
|
+
}
|
|
993
|
+
}
|
|
994
|
+
`;
|
|
995
|
+
const response = await this.client.query(query, {
|
|
996
|
+
language,
|
|
997
|
+
namespace
|
|
998
|
+
}, this.context);
|
|
999
|
+
logger.info("[Translation] Translations exported", {
|
|
1000
|
+
language,
|
|
1001
|
+
count: Object.keys(response.exportTranslations.translations).length
|
|
1002
|
+
});
|
|
1003
|
+
return response.exportTranslations.translations;
|
|
1004
|
+
}
|
|
1005
|
+
/**
|
|
1006
|
+
* Export product translations from global i18n service (federated schema).
|
|
1007
|
+
*
|
|
1008
|
+
* This uses the modern i18n GraphQL contract:
|
|
1009
|
+
* - i18nProductByName
|
|
1010
|
+
* - exportTranslations(productId, languageCode, format)
|
|
1011
|
+
*/
|
|
1012
|
+
async exportProductTranslations(options) {
|
|
1013
|
+
const productId = await this.resolveProductId(options.productId, options.productName);
|
|
1014
|
+
const languageCode = options.languageCode ?? "en";
|
|
1015
|
+
const format = options.format ?? "JSON";
|
|
1016
|
+
const query = `
|
|
1017
|
+
query ExportProductTranslations(
|
|
1018
|
+
$productId: String!
|
|
1019
|
+
$languageCode: String!
|
|
1020
|
+
$format: ImportJobFormat!
|
|
1021
|
+
) {
|
|
1022
|
+
exportTranslations(productId: $productId, languageCode: $languageCode, format: $format) {
|
|
1023
|
+
format
|
|
1024
|
+
url
|
|
1025
|
+
}
|
|
1026
|
+
}
|
|
1027
|
+
`;
|
|
1028
|
+
const response = await this.client.query(query, {
|
|
1029
|
+
productId,
|
|
1030
|
+
languageCode,
|
|
1031
|
+
format
|
|
1032
|
+
}, this.context);
|
|
1033
|
+
const result = {
|
|
1034
|
+
format: response.exportTranslations.format,
|
|
1035
|
+
url: response.exportTranslations.url
|
|
1036
|
+
};
|
|
1037
|
+
if ((options.decodeJson ?? true) && format === "JSON") {
|
|
1038
|
+
result.translations = this.decodeDataUrlJson(response.exportTranslations.url);
|
|
1039
|
+
}
|
|
1040
|
+
return result;
|
|
1041
|
+
}
|
|
1042
|
+
/**
|
|
1043
|
+
* Upsert product translations in global i18n service.
|
|
1044
|
+
*
|
|
1045
|
+
* Behavior:
|
|
1046
|
+
* - Ensures translation keys exist (create if missing)
|
|
1047
|
+
* - Optionally updates translation key defaultValue
|
|
1048
|
+
* - Creates/updates language translation entries
|
|
1049
|
+
* - Optionally publishes translations
|
|
1050
|
+
*/
|
|
1051
|
+
async upsertProductTranslations(options) {
|
|
1052
|
+
if (!options.languageCode) {
|
|
1053
|
+
throw new ValidationError("languageCode is required");
|
|
1054
|
+
}
|
|
1055
|
+
const flatTranslations = this.flattenTranslationTree(options.translations);
|
|
1056
|
+
if (Object.keys(flatTranslations).length === 0) {
|
|
1057
|
+
throw new ValidationError("translations cannot be empty");
|
|
1058
|
+
}
|
|
1059
|
+
const flatDefaults = options.defaultValues ? this.flattenTranslationTree(options.defaultValues) : flatTranslations;
|
|
1060
|
+
const productId = await this.resolveProductId(options.productId, options.productName);
|
|
1061
|
+
const publish = options.publish ?? true;
|
|
1062
|
+
const updateExisting = options.updateExisting ?? true;
|
|
1063
|
+
const result = {
|
|
1064
|
+
productId,
|
|
1065
|
+
languageCode: options.languageCode,
|
|
1066
|
+
keysProcessed: 0,
|
|
1067
|
+
createdKeys: 0,
|
|
1068
|
+
updatedKeyDefaults: 0,
|
|
1069
|
+
createdTranslations: 0,
|
|
1070
|
+
updatedTranslations: 0
|
|
1071
|
+
};
|
|
1072
|
+
const entries = Object.entries(flatTranslations);
|
|
1073
|
+
const existingKeysMap = /* @__PURE__ */ new Map();
|
|
1074
|
+
for (const [key] of entries) {
|
|
1075
|
+
const keyRow = await this.findTranslationKeyByKey(productId, key);
|
|
1076
|
+
if (keyRow) {
|
|
1077
|
+
existingKeysMap.set(key, keyRow);
|
|
1078
|
+
}
|
|
1079
|
+
}
|
|
1080
|
+
for (const [key, value] of entries) {
|
|
1081
|
+
const defaultValue = flatDefaults[key] ?? value;
|
|
1082
|
+
let keyId = "";
|
|
1083
|
+
const keyRow = existingKeysMap.get(key);
|
|
1084
|
+
if (!keyRow) {
|
|
1085
|
+
const createdKey = await this.createTranslationKey(productId, key, defaultValue);
|
|
1086
|
+
keyId = createdKey.id;
|
|
1087
|
+
result.createdKeys += 1;
|
|
1088
|
+
} else {
|
|
1089
|
+
keyId = keyRow.id;
|
|
1090
|
+
if (defaultValue && keyRow.defaultValue !== defaultValue) {
|
|
1091
|
+
await this.updateTranslationKeyDefaultValue(keyId, defaultValue);
|
|
1092
|
+
result.updatedKeyDefaults += 1;
|
|
1093
|
+
}
|
|
1094
|
+
}
|
|
1095
|
+
const translationRow = await this.findGlobalTranslation(keyId, options.languageCode);
|
|
1096
|
+
if (!translationRow) {
|
|
1097
|
+
const createdTranslation = await this.createLanguageTranslation(keyId, options.languageCode, value);
|
|
1098
|
+
result.createdTranslations += 1;
|
|
1099
|
+
if (publish && createdTranslation.status !== "PUBLISHED") {
|
|
1100
|
+
await this.updateLanguageTranslation(createdTranslation.id, value, "PUBLISHED");
|
|
1101
|
+
}
|
|
1102
|
+
} else {
|
|
1103
|
+
const shouldUpdateValue = updateExisting && translationRow.value !== value;
|
|
1104
|
+
const shouldPublish = publish && translationRow.status !== "PUBLISHED";
|
|
1105
|
+
if (shouldUpdateValue || shouldPublish) {
|
|
1106
|
+
await this.updateLanguageTranslation(translationRow.id, shouldUpdateValue ? value : translationRow.value, publish ? "PUBLISHED" : translationRow.status);
|
|
1107
|
+
if (shouldUpdateValue) {
|
|
1108
|
+
result.updatedTranslations += 1;
|
|
1109
|
+
}
|
|
1110
|
+
}
|
|
1111
|
+
}
|
|
1112
|
+
result.keysProcessed += 1;
|
|
1113
|
+
}
|
|
1114
|
+
logger.info("[Translation] Product translations upsert completed", {
|
|
1115
|
+
productId,
|
|
1116
|
+
languageCode: options.languageCode,
|
|
1117
|
+
keysProcessed: result.keysProcessed,
|
|
1118
|
+
createdKeys: result.createdKeys,
|
|
1119
|
+
createdTranslations: result.createdTranslations,
|
|
1120
|
+
updatedTranslations: result.updatedTranslations
|
|
1121
|
+
});
|
|
1122
|
+
return result;
|
|
1123
|
+
}
|
|
1124
|
+
flattenTranslationTree(input, prefix = "") {
|
|
1125
|
+
const out = {};
|
|
1126
|
+
for (const [key, rawValue] of Object.entries(input)) {
|
|
1127
|
+
const nextKey = prefix ? `${prefix}.${key}` : key;
|
|
1128
|
+
if (typeof rawValue === "string") {
|
|
1129
|
+
out[nextKey] = rawValue;
|
|
1130
|
+
continue;
|
|
1131
|
+
}
|
|
1132
|
+
if (rawValue && typeof rawValue === "object") {
|
|
1133
|
+
Object.assign(out, this.flattenTranslationTree(rawValue, nextKey));
|
|
1134
|
+
}
|
|
1135
|
+
}
|
|
1136
|
+
return out;
|
|
1137
|
+
}
|
|
1138
|
+
decodeDataUrlJson(url) {
|
|
1139
|
+
if (!url.startsWith("data:")) {
|
|
1140
|
+
throw new ValidationError("Expected exportTranslations URL to be a data URL");
|
|
1141
|
+
}
|
|
1142
|
+
const commaIdx = url.indexOf(",");
|
|
1143
|
+
if (commaIdx === -1) {
|
|
1144
|
+
throw new ValidationError("Invalid data URL: missing comma separator");
|
|
1145
|
+
}
|
|
1146
|
+
const header = url.slice(0, commaIdx);
|
|
1147
|
+
const payload = url.slice(commaIdx + 1);
|
|
1148
|
+
if (!payload) {
|
|
1149
|
+
throw new ValidationError("Invalid data URL payload");
|
|
1150
|
+
}
|
|
1151
|
+
try {
|
|
1152
|
+
const decoded = header.includes("base64") ? Buffer.from(payload, "base64").toString("utf8") : decodeURIComponent(payload);
|
|
1153
|
+
return JSON.parse(decoded);
|
|
1154
|
+
} catch (error) {
|
|
1155
|
+
logger.error("[Translation] Failed to decode data URL JSON", {
|
|
1156
|
+
error,
|
|
1157
|
+
isBase64: header.includes("base64")
|
|
1158
|
+
});
|
|
1159
|
+
throw new ValidationError("Failed to decode JSON translation payload");
|
|
1160
|
+
}
|
|
1161
|
+
}
|
|
1162
|
+
async resolveProductId(productId, productName) {
|
|
1163
|
+
if (productId) {
|
|
1164
|
+
return productId;
|
|
1165
|
+
}
|
|
1166
|
+
if (!productName) {
|
|
1167
|
+
throw new ValidationError("Either productId or productName is required");
|
|
1168
|
+
}
|
|
1169
|
+
const query = `
|
|
1170
|
+
query GetI18nProductByName($name: String!) {
|
|
1171
|
+
i18nProductByName(name: $name) {
|
|
1172
|
+
id
|
|
1173
|
+
}
|
|
1174
|
+
}
|
|
1175
|
+
`;
|
|
1176
|
+
const response = await this.client.query(query, {
|
|
1177
|
+
name: productName
|
|
1178
|
+
}, this.context);
|
|
1179
|
+
if (!response.i18nProductByName?.id) {
|
|
1180
|
+
throw new ValidationError(`i18n product not found: ${productName}`);
|
|
1181
|
+
}
|
|
1182
|
+
return response.i18nProductByName.id;
|
|
1183
|
+
}
|
|
1184
|
+
async findTranslationKeyByKey(productId, key) {
|
|
1185
|
+
const query = `
|
|
1186
|
+
query FindTranslationKey($productId: String!, $key: String!) {
|
|
1187
|
+
translationKeyByKey(productId: $productId, key: $key) {
|
|
1188
|
+
id
|
|
1189
|
+
defaultValue
|
|
1190
|
+
}
|
|
1191
|
+
}
|
|
1192
|
+
`;
|
|
1193
|
+
const response = await this.client.query(query, {
|
|
1194
|
+
productId,
|
|
1195
|
+
key
|
|
1196
|
+
}, this.context);
|
|
1197
|
+
return response.translationKeyByKey;
|
|
1198
|
+
}
|
|
1199
|
+
async createTranslationKey(productId, key, defaultValue) {
|
|
1200
|
+
const mutation = `
|
|
1201
|
+
mutation CreateTranslationKey($input: CreateTranslationKeyInput!) {
|
|
1202
|
+
createTranslationKey(input: $input) {
|
|
1203
|
+
id
|
|
1204
|
+
}
|
|
1205
|
+
}
|
|
1206
|
+
`;
|
|
1207
|
+
const response = await this.client.query(mutation, {
|
|
1208
|
+
input: {
|
|
1209
|
+
productId,
|
|
1210
|
+
key,
|
|
1211
|
+
defaultValue
|
|
1212
|
+
}
|
|
1213
|
+
}, this.context);
|
|
1214
|
+
return response.createTranslationKey;
|
|
1215
|
+
}
|
|
1216
|
+
async updateTranslationKeyDefaultValue(id, defaultValue) {
|
|
1217
|
+
const mutation = `
|
|
1218
|
+
mutation UpdateTranslationKeyDefault($id: ID!, $input: UpdateTranslationKeyInput!) {
|
|
1219
|
+
updateTranslationKey(id: $id, input: $input) {
|
|
1220
|
+
id
|
|
1221
|
+
}
|
|
1222
|
+
}
|
|
1223
|
+
`;
|
|
1224
|
+
await this.client.query(mutation, {
|
|
1225
|
+
id,
|
|
1226
|
+
input: {
|
|
1227
|
+
defaultValue
|
|
1228
|
+
}
|
|
1229
|
+
}, this.context);
|
|
1230
|
+
}
|
|
1231
|
+
async findGlobalTranslation(keyId, languageCode) {
|
|
1232
|
+
const query = `
|
|
1233
|
+
query FindTranslation($filter: TranslationFilterInput) {
|
|
1234
|
+
translations(filter: $filter, limit: 50, offset: 0) {
|
|
1235
|
+
items {
|
|
1236
|
+
id
|
|
1237
|
+
value
|
|
1238
|
+
status
|
|
1239
|
+
tenantId
|
|
1240
|
+
}
|
|
1241
|
+
}
|
|
1242
|
+
}
|
|
1243
|
+
`;
|
|
1244
|
+
const response = await this.client.query(query, {
|
|
1245
|
+
filter: {
|
|
1246
|
+
keyId,
|
|
1247
|
+
languageCode
|
|
1248
|
+
}
|
|
1249
|
+
}, this.context);
|
|
1250
|
+
const globalTranslation = response.translations.items.find((item) => item.tenantId === null);
|
|
1251
|
+
if (!globalTranslation) {
|
|
1252
|
+
return null;
|
|
1253
|
+
}
|
|
1254
|
+
return {
|
|
1255
|
+
id: globalTranslation.id,
|
|
1256
|
+
value: globalTranslation.value,
|
|
1257
|
+
status: globalTranslation.status
|
|
1258
|
+
};
|
|
1259
|
+
}
|
|
1260
|
+
async createLanguageTranslation(keyId, languageCode, value) {
|
|
1261
|
+
const mutation = `
|
|
1262
|
+
mutation CreateTranslation($input: CreateTranslationInput!) {
|
|
1263
|
+
createTranslation(input: $input) {
|
|
1264
|
+
id
|
|
1265
|
+
status
|
|
1266
|
+
}
|
|
1267
|
+
}
|
|
1268
|
+
`;
|
|
1269
|
+
const response = await this.client.query(mutation, {
|
|
1270
|
+
input: {
|
|
1271
|
+
keyId,
|
|
1272
|
+
languageCode,
|
|
1273
|
+
value
|
|
1274
|
+
}
|
|
1275
|
+
}, this.context);
|
|
1276
|
+
return response.createTranslation;
|
|
1277
|
+
}
|
|
1278
|
+
async updateLanguageTranslation(id, value, status) {
|
|
1279
|
+
const mutation = `
|
|
1280
|
+
mutation UpdateTranslation($id: ID!, $input: UpdateTranslationInput!) {
|
|
1281
|
+
updateTranslation(id: $id, input: $input) {
|
|
1282
|
+
id
|
|
1283
|
+
status
|
|
1284
|
+
}
|
|
1285
|
+
}
|
|
1286
|
+
`;
|
|
1287
|
+
await this.client.query(mutation, {
|
|
1288
|
+
id,
|
|
1289
|
+
input: {
|
|
1290
|
+
value,
|
|
1291
|
+
status
|
|
1292
|
+
}
|
|
1293
|
+
}, this.context);
|
|
1294
|
+
}
|
|
1295
|
+
/**
|
|
1296
|
+
* Escape regex special characters in a string
|
|
1297
|
+
*/
|
|
1298
|
+
escapeRegex(str) {
|
|
1299
|
+
return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
1300
|
+
}
|
|
1301
|
+
/**
|
|
1302
|
+
* Interpolate variables in translation string
|
|
1303
|
+
*/
|
|
1304
|
+
interpolate(text, variables) {
|
|
1305
|
+
let result = text;
|
|
1306
|
+
for (const [key, value] of Object.entries(variables)) {
|
|
1307
|
+
const escapedKey = this.escapeRegex(key);
|
|
1308
|
+
const regex = new RegExp(`{{\\s*${escapedKey}\\s*}}`, "g");
|
|
1309
|
+
result = result.replace(regex, String(value));
|
|
1310
|
+
}
|
|
1311
|
+
return result;
|
|
1312
|
+
}
|
|
1313
|
+
/**
|
|
1314
|
+
* Format translation with pluralization
|
|
1315
|
+
*/
|
|
1316
|
+
formatPlural(_language, count, translations) {
|
|
1317
|
+
if (count === 0 && translations.zero) {
|
|
1318
|
+
return translations.zero;
|
|
1319
|
+
}
|
|
1320
|
+
if (count === 1) {
|
|
1321
|
+
return translations.one;
|
|
1322
|
+
}
|
|
1323
|
+
return translations.other;
|
|
1324
|
+
}
|
|
1325
|
+
/**
|
|
1326
|
+
* Format date/time for locale
|
|
1327
|
+
*/
|
|
1328
|
+
formatDateTime(date, language, options) {
|
|
1329
|
+
const dateObj = typeof date === "string" ? new Date(date) : date;
|
|
1330
|
+
return new Intl.DateTimeFormat(language, options).format(dateObj);
|
|
1331
|
+
}
|
|
1332
|
+
/**
|
|
1333
|
+
* Format number for locale
|
|
1334
|
+
*/
|
|
1335
|
+
formatNumber(number, language, options) {
|
|
1336
|
+
return new Intl.NumberFormat(language, options).format(number);
|
|
1337
|
+
}
|
|
1338
|
+
/**
|
|
1339
|
+
* Format currency for locale
|
|
1340
|
+
*/
|
|
1341
|
+
formatCurrency(amount, currency, language) {
|
|
1342
|
+
return new Intl.NumberFormat(language, {
|
|
1343
|
+
style: "currency",
|
|
1344
|
+
currency
|
|
1345
|
+
}).format(amount);
|
|
1346
|
+
}
|
|
1347
|
+
};
|
|
1348
|
+
|
|
1349
|
+
// src/payments/index.ts
|
|
1350
|
+
var PaymentsModule = class {
|
|
1351
|
+
static {
|
|
1352
|
+
__name(this, "PaymentsModule");
|
|
1353
|
+
}
|
|
1354
|
+
client;
|
|
1355
|
+
context;
|
|
1356
|
+
constructor(client, context) {
|
|
1357
|
+
this.client = client;
|
|
1358
|
+
this.context = context;
|
|
1359
|
+
}
|
|
1360
|
+
/**
|
|
1361
|
+
* Create payment
|
|
1362
|
+
*/
|
|
1363
|
+
async createPayment(options) {
|
|
1364
|
+
if (typeof options.amount !== "number" || options.amount <= 0) {
|
|
1365
|
+
throw new ValidationError("Amount must be a positive number");
|
|
1366
|
+
}
|
|
1367
|
+
if (!options.currency) {
|
|
1368
|
+
throw new ValidationError("Currency is required");
|
|
1369
|
+
}
|
|
1370
|
+
if (!options.paymentMethod) {
|
|
1371
|
+
throw new ValidationError("Payment method is required");
|
|
1372
|
+
}
|
|
1373
|
+
if (!options.customerId) {
|
|
1374
|
+
throw new ValidationError("Customer ID is required");
|
|
1375
|
+
}
|
|
1376
|
+
logger.debug("[Payments] Creating payment", {
|
|
1377
|
+
amount: options.amount,
|
|
1378
|
+
currency: options.currency,
|
|
1379
|
+
customerId: options.customerId
|
|
1380
|
+
});
|
|
1381
|
+
const mutation = `
|
|
1382
|
+
mutation CreatePayment($input: CreatePaymentInput!) {
|
|
1383
|
+
createPayment(input: $input) {
|
|
1384
|
+
id
|
|
1385
|
+
amount
|
|
1386
|
+
currency
|
|
1387
|
+
status
|
|
1388
|
+
paymentMethod
|
|
1389
|
+
customerId
|
|
1390
|
+
description
|
|
1391
|
+
metadata
|
|
1392
|
+
workspaceId
|
|
1393
|
+
organizationId
|
|
1394
|
+
transactionId
|
|
1395
|
+
failureReason
|
|
1396
|
+
createdAt
|
|
1397
|
+
updatedAt
|
|
1398
|
+
}
|
|
1399
|
+
}
|
|
1400
|
+
`;
|
|
1401
|
+
const variables = {
|
|
1402
|
+
input: {
|
|
1403
|
+
amount: options.amount,
|
|
1404
|
+
currency: options.currency,
|
|
1405
|
+
paymentMethod: options.paymentMethod,
|
|
1406
|
+
customerId: options.customerId,
|
|
1407
|
+
description: options.description,
|
|
1408
|
+
metadata: options.metadata,
|
|
1409
|
+
workspaceId: options.workspaceId || this.context.workspaceId,
|
|
1410
|
+
organizationId: options.organizationId
|
|
1411
|
+
}
|
|
1412
|
+
};
|
|
1413
|
+
const response = await this.client.query(mutation, variables, this.context);
|
|
1414
|
+
logger.info("[Payments] Payment created", {
|
|
1415
|
+
paymentId: response.createPayment.id
|
|
1416
|
+
});
|
|
1417
|
+
return response.createPayment;
|
|
1418
|
+
}
|
|
1419
|
+
/**
|
|
1420
|
+
* Get payment by ID
|
|
1421
|
+
*/
|
|
1422
|
+
async getPayment(paymentId) {
|
|
1423
|
+
logger.debug("[Payments] Getting payment", {
|
|
1424
|
+
paymentId
|
|
1425
|
+
});
|
|
1426
|
+
const query = `
|
|
1427
|
+
query GetPayment($paymentId: ID!) {
|
|
1428
|
+
getPayment(paymentId: $paymentId) {
|
|
1429
|
+
id
|
|
1430
|
+
amount
|
|
1431
|
+
currency
|
|
1432
|
+
status
|
|
1433
|
+
paymentMethod
|
|
1434
|
+
customerId
|
|
1435
|
+
description
|
|
1436
|
+
metadata
|
|
1437
|
+
workspaceId
|
|
1438
|
+
organizationId
|
|
1439
|
+
transactionId
|
|
1440
|
+
failureReason
|
|
1441
|
+
createdAt
|
|
1442
|
+
updatedAt
|
|
1443
|
+
}
|
|
1444
|
+
}
|
|
1445
|
+
`;
|
|
1446
|
+
const response = await this.client.query(query, {
|
|
1447
|
+
paymentId
|
|
1448
|
+
}, this.context);
|
|
1449
|
+
return response.getPayment;
|
|
1450
|
+
}
|
|
1451
|
+
/**
|
|
1452
|
+
* Get payments with filters
|
|
1453
|
+
*/
|
|
1454
|
+
async getPayments(options) {
|
|
1455
|
+
logger.debug("[Payments] Getting payments", options);
|
|
1456
|
+
const query = `
|
|
1457
|
+
query GetPayments(
|
|
1458
|
+
$customerId: ID
|
|
1459
|
+
$status: PaymentStatus
|
|
1460
|
+
$workspaceId: ID
|
|
1461
|
+
$organizationId: ID
|
|
1462
|
+
$startDate: DateTime
|
|
1463
|
+
$endDate: DateTime
|
|
1464
|
+
$limit: Int
|
|
1465
|
+
$offset: Int
|
|
1466
|
+
) {
|
|
1467
|
+
getPayments(
|
|
1468
|
+
customerId: $customerId
|
|
1469
|
+
status: $status
|
|
1470
|
+
workspaceId: $workspaceId
|
|
1471
|
+
organizationId: $organizationId
|
|
1472
|
+
startDate: $startDate
|
|
1473
|
+
endDate: $endDate
|
|
1474
|
+
limit: $limit
|
|
1475
|
+
offset: $offset
|
|
1476
|
+
) {
|
|
1477
|
+
payments {
|
|
1478
|
+
id
|
|
1479
|
+
amount
|
|
1480
|
+
currency
|
|
1481
|
+
status
|
|
1482
|
+
paymentMethod
|
|
1483
|
+
customerId
|
|
1484
|
+
description
|
|
1485
|
+
transactionId
|
|
1486
|
+
createdAt
|
|
1487
|
+
updatedAt
|
|
1488
|
+
}
|
|
1489
|
+
total
|
|
1490
|
+
}
|
|
1491
|
+
}
|
|
1492
|
+
`;
|
|
1493
|
+
const variables = {
|
|
1494
|
+
customerId: options.customerId,
|
|
1495
|
+
status: options.status,
|
|
1496
|
+
workspaceId: options.workspaceId || this.context.workspaceId,
|
|
1497
|
+
organizationId: options.organizationId,
|
|
1498
|
+
startDate: options.startDate,
|
|
1499
|
+
endDate: options.endDate,
|
|
1500
|
+
limit: options.limit || 50,
|
|
1501
|
+
offset: options.offset || 0
|
|
1502
|
+
};
|
|
1503
|
+
const response = await this.client.query(query, variables, this.context);
|
|
1504
|
+
return response.getPayments;
|
|
1505
|
+
}
|
|
1506
|
+
/**
|
|
1507
|
+
* Refund payment
|
|
1508
|
+
*/
|
|
1509
|
+
async refundPayment(paymentId, amount, reason) {
|
|
1510
|
+
logger.debug("[Payments] Refunding payment", {
|
|
1511
|
+
paymentId,
|
|
1512
|
+
amount
|
|
1513
|
+
});
|
|
1514
|
+
const mutation = `
|
|
1515
|
+
mutation RefundPayment($paymentId: ID!, $amount: Int, $reason: String) {
|
|
1516
|
+
refundPayment(paymentId: $paymentId, amount: $amount, reason: $reason) {
|
|
1517
|
+
id
|
|
1518
|
+
amount
|
|
1519
|
+
currency
|
|
1520
|
+
status
|
|
1521
|
+
paymentMethod
|
|
1522
|
+
customerId
|
|
1523
|
+
failureReason
|
|
1524
|
+
updatedAt
|
|
1525
|
+
}
|
|
1526
|
+
}
|
|
1527
|
+
`;
|
|
1528
|
+
const response = await this.client.query(mutation, {
|
|
1529
|
+
paymentId,
|
|
1530
|
+
amount,
|
|
1531
|
+
reason
|
|
1532
|
+
}, this.context);
|
|
1533
|
+
logger.info("[Payments] Payment refunded", {
|
|
1534
|
+
paymentId
|
|
1535
|
+
});
|
|
1536
|
+
return response.refundPayment;
|
|
1537
|
+
}
|
|
1538
|
+
/**
|
|
1539
|
+
* Verify payment
|
|
1540
|
+
*/
|
|
1541
|
+
async verifyPayment(paymentId, verificationData) {
|
|
1542
|
+
logger.debug("[Payments] Verifying payment", {
|
|
1543
|
+
paymentId
|
|
1544
|
+
});
|
|
1545
|
+
const mutation = `
|
|
1546
|
+
mutation VerifyPayment($paymentId: ID!, $verificationData: JSON!) {
|
|
1547
|
+
verifyPayment(paymentId: $paymentId, verificationData: $verificationData) {
|
|
1548
|
+
verified
|
|
1549
|
+
status
|
|
1550
|
+
}
|
|
1551
|
+
}
|
|
1552
|
+
`;
|
|
1553
|
+
const response = await this.client.query(mutation, {
|
|
1554
|
+
paymentId,
|
|
1555
|
+
verificationData
|
|
1556
|
+
}, this.context);
|
|
1557
|
+
logger.info("[Payments] Payment verified", {
|
|
1558
|
+
paymentId,
|
|
1559
|
+
verified: response.verifyPayment.verified
|
|
1560
|
+
});
|
|
1561
|
+
return response.verifyPayment;
|
|
1562
|
+
}
|
|
1563
|
+
/**
|
|
1564
|
+
* Create subscription
|
|
1565
|
+
*/
|
|
1566
|
+
async createSubscription(options) {
|
|
1567
|
+
if (!options.customerId) {
|
|
1568
|
+
throw new ValidationError("Customer ID is required");
|
|
1569
|
+
}
|
|
1570
|
+
if (!options.planId) {
|
|
1571
|
+
throw new ValidationError("Plan ID is required");
|
|
1572
|
+
}
|
|
1573
|
+
if (!options.paymentMethod) {
|
|
1574
|
+
throw new ValidationError("Payment method is required");
|
|
1575
|
+
}
|
|
1576
|
+
logger.debug("[Payments] Creating subscription", {
|
|
1577
|
+
customerId: options.customerId,
|
|
1578
|
+
planId: options.planId
|
|
1579
|
+
});
|
|
1580
|
+
const mutation = `
|
|
1581
|
+
mutation CreateSubscription($input: CreateSubscriptionInput!) {
|
|
1582
|
+
createSubscription(input: $input) {
|
|
1583
|
+
id
|
|
1584
|
+
customerId
|
|
1585
|
+
planId
|
|
1586
|
+
status
|
|
1587
|
+
currentPeriodStart
|
|
1588
|
+
currentPeriodEnd
|
|
1589
|
+
trialEnd
|
|
1590
|
+
cancelledAt
|
|
1591
|
+
endedAt
|
|
1592
|
+
metadata
|
|
1593
|
+
workspaceId
|
|
1594
|
+
organizationId
|
|
1595
|
+
createdAt
|
|
1596
|
+
updatedAt
|
|
1597
|
+
}
|
|
1598
|
+
}
|
|
1599
|
+
`;
|
|
1600
|
+
const variables = {
|
|
1601
|
+
input: {
|
|
1602
|
+
customerId: options.customerId,
|
|
1603
|
+
planId: options.planId,
|
|
1604
|
+
paymentMethod: options.paymentMethod,
|
|
1605
|
+
trialDays: options.trialDays,
|
|
1606
|
+
metadata: options.metadata,
|
|
1607
|
+
workspaceId: options.workspaceId || this.context.workspaceId,
|
|
1608
|
+
organizationId: options.organizationId
|
|
1609
|
+
}
|
|
1610
|
+
};
|
|
1611
|
+
const response = await this.client.query(mutation, variables, this.context);
|
|
1612
|
+
logger.info("[Payments] Subscription created", {
|
|
1613
|
+
subscriptionId: response.createSubscription.id
|
|
1614
|
+
});
|
|
1615
|
+
return response.createSubscription;
|
|
1616
|
+
}
|
|
1617
|
+
/**
|
|
1618
|
+
* Get subscription
|
|
1619
|
+
*/
|
|
1620
|
+
async getSubscription(subscriptionId) {
|
|
1621
|
+
logger.debug("[Payments] Getting subscription", {
|
|
1622
|
+
subscriptionId
|
|
1623
|
+
});
|
|
1624
|
+
const query = `
|
|
1625
|
+
query GetSubscription($subscriptionId: ID!) {
|
|
1626
|
+
getSubscription(subscriptionId: $subscriptionId) {
|
|
1627
|
+
id
|
|
1628
|
+
customerId
|
|
1629
|
+
planId
|
|
1630
|
+
status
|
|
1631
|
+
currentPeriodStart
|
|
1632
|
+
currentPeriodEnd
|
|
1633
|
+
trialEnd
|
|
1634
|
+
cancelledAt
|
|
1635
|
+
endedAt
|
|
1636
|
+
metadata
|
|
1637
|
+
workspaceId
|
|
1638
|
+
organizationId
|
|
1639
|
+
createdAt
|
|
1640
|
+
updatedAt
|
|
1641
|
+
}
|
|
1642
|
+
}
|
|
1643
|
+
`;
|
|
1644
|
+
const response = await this.client.query(query, {
|
|
1645
|
+
subscriptionId
|
|
1646
|
+
}, this.context);
|
|
1647
|
+
return response.getSubscription;
|
|
1648
|
+
}
|
|
1649
|
+
/**
|
|
1650
|
+
* Cancel subscription
|
|
1651
|
+
*/
|
|
1652
|
+
async cancelSubscription(subscriptionId, cancelAtPeriodEnd = false) {
|
|
1653
|
+
logger.debug("[Payments] Cancelling subscription", {
|
|
1654
|
+
subscriptionId,
|
|
1655
|
+
cancelAtPeriodEnd
|
|
1656
|
+
});
|
|
1657
|
+
const mutation = `
|
|
1658
|
+
mutation CancelSubscription($subscriptionId: ID!, $cancelAtPeriodEnd: Boolean!) {
|
|
1659
|
+
cancelSubscription(subscriptionId: $subscriptionId, cancelAtPeriodEnd: $cancelAtPeriodEnd) {
|
|
1660
|
+
id
|
|
1661
|
+
status
|
|
1662
|
+
cancelledAt
|
|
1663
|
+
endedAt
|
|
1664
|
+
updatedAt
|
|
1665
|
+
}
|
|
1666
|
+
}
|
|
1667
|
+
`;
|
|
1668
|
+
const response = await this.client.query(mutation, {
|
|
1669
|
+
subscriptionId,
|
|
1670
|
+
cancelAtPeriodEnd
|
|
1671
|
+
}, this.context);
|
|
1672
|
+
logger.info("[Payments] Subscription cancelled", {
|
|
1673
|
+
subscriptionId
|
|
1674
|
+
});
|
|
1675
|
+
return response.cancelSubscription;
|
|
1676
|
+
}
|
|
1677
|
+
/**
|
|
1678
|
+
* Get customer subscriptions
|
|
1679
|
+
*/
|
|
1680
|
+
async getCustomerSubscriptions(customerId) {
|
|
1681
|
+
logger.debug("[Payments] Getting customer subscriptions", {
|
|
1682
|
+
customerId
|
|
1683
|
+
});
|
|
1684
|
+
const query = `
|
|
1685
|
+
query GetCustomerSubscriptions($customerId: ID!) {
|
|
1686
|
+
getCustomerSubscriptions(customerId: $customerId) {
|
|
1687
|
+
id
|
|
1688
|
+
customerId
|
|
1689
|
+
planId
|
|
1690
|
+
status
|
|
1691
|
+
currentPeriodStart
|
|
1692
|
+
currentPeriodEnd
|
|
1693
|
+
trialEnd
|
|
1694
|
+
cancelledAt
|
|
1695
|
+
createdAt
|
|
1696
|
+
}
|
|
1697
|
+
}
|
|
1698
|
+
`;
|
|
1699
|
+
const response = await this.client.query(query, {
|
|
1700
|
+
customerId
|
|
1701
|
+
}, this.context);
|
|
1702
|
+
return response.getCustomerSubscriptions;
|
|
1703
|
+
}
|
|
1704
|
+
/**
|
|
1705
|
+
* Get plans
|
|
1706
|
+
*/
|
|
1707
|
+
async getPlans(activeOnly = true) {
|
|
1708
|
+
logger.debug("[Payments] Getting plans", {
|
|
1709
|
+
activeOnly
|
|
1710
|
+
});
|
|
1711
|
+
const query = `
|
|
1712
|
+
query GetPlans($activeOnly: Boolean!) {
|
|
1713
|
+
getPlans(activeOnly: $activeOnly) {
|
|
1714
|
+
id
|
|
1715
|
+
name
|
|
1716
|
+
description
|
|
1717
|
+
amount
|
|
1718
|
+
currency
|
|
1719
|
+
interval
|
|
1720
|
+
intervalCount
|
|
1721
|
+
features
|
|
1722
|
+
metadata
|
|
1723
|
+
active
|
|
1724
|
+
createdAt
|
|
1725
|
+
}
|
|
1726
|
+
}
|
|
1727
|
+
`;
|
|
1728
|
+
const response = await this.client.query(query, {
|
|
1729
|
+
activeOnly
|
|
1730
|
+
}, this.context);
|
|
1731
|
+
return response.getPlans;
|
|
1732
|
+
}
|
|
1733
|
+
/**
|
|
1734
|
+
* Create payment intent
|
|
1735
|
+
*/
|
|
1736
|
+
async createPaymentIntent(options) {
|
|
1737
|
+
logger.debug("[Payments] Creating payment intent", {
|
|
1738
|
+
amount: options.amount,
|
|
1739
|
+
currency: options.currency
|
|
1740
|
+
});
|
|
1741
|
+
const mutation = `
|
|
1742
|
+
mutation CreatePaymentIntent($input: CreatePaymentIntentInput!) {
|
|
1743
|
+
createPaymentIntent(input: $input) {
|
|
1744
|
+
clientSecret
|
|
1745
|
+
paymentIntentId
|
|
1746
|
+
}
|
|
1747
|
+
}
|
|
1748
|
+
`;
|
|
1749
|
+
const response = await this.client.query(mutation, {
|
|
1750
|
+
input: options
|
|
1751
|
+
}, this.context);
|
|
1752
|
+
logger.info("[Payments] Payment intent created", {
|
|
1753
|
+
paymentIntentId: response.createPaymentIntent.paymentIntentId
|
|
1754
|
+
});
|
|
1755
|
+
return response.createPaymentIntent;
|
|
1756
|
+
}
|
|
1757
|
+
/**
|
|
1758
|
+
* Get payment statistics
|
|
1759
|
+
*/
|
|
1760
|
+
async getPaymentStats(options) {
|
|
1761
|
+
logger.debug("[Payments] Getting payment statistics", options);
|
|
1762
|
+
const query = `
|
|
1763
|
+
query GetPaymentStats($startDate: DateTime, $endDate: DateTime, $workspaceId: ID) {
|
|
1764
|
+
getPaymentStats(startDate: $startDate, endDate: $endDate, workspaceId: $workspaceId) {
|
|
1765
|
+
totalRevenue
|
|
1766
|
+
totalPayments
|
|
1767
|
+
successfulPayments
|
|
1768
|
+
failedPayments
|
|
1769
|
+
refundedAmount
|
|
1770
|
+
averagePaymentAmount
|
|
1771
|
+
}
|
|
1772
|
+
}
|
|
1773
|
+
`;
|
|
1774
|
+
const response = await this.client.query(query, {
|
|
1775
|
+
startDate: options?.startDate,
|
|
1776
|
+
endDate: options?.endDate,
|
|
1777
|
+
workspaceId: options?.workspaceId || this.context.workspaceId
|
|
1778
|
+
}, this.context);
|
|
1779
|
+
return response.getPaymentStats;
|
|
1780
|
+
}
|
|
1781
|
+
};
|
|
1782
|
+
|
|
1783
|
+
// src/bootstrap/index.ts
|
|
1784
|
+
var BootstrapModule = class {
|
|
1785
|
+
static {
|
|
1786
|
+
__name(this, "BootstrapModule");
|
|
1787
|
+
}
|
|
1788
|
+
config;
|
|
1789
|
+
startTime;
|
|
1790
|
+
healthCheckInterval;
|
|
1791
|
+
constructor(config) {
|
|
1792
|
+
this.config = config;
|
|
1793
|
+
this.startTime = /* @__PURE__ */ new Date();
|
|
1794
|
+
}
|
|
1795
|
+
/**
|
|
1796
|
+
* Bootstrap the application
|
|
1797
|
+
*/
|
|
1798
|
+
async bootstrap() {
|
|
1799
|
+
logger.info("[Bootstrap] Starting application bootstrap", {
|
|
1800
|
+
serviceName: this.config.serviceName,
|
|
1801
|
+
environment: this.config.environment || "development"
|
|
1802
|
+
});
|
|
1803
|
+
try {
|
|
1804
|
+
await this.validateConfiguration();
|
|
1805
|
+
const databaseConnected = await this.connectDatabase();
|
|
1806
|
+
const loadedModules = await this.loadModules();
|
|
1807
|
+
if (this.config.healthCheck) {
|
|
1808
|
+
await this.setupHealthChecks();
|
|
1809
|
+
}
|
|
1810
|
+
this.setupSignalHandlers();
|
|
1811
|
+
const result = {
|
|
1812
|
+
success: true,
|
|
1813
|
+
serviceName: this.config.serviceName || "burdenoff-service",
|
|
1814
|
+
serviceVersion: this.config.serviceVersion || "1.0.0",
|
|
1815
|
+
environment: this.config.environment || "development",
|
|
1816
|
+
startedAt: this.startTime.toISOString(),
|
|
1817
|
+
modules: loadedModules,
|
|
1818
|
+
databaseConnected,
|
|
1819
|
+
healthCheckEnabled: this.config.healthCheck || false
|
|
1820
|
+
};
|
|
1821
|
+
logger.info("[Bootstrap] Application bootstrapped successfully", result);
|
|
1822
|
+
return result;
|
|
1823
|
+
} catch (error) {
|
|
1824
|
+
logger.error("[Bootstrap] Failed to bootstrap application", {
|
|
1825
|
+
error
|
|
1826
|
+
});
|
|
1827
|
+
throw error;
|
|
1828
|
+
}
|
|
1829
|
+
}
|
|
1830
|
+
/**
|
|
1831
|
+
* Validate configuration
|
|
1832
|
+
*/
|
|
1833
|
+
async validateConfiguration() {
|
|
1834
|
+
logger.debug("[Bootstrap] Validating configuration");
|
|
1835
|
+
if (!this.config.workspacesApiUrl) {
|
|
1836
|
+
throw new Error("Workspaces API URL is required");
|
|
1837
|
+
}
|
|
1838
|
+
if (this.config.database?.url && !DatabaseModule.validateDatabaseUrl(this.config.database.url)) {
|
|
1839
|
+
throw new Error("Invalid database URL");
|
|
1840
|
+
}
|
|
1841
|
+
logger.debug("[Bootstrap] Configuration validated");
|
|
1842
|
+
}
|
|
1843
|
+
/**
|
|
1844
|
+
* Connect to database
|
|
1845
|
+
*/
|
|
1846
|
+
async connectDatabase() {
|
|
1847
|
+
if (!this.config.database) {
|
|
1848
|
+
logger.debug("[Bootstrap] No database configuration provided, skipping");
|
|
1849
|
+
return false;
|
|
1850
|
+
}
|
|
1851
|
+
logger.info("[Bootstrap] Connecting to database");
|
|
1852
|
+
try {
|
|
1853
|
+
const sanitizedUrl = DatabaseModule.sanitizeDatabaseUrl(this.config.database.url);
|
|
1854
|
+
logger.info("[Bootstrap] Database connection configured", {
|
|
1855
|
+
url: sanitizedUrl
|
|
1856
|
+
});
|
|
1857
|
+
return true;
|
|
1858
|
+
} catch (error) {
|
|
1859
|
+
logger.error("[Bootstrap] Failed to connect to database", {
|
|
1860
|
+
error
|
|
1861
|
+
});
|
|
1862
|
+
return false;
|
|
1863
|
+
}
|
|
1864
|
+
}
|
|
1865
|
+
/**
|
|
1866
|
+
* Load modules
|
|
1867
|
+
*/
|
|
1868
|
+
async loadModules() {
|
|
1869
|
+
const modules = this.config.modules || [];
|
|
1870
|
+
if (modules.length === 0) {
|
|
1871
|
+
logger.debug("[Bootstrap] No modules to load");
|
|
1872
|
+
return [];
|
|
1873
|
+
}
|
|
1874
|
+
logger.info("[Bootstrap] Loading modules", {
|
|
1875
|
+
modules
|
|
1876
|
+
});
|
|
1877
|
+
const loadedModules = [];
|
|
1878
|
+
for (const moduleName of modules) {
|
|
1879
|
+
try {
|
|
1880
|
+
logger.debug(`[Bootstrap] Loading module: ${moduleName}`);
|
|
1881
|
+
loadedModules.push(moduleName);
|
|
1882
|
+
} catch (error) {
|
|
1883
|
+
logger.error(`[Bootstrap] Failed to load module: ${moduleName}`, {
|
|
1884
|
+
error
|
|
1885
|
+
});
|
|
1886
|
+
}
|
|
1887
|
+
}
|
|
1888
|
+
logger.info("[Bootstrap] Modules loaded", {
|
|
1889
|
+
loaded: loadedModules
|
|
1890
|
+
});
|
|
1891
|
+
return loadedModules;
|
|
1892
|
+
}
|
|
1893
|
+
/**
|
|
1894
|
+
* Setup health checks
|
|
1895
|
+
*/
|
|
1896
|
+
async setupHealthChecks() {
|
|
1897
|
+
logger.info("[Bootstrap] Setting up health checks");
|
|
1898
|
+
const interval = this.config.healthCheckInterval || 3e4;
|
|
1899
|
+
this.healthCheckInterval = setInterval(() => {
|
|
1900
|
+
void (async () => {
|
|
1901
|
+
const health = await this.performHealthCheck();
|
|
1902
|
+
if (!health.healthy) {
|
|
1903
|
+
logger.warn("[Bootstrap] Health check failed", health);
|
|
1904
|
+
} else {
|
|
1905
|
+
logger.debug("[Bootstrap] Health check passed", health);
|
|
1906
|
+
}
|
|
1907
|
+
})();
|
|
1908
|
+
}, interval);
|
|
1909
|
+
logger.info("[Bootstrap] Health checks configured", {
|
|
1910
|
+
interval
|
|
1911
|
+
});
|
|
1912
|
+
}
|
|
1913
|
+
/**
|
|
1914
|
+
* Perform health check
|
|
1915
|
+
*/
|
|
1916
|
+
async performHealthCheck() {
|
|
1917
|
+
const checks = {};
|
|
1918
|
+
const memUsage = process.memoryUsage();
|
|
1919
|
+
checks.memory = {
|
|
1920
|
+
used: memUsage.heapUsed,
|
|
1921
|
+
total: memUsage.heapTotal,
|
|
1922
|
+
percentage: memUsage.heapUsed / memUsage.heapTotal * 100
|
|
1923
|
+
};
|
|
1924
|
+
checks.uptime = Math.floor((Date.now() - this.startTime.getTime()) / 1e3);
|
|
1925
|
+
const healthy = checks.memory.percentage < 90 && // Less than 90% memory usage
|
|
1926
|
+
checks.uptime > 0;
|
|
1927
|
+
return {
|
|
1928
|
+
healthy,
|
|
1929
|
+
checks,
|
|
1930
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
1931
|
+
};
|
|
1932
|
+
}
|
|
1933
|
+
/**
|
|
1934
|
+
* Setup signal handlers for graceful shutdown
|
|
1935
|
+
*/
|
|
1936
|
+
setupSignalHandlers() {
|
|
1937
|
+
logger.debug("[Bootstrap] Setting up signal handlers");
|
|
1938
|
+
const gracefulShutdown = /* @__PURE__ */ __name(async (signal) => {
|
|
1939
|
+
logger.info(`[Bootstrap] Received ${signal}, shutting down gracefully`);
|
|
1940
|
+
if (this.healthCheckInterval) {
|
|
1941
|
+
clearInterval(this.healthCheckInterval);
|
|
1942
|
+
}
|
|
1943
|
+
await this.cleanup();
|
|
1944
|
+
process.exit(0);
|
|
1945
|
+
}, "gracefulShutdown");
|
|
1946
|
+
process.on("SIGTERM", () => {
|
|
1947
|
+
void gracefulShutdown("SIGTERM");
|
|
1948
|
+
});
|
|
1949
|
+
process.on("SIGINT", () => {
|
|
1950
|
+
void gracefulShutdown("SIGINT");
|
|
1951
|
+
});
|
|
1952
|
+
process.on("unhandledRejection", (reason, promise) => {
|
|
1953
|
+
logger.error("[Bootstrap] Unhandled rejection", {
|
|
1954
|
+
reason,
|
|
1955
|
+
promise
|
|
1956
|
+
});
|
|
1957
|
+
});
|
|
1958
|
+
process.on("uncaughtException", (error) => {
|
|
1959
|
+
logger.error("[Bootstrap] Uncaught exception", {
|
|
1960
|
+
error
|
|
1961
|
+
});
|
|
1962
|
+
process.exit(1);
|
|
1963
|
+
});
|
|
1964
|
+
}
|
|
1965
|
+
/**
|
|
1966
|
+
* Cleanup on shutdown
|
|
1967
|
+
*/
|
|
1968
|
+
async cleanup() {
|
|
1969
|
+
logger.info("[Bootstrap] Performing cleanup");
|
|
1970
|
+
ContextManager.clearContext();
|
|
1971
|
+
logger.info("[Bootstrap] Cleanup completed");
|
|
1972
|
+
}
|
|
1973
|
+
/**
|
|
1974
|
+
* Get application info
|
|
1975
|
+
*/
|
|
1976
|
+
getInfo() {
|
|
1977
|
+
return {
|
|
1978
|
+
serviceName: this.config.serviceName || "burdenoff-service",
|
|
1979
|
+
serviceVersion: this.config.serviceVersion || "1.0.0",
|
|
1980
|
+
environment: this.config.environment || "development",
|
|
1981
|
+
uptime: Math.floor((Date.now() - this.startTime.getTime()) / 1e3),
|
|
1982
|
+
startedAt: this.startTime.toISOString()
|
|
1983
|
+
};
|
|
1984
|
+
}
|
|
1985
|
+
/**
|
|
1986
|
+
* Setup default context
|
|
1987
|
+
*/
|
|
1988
|
+
setupDefaultContext(context) {
|
|
1989
|
+
logger.debug("[Bootstrap] Setting up default context", context);
|
|
1990
|
+
ContextManager.setContext(context);
|
|
1991
|
+
}
|
|
1992
|
+
/**
|
|
1993
|
+
* Create context middleware (for Elysia/HTTP frameworks)
|
|
1994
|
+
*/
|
|
1995
|
+
createContextMiddleware() {
|
|
1996
|
+
return async (req, _res, next) => {
|
|
1997
|
+
const context = this.extractContextFromRequest(req);
|
|
1998
|
+
ContextManager.setContext(context);
|
|
1999
|
+
next();
|
|
2000
|
+
};
|
|
2001
|
+
}
|
|
2002
|
+
/**
|
|
2003
|
+
* Extract context from request
|
|
2004
|
+
*/
|
|
2005
|
+
extractContextFromRequest(req) {
|
|
2006
|
+
const context = {};
|
|
2007
|
+
if (req.user) {
|
|
2008
|
+
context.actorId = req.user.user_id;
|
|
2009
|
+
context.workspaceId = req.user.workspace_id;
|
|
2010
|
+
context.tenantId = req.user.tenant_endpoint;
|
|
2011
|
+
}
|
|
2012
|
+
const getHeader = /* @__PURE__ */ __name((key) => {
|
|
2013
|
+
const value = req.headers[key.toLowerCase()];
|
|
2014
|
+
return Array.isArray(value) ? value[0] : value;
|
|
2015
|
+
}, "getHeader");
|
|
2016
|
+
const authHeader = getHeader("authorization");
|
|
2017
|
+
if (authHeader) {
|
|
2018
|
+
context.token = authHeader.replace("Bearer ", "");
|
|
2019
|
+
}
|
|
2020
|
+
const workspaceId = getHeader("x-workspace-id");
|
|
2021
|
+
if (workspaceId && !context.workspaceId) {
|
|
2022
|
+
context.workspaceId = workspaceId;
|
|
2023
|
+
}
|
|
2024
|
+
const productId = getHeader("x-product-id");
|
|
2025
|
+
if (productId) {
|
|
2026
|
+
context.productId = productId;
|
|
2027
|
+
}
|
|
2028
|
+
const projectId = getHeader("x-project-id");
|
|
2029
|
+
if (projectId) {
|
|
2030
|
+
context.projectId = projectId;
|
|
2031
|
+
}
|
|
2032
|
+
const appId = getHeader("x-app-id");
|
|
2033
|
+
if (appId) {
|
|
2034
|
+
context.appId = appId;
|
|
2035
|
+
}
|
|
2036
|
+
return context;
|
|
2037
|
+
}
|
|
2038
|
+
/**
|
|
2039
|
+
* Load GraphQL schema
|
|
2040
|
+
*/
|
|
2041
|
+
async loadSchema(schemaPath) {
|
|
2042
|
+
logger.info("[Bootstrap] Loading GraphQL schema", {
|
|
2043
|
+
schemaPath
|
|
2044
|
+
});
|
|
2045
|
+
try {
|
|
2046
|
+
logger.info("[Bootstrap] Schema loaded successfully");
|
|
2047
|
+
return true;
|
|
2048
|
+
} catch (error) {
|
|
2049
|
+
logger.error("[Bootstrap] Failed to load schema", {
|
|
2050
|
+
error
|
|
2051
|
+
});
|
|
2052
|
+
return false;
|
|
2053
|
+
}
|
|
2054
|
+
}
|
|
2055
|
+
/**
|
|
2056
|
+
* Register shutdown hook
|
|
2057
|
+
*/
|
|
2058
|
+
onShutdown(callback) {
|
|
2059
|
+
const originalHandler = process.listeners("SIGTERM")[0];
|
|
2060
|
+
process.removeAllListeners("SIGTERM");
|
|
2061
|
+
process.on("SIGTERM", () => {
|
|
2062
|
+
void (async () => {
|
|
2063
|
+
await callback();
|
|
2064
|
+
if (typeof originalHandler === "function") {
|
|
2065
|
+
originalHandler();
|
|
2066
|
+
}
|
|
2067
|
+
})();
|
|
2068
|
+
});
|
|
2069
|
+
}
|
|
2070
|
+
/**
|
|
2071
|
+
* Check if application is ready
|
|
2072
|
+
*/
|
|
2073
|
+
async isReady() {
|
|
2074
|
+
const health = await this.performHealthCheck();
|
|
2075
|
+
return health.healthy;
|
|
2076
|
+
}
|
|
2077
|
+
/**
|
|
2078
|
+
* Get metrics
|
|
2079
|
+
*/
|
|
2080
|
+
getMetrics() {
|
|
2081
|
+
return {
|
|
2082
|
+
memory: process.memoryUsage(),
|
|
2083
|
+
uptime: Math.floor((Date.now() - this.startTime.getTime()) / 1e3),
|
|
2084
|
+
cpu: process.cpuUsage()
|
|
2085
|
+
};
|
|
2086
|
+
}
|
|
2087
|
+
};
|
|
2088
|
+
function createBootstrap(config) {
|
|
2089
|
+
return new BootstrapModule(config);
|
|
2090
|
+
}
|
|
2091
|
+
__name(createBootstrap, "createBootstrap");
|
|
2092
|
+
|
|
2093
|
+
// src/client/tenant-backend.ts
|
|
2094
|
+
var TenantBackendClient = class {
|
|
2095
|
+
static {
|
|
2096
|
+
__name(this, "TenantBackendClient");
|
|
2097
|
+
}
|
|
2098
|
+
graphqlClient;
|
|
2099
|
+
config;
|
|
2100
|
+
/** Notifications module for sending notifications via tenant backend */
|
|
2101
|
+
notifications;
|
|
2102
|
+
constructor(config) {
|
|
2103
|
+
this.config = config;
|
|
2104
|
+
const sdkConfig = {
|
|
2105
|
+
workspacesApiUrl: config.workspacesBackendUrl || "http://localhost:4000/graphql",
|
|
2106
|
+
tenantApiUrl: config.tenantBackendUrl
|
|
2107
|
+
};
|
|
2108
|
+
this.graphqlClient = new BurdenoffGraphQLClient(sdkConfig, {});
|
|
2109
|
+
this.notifications = new TenantNotificationsModule(this.graphqlClient, {});
|
|
2110
|
+
logger.info("[TenantBackend] Client initialized using be-sdk", {
|
|
2111
|
+
tenantUrl: config.tenantBackendUrl,
|
|
2112
|
+
workspacesUrl: config.workspacesBackendUrl,
|
|
2113
|
+
hasApiKey: !!config.apiKey
|
|
2114
|
+
});
|
|
2115
|
+
}
|
|
2116
|
+
/**
|
|
2117
|
+
* Update authentication token
|
|
2118
|
+
*
|
|
2119
|
+
* @param token - JWT token for authentication
|
|
2120
|
+
*/
|
|
2121
|
+
setAuthToken(token) {
|
|
2122
|
+
this.graphqlClient.updateContext({
|
|
2123
|
+
token
|
|
2124
|
+
});
|
|
2125
|
+
logger.debug("[TenantBackend] Authentication token updated");
|
|
2126
|
+
}
|
|
2127
|
+
/**
|
|
2128
|
+
* Execute raw GraphQL query against tenant backend
|
|
2129
|
+
*
|
|
2130
|
+
* @param query - GraphQL query string
|
|
2131
|
+
* @param variables - Query variables
|
|
2132
|
+
* @returns Query result
|
|
2133
|
+
*
|
|
2134
|
+
* @example
|
|
2135
|
+
* ```typescript
|
|
2136
|
+
* const result = await client.query(`
|
|
2137
|
+
* query GetTemplate($slug: String!) {
|
|
2138
|
+
* notificationTemplate(slug: $slug) {
|
|
2139
|
+
* id
|
|
2140
|
+
* slug
|
|
2141
|
+
* subject
|
|
2142
|
+
* }
|
|
2143
|
+
* }
|
|
2144
|
+
* `, { slug: 'user-welcome' });
|
|
2145
|
+
* ```
|
|
2146
|
+
*/
|
|
2147
|
+
async query(query, variables) {
|
|
2148
|
+
return this.graphqlClient.tenantQuery(query, variables);
|
|
2149
|
+
}
|
|
2150
|
+
/**
|
|
2151
|
+
* Health check
|
|
2152
|
+
*
|
|
2153
|
+
* @returns True if tenant backend is reachable, false otherwise
|
|
2154
|
+
*/
|
|
2155
|
+
async healthCheck() {
|
|
2156
|
+
try {
|
|
2157
|
+
const query = `
|
|
2158
|
+
query HealthCheck {
|
|
2159
|
+
__typename
|
|
2160
|
+
}
|
|
2161
|
+
`;
|
|
2162
|
+
await this.graphqlClient.tenantQuery(query);
|
|
2163
|
+
return true;
|
|
2164
|
+
} catch (error) {
|
|
2165
|
+
logger.error("[TenantBackend] Health check failed", {
|
|
2166
|
+
error: error instanceof Error ? error.message : "Unknown error"
|
|
2167
|
+
});
|
|
2168
|
+
return false;
|
|
2169
|
+
}
|
|
2170
|
+
}
|
|
2171
|
+
/**
|
|
2172
|
+
* Get GraphQL client for advanced usage
|
|
2173
|
+
*
|
|
2174
|
+
* @returns Underlying BurdenoffGraphQLClient
|
|
2175
|
+
*/
|
|
2176
|
+
getClient() {
|
|
2177
|
+
return this.graphqlClient;
|
|
2178
|
+
}
|
|
2179
|
+
/**
|
|
2180
|
+
* Get client configuration
|
|
2181
|
+
*
|
|
2182
|
+
* @returns Client configuration
|
|
2183
|
+
*/
|
|
2184
|
+
getConfig() {
|
|
2185
|
+
return {
|
|
2186
|
+
...this.config
|
|
2187
|
+
};
|
|
2188
|
+
}
|
|
2189
|
+
};
|
|
2190
|
+
function createTenantBackendClient(config) {
|
|
2191
|
+
return new TenantBackendClient(config);
|
|
2192
|
+
}
|
|
2193
|
+
__name(createTenantBackendClient, "createTenantBackendClient");
|
|
2194
|
+
|
|
2195
|
+
// src/index.ts
|
|
2196
|
+
var BurdenoffSDK = class {
|
|
2197
|
+
static {
|
|
2198
|
+
__name(this, "BurdenoffSDK");
|
|
2199
|
+
}
|
|
2200
|
+
client;
|
|
2201
|
+
defaultContext;
|
|
2202
|
+
// NATS publisher state
|
|
2203
|
+
natsPublisher = null;
|
|
2204
|
+
natsInitPromise = null;
|
|
2205
|
+
natsConfig = null;
|
|
2206
|
+
// Initialization state
|
|
2207
|
+
initializationPromise = null;
|
|
2208
|
+
isShuttingDown = false;
|
|
2209
|
+
/** Authentication module */
|
|
2210
|
+
auth;
|
|
2211
|
+
/** Notifications module (workspaces-backend) */
|
|
2212
|
+
notifications;
|
|
2213
|
+
/** Tenant Notifications module (tenant-backend - RECOMMENDED) */
|
|
2214
|
+
tenantNotifications;
|
|
2215
|
+
/** RBAC module */
|
|
2216
|
+
rbac;
|
|
2217
|
+
/** Activity tracking module */
|
|
2218
|
+
activity;
|
|
2219
|
+
/** Usage & Quota module */
|
|
2220
|
+
usage;
|
|
2221
|
+
/** Unified Tracker module (MOST IMPORTANT - use this for most operations) */
|
|
2222
|
+
tracker;
|
|
2223
|
+
/** Database helpers module */
|
|
2224
|
+
database;
|
|
2225
|
+
/** Events/Queue module */
|
|
2226
|
+
events;
|
|
2227
|
+
/** Graph integration module */
|
|
2228
|
+
graph;
|
|
2229
|
+
/** Search module */
|
|
2230
|
+
search;
|
|
2231
|
+
/** Translation module */
|
|
2232
|
+
translation;
|
|
2233
|
+
/** Payments module */
|
|
2234
|
+
payments;
|
|
2235
|
+
/** Temporal workflow orchestration module (lazy-loaded) */
|
|
2236
|
+
_temporal = null;
|
|
2237
|
+
_temporalPromise = null;
|
|
2238
|
+
_temporalConfig = null;
|
|
2239
|
+
/**
|
|
2240
|
+
* Get Temporal workflow orchestration module (lazy-loaded)
|
|
2241
|
+
* Only loads @temporalio/client when this method is called
|
|
2242
|
+
*
|
|
2243
|
+
* @returns Promise resolving to TemporalModule
|
|
2244
|
+
* @example
|
|
2245
|
+
* ```typescript
|
|
2246
|
+
* const temporal = await sdk.getTemporalModule();
|
|
2247
|
+
* await temporal.startWorkflow('myWorkflow', { ... });
|
|
2248
|
+
* ```
|
|
2249
|
+
*/
|
|
2250
|
+
async getTemporalModule() {
|
|
2251
|
+
if (this._temporal) {
|
|
2252
|
+
return this._temporal;
|
|
2253
|
+
}
|
|
2254
|
+
if (!this._temporalPromise) {
|
|
2255
|
+
this._temporalPromise = this.loadTemporalModule();
|
|
2256
|
+
}
|
|
2257
|
+
return this._temporalPromise;
|
|
2258
|
+
}
|
|
2259
|
+
/**
|
|
2260
|
+
* Load temporal module dynamically
|
|
2261
|
+
* @private
|
|
2262
|
+
*/
|
|
2263
|
+
async loadTemporalModule() {
|
|
2264
|
+
const { TemporalModule: TM } = await import('./temporal/index.js');
|
|
2265
|
+
this._temporal = this._temporalConfig?.moduleType ? new TM({
|
|
2266
|
+
moduleType: this._temporalConfig.moduleType,
|
|
2267
|
+
address: this._temporalConfig.address,
|
|
2268
|
+
namespace: this._temporalConfig.namespace,
|
|
2269
|
+
taskQueue: this._temporalConfig.taskQueue
|
|
2270
|
+
}) : TM.fromEnv();
|
|
2271
|
+
return this._temporal;
|
|
2272
|
+
}
|
|
2273
|
+
/** Logger instance */
|
|
2274
|
+
logger;
|
|
2275
|
+
/** Context manager */
|
|
2276
|
+
context;
|
|
2277
|
+
/**
|
|
2278
|
+
* Create SDK instance
|
|
2279
|
+
*/
|
|
2280
|
+
constructor(config, defaultContext = {}) {
|
|
2281
|
+
this.client = new BurdenoffGraphQLClient(config, defaultContext);
|
|
2282
|
+
this.defaultContext = defaultContext;
|
|
2283
|
+
this.logger = logger;
|
|
2284
|
+
if (config.logger) {
|
|
2285
|
+
this.logger.setLevel(config.logger.level);
|
|
2286
|
+
this.logger.setConsoleEnabled(config.logger.console);
|
|
2287
|
+
if (config.logger.transport) {
|
|
2288
|
+
this.logger.addTransport(config.logger.transport);
|
|
2289
|
+
}
|
|
2290
|
+
}
|
|
2291
|
+
this.context = ContextManager;
|
|
2292
|
+
if (Object.keys(defaultContext).length > 0) {
|
|
2293
|
+
ContextManager.setContext(defaultContext);
|
|
2294
|
+
}
|
|
2295
|
+
this.auth = new AuthModule(this.client, defaultContext);
|
|
2296
|
+
this.notifications = new NotificationsModule(this.client, defaultContext);
|
|
2297
|
+
this.tenantNotifications = new TenantNotificationsModule(this.client, defaultContext);
|
|
2298
|
+
this.rbac = new RBACModule(this.client, defaultContext);
|
|
2299
|
+
this.activity = new ActivityModule(this.client, defaultContext);
|
|
2300
|
+
this.usage = new UsageModule(this.client, defaultContext);
|
|
2301
|
+
this.tracker = new TrackerModule(this.client, defaultContext);
|
|
2302
|
+
this.database = DatabaseModule;
|
|
2303
|
+
this.events = new EventsModule(this.client, defaultContext);
|
|
2304
|
+
this.graph = new GraphModule(this.client, defaultContext);
|
|
2305
|
+
this.search = new SearchModule(this.client, defaultContext);
|
|
2306
|
+
this.translation = new TranslationModule(this.client, defaultContext);
|
|
2307
|
+
this.payments = new PaymentsModule(this.client, defaultContext);
|
|
2308
|
+
if (config.temporalModuleType) {
|
|
2309
|
+
this._temporalConfig = {
|
|
2310
|
+
moduleType: config.temporalModuleType,
|
|
2311
|
+
address: config.temporalAddress,
|
|
2312
|
+
namespace: config.temporalNamespace,
|
|
2313
|
+
taskQueue: config.temporalTaskQueue
|
|
2314
|
+
};
|
|
2315
|
+
}
|
|
2316
|
+
if (config.nats) {
|
|
2317
|
+
this.natsConfig = {
|
|
2318
|
+
autoConnect: true,
|
|
2319
|
+
maxReconnectAttempts: -1,
|
|
2320
|
+
reconnectTimeWait: 2e3,
|
|
2321
|
+
...config.nats
|
|
2322
|
+
};
|
|
2323
|
+
}
|
|
2324
|
+
logger.info("[SDK] Burdenoff SDK initialized", {
|
|
2325
|
+
workspacesApiUrl: config.workspacesApiUrl,
|
|
2326
|
+
tenantApiUrl: config.tenantApiUrl,
|
|
2327
|
+
hasDefaultContext: Object.keys(defaultContext).length > 0,
|
|
2328
|
+
hasNatsConfig: !!config.nats,
|
|
2329
|
+
hasTemporalConfig: !!config.temporalModuleType
|
|
2330
|
+
});
|
|
2331
|
+
this.scheduleBackgroundInit();
|
|
2332
|
+
}
|
|
2333
|
+
/**
|
|
2334
|
+
* Schedule background initialization outside constructor
|
|
2335
|
+
* Uses queueMicrotask to defer async work while maintaining initialization order
|
|
2336
|
+
* @private
|
|
2337
|
+
*/
|
|
2338
|
+
scheduleBackgroundInit() {
|
|
2339
|
+
queueMicrotask(() => {
|
|
2340
|
+
this.initializationPromise = this.initializeAsync();
|
|
2341
|
+
});
|
|
2342
|
+
}
|
|
2343
|
+
/**
|
|
2344
|
+
* Update SDK context
|
|
2345
|
+
*/
|
|
2346
|
+
setContext(context) {
|
|
2347
|
+
this.defaultContext = {
|
|
2348
|
+
...this.defaultContext,
|
|
2349
|
+
...context
|
|
2350
|
+
};
|
|
2351
|
+
this.client.updateContext(context);
|
|
2352
|
+
ContextManager.setContext(this.defaultContext);
|
|
2353
|
+
}
|
|
2354
|
+
/**
|
|
2355
|
+
* Get current context
|
|
2356
|
+
*/
|
|
2357
|
+
getContext() {
|
|
2358
|
+
return ContextManager.getContext();
|
|
2359
|
+
}
|
|
2360
|
+
/**
|
|
2361
|
+
* Run function with specific context
|
|
2362
|
+
*/
|
|
2363
|
+
async withContext(context, fn) {
|
|
2364
|
+
return ContextManager.withContext(context, fn);
|
|
2365
|
+
}
|
|
2366
|
+
/**
|
|
2367
|
+
* Background initialization for NATS and other async resources
|
|
2368
|
+
* @private
|
|
2369
|
+
*/
|
|
2370
|
+
async initializeAsync() {
|
|
2371
|
+
const promises = [];
|
|
2372
|
+
if (this.natsConfig?.autoConnect) {
|
|
2373
|
+
promises.push(this.initializeNats().catch((error) => {
|
|
2374
|
+
logger.warn("[SDK] Failed to auto-connect to NATS", {
|
|
2375
|
+
error
|
|
2376
|
+
});
|
|
2377
|
+
}));
|
|
2378
|
+
}
|
|
2379
|
+
await Promise.allSettled(promises);
|
|
2380
|
+
}
|
|
2381
|
+
/**
|
|
2382
|
+
* Initialize NATS publisher
|
|
2383
|
+
* @private
|
|
2384
|
+
*/
|
|
2385
|
+
async initializeNats() {
|
|
2386
|
+
if (!this.natsConfig || this.natsPublisher) return;
|
|
2387
|
+
const natsUrl = this.natsConfig.url || this.natsConfig.servers && this.natsConfig.servers[0] || "nats://localhost:4222";
|
|
2388
|
+
const publisherConfig = {
|
|
2389
|
+
type: "nats",
|
|
2390
|
+
url: natsUrl,
|
|
2391
|
+
user: this.natsConfig.user,
|
|
2392
|
+
password: this.natsConfig.password,
|
|
2393
|
+
token: this.natsConfig.token,
|
|
2394
|
+
name: this.natsConfig.name || "burdenoff-sdk-publisher",
|
|
2395
|
+
maxReconnectAttempts: this.natsConfig.maxReconnectAttempts ?? -1,
|
|
2396
|
+
reconnectTimeWait: this.natsConfig.reconnectTimeWait ?? 2e3
|
|
2397
|
+
};
|
|
2398
|
+
this.natsPublisher = EventQueueFactory.createPublisher(publisherConfig);
|
|
2399
|
+
await this.natsPublisher.connect();
|
|
2400
|
+
logger.info("[SDK] NATS publisher initialized", {
|
|
2401
|
+
url: natsUrl,
|
|
2402
|
+
name: this.natsConfig.name
|
|
2403
|
+
});
|
|
2404
|
+
}
|
|
2405
|
+
/**
|
|
2406
|
+
* Get NATS publisher instance (lazy initialization)
|
|
2407
|
+
*
|
|
2408
|
+
* @example
|
|
2409
|
+
* ```typescript
|
|
2410
|
+
* const publisher = await sdk.getNatsPublisher();
|
|
2411
|
+
* await publisher.publish({
|
|
2412
|
+
* subject: 'events.user.created',
|
|
2413
|
+
* data: { userId: 'user-123' }
|
|
2414
|
+
* });
|
|
2415
|
+
* ```
|
|
2416
|
+
*/
|
|
2417
|
+
async getNatsPublisher() {
|
|
2418
|
+
if (this.natsPublisher) return this.natsPublisher;
|
|
2419
|
+
if (!this.natsConfig) {
|
|
2420
|
+
throw new Error("[SDK] NATS not configured - provide nats config in SDKConfig");
|
|
2421
|
+
}
|
|
2422
|
+
if (!this.natsInitPromise) {
|
|
2423
|
+
this.natsInitPromise = this.initializeNats();
|
|
2424
|
+
}
|
|
2425
|
+
await this.natsInitPromise;
|
|
2426
|
+
if (!this.natsPublisher) {
|
|
2427
|
+
throw new Error("[SDK] NATS initialization failed");
|
|
2428
|
+
}
|
|
2429
|
+
return this.natsPublisher;
|
|
2430
|
+
}
|
|
2431
|
+
/**
|
|
2432
|
+
* Wait for SDK initialization to complete
|
|
2433
|
+
*
|
|
2434
|
+
* @example
|
|
2435
|
+
* ```typescript
|
|
2436
|
+
* await sdk.waitForReady();
|
|
2437
|
+
* ```
|
|
2438
|
+
*/
|
|
2439
|
+
async waitForReady() {
|
|
2440
|
+
if (this.initializationPromise) {
|
|
2441
|
+
await this.initializationPromise;
|
|
2442
|
+
}
|
|
2443
|
+
}
|
|
2444
|
+
/**
|
|
2445
|
+
* Check NATS health status
|
|
2446
|
+
*
|
|
2447
|
+
* @example
|
|
2448
|
+
* ```typescript
|
|
2449
|
+
* const health = await sdk.isNatsHealthy();
|
|
2450
|
+
* if (health.status === 'healthy') {
|
|
2451
|
+
* console.log('NATS is connected');
|
|
2452
|
+
* }
|
|
2453
|
+
* ```
|
|
2454
|
+
*/
|
|
2455
|
+
async isNatsHealthy() {
|
|
2456
|
+
if (!this.natsConfig) {
|
|
2457
|
+
return {
|
|
2458
|
+
status: "not_configured",
|
|
2459
|
+
connected: false
|
|
2460
|
+
};
|
|
2461
|
+
}
|
|
2462
|
+
if (!this.natsPublisher) {
|
|
2463
|
+
return {
|
|
2464
|
+
status: "unhealthy",
|
|
2465
|
+
connected: false,
|
|
2466
|
+
url: this.natsConfig.url,
|
|
2467
|
+
details: {
|
|
2468
|
+
reason: "Not initialized"
|
|
2469
|
+
}
|
|
2470
|
+
};
|
|
2471
|
+
}
|
|
2472
|
+
try {
|
|
2473
|
+
const healthCheck = await this.natsPublisher.healthCheck();
|
|
2474
|
+
return {
|
|
2475
|
+
status: healthCheck.status,
|
|
2476
|
+
connected: this.natsPublisher.isConnected(),
|
|
2477
|
+
url: this.natsConfig.url,
|
|
2478
|
+
details: healthCheck.details
|
|
2479
|
+
};
|
|
2480
|
+
} catch (error) {
|
|
2481
|
+
return {
|
|
2482
|
+
status: "unhealthy",
|
|
2483
|
+
connected: false,
|
|
2484
|
+
url: this.natsConfig.url,
|
|
2485
|
+
details: {
|
|
2486
|
+
error: error instanceof Error ? error.message : String(error)
|
|
2487
|
+
}
|
|
2488
|
+
};
|
|
2489
|
+
}
|
|
2490
|
+
}
|
|
2491
|
+
/**
|
|
2492
|
+
* Check Temporal health status
|
|
2493
|
+
*
|
|
2494
|
+
* @example
|
|
2495
|
+
* ```typescript
|
|
2496
|
+
* const health = await sdk.isTemporalHealthy();
|
|
2497
|
+
* if (health.status === 'healthy') {
|
|
2498
|
+
* console.log('Temporal is connected');
|
|
2499
|
+
* }
|
|
2500
|
+
* ```
|
|
2501
|
+
*/
|
|
2502
|
+
async isTemporalHealthy() {
|
|
2503
|
+
if (!this._temporal) {
|
|
2504
|
+
return {
|
|
2505
|
+
status: "not_configured",
|
|
2506
|
+
connected: false
|
|
2507
|
+
};
|
|
2508
|
+
}
|
|
2509
|
+
const config = this._temporal.getConfig();
|
|
2510
|
+
if (!config.address) {
|
|
2511
|
+
return {
|
|
2512
|
+
status: "not_configured",
|
|
2513
|
+
connected: false
|
|
2514
|
+
};
|
|
2515
|
+
}
|
|
2516
|
+
try {
|
|
2517
|
+
const client = this._temporal.getClient();
|
|
2518
|
+
const connected = client.isClientConnected();
|
|
2519
|
+
return {
|
|
2520
|
+
status: connected ? "healthy" : "unhealthy",
|
|
2521
|
+
connected,
|
|
2522
|
+
address: config.address,
|
|
2523
|
+
namespace: config.namespace,
|
|
2524
|
+
details: {
|
|
2525
|
+
taskQueue: config.taskQueue
|
|
2526
|
+
}
|
|
2527
|
+
};
|
|
2528
|
+
} catch (error) {
|
|
2529
|
+
return {
|
|
2530
|
+
status: "unhealthy",
|
|
2531
|
+
connected: false,
|
|
2532
|
+
address: config.address,
|
|
2533
|
+
namespace: config.namespace,
|
|
2534
|
+
details: {
|
|
2535
|
+
error: error instanceof Error ? error.message : String(error)
|
|
2536
|
+
}
|
|
2537
|
+
};
|
|
2538
|
+
}
|
|
2539
|
+
}
|
|
2540
|
+
/**
|
|
2541
|
+
* Get overall SDK health status
|
|
2542
|
+
*
|
|
2543
|
+
* @example
|
|
2544
|
+
* ```typescript
|
|
2545
|
+
* const health = await sdk.getHealthStatus();
|
|
2546
|
+
* console.log('NATS:', health.nats.status);
|
|
2547
|
+
* console.log('Temporal:', health.temporal.status);
|
|
2548
|
+
* ```
|
|
2549
|
+
*/
|
|
2550
|
+
async getHealthStatus() {
|
|
2551
|
+
const [nats, temporal] = await Promise.all([
|
|
2552
|
+
this.isNatsHealthy(),
|
|
2553
|
+
this.isTemporalHealthy()
|
|
2554
|
+
]);
|
|
2555
|
+
return {
|
|
2556
|
+
nats,
|
|
2557
|
+
temporal
|
|
2558
|
+
};
|
|
2559
|
+
}
|
|
2560
|
+
/**
|
|
2561
|
+
* Graceful shutdown - disconnect all resources
|
|
2562
|
+
*
|
|
2563
|
+
* @example
|
|
2564
|
+
* ```typescript
|
|
2565
|
+
* // On application shutdown
|
|
2566
|
+
* await sdk.shutdown({ timeout: 5000, force: true });
|
|
2567
|
+
* ```
|
|
2568
|
+
*/
|
|
2569
|
+
async shutdown(options = {}) {
|
|
2570
|
+
if (this.isShuttingDown) {
|
|
2571
|
+
logger.warn("[SDK] Shutdown already in progress");
|
|
2572
|
+
return;
|
|
2573
|
+
}
|
|
2574
|
+
this.isShuttingDown = true;
|
|
2575
|
+
const timeout = options.timeout ?? 5e3;
|
|
2576
|
+
const force = options.force ?? true;
|
|
2577
|
+
logger.info("[SDK] Starting graceful shutdown", {
|
|
2578
|
+
timeout,
|
|
2579
|
+
force
|
|
2580
|
+
});
|
|
2581
|
+
const shutdownPromises = [];
|
|
2582
|
+
if (this.natsPublisher) {
|
|
2583
|
+
shutdownPromises.push(this.natsPublisher.disconnect().catch((error) => {
|
|
2584
|
+
logger.error("[SDK] Error disconnecting NATS", {
|
|
2585
|
+
error
|
|
2586
|
+
});
|
|
2587
|
+
}));
|
|
2588
|
+
}
|
|
2589
|
+
if (this._temporal) {
|
|
2590
|
+
shutdownPromises.push(this._temporal.disconnect().catch((error) => {
|
|
2591
|
+
logger.error("[SDK] Error disconnecting Temporal", {
|
|
2592
|
+
error
|
|
2593
|
+
});
|
|
2594
|
+
}));
|
|
2595
|
+
}
|
|
2596
|
+
try {
|
|
2597
|
+
await Promise.race([
|
|
2598
|
+
Promise.allSettled(shutdownPromises),
|
|
2599
|
+
new Promise((_, reject) => setTimeout(() => reject(new Error("[SDK] Shutdown timeout exceeded")), timeout))
|
|
2600
|
+
]);
|
|
2601
|
+
logger.info("[SDK] Shutdown completed successfully");
|
|
2602
|
+
} catch (error) {
|
|
2603
|
+
if (!force) {
|
|
2604
|
+
throw error;
|
|
2605
|
+
}
|
|
2606
|
+
logger.warn("[SDK] Shutdown timeout exceeded, forcing shutdown", {
|
|
2607
|
+
error
|
|
2608
|
+
});
|
|
2609
|
+
}
|
|
2610
|
+
}
|
|
2611
|
+
/**
|
|
2612
|
+
* Create bootstrap instance
|
|
2613
|
+
*/
|
|
2614
|
+
static createBootstrap(config) {
|
|
2615
|
+
return createBootstrap(config);
|
|
2616
|
+
}
|
|
2617
|
+
};
|
|
2618
|
+
var index_default = BurdenoffSDK;
|
|
2619
|
+
function createSDK(config, context) {
|
|
2620
|
+
return new BurdenoffSDK(config, context);
|
|
2621
|
+
}
|
|
2622
|
+
__name(createSDK, "createSDK");
|
|
2623
|
+
|
|
2624
|
+
export { BootstrapModule, BurdenoffGraphQLClient, BurdenoffSDK, ContextManager, PaymentsModule, SearchModule, TenantBackendClient, TranslationModule, createBootstrap, createSDK, createTenantBackendClient, index_default as default };
|
|
2625
|
+
//# sourceMappingURL=index.js.map
|
|
2626
|
+
//# sourceMappingURL=index.js.map
|