@classytic/arc 1.1.0 → 2.1.2
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 +247 -794
- package/bin/arc.js +91 -52
- package/dist/EventTransport-BD2U0BTc.d.mts +100 -0
- package/dist/EventTransport-BD2U0BTc.d.mts.map +1 -0
- package/dist/HookSystem-BsGV-j2l.mjs +405 -0
- package/dist/HookSystem-BsGV-j2l.mjs.map +1 -0
- package/dist/ResourceRegistry-DsN4KJjV.mjs +250 -0
- package/dist/ResourceRegistry-DsN4KJjV.mjs.map +1 -0
- package/dist/adapters/index.d.mts +5 -0
- package/dist/adapters/index.mjs +3 -0
- package/dist/audit/index.d.mts +82 -0
- package/dist/audit/index.d.mts.map +1 -0
- package/dist/audit/index.mjs +276 -0
- package/dist/audit/index.mjs.map +1 -0
- package/dist/audit/mongodb.d.mts +5 -0
- package/dist/audit/mongodb.mjs +3 -0
- package/dist/audited-C3T5DTUx.mjs +141 -0
- package/dist/audited-C3T5DTUx.mjs.map +1 -0
- package/dist/auth/index.d.mts +189 -0
- package/dist/auth/index.d.mts.map +1 -0
- package/dist/auth/index.mjs +1102 -0
- package/dist/auth/index.mjs.map +1 -0
- package/dist/auth/redis-session.d.mts +44 -0
- package/dist/auth/redis-session.d.mts.map +1 -0
- package/dist/auth/redis-session.mjs +76 -0
- package/dist/auth/redis-session.mjs.map +1 -0
- package/dist/betterAuthOpenApi-BrHKeSAx.mjs +250 -0
- package/dist/betterAuthOpenApi-BrHKeSAx.mjs.map +1 -0
- package/dist/cache/index.d.mts +146 -0
- package/dist/cache/index.d.mts.map +1 -0
- package/dist/cache/index.mjs +92 -0
- package/dist/cache/index.mjs.map +1 -0
- package/dist/caching-Bl28lYsR.mjs +94 -0
- package/dist/caching-Bl28lYsR.mjs.map +1 -0
- package/dist/chunk-C7Uep-_p.mjs +20 -0
- package/dist/circuitBreaker-DeY4FCjs.mjs +1097 -0
- package/dist/circuitBreaker-DeY4FCjs.mjs.map +1 -0
- package/dist/cli/commands/describe.d.mts +19 -0
- package/dist/cli/commands/describe.d.mts.map +1 -0
- package/dist/cli/commands/describe.mjs +239 -0
- package/dist/cli/commands/describe.mjs.map +1 -0
- package/dist/cli/commands/docs.d.mts +14 -0
- package/dist/cli/commands/docs.d.mts.map +1 -0
- package/dist/cli/commands/docs.mjs +53 -0
- package/dist/cli/commands/docs.mjs.map +1 -0
- package/dist/cli/commands/{generate.d.ts → generate.d.mts} +3 -1
- package/dist/cli/commands/generate.d.mts.map +1 -0
- package/dist/cli/commands/generate.mjs +358 -0
- package/dist/cli/commands/generate.mjs.map +1 -0
- package/dist/cli/commands/{init.d.ts → init.d.mts} +12 -8
- package/dist/cli/commands/init.d.mts.map +1 -0
- package/dist/cli/commands/{init.js → init.mjs} +807 -616
- package/dist/cli/commands/init.mjs.map +1 -0
- package/dist/cli/commands/introspect.d.mts +11 -0
- package/dist/cli/commands/introspect.d.mts.map +1 -0
- package/dist/cli/commands/introspect.mjs +76 -0
- package/dist/cli/commands/introspect.mjs.map +1 -0
- package/dist/cli/index.d.mts +17 -0
- package/dist/cli/index.d.mts.map +1 -0
- package/dist/cli/index.mjs +157 -0
- package/dist/cli/index.mjs.map +1 -0
- package/dist/constants-DdXFXQtN.mjs +85 -0
- package/dist/constants-DdXFXQtN.mjs.map +1 -0
- package/dist/core/index.d.mts +5 -0
- package/dist/core/index.mjs +4 -0
- package/dist/createApp-CUgNqegw.mjs +560 -0
- package/dist/createApp-CUgNqegw.mjs.map +1 -0
- package/dist/defineResource-k0_BDn8v.mjs +2197 -0
- package/dist/defineResource-k0_BDn8v.mjs.map +1 -0
- package/dist/discovery/index.d.mts +47 -0
- package/dist/discovery/index.d.mts.map +1 -0
- package/dist/discovery/index.mjs +110 -0
- package/dist/discovery/index.mjs.map +1 -0
- package/dist/docs/index.d.mts +163 -0
- package/dist/docs/index.d.mts.map +1 -0
- package/dist/docs/index.mjs +73 -0
- package/dist/docs/index.mjs.map +1 -0
- package/dist/elevation-BRy3yFWT.mjs +113 -0
- package/dist/elevation-BRy3yFWT.mjs.map +1 -0
- package/dist/elevation-B_2dRLVP.d.mts +88 -0
- package/dist/elevation-B_2dRLVP.d.mts.map +1 -0
- package/dist/errorHandler-BbcgBmIH.d.mts +73 -0
- package/dist/errorHandler-BbcgBmIH.d.mts.map +1 -0
- package/dist/errorHandler-C1okiriz.mjs +109 -0
- package/dist/errorHandler-C1okiriz.mjs.map +1 -0
- package/dist/errors-B9bZok84.mjs +212 -0
- package/dist/errors-B9bZok84.mjs.map +1 -0
- package/dist/errors-ChKiFz62.d.mts +125 -0
- package/dist/errors-ChKiFz62.d.mts.map +1 -0
- package/dist/eventPlugin-CTrLH3mt.d.mts +125 -0
- package/dist/eventPlugin-CTrLH3mt.d.mts.map +1 -0
- package/dist/eventPlugin-DGR_B2on.mjs +230 -0
- package/dist/eventPlugin-DGR_B2on.mjs.map +1 -0
- package/dist/events/index.d.mts +54 -0
- package/dist/events/index.d.mts.map +1 -0
- package/dist/events/index.mjs +52 -0
- package/dist/events/index.mjs.map +1 -0
- package/dist/events/transports/redis-stream-entry.d.mts +2 -0
- package/dist/events/transports/redis-stream-entry.mjs +178 -0
- package/dist/events/transports/redis-stream-entry.mjs.map +1 -0
- package/dist/events/transports/redis.d.mts +77 -0
- package/dist/events/transports/redis.d.mts.map +1 -0
- package/dist/events/transports/redis.mjs +125 -0
- package/dist/events/transports/redis.mjs.map +1 -0
- package/dist/externalPaths-DlINfKbP.d.mts +51 -0
- package/dist/externalPaths-DlINfKbP.d.mts.map +1 -0
- package/dist/factory/index.d.mts +64 -0
- package/dist/factory/index.d.mts.map +1 -0
- package/dist/factory/index.mjs +3 -0
- package/dist/fastifyAdapter-BkrGrlFi.d.mts +217 -0
- package/dist/fastifyAdapter-BkrGrlFi.d.mts.map +1 -0
- package/dist/fields-DyaDVX4J.d.mts +110 -0
- package/dist/fields-DyaDVX4J.d.mts.map +1 -0
- package/dist/fields-iagOozy0.mjs +115 -0
- package/dist/fields-iagOozy0.mjs.map +1 -0
- package/dist/hooks/index.d.mts +4 -0
- package/dist/hooks/index.mjs +3 -0
- package/dist/idempotency/index.d.mts +97 -0
- package/dist/idempotency/index.d.mts.map +1 -0
- package/dist/idempotency/index.mjs +320 -0
- package/dist/idempotency/index.mjs.map +1 -0
- package/dist/idempotency/mongodb.d.mts +2 -0
- package/dist/idempotency/mongodb.mjs +115 -0
- package/dist/idempotency/mongodb.mjs.map +1 -0
- package/dist/idempotency/redis.d.mts +2 -0
- package/dist/idempotency/redis.mjs +104 -0
- package/dist/idempotency/redis.mjs.map +1 -0
- package/dist/index.d.mts +261 -0
- package/dist/index.d.mts.map +1 -0
- package/dist/index.mjs +105 -0
- package/dist/index.mjs.map +1 -0
- package/dist/integrations/event-gateway.d.mts +47 -0
- package/dist/integrations/event-gateway.d.mts.map +1 -0
- package/dist/integrations/event-gateway.mjs +44 -0
- package/dist/integrations/event-gateway.mjs.map +1 -0
- package/dist/integrations/index.d.mts +5 -0
- package/dist/integrations/index.mjs +1 -0
- package/dist/integrations/jobs.d.mts +104 -0
- package/dist/integrations/jobs.d.mts.map +1 -0
- package/dist/integrations/jobs.mjs +124 -0
- package/dist/integrations/jobs.mjs.map +1 -0
- package/dist/integrations/streamline.d.mts +61 -0
- package/dist/integrations/streamline.d.mts.map +1 -0
- package/dist/integrations/streamline.mjs +126 -0
- package/dist/integrations/streamline.mjs.map +1 -0
- package/dist/integrations/websocket.d.mts +83 -0
- package/dist/integrations/websocket.d.mts.map +1 -0
- package/dist/integrations/websocket.mjs +289 -0
- package/dist/integrations/websocket.mjs.map +1 -0
- package/dist/interface-B01JvPVc.d.mts +78 -0
- package/dist/interface-B01JvPVc.d.mts.map +1 -0
- package/dist/interface-CZe8IkMf.d.mts +55 -0
- package/dist/interface-CZe8IkMf.d.mts.map +1 -0
- package/dist/interface-Ch8HU9uM.d.mts +1098 -0
- package/dist/interface-Ch8HU9uM.d.mts.map +1 -0
- package/dist/introspectionPlugin-rFdO8ZUa.mjs +54 -0
- package/dist/introspectionPlugin-rFdO8ZUa.mjs.map +1 -0
- package/dist/keys-BqNejWup.mjs +43 -0
- package/dist/keys-BqNejWup.mjs.map +1 -0
- package/dist/logger-Df2O2WsW.mjs +79 -0
- package/dist/logger-Df2O2WsW.mjs.map +1 -0
- package/dist/memory-cQgelFOj.mjs +144 -0
- package/dist/memory-cQgelFOj.mjs.map +1 -0
- package/dist/migrations/index.d.mts +157 -0
- package/dist/migrations/index.d.mts.map +1 -0
- package/dist/migrations/index.mjs +261 -0
- package/dist/migrations/index.mjs.map +1 -0
- package/dist/mongodb-BfJVlUJH.mjs +94 -0
- package/dist/mongodb-BfJVlUJH.mjs.map +1 -0
- package/dist/mongodb-CGzRbfAK.d.mts +119 -0
- package/dist/mongodb-CGzRbfAK.d.mts.map +1 -0
- package/dist/mongodb-JN-9JA7K.d.mts +72 -0
- package/dist/mongodb-JN-9JA7K.d.mts.map +1 -0
- package/dist/openapi-G3Cw7XuM.mjs +524 -0
- package/dist/openapi-G3Cw7XuM.mjs.map +1 -0
- package/dist/org/index.d.mts +69 -0
- package/dist/org/index.d.mts.map +1 -0
- package/dist/org/index.mjs +514 -0
- package/dist/org/index.mjs.map +1 -0
- package/dist/org/types.d.mts +83 -0
- package/dist/org/types.d.mts.map +1 -0
- package/dist/org/types.mjs +1 -0
- package/dist/permissions/index.d.mts +279 -0
- package/dist/permissions/index.d.mts.map +1 -0
- package/dist/permissions/index.mjs +579 -0
- package/dist/permissions/index.mjs.map +1 -0
- package/dist/plugins/index.d.mts +173 -0
- package/dist/plugins/index.d.mts.map +1 -0
- package/dist/plugins/index.mjs +523 -0
- package/dist/plugins/index.mjs.map +1 -0
- package/dist/plugins/response-cache.d.mts +88 -0
- package/dist/plugins/response-cache.d.mts.map +1 -0
- package/dist/plugins/response-cache.mjs +284 -0
- package/dist/plugins/response-cache.mjs.map +1 -0
- package/dist/plugins/tracing-entry.d.mts +2 -0
- package/dist/plugins/tracing-entry.mjs +186 -0
- package/dist/plugins/tracing-entry.mjs.map +1 -0
- package/dist/pluralize-CEweyOEm.mjs +87 -0
- package/dist/pluralize-CEweyOEm.mjs.map +1 -0
- package/dist/policies/{index.d.ts → index.d.mts} +204 -169
- package/dist/policies/index.d.mts.map +1 -0
- package/dist/policies/index.mjs +322 -0
- package/dist/policies/index.mjs.map +1 -0
- package/dist/presets/{index.d.ts → index.d.mts} +63 -131
- package/dist/presets/index.d.mts.map +1 -0
- package/dist/presets/index.mjs +144 -0
- package/dist/presets/index.mjs.map +1 -0
- package/dist/presets/multiTenant.d.mts +25 -0
- package/dist/presets/multiTenant.d.mts.map +1 -0
- package/dist/presets/multiTenant.mjs +114 -0
- package/dist/presets/multiTenant.mjs.map +1 -0
- package/dist/presets-BITljm96.mjs +120 -0
- package/dist/presets-BITljm96.mjs.map +1 -0
- package/dist/presets-DzSMwlKj.d.mts +58 -0
- package/dist/presets-DzSMwlKj.d.mts.map +1 -0
- package/dist/prisma-DJbMt3yf.mjs +628 -0
- package/dist/prisma-DJbMt3yf.mjs.map +1 -0
- package/dist/prisma-Dg9GoVdj.d.mts +275 -0
- package/dist/prisma-Dg9GoVdj.d.mts.map +1 -0
- package/dist/queryCachePlugin-7THaI5mt.d.mts +72 -0
- package/dist/queryCachePlugin-7THaI5mt.d.mts.map +1 -0
- package/dist/queryCachePlugin-DMBnp2Q0.mjs +139 -0
- package/dist/queryCachePlugin-DMBnp2Q0.mjs.map +1 -0
- package/dist/redis-D-JAeLtm.d.mts +50 -0
- package/dist/redis-D-JAeLtm.d.mts.map +1 -0
- package/dist/redis-stream-Bdh_vUU8.d.mts +104 -0
- package/dist/redis-stream-Bdh_vUU8.d.mts.map +1 -0
- package/dist/registry/index.d.mts +12 -0
- package/dist/registry/index.d.mts.map +1 -0
- package/dist/registry/index.mjs +4 -0
- package/dist/requestContext-QQD6ROJc.mjs +56 -0
- package/dist/requestContext-QQD6ROJc.mjs.map +1 -0
- package/dist/schemaConverter-BwrmWroW.mjs +99 -0
- package/dist/schemaConverter-BwrmWroW.mjs.map +1 -0
- package/dist/schemas/index.d.mts +64 -0
- package/dist/schemas/index.d.mts.map +1 -0
- package/dist/schemas/index.mjs +83 -0
- package/dist/schemas/index.mjs.map +1 -0
- package/dist/scope/index.d.mts +22 -0
- package/dist/scope/index.d.mts.map +1 -0
- package/dist/scope/index.mjs +66 -0
- package/dist/scope/index.mjs.map +1 -0
- package/dist/sessionManager-jPKLbHE0.d.mts +187 -0
- package/dist/sessionManager-jPKLbHE0.d.mts.map +1 -0
- package/dist/sse-B3c3_yZp.mjs +124 -0
- package/dist/sse-B3c3_yZp.mjs.map +1 -0
- package/dist/testing/index.d.mts +908 -0
- package/dist/testing/index.d.mts.map +1 -0
- package/dist/testing/index.mjs +1977 -0
- package/dist/testing/index.mjs.map +1 -0
- package/dist/tracing-Cc7vVQPp.d.mts +71 -0
- package/dist/tracing-Cc7vVQPp.d.mts.map +1 -0
- package/dist/typeGuards-DhMNLuvU.mjs +10 -0
- package/dist/typeGuards-DhMNLuvU.mjs.map +1 -0
- package/dist/types/index.d.mts +947 -0
- package/dist/types/index.d.mts.map +1 -0
- package/dist/types/index.mjs +15 -0
- package/dist/types/index.mjs.map +1 -0
- package/dist/types-Beqn1Un7.mjs +39 -0
- package/dist/types-Beqn1Un7.mjs.map +1 -0
- package/dist/types-CIgB7UUl.d.mts +446 -0
- package/dist/types-CIgB7UUl.d.mts.map +1 -0
- package/dist/types-aYB4V7uN.d.mts +87 -0
- package/dist/types-aYB4V7uN.d.mts.map +1 -0
- package/dist/utils/index.d.mts +748 -0
- package/dist/utils/index.d.mts.map +1 -0
- package/dist/utils/index.mjs +6 -0
- package/package.json +194 -68
- package/dist/BaseController-DVAiHxEQ.d.ts +0 -233
- package/dist/adapters/index.d.ts +0 -237
- package/dist/adapters/index.js +0 -668
- package/dist/arcCorePlugin-CsShQdyP.d.ts +0 -273
- package/dist/audit/index.d.ts +0 -195
- package/dist/audit/index.js +0 -319
- package/dist/auth/index.d.ts +0 -47
- package/dist/auth/index.js +0 -174
- package/dist/cli/commands/docs.d.ts +0 -11
- package/dist/cli/commands/docs.js +0 -474
- package/dist/cli/commands/generate.js +0 -334
- package/dist/cli/commands/introspect.d.ts +0 -8
- package/dist/cli/commands/introspect.js +0 -338
- package/dist/cli/index.d.ts +0 -4
- package/dist/cli/index.js +0 -3269
- package/dist/core/index.d.ts +0 -220
- package/dist/core/index.js +0 -2786
- package/dist/createApp-Ce9wl8W9.d.ts +0 -77
- package/dist/docs/index.d.ts +0 -166
- package/dist/docs/index.js +0 -658
- package/dist/errors-8WIxGS_6.d.ts +0 -122
- package/dist/events/index.d.ts +0 -117
- package/dist/events/index.js +0 -89
- package/dist/factory/index.d.ts +0 -38
- package/dist/factory/index.js +0 -1652
- package/dist/hooks/index.d.ts +0 -4
- package/dist/hooks/index.js +0 -199
- package/dist/idempotency/index.d.ts +0 -323
- package/dist/idempotency/index.js +0 -500
- package/dist/index-B4t03KQ0.d.ts +0 -1366
- package/dist/index.d.ts +0 -135
- package/dist/index.js +0 -4756
- package/dist/migrations/index.d.ts +0 -185
- package/dist/migrations/index.js +0 -274
- package/dist/org/index.d.ts +0 -129
- package/dist/org/index.js +0 -220
- package/dist/permissions/index.d.ts +0 -144
- package/dist/permissions/index.js +0 -103
- package/dist/plugins/index.d.ts +0 -46
- package/dist/plugins/index.js +0 -1069
- package/dist/policies/index.js +0 -196
- package/dist/presets/index.js +0 -384
- package/dist/presets/multiTenant.d.ts +0 -39
- package/dist/presets/multiTenant.js +0 -112
- package/dist/registry/index.d.ts +0 -16
- package/dist/registry/index.js +0 -253
- package/dist/testing/index.d.ts +0 -618
- package/dist/testing/index.js +0 -48020
- package/dist/types/index.d.ts +0 -4
- package/dist/types/index.js +0 -8
- package/dist/types-B99TBmFV.d.ts +0 -76
- package/dist/types-BvckRbs2.d.ts +0 -143
- package/dist/utils/index.d.ts +0 -679
- package/dist/utils/index.js +0 -931
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
import { createRequire } from "node:module";
|
|
2
|
+
import fp from "fastify-plugin";
|
|
3
|
+
|
|
4
|
+
//#region src/plugins/tracing.ts
|
|
5
|
+
const require = createRequire(import.meta.url);
|
|
6
|
+
let trace;
|
|
7
|
+
let context;
|
|
8
|
+
let SpanStatusCode;
|
|
9
|
+
let NodeTracerProvider;
|
|
10
|
+
let BatchSpanProcessor;
|
|
11
|
+
let OTLPTraceExporter;
|
|
12
|
+
let getNodeAutoInstrumentations;
|
|
13
|
+
let isAvailable = false;
|
|
14
|
+
try {
|
|
15
|
+
const api = require("@opentelemetry/api");
|
|
16
|
+
trace = api.trace;
|
|
17
|
+
context = api.context;
|
|
18
|
+
SpanStatusCode = api.SpanStatusCode;
|
|
19
|
+
const sdkNode = require("@opentelemetry/sdk-node");
|
|
20
|
+
NodeTracerProvider = sdkNode.NodeTracerProvider;
|
|
21
|
+
BatchSpanProcessor = sdkNode.BatchSpanProcessor;
|
|
22
|
+
OTLPTraceExporter = require("@opentelemetry/exporter-trace-otlp-http").OTLPTraceExporter;
|
|
23
|
+
require("@opentelemetry/instrumentation-http").HttpInstrumentation;
|
|
24
|
+
require("@opentelemetry/instrumentation-mongodb").MongoDBInstrumentation;
|
|
25
|
+
getNodeAutoInstrumentations = require("@opentelemetry/auto-instrumentations-node").getNodeAutoInstrumentations;
|
|
26
|
+
isAvailable = true;
|
|
27
|
+
} catch (e) {}
|
|
28
|
+
/**
|
|
29
|
+
* Create a tracer provider
|
|
30
|
+
*/
|
|
31
|
+
function createTracerProvider(options) {
|
|
32
|
+
if (!isAvailable) return null;
|
|
33
|
+
const { serviceName = "@classytic/arc", exporterUrl = "http://localhost:4318/v1/traces" } = options;
|
|
34
|
+
const exporter = new OTLPTraceExporter({ url: exporterUrl });
|
|
35
|
+
const provider = new NodeTracerProvider({ resource: { attributes: {
|
|
36
|
+
"service.name": serviceName,
|
|
37
|
+
"service.version": "1.0.0"
|
|
38
|
+
} } });
|
|
39
|
+
provider.addSpanProcessor(new BatchSpanProcessor(exporter));
|
|
40
|
+
provider.register();
|
|
41
|
+
return provider;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* OpenTelemetry Distributed Tracing Plugin
|
|
45
|
+
*/
|
|
46
|
+
async function tracingPlugin(fastify, options = {}) {
|
|
47
|
+
const { serviceName = "@classytic/arc", autoInstrumentation = true, sampleRate = 1 } = options;
|
|
48
|
+
if (!isAvailable) {
|
|
49
|
+
fastify.log.warn("OpenTelemetry not installed. Tracing disabled.");
|
|
50
|
+
fastify.log.warn("Install: npm install @opentelemetry/api @opentelemetry/sdk-node");
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
if (!createTracerProvider(options)) return;
|
|
54
|
+
if (autoInstrumentation && getNodeAutoInstrumentations) {
|
|
55
|
+
const instrumentations = getNodeAutoInstrumentations({
|
|
56
|
+
"@opentelemetry/instrumentation-http": { enabled: true },
|
|
57
|
+
"@opentelemetry/instrumentation-mongodb": { enabled: true }
|
|
58
|
+
});
|
|
59
|
+
for (const instrumentation of instrumentations) instrumentation.enable();
|
|
60
|
+
fastify.log.debug("OpenTelemetry auto-instrumentation enabled");
|
|
61
|
+
}
|
|
62
|
+
const tracer = trace.getTracer(serviceName);
|
|
63
|
+
fastify.decorateRequest("tracer", void 0);
|
|
64
|
+
fastify.addHook("onRequest", async (request, reply) => {
|
|
65
|
+
if (Math.random() > sampleRate) return;
|
|
66
|
+
const span = tracer.startSpan(`HTTP ${request.method} ${request.url}`, {
|
|
67
|
+
kind: 1,
|
|
68
|
+
attributes: {
|
|
69
|
+
"http.method": request.method,
|
|
70
|
+
"http.url": request.url,
|
|
71
|
+
"http.target": request.routeOptions?.url ?? request.url,
|
|
72
|
+
"http.host": request.hostname,
|
|
73
|
+
"http.scheme": request.protocol,
|
|
74
|
+
"http.user_agent": request.headers["user-agent"]
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
request.tracer = {
|
|
78
|
+
tracer,
|
|
79
|
+
currentSpan: span
|
|
80
|
+
};
|
|
81
|
+
context.with(trace.setSpan(context.active(), span), () => {});
|
|
82
|
+
});
|
|
83
|
+
fastify.addHook("onResponse", async (request, reply) => {
|
|
84
|
+
if (!request.tracer?.currentSpan) return;
|
|
85
|
+
const span = request.tracer.currentSpan;
|
|
86
|
+
span.setAttributes({
|
|
87
|
+
"http.status_code": reply.statusCode,
|
|
88
|
+
"http.response_content_length": reply.getHeader("content-length")
|
|
89
|
+
});
|
|
90
|
+
if (reply.statusCode >= 500) span.setStatus({
|
|
91
|
+
code: SpanStatusCode.ERROR,
|
|
92
|
+
message: `HTTP ${reply.statusCode}`
|
|
93
|
+
});
|
|
94
|
+
else span.setStatus({ code: SpanStatusCode.OK });
|
|
95
|
+
span.end();
|
|
96
|
+
});
|
|
97
|
+
fastify.addHook("onError", async (request, reply, error) => {
|
|
98
|
+
if (!request.tracer?.currentSpan) return;
|
|
99
|
+
const span = request.tracer.currentSpan;
|
|
100
|
+
span.recordException(error);
|
|
101
|
+
span.setStatus({
|
|
102
|
+
code: SpanStatusCode.ERROR,
|
|
103
|
+
message: error.message
|
|
104
|
+
});
|
|
105
|
+
});
|
|
106
|
+
fastify.log.debug({ serviceName }, "OpenTelemetry tracing enabled");
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Utility to create custom spans in your code
|
|
110
|
+
*
|
|
111
|
+
* @example
|
|
112
|
+
* import { createSpan } from '@classytic/arc/plugins';
|
|
113
|
+
*
|
|
114
|
+
* async function expensiveOperation(req) {
|
|
115
|
+
* return createSpan(req, 'expensiveOperation', async (span) => {
|
|
116
|
+
* span.setAttribute('custom.attribute', 'value');
|
|
117
|
+
* return await doWork();
|
|
118
|
+
* });
|
|
119
|
+
* }
|
|
120
|
+
*/
|
|
121
|
+
function createSpan(request, name, fn, attributes) {
|
|
122
|
+
if (!isAvailable || !request.tracer) return fn(null);
|
|
123
|
+
const { tracer, currentSpan } = request.tracer;
|
|
124
|
+
const span = tracer.startSpan(name, {
|
|
125
|
+
parent: currentSpan,
|
|
126
|
+
attributes: attributes || {}
|
|
127
|
+
}, trace.setSpan(context.active(), currentSpan));
|
|
128
|
+
return context.with(trace.setSpan(context.active(), span), async () => {
|
|
129
|
+
try {
|
|
130
|
+
const result = await fn(span);
|
|
131
|
+
span.setStatus({ code: SpanStatusCode.OK });
|
|
132
|
+
return result;
|
|
133
|
+
} catch (error) {
|
|
134
|
+
span.recordException(error);
|
|
135
|
+
span.setStatus({
|
|
136
|
+
code: SpanStatusCode.ERROR,
|
|
137
|
+
message: error.message
|
|
138
|
+
});
|
|
139
|
+
throw error;
|
|
140
|
+
} finally {
|
|
141
|
+
span.end();
|
|
142
|
+
}
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Decorator to automatically trace repository methods
|
|
147
|
+
*
|
|
148
|
+
* @example
|
|
149
|
+
* class ProductRepository extends Repository {
|
|
150
|
+
* @traced()
|
|
151
|
+
* async findActive() {
|
|
152
|
+
* return this.findAll({ filter: { isActive: true } });
|
|
153
|
+
* }
|
|
154
|
+
* }
|
|
155
|
+
*/
|
|
156
|
+
function traced(spanName) {
|
|
157
|
+
return function(target, propertyKey, descriptor) {
|
|
158
|
+
const originalMethod = descriptor.value;
|
|
159
|
+
descriptor.value = async function(...args) {
|
|
160
|
+
const request = args.find((arg) => arg && arg.tracer);
|
|
161
|
+
if (!request?.tracer) return originalMethod.apply(this, args);
|
|
162
|
+
return createSpan(request, spanName || `${target.constructor.name}.${propertyKey}`, async (span) => {
|
|
163
|
+
if (span) {
|
|
164
|
+
span.setAttribute("db.operation", propertyKey);
|
|
165
|
+
span.setAttribute("db.system", "mongodb");
|
|
166
|
+
}
|
|
167
|
+
return originalMethod.apply(this, args);
|
|
168
|
+
});
|
|
169
|
+
};
|
|
170
|
+
return descriptor;
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* Check if OpenTelemetry is available
|
|
175
|
+
*/
|
|
176
|
+
function isTracingAvailable() {
|
|
177
|
+
return isAvailable;
|
|
178
|
+
}
|
|
179
|
+
var tracing_default = fp(tracingPlugin, {
|
|
180
|
+
name: "arc-tracing",
|
|
181
|
+
fastify: "5.x"
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
//#endregion
|
|
185
|
+
export { createSpan, isTracingAvailable, traced, tracing_default as tracingPlugin };
|
|
186
|
+
//# sourceMappingURL=tracing-entry.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tracing-entry.mjs","names":[],"sources":["../../src/plugins/tracing.ts"],"sourcesContent":["/**\n * OpenTelemetry Distributed Tracing Plugin\n *\n * Traces HTTP requests, repository operations, and MongoDB queries\n * across the entire application lifecycle.\n *\n * @example\n * import { tracingPlugin } from '@classytic/arc/plugins';\n *\n * await fastify.register(tracingPlugin, {\n * serviceName: 'my-api',\n * exporterUrl: 'http://localhost:4318/v1/traces', // OTLP endpoint\n * });\n */\n\nimport type { FastifyInstance, FastifyRequest, FastifyReply } from 'fastify';\nimport fp from 'fastify-plugin';\nimport { createRequire } from 'node:module';\n\nconst require = createRequire(import.meta.url);\n\n// OpenTelemetry imports (peer dependencies)\nlet trace: any;\nlet context: any;\nlet SpanStatusCode: any;\nlet NodeTracerProvider: any;\nlet BatchSpanProcessor: any;\nlet OTLPTraceExporter: any;\nlet HttpInstrumentation: any;\nlet MongoDBInstrumentation: any;\nlet getNodeAutoInstrumentations: any;\n\n// Try to load OpenTelemetry (optional peer dependency)\nlet isAvailable = false;\ntry {\n const api = require('@opentelemetry/api');\n trace = api.trace;\n context = api.context;\n SpanStatusCode = api.SpanStatusCode;\n\n const sdkNode = require('@opentelemetry/sdk-node');\n NodeTracerProvider = sdkNode.NodeTracerProvider;\n BatchSpanProcessor = sdkNode.BatchSpanProcessor;\n\n const exporterTraceOtlp = require('@opentelemetry/exporter-trace-otlp-http');\n OTLPTraceExporter = exporterTraceOtlp.OTLPTraceExporter;\n\n const instrHttp = require('@opentelemetry/instrumentation-http');\n HttpInstrumentation = instrHttp.HttpInstrumentation;\n\n const instrMongo = require('@opentelemetry/instrumentation-mongodb');\n MongoDBInstrumentation = instrMongo.MongoDBInstrumentation;\n\n const autoInstr = require('@opentelemetry/auto-instrumentations-node');\n getNodeAutoInstrumentations = autoInstr.getNodeAutoInstrumentations;\n\n isAvailable = true;\n} catch (e) {\n // OpenTelemetry not installed - plugin will be no-op\n}\n\nexport interface TracingOptions {\n /**\n * Service name for traces\n */\n serviceName?: string;\n\n /**\n * OTLP exporter endpoint URL\n * @default 'http://localhost:4318/v1/traces'\n */\n exporterUrl?: string;\n\n /**\n * Enable auto-instrumentation for HTTP, MongoDB, etc.\n * @default true\n */\n autoInstrumentation?: boolean;\n\n /**\n * Sample rate (0.0 to 1.0)\n * @default 1.0 (trace everything)\n */\n sampleRate?: number;\n}\n\ninterface TracerContext {\n tracer: any;\n currentSpan: any;\n}\n\ndeclare module 'fastify' {\n interface FastifyRequest {\n tracer?: TracerContext;\n }\n}\n\n/**\n * Create a tracer provider\n */\nfunction createTracerProvider(options: TracingOptions) {\n if (!isAvailable) {\n return null;\n }\n\n const { serviceName = '@classytic/arc', exporterUrl = 'http://localhost:4318/v1/traces' } =\n options;\n\n const exporter = new OTLPTraceExporter({\n url: exporterUrl,\n });\n\n const provider = new NodeTracerProvider({\n resource: {\n attributes: {\n 'service.name': serviceName,\n 'service.version': '1.0.0',\n },\n },\n });\n\n provider.addSpanProcessor(new BatchSpanProcessor(exporter));\n provider.register();\n\n return provider;\n}\n\n/**\n * OpenTelemetry Distributed Tracing Plugin\n */\nasync function tracingPlugin(fastify: FastifyInstance, options: TracingOptions = {}) {\n const {\n serviceName = '@classytic/arc',\n autoInstrumentation = true,\n sampleRate = 1.0,\n } = options;\n\n // Skip if OpenTelemetry is not available\n if (!isAvailable) {\n fastify.log.warn('OpenTelemetry not installed. Tracing disabled.');\n fastify.log.warn('Install: npm install @opentelemetry/api @opentelemetry/sdk-node');\n return;\n }\n\n // Initialize tracer provider\n const provider = createTracerProvider(options);\n if (!provider) {\n return;\n }\n\n // Auto-instrumentation — enable HTTP + MongoDB tracing\n if (autoInstrumentation && getNodeAutoInstrumentations) {\n const instrumentations = getNodeAutoInstrumentations({\n '@opentelemetry/instrumentation-http': {\n enabled: true,\n },\n '@opentelemetry/instrumentation-mongodb': {\n enabled: true,\n },\n });\n for (const instrumentation of instrumentations) {\n instrumentation.enable();\n }\n fastify.log.debug('OpenTelemetry auto-instrumentation enabled');\n }\n\n const tracer = trace.getTracer(serviceName);\n\n // Add tracer to request\n fastify.decorateRequest('tracer', undefined);\n\n // Create span for each HTTP request\n fastify.addHook('onRequest', async (request: FastifyRequest, reply: FastifyReply) => {\n // Sampling\n if (Math.random() > sampleRate) {\n return;\n }\n\n const span = tracer.startSpan(`HTTP ${request.method} ${request.url}`, {\n kind: 1, // SpanKind.SERVER\n attributes: {\n 'http.method': request.method,\n 'http.url': request.url,\n 'http.target': request.routeOptions?.url ?? request.url,\n 'http.host': request.hostname,\n 'http.scheme': request.protocol,\n 'http.user_agent': request.headers['user-agent'],\n },\n });\n\n // Store span in request for child spans\n request.tracer = {\n tracer,\n currentSpan: span,\n };\n\n // Set active context\n context.with(trace.setSpan(context.active(), span), () => {\n // Context is now active for this request\n });\n });\n\n // End span after response\n fastify.addHook('onResponse', async (request: FastifyRequest, reply: FastifyReply) => {\n if (!request.tracer?.currentSpan) {\n return;\n }\n\n const span = request.tracer.currentSpan;\n\n // Add response attributes\n span.setAttributes({\n 'http.status_code': reply.statusCode,\n 'http.response_content_length': reply.getHeader('content-length'),\n });\n\n // Set span status\n if (reply.statusCode >= 500) {\n span.setStatus({\n code: SpanStatusCode.ERROR,\n message: `HTTP ${reply.statusCode}`,\n });\n } else {\n span.setStatus({ code: SpanStatusCode.OK });\n }\n\n span.end();\n });\n\n // Error tracking\n fastify.addHook('onError', async (request: FastifyRequest, reply: FastifyReply, error: Error) => {\n if (!request.tracer?.currentSpan) {\n return;\n }\n\n const span = request.tracer.currentSpan;\n span.recordException(error);\n span.setStatus({\n code: SpanStatusCode.ERROR,\n message: error.message,\n });\n });\n\n fastify.log.debug({ serviceName }, 'OpenTelemetry tracing enabled');\n}\n\n/**\n * Utility to create custom spans in your code\n *\n * @example\n * import { createSpan } from '@classytic/arc/plugins';\n *\n * async function expensiveOperation(req) {\n * return createSpan(req, 'expensiveOperation', async (span) => {\n * span.setAttribute('custom.attribute', 'value');\n * return await doWork();\n * });\n * }\n */\nexport function createSpan<T>(\n request: FastifyRequest,\n name: string,\n fn: (span: any) => Promise<T>,\n attributes?: Record<string, any>\n): Promise<T> {\n if (!isAvailable || !request.tracer) {\n // No tracing available, just execute function\n return fn(null);\n }\n\n const { tracer, currentSpan } = request.tracer;\n\n const span = tracer.startSpan(\n name,\n {\n parent: currentSpan,\n attributes: attributes || {},\n },\n trace.setSpan(context.active(), currentSpan)\n );\n\n return context.with(trace.setSpan(context.active(), span), async () => {\n try {\n const result = await fn(span);\n span.setStatus({ code: SpanStatusCode.OK });\n return result;\n } catch (error: any) {\n span.recordException(error);\n span.setStatus({\n code: SpanStatusCode.ERROR,\n message: error.message,\n });\n throw error;\n } finally {\n span.end();\n }\n });\n}\n\n/**\n * Decorator to automatically trace repository methods\n *\n * @example\n * class ProductRepository extends Repository {\n * @traced()\n * async findActive() {\n * return this.findAll({ filter: { isActive: true } });\n * }\n * }\n */\nexport function traced(spanName?: string) {\n return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {\n const originalMethod = descriptor.value;\n\n descriptor.value = async function (this: any, ...args: any[]) {\n // Extract request from args if available\n const request = args.find((arg) => arg && arg.tracer);\n\n if (!request?.tracer) {\n // No tracing context, just execute\n return originalMethod.apply(this, args);\n }\n\n const name = spanName || `${target.constructor.name}.${propertyKey}`;\n return createSpan(request, name, async (span) => {\n if (span) {\n span.setAttribute('db.operation', propertyKey);\n span.setAttribute('db.system', 'mongodb');\n }\n return originalMethod.apply(this, args);\n });\n };\n\n return descriptor;\n };\n}\n\n/**\n * Check if OpenTelemetry is available\n */\nexport function isTracingAvailable(): boolean {\n return isAvailable;\n}\n\nexport default fp(tracingPlugin, {\n name: 'arc-tracing',\n fastify: '5.x',\n});\n"],"mappings":"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"}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
//#region src/cli/utils/pluralize.ts
|
|
2
|
+
/**
|
|
3
|
+
* Lightweight English pluralization for the Arc CLI.
|
|
4
|
+
*
|
|
5
|
+
* Covers the common cases developers hit when naming resources:
|
|
6
|
+
* company → companies
|
|
7
|
+
* category → categories
|
|
8
|
+
* status → statuses
|
|
9
|
+
* address → addresses
|
|
10
|
+
* person → people
|
|
11
|
+
* child → children
|
|
12
|
+
* bus → buses
|
|
13
|
+
* box → boxes
|
|
14
|
+
* quiz → quizzes
|
|
15
|
+
* leaf → leaves
|
|
16
|
+
* wolf → wolves
|
|
17
|
+
*
|
|
18
|
+
* No external dependencies — designed to keep the CLI install-free.
|
|
19
|
+
*/
|
|
20
|
+
const IRREGULARS = {
|
|
21
|
+
person: "people",
|
|
22
|
+
child: "children",
|
|
23
|
+
man: "men",
|
|
24
|
+
woman: "women",
|
|
25
|
+
mouse: "mice",
|
|
26
|
+
goose: "geese",
|
|
27
|
+
tooth: "teeth",
|
|
28
|
+
foot: "feet",
|
|
29
|
+
ox: "oxen",
|
|
30
|
+
datum: "data",
|
|
31
|
+
medium: "media",
|
|
32
|
+
index: "indices",
|
|
33
|
+
matrix: "matrices",
|
|
34
|
+
vertex: "vertices",
|
|
35
|
+
criterion: "criteria"
|
|
36
|
+
};
|
|
37
|
+
const UNCOUNTABLES = new Set([
|
|
38
|
+
"sheep",
|
|
39
|
+
"fish",
|
|
40
|
+
"deer",
|
|
41
|
+
"series",
|
|
42
|
+
"species",
|
|
43
|
+
"money",
|
|
44
|
+
"rice",
|
|
45
|
+
"information",
|
|
46
|
+
"equipment",
|
|
47
|
+
"media",
|
|
48
|
+
"data"
|
|
49
|
+
]);
|
|
50
|
+
/**
|
|
51
|
+
* Pluralize an English word.
|
|
52
|
+
*
|
|
53
|
+
* @param word - Singular noun (e.g. "company", "product", "person")
|
|
54
|
+
* @returns Plural form (e.g. "companies", "products", "people")
|
|
55
|
+
*/
|
|
56
|
+
function pluralize(word) {
|
|
57
|
+
const lower = word.toLowerCase();
|
|
58
|
+
if (UNCOUNTABLES.has(lower)) return word;
|
|
59
|
+
if (IRREGULARS[lower]) {
|
|
60
|
+
const plural = IRREGULARS[lower];
|
|
61
|
+
return word[0] === word[0].toUpperCase() ? plural.charAt(0).toUpperCase() + plural.slice(1) : plural;
|
|
62
|
+
}
|
|
63
|
+
if (lower.endsWith("fe")) return word.slice(0, -2) + "ves";
|
|
64
|
+
if (lower.endsWith("f") && !lower.endsWith("ff") && !lower.endsWith("roof") && !lower.endsWith("chief") && !lower.endsWith("belief")) return word.slice(0, -1) + "ves";
|
|
65
|
+
if (lower.endsWith("y") && !/[aeiou]y$/i.test(lower)) return word.slice(0, -1) + "ies";
|
|
66
|
+
if (lower.endsWith("is")) return word.slice(0, -2) + "es";
|
|
67
|
+
if (new Set([
|
|
68
|
+
"cactus",
|
|
69
|
+
"stimulus",
|
|
70
|
+
"focus",
|
|
71
|
+
"fungus",
|
|
72
|
+
"nucleus",
|
|
73
|
+
"syllabus",
|
|
74
|
+
"radius",
|
|
75
|
+
"alumnus",
|
|
76
|
+
"terminus",
|
|
77
|
+
"bacillus"
|
|
78
|
+
]).has(lower)) return word.slice(0, -2) + "i";
|
|
79
|
+
if (lower.endsWith("z") && !lower.endsWith("zz")) return word + "zes";
|
|
80
|
+
if (/(?:s|sh|ch|x|zz)$/i.test(lower)) return word + "es";
|
|
81
|
+
if (lower.endsWith("o") && !/[aeiou]o$/i.test(lower)) return word + "es";
|
|
82
|
+
return word + "s";
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
//#endregion
|
|
86
|
+
export { pluralize as t };
|
|
87
|
+
//# sourceMappingURL=pluralize-CEweyOEm.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pluralize-CEweyOEm.mjs","names":[],"sources":["../src/cli/utils/pluralize.ts"],"sourcesContent":["/**\n * Lightweight English pluralization for the Arc CLI.\n *\n * Covers the common cases developers hit when naming resources:\n * company → companies\n * category → categories\n * status → statuses\n * address → addresses\n * person → people\n * child → children\n * bus → buses\n * box → boxes\n * quiz → quizzes\n * leaf → leaves\n * wolf → wolves\n *\n * No external dependencies — designed to keep the CLI install-free.\n */\n\n// Irregular nouns that can't be handled by suffix rules\nconst IRREGULARS: Record<string, string> = {\n person: 'people',\n child: 'children',\n man: 'men',\n woman: 'women',\n mouse: 'mice',\n goose: 'geese',\n tooth: 'teeth',\n foot: 'feet',\n ox: 'oxen',\n datum: 'data',\n medium: 'media',\n index: 'indices',\n matrix: 'matrices',\n vertex: 'vertices',\n criterion: 'criteria',\n};\n\n// Words that are the same singular and plural\nconst UNCOUNTABLES = new Set([\n 'sheep', 'fish', 'deer', 'series', 'species', 'money',\n 'rice', 'information', 'equipment', 'media', 'data',\n]);\n\n/**\n * Pluralize an English word.\n *\n * @param word - Singular noun (e.g. \"company\", \"product\", \"person\")\n * @returns Plural form (e.g. \"companies\", \"products\", \"people\")\n */\nexport function pluralize(word: string): string {\n const lower = word.toLowerCase();\n\n // Uncountable — return as-is\n if (UNCOUNTABLES.has(lower)) return word;\n\n // Irregular — preserve original casing of first char\n if (IRREGULARS[lower]) {\n const plural = IRREGULARS[lower];\n return word[0]! === word[0]!.toUpperCase()\n ? plural.charAt(0).toUpperCase() + plural.slice(1)\n : plural;\n }\n\n // Suffix rules (order matters — most specific first)\n\n // -fe / -f → -ves (leaf → leaves, wolf → wolves, knife → knives)\n if (lower.endsWith('fe')) return word.slice(0, -2) + 'ves';\n if (lower.endsWith('f') && !lower.endsWith('ff') && !lower.endsWith('roof') && !lower.endsWith('chief') && !lower.endsWith('belief')) {\n return word.slice(0, -1) + 'ves';\n }\n\n // consonant + y → -ies (company → companies, category → categories)\n if (lower.endsWith('y') && !/[aeiou]y$/i.test(lower)) {\n return word.slice(0, -1) + 'ies';\n }\n\n // -is → -es (analysis → analyses, crisis → crises)\n if (lower.endsWith('is')) return word.slice(0, -2) + 'es';\n\n // -us → -i (only Latin-origin words, not status/bus/campus/virus)\n const LATIN_US_TO_I = new Set([\n 'cactus', 'stimulus', 'focus', 'fungus', 'nucleus',\n 'syllabus', 'radius', 'alumnus', 'terminus', 'bacillus',\n ]);\n if (LATIN_US_TO_I.has(lower)) return word.slice(0, -2) + 'i';\n\n // -z at end → double z + -es (quiz → quizzes, fez → fezzes)\n if (lower.endsWith('z') && !lower.endsWith('zz')) return word + 'zes';\n\n // sibilant endings: -s, -ss, -sh, -ch, -x, -zz → -es\n if (/(?:s|sh|ch|x|zz)$/i.test(lower)) return word + 'es';\n\n // -o → -es for common cases (hero → heroes, tomato → tomatoes)\n // but not for words ending in a vowel + o (radio → radios)\n if (lower.endsWith('o') && !/[aeiou]o$/i.test(lower)) return word + 'es';\n\n // Default: just add -s\n return word + 's';\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;AAoBA,MAAM,aAAqC;CACzC,QAAQ;CACR,OAAO;CACP,KAAK;CACL,OAAO;CACP,OAAO;CACP,OAAO;CACP,OAAO;CACP,MAAM;CACN,IAAI;CACJ,OAAO;CACP,QAAQ;CACR,OAAO;CACP,QAAQ;CACR,QAAQ;CACR,WAAW;CACZ;AAGD,MAAM,eAAe,IAAI,IAAI;CAC3B;CAAS;CAAQ;CAAQ;CAAU;CAAW;CAC9C;CAAQ;CAAe;CAAa;CAAS;CAC9C,CAAC;;;;;;;AAQF,SAAgB,UAAU,MAAsB;CAC9C,MAAM,QAAQ,KAAK,aAAa;AAGhC,KAAI,aAAa,IAAI,MAAM,CAAE,QAAO;AAGpC,KAAI,WAAW,QAAQ;EACrB,MAAM,SAAS,WAAW;AAC1B,SAAO,KAAK,OAAQ,KAAK,GAAI,aAAa,GACtC,OAAO,OAAO,EAAE,CAAC,aAAa,GAAG,OAAO,MAAM,EAAE,GAChD;;AAMN,KAAI,MAAM,SAAS,KAAK,CAAE,QAAO,KAAK,MAAM,GAAG,GAAG,GAAG;AACrD,KAAI,MAAM,SAAS,IAAI,IAAI,CAAC,MAAM,SAAS,KAAK,IAAI,CAAC,MAAM,SAAS,OAAO,IAAI,CAAC,MAAM,SAAS,QAAQ,IAAI,CAAC,MAAM,SAAS,SAAS,CAClI,QAAO,KAAK,MAAM,GAAG,GAAG,GAAG;AAI7B,KAAI,MAAM,SAAS,IAAI,IAAI,CAAC,aAAa,KAAK,MAAM,CAClD,QAAO,KAAK,MAAM,GAAG,GAAG,GAAG;AAI7B,KAAI,MAAM,SAAS,KAAK,CAAE,QAAO,KAAK,MAAM,GAAG,GAAG,GAAG;AAOrD,KAJsB,IAAI,IAAI;EAC5B;EAAU;EAAY;EAAS;EAAU;EACzC;EAAY;EAAU;EAAW;EAAY;EAC9C,CAAC,CACgB,IAAI,MAAM,CAAE,QAAO,KAAK,MAAM,GAAG,GAAG,GAAG;AAGzD,KAAI,MAAM,SAAS,IAAI,IAAI,CAAC,MAAM,SAAS,KAAK,CAAE,QAAO,OAAO;AAGhE,KAAI,qBAAqB,KAAK,MAAM,CAAE,QAAO,OAAO;AAIpD,KAAI,MAAM,SAAS,IAAI,IAAI,CAAC,aAAa,KAAK,MAAM,CAAE,QAAO,OAAO;AAGpE,QAAO,OAAO"}
|