@useairfoil/connector-kit 0.5.2 → 0.6.0
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/METRICS.md +16 -14
- package/README.md +15 -3
- package/dist/auth/index.d.ts +1 -1
- package/dist/connector-app/index.d.ts +13 -13
- package/dist/connector-app/index.js +9 -8
- package/dist/{connector-app-d7Z-SZLQ.js → connector-app-BljHd0Pe.js} +2 -2
- package/dist/{connector-app-d7Z-SZLQ.js.map → connector-app-BljHd0Pe.js.map} +1 -1
- package/dist/{constants-BRhIX_rz.js → constants-D113Ut1x.js} +7 -2
- package/dist/constants-D113Ut1x.js.map +1 -0
- package/dist/errors/index.d.ts +1 -1
- package/dist/{index-Byr0Lf0C.d.ts → index-BA8ASHKr.d.ts} +12 -6
- package/dist/{index-DKWIh6M1.d.ts → index-BKw7jA2a.d.ts} +3 -3
- package/dist/{index-DtJX_tic.d.ts → index-C5Ma7V7t.d.ts} +21 -7
- package/dist/{index-CBT-JX-T.d.ts → index-CR9HJDdr.d.ts} +1 -1
- package/dist/{index-D54ySAKn.d.ts → index-DKoRwyFn.d.ts} +1 -1
- package/dist/{index-CTHVudvQ.d.ts → index-DXa26Ry5.d.ts} +1 -1
- package/dist/{index-Coh_SkBs.d.ts → index-DYpEMJf-.d.ts} +2 -2
- package/dist/{index-Cq2IzY7T.d.ts → index-VpHaZXZw.d.ts} +3 -3
- package/dist/index-ZSuiZX5H.d.ts +85 -0
- package/dist/{index-J42AZaXU.d.ts → index-bfjH7Yb0.d.ts} +5 -5
- package/dist/index.d.ts +13 -13
- package/dist/index.js +14 -12
- package/dist/index.js.map +1 -1
- package/dist/ingestion/index.d.ts +5 -5
- package/dist/ingestion/index.js +8 -7
- package/dist/{ingestion-BQo4cbGw.js → ingestion-QBGKrFr9.js} +6 -6
- package/dist/{ingestion-BQo4cbGw.js.map → ingestion-QBGKrFr9.js.map} +1 -1
- package/dist/manifest/index.d.ts +1 -1
- package/dist/manifest/index.js +2 -2
- package/dist/{manifest-ByE6YPG0.js → manifest-P7kBnch9.js} +2 -2
- package/dist/{manifest-ByE6YPG0.js.map → manifest-P7kBnch9.js.map} +1 -1
- package/dist/{metrics-CVAk1ALR.d.ts → metrics-C4V1pm6v.d.ts} +15 -9
- package/dist/metrics-inkCAxKq.js +136 -0
- package/dist/metrics-inkCAxKq.js.map +1 -0
- package/dist/metrics.d.ts +4 -4
- package/dist/metrics.js +4 -2
- package/dist/publisher/index.d.ts +6 -5
- package/dist/publisher/index.js +6 -4
- package/dist/{publisher-BYT3RLVO.js → publisher-BdWJOfer.js} +14 -7
- package/dist/publisher-BdWJOfer.js.map +1 -0
- package/dist/runtime-config/index.d.ts +4 -2
- package/dist/runtime-config/index.js +4 -3
- package/dist/{runtime-config-BpB-d_bF.js → runtime-config-IbyCVpei.js} +17 -8
- package/dist/runtime-config-IbyCVpei.js.map +1 -0
- package/dist/{server-CXTQ7odl.js → server-D-eLX3yN.js} +24 -24
- package/dist/server-D-eLX3yN.js.map +1 -0
- package/dist/{service-IE5qaf9x.d.ts → service-CUjGhe-7.d.ts} +3 -3
- package/dist/service-DiP-XETo.js +8 -0
- package/dist/service-DiP-XETo.js.map +1 -0
- package/dist/state-store/index.d.ts +2 -2
- package/dist/state-store/index.js +2 -2
- package/dist/{state-store-yl6LiTQ0.js → state-store-BdYulQOY.js} +2 -2
- package/dist/{state-store-yl6LiTQ0.js.map → state-store-BdYulQOY.js.map} +1 -1
- package/dist/status/index.d.ts +4 -4
- package/dist/status/index.js +3 -3
- package/dist/{status-B-oZBc33.js → status-BTTd7AUR.js} +2 -2
- package/dist/{status-B-oZBc33.js.map → status-BTTd7AUR.js.map} +1 -1
- package/dist/table-bindings-Dp1DhTeS.js +106 -0
- package/dist/table-bindings-Dp1DhTeS.js.map +1 -0
- package/dist/{service-DuFiQwid.js → telemetry-j_nxwYfh.js} +18 -8
- package/dist/telemetry-j_nxwYfh.js.map +1 -0
- package/dist/{types-B35J5YPu.d.ts → types-jVD5Ys43.d.ts} +2 -2
- package/dist/webhook/index.d.ts +6 -5
- package/dist/webhook/index.js +6 -5
- package/dist/{webhook-DI-Afxkg.js → webhook-MhFcLW-o.js} +2 -2
- package/dist/{webhook-DI-Afxkg.js.map → webhook-MhFcLW-o.js.map} +1 -1
- package/package.json +3 -3
- package/src/metrics.ts +67 -21
- package/src/publisher/index.ts +1 -1
- package/src/publisher/instrumented.ts +23 -16
- package/src/publisher/wings.ts +11 -11
- package/src/runtime-config/config.ts +16 -0
- package/src/runtime-config/constants.ts +3 -0
- package/src/runtime-config/error.ts +4 -1
- package/src/runtime-config/index.ts +7 -0
- package/src/runtime-config/table-bindings.ts +180 -0
- package/src/telemetry.ts +15 -0
- package/dist/constants-BRhIX_rz.js.map +0 -1
- package/dist/index-o1-Z3n7V.d.ts +0 -55
- package/dist/metrics-CWPy2Aqc.js +0 -96
- package/dist/metrics-CWPy2Aqc.js.map +0 -1
- package/dist/publisher-BYT3RLVO.js.map +0 -1
- package/dist/runtime-config-BpB-d_bF.js.map +0 -1
- package/dist/server-CXTQ7odl.js.map +0 -1
- package/dist/service-DuFiQwid.js.map +0 -1
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { n as PlatformRuntimeKey } from "./constants-D113Ut1x.js";
|
|
2
|
+
import { Config, Data, Effect, Schema } from "effect";
|
|
3
|
+
import * as Wings from "@useairfoil/wings";
|
|
4
|
+
|
|
5
|
+
//#region src/runtime-config/error.ts
|
|
6
|
+
/** Expected hosted-config failure with a stable code and diagnostic cause. */
|
|
7
|
+
var RuntimeConfigError = class extends Data.TaggedError("RuntimeConfigError") {};
|
|
8
|
+
|
|
9
|
+
//#endregion
|
|
10
|
+
//#region src/runtime-config/table-bindings.ts
|
|
11
|
+
const RawBindingsSchema = Schema.fromJsonString(Schema.Record(Schema.String, Schema.Unknown));
|
|
12
|
+
const duplicateTopLevelKey = (json) => {
|
|
13
|
+
const keys = /* @__PURE__ */ new Set();
|
|
14
|
+
let depth = 0;
|
|
15
|
+
let expectKey = false;
|
|
16
|
+
for (let index = 0; index < json.length; index += 1) {
|
|
17
|
+
const char = json[index];
|
|
18
|
+
if (char === "\"") {
|
|
19
|
+
const start = index;
|
|
20
|
+
for (index += 1; index < json.length; index += 1) if (json[index] === "\\") index += 1;
|
|
21
|
+
else if (json[index] === "\"") break;
|
|
22
|
+
if (depth === 1 && expectKey) {
|
|
23
|
+
const key = JSON.parse(json.slice(start, index + 1));
|
|
24
|
+
if (keys.has(key)) return key;
|
|
25
|
+
keys.add(key);
|
|
26
|
+
expectKey = false;
|
|
27
|
+
}
|
|
28
|
+
continue;
|
|
29
|
+
}
|
|
30
|
+
if (char === "{") {
|
|
31
|
+
depth += 1;
|
|
32
|
+
if (depth === 1) expectKey = true;
|
|
33
|
+
} else if (char === "}") depth -= 1;
|
|
34
|
+
else if (char === "," && depth === 1) expectKey = true;
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
const asError = (cause) => cause instanceof Error ? cause : new Error(String(cause));
|
|
38
|
+
const fail = (code, message) => new RuntimeConfigError({
|
|
39
|
+
code,
|
|
40
|
+
message,
|
|
41
|
+
cause: new Error(message)
|
|
42
|
+
});
|
|
43
|
+
const isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
44
|
+
const hasExactKeys = (value, allowed) => {
|
|
45
|
+
const keys = Object.keys(value);
|
|
46
|
+
return keys.length === allowed.filter((key) => key in value).length && keys.every((key) => allowed.includes(key));
|
|
47
|
+
};
|
|
48
|
+
const nonEmptyName = (value) => {
|
|
49
|
+
if (typeof value !== "string" || value.length === 0 || value.trim() !== value) throw fail("TABLE_BINDINGS_INVALID", "Table binding names must be non-empty strings");
|
|
50
|
+
return value;
|
|
51
|
+
};
|
|
52
|
+
const resolveBinding = (input) => Effect.gen(function* () {
|
|
53
|
+
const binding = yield* Effect.try({
|
|
54
|
+
try: () => {
|
|
55
|
+
if (typeof input === "string") return { name: nonEmptyName(input) };
|
|
56
|
+
if (!isRecord(input) || !hasExactKeys(input, ["name", "partition"])) throw fail("TABLE_BINDINGS_INVALID", "Table bindings must be a name string or an object containing name and optional partition");
|
|
57
|
+
return {
|
|
58
|
+
name: nonEmptyName(input.name),
|
|
59
|
+
partition: input.partition
|
|
60
|
+
};
|
|
61
|
+
},
|
|
62
|
+
catch: (cause) => cause instanceof RuntimeConfigError ? cause : fail("TABLE_BINDINGS_INVALID", "Hosted table binding is invalid")
|
|
63
|
+
});
|
|
64
|
+
if (!("partition" in binding) || binding.partition === void 0) return { name: binding.name };
|
|
65
|
+
const partitionValue = yield* Wings.PartitionValue.decodeJson(binding.partition).pipe(Effect.mapError((cause) => new RuntimeConfigError({
|
|
66
|
+
code: "TABLE_BINDINGS_INVALID",
|
|
67
|
+
message: "Hosted table binding has an invalid partition value",
|
|
68
|
+
cause
|
|
69
|
+
})));
|
|
70
|
+
return {
|
|
71
|
+
name: binding.name,
|
|
72
|
+
partitionValue
|
|
73
|
+
};
|
|
74
|
+
});
|
|
75
|
+
/** Loads and validates the complete hosted Wings table map for a connector. */
|
|
76
|
+
const loadTableBindings = (connector) => Effect.gen(function* () {
|
|
77
|
+
const raw = yield* Config.nonEmptyString(PlatformRuntimeKey.tableBindings).pipe(Effect.mapError((cause) => new RuntimeConfigError({
|
|
78
|
+
code: "TABLE_BINDINGS_MISSING",
|
|
79
|
+
message: `${PlatformRuntimeKey.tableBindings} is required in hosted mode`,
|
|
80
|
+
cause: asError(cause)
|
|
81
|
+
})));
|
|
82
|
+
if ((yield* Effect.try({
|
|
83
|
+
try: () => duplicateTopLevelKey(raw),
|
|
84
|
+
catch: (cause) => new RuntimeConfigError({
|
|
85
|
+
code: "TABLE_BINDINGS_INVALID",
|
|
86
|
+
message: "Hosted table bindings are not valid JSON",
|
|
87
|
+
cause: asError(cause)
|
|
88
|
+
})
|
|
89
|
+
})) !== void 0) return yield* Effect.fail(fail("TABLE_BINDINGS_INVALID", "Hosted table bindings contain a duplicate resource"));
|
|
90
|
+
const decoded = yield* Schema.decodeUnknownEffect(RawBindingsSchema)(raw).pipe(Effect.mapError((cause) => new RuntimeConfigError({
|
|
91
|
+
code: "TABLE_BINDINGS_INVALID",
|
|
92
|
+
message: "Hosted table bindings are not valid JSON",
|
|
93
|
+
cause: asError(cause)
|
|
94
|
+
})));
|
|
95
|
+
const expected = new Set(connector.resources.map((resource) => resource.name));
|
|
96
|
+
const actual = Object.keys(decoded);
|
|
97
|
+
const missing = [...expected].filter((name) => !(name in decoded));
|
|
98
|
+
const unknown = actual.filter((name) => !expected.has(name));
|
|
99
|
+
if (missing.length > 0 || unknown.length > 0) return yield* Effect.fail(fail("TABLE_BINDINGS_MISMATCH", `Hosted table bindings do not match connector resources (missing: ${missing.join(", ") || "none"}; unknown resources: ${unknown.length})`));
|
|
100
|
+
const entries = yield* Effect.forEach(actual, (name) => resolveBinding(decoded[name]).pipe(Effect.map((binding) => [name, binding])));
|
|
101
|
+
return Object.fromEntries(entries);
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
//#endregion
|
|
105
|
+
export { RuntimeConfigError as n, loadTableBindings as t };
|
|
106
|
+
//# sourceMappingURL=table-bindings-Dp1DhTeS.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"table-bindings-Dp1DhTeS.js","names":[],"sources":["../src/runtime-config/error.ts","../src/runtime-config/table-bindings.ts"],"sourcesContent":["import { Data } from \"effect\";\n\nexport type RuntimeConfigErrorCode =\n | \"CONFIG_PATH_MISSING\"\n | \"CONFIG_FILE_UNREADABLE\"\n | \"CONFIG_JSON_INVALID\"\n | \"TABLE_BINDINGS_MISSING\"\n | \"TABLE_BINDINGS_INVALID\"\n | \"TABLE_BINDINGS_MISMATCH\";\n\n/** Expected hosted-config failure with a stable code and diagnostic cause. */\nexport class RuntimeConfigError extends Data.TaggedError(\"RuntimeConfigError\")<{\n readonly code: RuntimeConfigErrorCode;\n readonly message: string;\n readonly cause: Error;\n}> {}\n","import * as Wings from \"@useairfoil/wings\";\nimport { Config, Effect, Schema } from \"effect\";\n\nimport type { ConnectorDefinition, ResourceName } from \"../core/types\";\n\nimport { PlatformRuntimeKey } from \"./constants\";\nimport { RuntimeConfigError } from \"./error\";\n\n/** Table binding shape accepted in `AIRFOIL_TABLE_BINDINGS`. */\nexport type HostedTableBinding =\n | string\n | {\n readonly name: string;\n readonly partition?: Wings.PartitionValue.Json;\n };\n\n/** Validated table binding ready for the Wings publisher. */\nexport type ResolvedTableBinding = {\n readonly name: string;\n readonly partitionValue?: Wings.PartitionValue.PartitionValue;\n};\n\n/** One resolved binding for each resource in a connector manifest. */\nexport type ResolvedTableBindings<Connector extends ConnectorDefinition> = Readonly<\n Record<ResourceName<Connector[\"resources\"]>, ResolvedTableBinding>\n>;\n\nconst RawBindingsSchema = Schema.fromJsonString(Schema.Record(Schema.String, Schema.Unknown));\n\n// JSON.parse keeps the last duplicate key, so check before decoding.\nconst duplicateTopLevelKey = (json: string): string | undefined => {\n const keys = new Set<string>();\n let depth = 0;\n let expectKey = false;\n for (let index = 0; index < json.length; index += 1) {\n const char = json[index];\n if (char === '\"') {\n const start = index;\n for (index += 1; index < json.length; index += 1) {\n if (json[index] === \"\\\\\") {\n index += 1;\n } else if (json[index] === '\"') {\n break;\n }\n }\n if (depth === 1 && expectKey) {\n const key = JSON.parse(json.slice(start, index + 1)) as string;\n if (keys.has(key)) return key;\n keys.add(key);\n expectKey = false;\n }\n continue;\n }\n if (char === \"{\") {\n depth += 1;\n if (depth === 1) expectKey = true;\n } else if (char === \"}\") {\n depth -= 1;\n } else if (char === \",\" && depth === 1) {\n expectKey = true;\n }\n }\n return undefined;\n};\n\nconst asError = (cause: unknown): Error =>\n cause instanceof Error ? cause : new Error(String(cause));\n\nconst fail = (code: \"TABLE_BINDINGS_INVALID\" | \"TABLE_BINDINGS_MISMATCH\", message: string) =>\n new RuntimeConfigError({ code, message, cause: new Error(message) });\n\nconst isRecord = (value: unknown): value is Record<string, unknown> =>\n typeof value === \"object\" && value !== null && !Array.isArray(value);\n\nconst hasExactKeys = (value: Record<string, unknown>, allowed: ReadonlyArray<string>) => {\n const keys = Object.keys(value);\n return (\n keys.length === allowed.filter((key) => key in value).length &&\n keys.every((key) => allowed.includes(key))\n );\n};\n\nconst nonEmptyName = (value: unknown): string => {\n if (typeof value !== \"string\" || value.length === 0 || value.trim() !== value) {\n throw fail(\"TABLE_BINDINGS_INVALID\", \"Table binding names must be non-empty strings\");\n }\n return value;\n};\n\nconst resolveBinding = (input: unknown): Effect.Effect<ResolvedTableBinding, RuntimeConfigError> =>\n Effect.gen(function* () {\n const binding = yield* Effect.try({\n try: () => {\n if (typeof input === \"string\") return { name: nonEmptyName(input) } as const;\n if (!isRecord(input) || !hasExactKeys(input, [\"name\", \"partition\"])) {\n throw fail(\n \"TABLE_BINDINGS_INVALID\",\n \"Table bindings must be a name string or an object containing name and optional partition\",\n );\n }\n return { name: nonEmptyName(input.name), partition: input.partition } as const;\n },\n catch: (cause) =>\n cause instanceof RuntimeConfigError\n ? cause\n : fail(\"TABLE_BINDINGS_INVALID\", \"Hosted table binding is invalid\"),\n });\n if (!(\"partition\" in binding) || binding.partition === undefined) {\n return { name: binding.name };\n }\n const partitionValue = yield* Wings.PartitionValue.decodeJson(binding.partition).pipe(\n Effect.mapError(\n (cause) =>\n new RuntimeConfigError({\n code: \"TABLE_BINDINGS_INVALID\",\n message: \"Hosted table binding has an invalid partition value\",\n cause,\n }),\n ),\n );\n return { name: binding.name, partitionValue };\n });\n\n/** Loads and validates the complete hosted Wings table map for a connector. */\nexport const loadTableBindings = <const Connector extends ConnectorDefinition>(\n connector: Connector,\n): Effect.Effect<ResolvedTableBindings<Connector>, RuntimeConfigError> =>\n Effect.gen(function* () {\n const raw = yield* Config.nonEmptyString(PlatformRuntimeKey.tableBindings).pipe(\n Effect.mapError(\n (cause) =>\n new RuntimeConfigError({\n code: \"TABLE_BINDINGS_MISSING\",\n message: `${PlatformRuntimeKey.tableBindings} is required in hosted mode`,\n cause: asError(cause),\n }),\n ),\n );\n const duplicate = yield* Effect.try({\n try: () => duplicateTopLevelKey(raw),\n catch: (cause) =>\n new RuntimeConfigError({\n code: \"TABLE_BINDINGS_INVALID\",\n message: \"Hosted table bindings are not valid JSON\",\n cause: asError(cause),\n }),\n });\n if (duplicate !== undefined) {\n return yield* Effect.fail(\n fail(\"TABLE_BINDINGS_INVALID\", \"Hosted table bindings contain a duplicate resource\"),\n );\n }\n const decoded = yield* Schema.decodeUnknownEffect(RawBindingsSchema)(raw).pipe(\n Effect.mapError(\n (cause) =>\n new RuntimeConfigError({\n code: \"TABLE_BINDINGS_INVALID\",\n message: \"Hosted table bindings are not valid JSON\",\n cause: asError(cause),\n }),\n ),\n );\n // Starting with a partial map would leave some manifest resources unwired.\n const expected = new Set(connector.resources.map((resource) => resource.name));\n const actual = Object.keys(decoded);\n const missing = [...expected].filter((name) => !(name in decoded));\n const unknown = actual.filter((name) => !expected.has(name));\n if (missing.length > 0 || unknown.length > 0) {\n return yield* Effect.fail(\n fail(\n \"TABLE_BINDINGS_MISMATCH\",\n `Hosted table bindings do not match connector resources (missing: ${missing.join(\", \") || \"none\"}; unknown resources: ${unknown.length})`,\n ),\n );\n }\n const entries = yield* Effect.forEach(actual, (name) =>\n resolveBinding(decoded[name]).pipe(Effect.map((binding) => [name, binding] as const)),\n );\n return Object.fromEntries(entries) as ResolvedTableBindings<Connector>;\n });\n"],"mappings":"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"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { t as __export } from "./chunk-Bp6m_JJh.js";
|
|
2
|
-
import { n as PlatformRuntimeKey } from "./constants-
|
|
3
|
-
import { Cause, Config,
|
|
2
|
+
import { n as PlatformRuntimeKey } from "./constants-D113Ut1x.js";
|
|
3
|
+
import { Cause, Config, Effect, Layer, Metric, Option } from "effect";
|
|
4
4
|
import { Headers } from "effect/unstable/http";
|
|
5
5
|
import * as Observability from "effect/unstable/observability";
|
|
6
6
|
|
|
@@ -9,6 +9,7 @@ var telemetry_exports = /* @__PURE__ */ __export({
|
|
|
9
9
|
Attr: () => Attr,
|
|
10
10
|
EventAttr: () => EventAttr,
|
|
11
11
|
EventName: () => EventName,
|
|
12
|
+
ResourceAttr: () => ResourceAttr,
|
|
12
13
|
SpanName: () => SpanName,
|
|
13
14
|
addCurrentSpanEvent: () => addCurrentSpanEvent,
|
|
14
15
|
annotateError: () => annotateError,
|
|
@@ -35,7 +36,11 @@ const Attr = {
|
|
|
35
36
|
resourceName: "airfoil.resource.name",
|
|
36
37
|
resourceSource: "airfoil.resource.source",
|
|
37
38
|
batchMutations: "airfoil.batch.mutations",
|
|
39
|
+
batchOutcome: "airfoil.batch.outcome",
|
|
38
40
|
webhookPath: "airfoil.webhook.path",
|
|
41
|
+
webhookOutcome: "airfoil.webhook.outcome",
|
|
42
|
+
syncState: "airfoil.connector.sync.state",
|
|
43
|
+
apiRetryReason: "airfoil.connector.api.retry.reason",
|
|
39
44
|
publisherSuccess: "airfoil.publisher.success",
|
|
40
45
|
stateKey: "airfoil.state.key",
|
|
41
46
|
apiPath: "airfoil.api.path",
|
|
@@ -44,6 +49,12 @@ const Attr = {
|
|
|
44
49
|
errorMessage: "airfoil.error.message",
|
|
45
50
|
errorDetails: "airfoil.error.details"
|
|
46
51
|
};
|
|
52
|
+
/** Resource attributes injected by the hosted operator through standard OTel environment keys. */
|
|
53
|
+
const ResourceAttr = {
|
|
54
|
+
serviceInstanceId: "service.instance.id",
|
|
55
|
+
connectorRevision: "airfoil.connector.revision",
|
|
56
|
+
teamId: "airfoil.team.id"
|
|
57
|
+
};
|
|
47
58
|
/** Span event names for high-cardinality reproduction details. */
|
|
48
59
|
const EventName = { batchCheckpoint: "airfoil.batch.checkpoint" };
|
|
49
60
|
/** Event-only attributes. These are intentionally not span attributes. */
|
|
@@ -165,6 +176,7 @@ const layerOtlpTracing = (options = {}) => Layer.unwrap(Effect.gen(function* ()
|
|
|
165
176
|
headers: Option.isSome(raw.rawHeaders) ? parseOtelHeaders(raw.rawHeaders.value) : void 0
|
|
166
177
|
}, options);
|
|
167
178
|
}));
|
|
179
|
+
/** Exports connector metrics over OTLP when telemetry is enabled. */
|
|
168
180
|
const layerOtlpMetrics = (options = {}) => Layer.unwrap(Effect.gen(function* () {
|
|
169
181
|
const raw = yield* OtlpEnvConfig;
|
|
170
182
|
if (!raw.enabled) return Layer.empty;
|
|
@@ -177,15 +189,13 @@ const layerOtlpMetrics = (options = {}) => Layer.unwrap(Effect.gen(function* ()
|
|
|
177
189
|
temporality: "cumulative"
|
|
178
190
|
}).pipe(Layer.provide(Observability.OtlpSerialization.layerJson));
|
|
179
191
|
}));
|
|
192
|
+
/** Enables OTLP traces and metrics with shared environment configuration. */
|
|
180
193
|
const layerOtlp = (options = {}) => Layer.mergeAll(layerOtlpTracing(options), layerOtlpMetrics(options));
|
|
194
|
+
/** Periodically logs the in-memory metric snapshot for local debugging. */
|
|
181
195
|
const layerMetricsConsoleDump = (interval = "30 seconds") => Layer.effectDiscard(Effect.gen(function* () {
|
|
182
196
|
yield* Effect.forkScoped(Effect.forever(Metric.dump.pipe(Effect.flatMap((snapshot) => Effect.logInfo(snapshot)), Effect.andThen(Effect.sleep(interval)))));
|
|
183
197
|
}));
|
|
184
198
|
|
|
185
199
|
//#endregion
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
//#endregion
|
|
190
|
-
export { SpanName as a, telemetry_exports as c, EventName as i, Attr as n, addCurrentSpanEvent as o, EventAttr as r, annotateError as s, Publisher as t };
|
|
191
|
-
//# sourceMappingURL=service-DuFiQwid.js.map
|
|
200
|
+
export { addCurrentSpanEvent as a, SpanName as i, EventAttr as n, annotateError as o, EventName as r, telemetry_exports as s, Attr as t };
|
|
201
|
+
//# sourceMappingURL=telemetry-j_nxwYfh.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"telemetry-j_nxwYfh.js","names":["defaultRedactedHeaders: ReadonlyArray<string | RegExp>"],"sources":["../src/telemetry.ts"],"sourcesContent":["import { Cause, Config, Effect, Layer, Metric, Option, type Duration } from \"effect\";\nimport { Headers } from \"effect/unstable/http\";\nimport * as Observability from \"effect/unstable/observability\";\n\nimport { PlatformRuntimeKey } from \"./runtime-config/constants\";\n\n/** Canonical span names emitted by connector-kit runtime instrumentation. */\nexport const SpanName = {\n batchProcess: \"connector.batch.process\",\n webhookDecode: \"connector.webhook.decode\",\n webhookHandle: \"connector.webhook.handle\",\n publish: \"connector.publish\",\n stateSet: \"connector.state.set\",\n apiFetch: \"connector.api.fetch\",\n} as const;\n\n/** Canonical span attribute names. Keep these stable for collector queries. */\nexport const Attr = {\n connectorName: \"airfoil.connector.name\",\n connectorResourcesCount: \"airfoil.connector.resources.count\",\n resourceName: \"airfoil.resource.name\",\n resourceSource: \"airfoil.resource.source\",\n batchMutations: \"airfoil.batch.mutations\",\n batchOutcome: \"airfoil.batch.outcome\",\n webhookPath: \"airfoil.webhook.path\",\n webhookOutcome: \"airfoil.webhook.outcome\",\n syncState: \"airfoil.connector.sync.state\",\n apiRetryReason: \"airfoil.connector.api.retry.reason\",\n publisherSuccess: \"airfoil.publisher.success\",\n stateKey: \"airfoil.state.key\",\n apiPath: \"airfoil.api.path\",\n errorPhase: \"airfoil.error.phase\",\n errorType: \"airfoil.error.type\",\n errorMessage: \"airfoil.error.message\",\n errorDetails: \"airfoil.error.details\",\n} as const;\n\n/** Resource attributes injected by the hosted operator through standard OTel environment keys. */\nexport const ResourceAttr = {\n serviceInstanceId: \"service.instance.id\",\n connectorRevision: \"airfoil.connector.revision\",\n teamId: \"airfoil.team.id\",\n} as const;\n\n/** Span event names for high-cardinality reproduction details. */\nexport const EventName = {\n batchCheckpoint: \"airfoil.batch.checkpoint\",\n} as const;\n\n/** Event-only attributes. These are intentionally not span attributes. */\nexport const EventAttr = {\n batchCursor: \"airfoil.batch.cursor\",\n} as const;\n\nconst isRecord = (value: unknown): value is Record<string, unknown> =>\n typeof value === \"object\" && value !== null;\n\nconst maxErrorMessageLength = 500;\n\nconst OtlpEnvConfig = Config.all({\n enabled: Config.boolean(PlatformRuntimeKey.otelEnabled).pipe(Config.withDefault(false)),\n baseUrl: Config.option(Config.string(PlatformRuntimeKey.otelExporterOtlpEndpoint)),\n rawHeaders: Config.option(Config.string(PlatformRuntimeKey.otelExporterOtlpHeaders)),\n});\n\nconst defaultRedactedHeaders: ReadonlyArray<string | RegExp> = [\n \"authorization\",\n \"cookie\",\n \"set-cookie\",\n \"x-api-key\",\n /api[-_]?key/i,\n /secret/i,\n /signature/i,\n /token/i,\n];\n\nconst parseOtelHeaders = (value: string): Record<string, string> =>\n Object.fromEntries(\n value\n .split(\",\")\n .map((entry) => entry.trim())\n .filter(Boolean)\n .flatMap((entry) => {\n const separator = entry.indexOf(\"=\");\n if (separator < 1) return [];\n return [[entry.slice(0, separator).trim(), entry.slice(separator + 1).trim()]];\n }),\n );\n\nconst appendPath = (baseUrl: string, path: string) =>\n `${baseUrl.replace(/\\/$/, \"\")}/${path.replace(/^\\//, \"\")}`;\n\n/**\n * Best-effort stable error classifier for span metadata.\n *\n * Effect/Data tagged errors expose `_tag`; that is the most useful value for\n * connector bugs (`ConnectorError`, `InvalidWebhookPayloadError`, etc.). For\n * non-tagged errors we fall back to the JS `Error.name`, then constructor name,\n * then the primitive type.\n */\nconst errorType = (error: unknown) => {\n if (isRecord(error) && typeof error._tag === \"string\") {\n return String(error._tag);\n }\n if (error instanceof Error) {\n return error.name;\n }\n if (isRecord(error) && typeof error.constructor?.name === \"string\") {\n return error.constructor.name;\n }\n return typeof error;\n};\n\n/**\n * Human-readable error summary for span metadata.\n *\n * Tagged errors in this package carry a `message` field, so this preserves the\n * important diagnostic text. Unknown object failures are intentionally reduced\n * to their type; full structured error payloads belong in logs, not span attrs.\n */\nconst errorMessage = (error: unknown) => {\n const message = (() => {\n if (isRecord(error) && typeof error.message === \"string\") {\n return error.message;\n }\n if (typeof error === \"string\") {\n return error;\n }\n return errorType(error);\n })();\n\n if (message.length > maxErrorMessageLength) {\n return `${message.slice(0, maxErrorMessageLength)}...`;\n }\n\n return message;\n};\n\nconst errorDetails = (error: unknown): string | undefined => {\n if (!isRecord(error) || error.cause === undefined) return undefined;\n return Cause.pretty(Cause.die(error.cause));\n};\n\n/** Annotates the active span with phase-aware error metadata before re-failing. */\nexport const annotateError = (phase: string, error: unknown) => {\n const details = errorDetails(error);\n return Effect.annotateCurrentSpan({\n [Attr.errorPhase]: phase,\n [Attr.errorType]: errorType(error),\n [Attr.errorMessage]: errorMessage(error),\n ...(details !== undefined ? { [Attr.errorDetails]: details } : {}),\n });\n};\n\n/** Adds an event to the active span, or no-ops when no span is active. */\nexport const addCurrentSpanEvent = (name: string, attributes?: Record<string, unknown>) =>\n Effect.currentSpan.pipe(\n Effect.flatMap((span) =>\n Effect.clockWith((clock) =>\n Effect.sync(() => span.event(name, clock.currentTimeNanosUnsafe(), attributes)),\n ),\n ),\n Effect.ignore,\n );\n\n/** Transport configuration for OTLP trace export. All fields are direct values. */\nexport type OtlpTracingConfig = {\n readonly enabled?: boolean;\n readonly endpoint?: string;\n readonly headers?: Record<string, string>;\n};\n\n/** Runtime options — not config-wrappable since RegExp cannot live in Config. */\nexport type OtlpTracingOptions = {\n /** Additional connector-specific header names or regexes to redact from HTTP logs/traces. */\n readonly redactedHeaders?: ReadonlyArray<string | RegExp>;\n};\n\n/** OTLP metric export settings. */\nexport type OtlpMetricsOptions = {\n readonly exportInterval?: Duration.Input;\n};\n\nconst buildOtlpTracingLayer = (config: OtlpTracingConfig, options: OtlpTracingOptions) =>\n Effect.gen(function* () {\n const allRedacted = [...defaultRedactedHeaders, ...(options.redactedHeaders ?? [])];\n const RedactionLayer = Layer.succeed(Headers.CurrentRedactedNames)(allRedacted);\n\n if (!config.enabled) {\n return RedactionLayer;\n }\n\n if (!config.endpoint) {\n return yield* Effect.fail(new Error(\"enabled=true requires endpoint\"));\n }\n\n yield* Effect.logInfo(\"telemetry enabled\").pipe(\n Effect.annotateLogs({\n endpoint: config.endpoint,\n headers: config.headers ? Object.keys(config.headers) : [],\n }),\n );\n\n return Layer.mergeAll(\n RedactionLayer,\n Observability.OtlpTracer.layer({\n url: appendPath(config.endpoint, \"/v1/traces\"),\n headers: config.headers,\n }).pipe(Layer.provide(Observability.OtlpSerialization.layerJson)),\n );\n });\n\n/**\n * Trace-only OTLP export with sensitive HTTP header redaction.\n * Takes direct config values — no `ConfigProvider` required.\n */\nexport const layer = (config: OtlpTracingConfig = {}, options: OtlpTracingOptions = {}) =>\n Layer.unwrap(buildOtlpTracingLayer(config, options));\n\n/**\n * Trace-only OTLP export with sensitive HTTP header redaction.\n * Takes Effect Config-wrapped values — reads from `ConfigProvider`.\n *\n * @example\n * Telemetry.layerConfig({\n * enabled: Config.boolean(\"MY_OTEL_ENABLED\"),\n * endpoint: Config.string(\"MY_COLLECTOR_URL\"),\n * })\n */\nexport const layerConfig = (\n config: Config.Wrap<OtlpTracingConfig>,\n options: OtlpTracingOptions = {},\n) =>\n Layer.unwrap(\n Effect.gen(function* () {\n const resolved = yield* Config.unwrap(config);\n return yield* buildOtlpTracingLayer(resolved, options);\n }),\n );\n\n/**\n * Trace-only OTLP export with sensitive HTTP header redaction.\n * Zero-config shortcut that reads the standard `OTEL_*` env var names.\n *\n * Reads `OTEL_ENABLED`, `OTEL_EXPORTER_OTLP_ENDPOINT`, and\n * `OTEL_EXPORTER_OTLP_HEADERS`. Effect's OTLP resource handling reads\n * `OTEL_SERVICE_NAME`, `OTEL_SERVICE_VERSION`, and `OTEL_RESOURCE_ATTRIBUTES`.\n */\nexport const layerOtlpTracing = (options: OtlpTracingOptions = {}) =>\n Layer.unwrap(\n Effect.gen(function* () {\n const raw = yield* OtlpEnvConfig;\n return yield* buildOtlpTracingLayer(\n {\n enabled: raw.enabled,\n endpoint: Option.getOrUndefined(raw.baseUrl),\n headers: Option.isSome(raw.rawHeaders)\n ? parseOtelHeaders(raw.rawHeaders.value)\n : undefined,\n },\n options,\n );\n }),\n );\n\n/** Exports connector metrics over OTLP when telemetry is enabled. */\nexport const layerOtlpMetrics = (options: OtlpMetricsOptions = {}) =>\n Layer.unwrap(\n Effect.gen(function* () {\n const raw = yield* OtlpEnvConfig;\n if (!raw.enabled) return Layer.empty;\n const endpoint = Option.getOrUndefined(raw.baseUrl);\n if (!endpoint)\n return yield* Effect.fail(\n new Error(\"OTEL_ENABLED=true requires OTEL_EXPORTER_OTLP_ENDPOINT\"),\n );\n return Observability.OtlpMetrics.layer({\n url: appendPath(endpoint, \"/v1/metrics\"),\n headers: Option.isSome(raw.rawHeaders) ? parseOtelHeaders(raw.rawHeaders.value) : undefined,\n exportInterval: options.exportInterval,\n temporality: \"cumulative\",\n }).pipe(Layer.provide(Observability.OtlpSerialization.layerJson));\n }),\n );\n\n/** Enables OTLP traces and metrics with shared environment configuration. */\nexport const layerOtlp = (options: OtlpTracingOptions & OtlpMetricsOptions = {}) =>\n Layer.mergeAll(layerOtlpTracing(options), layerOtlpMetrics(options));\n\n/** Periodically logs the in-memory metric snapshot for local debugging. */\nexport const layerMetricsConsoleDump = (interval: Duration.Input = \"30 seconds\") =>\n Layer.effectDiscard(\n Effect.gen(function* () {\n yield* Effect.forkScoped(\n Effect.forever(\n Metric.dump.pipe(\n Effect.flatMap((snapshot) => Effect.logInfo(snapshot)),\n Effect.andThen(Effect.sleep(interval)),\n ),\n ),\n );\n }),\n );\n"],"mappings":"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"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { t as ConnectorError } from "./index-
|
|
1
|
+
import { t as ConnectorError } from "./index-DKoRwyFn.js";
|
|
2
2
|
import { Duration, Effect, Schema } from "effect";
|
|
3
3
|
import { HttpRouter, HttpServerRequest, HttpServerResponse } from "effect/unstable/http";
|
|
4
4
|
|
|
@@ -171,4 +171,4 @@ type ConnectorDefinition<Resources extends ReadonlyArray<ResourceDefinition> = R
|
|
|
171
171
|
};
|
|
172
172
|
//#endregion
|
|
173
173
|
export { WebhookRouteInput as C, WebhookRouteContext as S, ResourceState as _, FetchChangesResult as a, WebhookHandler as b, ResourceBatch as c, ResourceMutation as d, ResourceName as f, ResourceSchema as g, ResourceRows as h, DeleteValue as i, ResourceDefinition as l, ResourceRow as m, ConnectorDefinition as n, FetchPageResult as o, ResourcePayload as p, Cursor as r, PageFetch as s, ChangesFetch as t, ResourceField as u, SyncState as v, WebhookRoute as x, WebhookAckMode as y };
|
|
174
|
-
//# sourceMappingURL=types-
|
|
174
|
+
//# sourceMappingURL=types-jVD5Ys43.d.ts.map
|
package/dist/webhook/index.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import "../index-
|
|
2
|
-
import { C as WebhookRouteInput, S as WebhookRouteContext, x as WebhookRoute, y as WebhookAckMode } from "../types-
|
|
3
|
-
import "../service-
|
|
4
|
-
import "../index-
|
|
5
|
-
import
|
|
1
|
+
import "../index-DKoRwyFn.js";
|
|
2
|
+
import { C as WebhookRouteInput, S as WebhookRouteContext, x as WebhookRoute, y as WebhookAckMode } from "../types-jVD5Ys43.js";
|
|
3
|
+
import "../service-CUjGhe-7.js";
|
|
4
|
+
import "../index-ZSuiZX5H.js";
|
|
5
|
+
import "../index-BA8ASHKr.js";
|
|
6
|
+
import { n as route, r as router } from "../index-BKw7jA2a.js";
|
|
6
7
|
export { WebhookAckMode, WebhookRoute, WebhookRouteContext, WebhookRouteInput, route, router };
|
package/dist/webhook/index.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import "../errors-DHWylqRZ.js";
|
|
2
|
-
import "../
|
|
3
|
-
import "../
|
|
4
|
-
import "../
|
|
5
|
-
import
|
|
6
|
-
import {
|
|
2
|
+
import "../constants-D113Ut1x.js";
|
|
3
|
+
import "../telemetry-j_nxwYfh.js";
|
|
4
|
+
import "../metrics-inkCAxKq.js";
|
|
5
|
+
import "../service-DiP-XETo.js";
|
|
6
|
+
import { n as router } from "../server-D-eLX3yN.js";
|
|
7
|
+
import { t as route } from "../webhook-MhFcLW-o.js";
|
|
7
8
|
|
|
8
9
|
export { route, router };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { t as __export } from "./chunk-Bp6m_JJh.js";
|
|
2
|
-
import { n as router } from "./server-
|
|
2
|
+
import { n as router } from "./server-D-eLX3yN.js";
|
|
3
3
|
|
|
4
4
|
//#region src/webhook/index.ts
|
|
5
5
|
var webhook_exports = /* @__PURE__ */ __export({
|
|
@@ -10,4 +10,4 @@ const route = (definition) => definition;
|
|
|
10
10
|
|
|
11
11
|
//#endregion
|
|
12
12
|
export { webhook_exports as n, route as t };
|
|
13
|
-
//# sourceMappingURL=webhook-
|
|
13
|
+
//# sourceMappingURL=webhook-MhFcLW-o.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"webhook-
|
|
1
|
+
{"version":3,"file":"webhook-MhFcLW-o.js","names":[],"sources":["../src/webhook/index.ts"],"sourcesContent":["import type { ResourceDefinition, WebhookRoute, WebhookRouteInput } from \"../core/types\";\n\nexport { router } from \"./server\";\nexport type { WebhookAckMode, WebhookRoute, WebhookRouteContext, WebhookRouteInput } from \"./types\";\n\nexport const route = <const Resources extends ReadonlyArray<ResourceDefinition>, Payload>(\n definition: WebhookRouteInput<Resources, Payload>,\n): WebhookRoute<Resources, Payload> => definition;\n"],"mappings":";;;;;;;;AAKA,MAAa,SACX,eACqC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@useairfoil/connector-kit",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"repository": "useairfoil/typescript-sdk",
|
|
5
5
|
"files": [
|
|
6
6
|
"dist",
|
|
@@ -81,8 +81,8 @@
|
|
|
81
81
|
},
|
|
82
82
|
"dependencies": {
|
|
83
83
|
"@effect/platform-node": "^4.0.0-rc.108",
|
|
84
|
-
"@useairfoil/flight": "0.4.
|
|
85
|
-
"@useairfoil/wings": "0.4.
|
|
84
|
+
"@useairfoil/flight": "0.4.6",
|
|
85
|
+
"@useairfoil/wings": "0.4.5"
|
|
86
86
|
},
|
|
87
87
|
"devDependencies": {
|
|
88
88
|
"@effect/sql-pg": "^4.0.0-rc.108",
|
package/src/metrics.ts
CHANGED
|
@@ -2,6 +2,8 @@ import { DateTime, Effect, Metric, Option } from "effect";
|
|
|
2
2
|
|
|
3
3
|
import type { SyncState } from "./core/types";
|
|
4
4
|
|
|
5
|
+
import { Attr } from "./telemetry";
|
|
6
|
+
|
|
5
7
|
export type BatchSource = "backfill" | "changes" | "webhook";
|
|
6
8
|
export type BatchOutcome = "accepted" | "rejected" | "error";
|
|
7
9
|
export type ApiRetryReason = "transport" | "timeout" | "rate_limit" | "server_error";
|
|
@@ -41,61 +43,89 @@ export type ApiRetryMetricAttrs = {
|
|
|
41
43
|
readonly reason: ApiRetryReason;
|
|
42
44
|
};
|
|
43
45
|
|
|
44
|
-
export const
|
|
46
|
+
export const entitiesUpserted = Metric.counter("airfoil.connector.entity.upserts", {
|
|
45
47
|
description: "Total entity upsert mutations published by connector sources",
|
|
46
48
|
incremental: true,
|
|
49
|
+
attributes: { unit: "{mutation}" },
|
|
47
50
|
});
|
|
48
51
|
|
|
49
|
-
export const
|
|
52
|
+
export const entitiesDeleted = Metric.counter("airfoil.connector.entity.deletes", {
|
|
50
53
|
description: "Total entity delete mutations published by connector sources",
|
|
51
54
|
incremental: true,
|
|
55
|
+
attributes: { unit: "{mutation}" },
|
|
52
56
|
});
|
|
53
57
|
|
|
54
|
-
export const
|
|
58
|
+
export const batches = Metric.counter("airfoil.connector.batches", {
|
|
55
59
|
description: "Total resource batches published by connector sources",
|
|
56
60
|
incremental: true,
|
|
61
|
+
attributes: { unit: "{batch}" },
|
|
57
62
|
});
|
|
58
63
|
|
|
59
|
-
export const batchSize = Metric.histogram("
|
|
64
|
+
export const batchSize = Metric.histogram("airfoil.connector.batch.size", {
|
|
60
65
|
description: "Distribution of resource mutation batch sizes",
|
|
61
66
|
boundaries: [1, 5, 10, 25, 50, 100, 250, 500, 1000],
|
|
67
|
+
attributes: { unit: "{mutation}" },
|
|
62
68
|
});
|
|
63
69
|
|
|
64
|
-
export const publishDuration = Metric.timer("
|
|
70
|
+
export const publishDuration = Metric.timer("airfoil.connector.publish.duration", {
|
|
65
71
|
description: "Publisher call duration in milliseconds",
|
|
66
72
|
boundaries: [1, 5, 10, 25, 50, 100, 250, 500, 1000, 2500, 5000, 10000],
|
|
73
|
+
attributes: { unit: "ms" },
|
|
67
74
|
});
|
|
68
75
|
|
|
69
|
-
export const
|
|
76
|
+
export const webhookRequests = Metric.counter("airfoil.connector.webhook.requests", {
|
|
70
77
|
description: "Total webhook HTTP requests handled by connector routes",
|
|
71
78
|
incremental: true,
|
|
79
|
+
attributes: { unit: "{request}" },
|
|
72
80
|
});
|
|
73
81
|
|
|
74
|
-
export const webhookQueueDepth = Metric.gauge("
|
|
82
|
+
export const webhookQueueDepth = Metric.gauge("airfoil.connector.webhook.queue.depth", {
|
|
75
83
|
description: "Current queued webhook batches waiting for publish",
|
|
84
|
+
attributes: { unit: "{batch}" },
|
|
76
85
|
});
|
|
77
86
|
|
|
78
|
-
export const syncState = Metric.gauge("
|
|
87
|
+
export const syncState = Metric.gauge("airfoil.connector.sync.state", {
|
|
79
88
|
description: "Connector resource sync state as a Prometheus state-set gauge",
|
|
89
|
+
attributes: { unit: "1" },
|
|
80
90
|
});
|
|
81
91
|
|
|
82
|
-
export const lastSuccessTimestamp = Metric.gauge(
|
|
83
|
-
"
|
|
84
|
-
{
|
|
85
|
-
|
|
86
|
-
},
|
|
87
|
-
);
|
|
92
|
+
export const lastSuccessTimestamp = Metric.gauge("airfoil.connector.last_success.timestamp", {
|
|
93
|
+
description: "Unix timestamp of the last successful durable source checkpoint",
|
|
94
|
+
attributes: { unit: "s" },
|
|
95
|
+
});
|
|
88
96
|
|
|
89
|
-
export const
|
|
97
|
+
export const apiRetries = Metric.counter("airfoil.connector.api.retries", {
|
|
90
98
|
description: "Total provider API retries performed by the connector",
|
|
91
99
|
incremental: true,
|
|
100
|
+
attributes: { unit: "{retry}" },
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
const resourceAttributes = (attrs: ResourceMetricAttrs) => ({
|
|
104
|
+
[Attr.connectorName]: attrs.connector,
|
|
105
|
+
[Attr.resourceName]: attrs.resource,
|
|
106
|
+
[Attr.resourceSource]: attrs.source,
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
const syncAttributes = (attrs: SyncStateAttrs) => ({
|
|
110
|
+
[Attr.connectorName]: attrs.connector,
|
|
111
|
+
[Attr.resourceName]: attrs.resource,
|
|
92
112
|
});
|
|
93
113
|
|
|
94
114
|
export const recordWebhookRequest = (attrs: WebhookMetricAttrs) =>
|
|
95
|
-
Metric.update(
|
|
115
|
+
Metric.update(
|
|
116
|
+
Metric.withAttributes(webhookRequests, {
|
|
117
|
+
[Attr.connectorName]: attrs.connector,
|
|
118
|
+
[Attr.webhookPath]: attrs.path,
|
|
119
|
+
[Attr.webhookOutcome]: attrs.outcome,
|
|
120
|
+
}),
|
|
121
|
+
1,
|
|
122
|
+
);
|
|
96
123
|
|
|
97
124
|
export const setWebhookQueueDepth = (connector: string, depth: number) =>
|
|
98
|
-
Metric.update(
|
|
125
|
+
Metric.update(
|
|
126
|
+
Metric.withAttributes(webhookQueueDepth, { [Attr.connectorName]: connector }),
|
|
127
|
+
depth,
|
|
128
|
+
);
|
|
99
129
|
|
|
100
130
|
export const setLastSuccessTimestamp = (attrs: LastSuccessMetricAttrs, lastSuccessAt?: string) => {
|
|
101
131
|
const timestamp =
|
|
@@ -105,11 +135,24 @@ export const setLastSuccessTimestamp = (attrs: LastSuccessMetricAttrs, lastSucce
|
|
|
105
135
|
Option.map((dateTime) => DateTime.toEpochMillis(dateTime) / 1000),
|
|
106
136
|
Option.getOrElse(() => 0),
|
|
107
137
|
);
|
|
108
|
-
return Metric.update(
|
|
138
|
+
return Metric.update(
|
|
139
|
+
Metric.withAttributes(lastSuccessTimestamp, {
|
|
140
|
+
[Attr.connectorName]: attrs.connector,
|
|
141
|
+
[Attr.resourceName]: attrs.resource,
|
|
142
|
+
[Attr.resourceSource]: attrs.source,
|
|
143
|
+
}),
|
|
144
|
+
timestamp,
|
|
145
|
+
);
|
|
109
146
|
};
|
|
110
147
|
|
|
111
148
|
export const recordApiRetry = (attrs: ApiRetryMetricAttrs) =>
|
|
112
|
-
Metric.update(
|
|
149
|
+
Metric.update(
|
|
150
|
+
Metric.withAttributes(apiRetries, {
|
|
151
|
+
[Attr.connectorName]: attrs.connector,
|
|
152
|
+
[Attr.apiRetryReason]: attrs.reason,
|
|
153
|
+
}),
|
|
154
|
+
1,
|
|
155
|
+
);
|
|
113
156
|
|
|
114
157
|
const syncStates = ["pending", "backfilling", "live", "error"] as const;
|
|
115
158
|
|
|
@@ -119,10 +162,13 @@ export const setSyncState = (attrs: SyncStateAttrs, current: SyncState) =>
|
|
|
119
162
|
(state) =>
|
|
120
163
|
Metric.update(
|
|
121
164
|
Metric.withAttributes(syncState, {
|
|
122
|
-
...attrs,
|
|
123
|
-
state,
|
|
165
|
+
...syncAttributes(attrs),
|
|
166
|
+
[Attr.syncState]: state,
|
|
124
167
|
}),
|
|
125
168
|
state === current ? 1 : 0,
|
|
126
169
|
),
|
|
127
170
|
{ discard: true },
|
|
128
171
|
);
|
|
172
|
+
|
|
173
|
+
/** @internal Converts the ergonomic SDK input to canonical OTel attributes. */
|
|
174
|
+
export const withResourceAttributes = resourceAttributes;
|
package/src/publisher/index.ts
CHANGED
|
@@ -18,28 +18,35 @@ export const publishBatch = Effect.fnUntraced(function* (options: {
|
|
|
18
18
|
resource: options.resource,
|
|
19
19
|
source: options.source,
|
|
20
20
|
};
|
|
21
|
+
const metricAttributes = Metrics.withResourceAttributes(metric);
|
|
21
22
|
const upserts = options.batch.mutations.filter((mutation) => mutation.op === "upsert").length;
|
|
22
23
|
const deletes = options.batch.mutations.length - upserts;
|
|
23
24
|
|
|
24
25
|
const publisher = yield* Publisher;
|
|
25
|
-
const
|
|
26
|
-
|
|
26
|
+
const ack = yield* publisher
|
|
27
|
+
.publish({
|
|
27
28
|
resource: options.resource,
|
|
28
29
|
source: options.source,
|
|
29
30
|
batch: options.batch,
|
|
30
|
-
})
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
Metric.
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
31
|
+
})
|
|
32
|
+
.pipe(
|
|
33
|
+
Effect.trackDuration(Metric.withAttributes(Metrics.publishDuration, metricAttributes)),
|
|
34
|
+
Effect.catchCause((cause) =>
|
|
35
|
+
Metric.update(
|
|
36
|
+
Metric.withAttributes(Metrics.batches, {
|
|
37
|
+
...metricAttributes,
|
|
38
|
+
[Attr.batchOutcome]: "error",
|
|
39
|
+
}),
|
|
40
|
+
1,
|
|
41
|
+
).pipe(Effect.andThen(Effect.failCause(cause))),
|
|
42
|
+
),
|
|
43
|
+
);
|
|
39
44
|
|
|
40
|
-
yield* Metric.update(Metric.withAttributes(Metrics.publishDuration, metric), duration);
|
|
41
45
|
yield* Metric.update(
|
|
42
|
-
Metric.withAttributes(Metrics.
|
|
46
|
+
Metric.withAttributes(Metrics.batches, {
|
|
47
|
+
...metricAttributes,
|
|
48
|
+
[Attr.batchOutcome]: ack.status,
|
|
49
|
+
}),
|
|
43
50
|
1,
|
|
44
51
|
);
|
|
45
52
|
|
|
@@ -53,10 +60,10 @@ export const publishBatch = Effect.fnUntraced(function* (options: {
|
|
|
53
60
|
);
|
|
54
61
|
}
|
|
55
62
|
|
|
56
|
-
yield* Metric.update(Metric.withAttributes(Metrics.
|
|
57
|
-
yield* Metric.update(Metric.withAttributes(Metrics.
|
|
63
|
+
yield* Metric.update(Metric.withAttributes(Metrics.entitiesUpserted, metricAttributes), upserts);
|
|
64
|
+
yield* Metric.update(Metric.withAttributes(Metrics.entitiesDeleted, metricAttributes), deletes);
|
|
58
65
|
yield* Metric.update(
|
|
59
|
-
Metric.withAttributes(Metrics.batchSize,
|
|
66
|
+
Metric.withAttributes(Metrics.batchSize, metricAttributes),
|
|
60
67
|
options.batch.mutations.length,
|
|
61
68
|
);
|
|
62
69
|
|
package/src/publisher/wings.ts
CHANGED
|
@@ -4,16 +4,19 @@ import { Effect, Layer } from "effect";
|
|
|
4
4
|
import type { ConnectorDefinition, ResourceDefinition } from "../core/types";
|
|
5
5
|
|
|
6
6
|
import { ConnectorError } from "../errors";
|
|
7
|
+
import { loadTableBindings } from "../runtime-config/table-bindings";
|
|
7
8
|
import { SpanName } from "../telemetry";
|
|
8
9
|
import { Publisher, type PublishAck, type PublisherService } from "./service";
|
|
9
10
|
|
|
10
11
|
type Rows = Record<string, unknown>;
|
|
11
12
|
|
|
13
|
+
/** Programmatic mapping between a connector resource and a Wings table. */
|
|
12
14
|
export type WingsTableMapping = {
|
|
13
15
|
readonly name: string;
|
|
14
16
|
readonly partitionValue?: Wings.PartitionValue.PartitionValue;
|
|
15
17
|
};
|
|
16
18
|
|
|
19
|
+
/** Direct Wings publisher configuration, mainly for tests and local runtimes. */
|
|
17
20
|
export type WingsPublisherConfig = {
|
|
18
21
|
readonly connector: ConnectorDefinition;
|
|
19
22
|
readonly tables: Record<string, string | WingsTableMapping>;
|
|
@@ -48,7 +51,6 @@ const validateTableMapping = (options: {
|
|
|
48
51
|
try: () => {
|
|
49
52
|
const keyField = Wings.TableUtils.getKeyField(options.table);
|
|
50
53
|
const versionField = Wings.TableUtils.getVersionField(options.table);
|
|
51
|
-
const partitionField = Wings.TableUtils.getPartitionField(options.table);
|
|
52
54
|
|
|
53
55
|
if (keyField.name !== options.resource.key) {
|
|
54
56
|
throw new Error(
|
|
@@ -63,16 +65,7 @@ const validateTableMapping = (options: {
|
|
|
63
65
|
if (options.resource.partition?.required === true && !options.partitionValue) {
|
|
64
66
|
throw new Error(`Resource ${options.resource.name} requires a partition value`);
|
|
65
67
|
}
|
|
66
|
-
|
|
67
|
-
throw new Error(
|
|
68
|
-
`Wings table ${options.table.name} is partitioned but no partition value was provided`,
|
|
69
|
-
);
|
|
70
|
-
}
|
|
71
|
-
if (!partitionField && options.partitionValue) {
|
|
72
|
-
throw new Error(
|
|
73
|
-
`Wings table ${options.table.name} is not partitioned but a partition value was provided`,
|
|
74
|
-
);
|
|
75
|
-
}
|
|
68
|
+
Wings.TableUtils.validatePartitionValueUnsafe(options.table, options.partitionValue);
|
|
76
69
|
|
|
77
70
|
return { keyField, versionField };
|
|
78
71
|
},
|
|
@@ -97,6 +90,7 @@ const rejected = (entry: PublisherEntry, reason: string, rejectedRows: number):
|
|
|
97
90
|
partition: entry.partitionValue,
|
|
98
91
|
});
|
|
99
92
|
|
|
93
|
+
/** Builds a Wings publisher from explicit table mappings. */
|
|
100
94
|
export const layerWings = (
|
|
101
95
|
config: WingsPublisherConfig,
|
|
102
96
|
): Layer.Layer<Publisher, ConnectorError, Wings.WingsClient.WingsClient> =>
|
|
@@ -202,3 +196,9 @@ export const layerWings = (
|
|
|
202
196
|
return Publisher.of(service);
|
|
203
197
|
}),
|
|
204
198
|
);
|
|
199
|
+
|
|
200
|
+
/** Builds a Wings publisher from the complete hosted `AIRFOIL_TABLE_BINDINGS` document. */
|
|
201
|
+
export const layerWingsConfig = (connector: ConnectorDefinition) =>
|
|
202
|
+
Layer.unwrap(
|
|
203
|
+
loadTableBindings(connector).pipe(Effect.map((tables) => layerWings({ connector, tables }))),
|
|
204
|
+
);
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { WingsClientOptions } from "@useairfoil/wings/wings-client";
|
|
2
|
+
|
|
3
|
+
import { Config } from "effect";
|
|
4
|
+
|
|
5
|
+
import { PlatformRuntimeDefault, PlatformRuntimeKey } from "./constants";
|
|
6
|
+
|
|
7
|
+
/** Shared HTTP port used by hosted and sandbox connector entrypoints. */
|
|
8
|
+
export const httpPort = Config.port(PlatformRuntimeKey.httpPort).pipe(
|
|
9
|
+
Config.withDefault(PlatformRuntimeDefault.httpPort),
|
|
10
|
+
);
|
|
11
|
+
|
|
12
|
+
/** Shared Wings client configuration supplied by the hosting platform. */
|
|
13
|
+
export const wingsClient = {
|
|
14
|
+
host: Config.nonEmptyString(PlatformRuntimeKey.wingsHost),
|
|
15
|
+
namespace: Config.nonEmptyString(PlatformRuntimeKey.wingsNamespace),
|
|
16
|
+
} satisfies Config.Wrap<WingsClientOptions>;
|