@useairfoil/connector-kit 0.5.3 → 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 +2 -2
- 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
|
@@ -3,7 +3,10 @@ import { Data } from "effect";
|
|
|
3
3
|
export type RuntimeConfigErrorCode =
|
|
4
4
|
| "CONFIG_PATH_MISSING"
|
|
5
5
|
| "CONFIG_FILE_UNREADABLE"
|
|
6
|
-
| "CONFIG_JSON_INVALID"
|
|
6
|
+
| "CONFIG_JSON_INVALID"
|
|
7
|
+
| "TABLE_BINDINGS_MISSING"
|
|
8
|
+
| "TABLE_BINDINGS_INVALID"
|
|
9
|
+
| "TABLE_BINDINGS_MISMATCH";
|
|
7
10
|
|
|
8
11
|
/** Expected hosted-config failure with a stable code and diagnostic cause. */
|
|
9
12
|
export class RuntimeConfigError extends Data.TaggedError("RuntimeConfigError")<{
|
|
@@ -1,3 +1,10 @@
|
|
|
1
1
|
export { RuntimeConfigError, type RuntimeConfigErrorCode } from "./error";
|
|
2
|
+
export { httpPort, wingsClient } from "./config";
|
|
2
3
|
export { PlatformRuntimeDefault, PlatformRuntimeKey } from "./constants";
|
|
3
4
|
export { layerHosted } from "./layer";
|
|
5
|
+
export {
|
|
6
|
+
type HostedTableBinding,
|
|
7
|
+
type ResolvedTableBinding,
|
|
8
|
+
type ResolvedTableBindings,
|
|
9
|
+
loadTableBindings,
|
|
10
|
+
} from "./table-bindings";
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
import * as Wings from "@useairfoil/wings";
|
|
2
|
+
import { Config, Effect, Schema } from "effect";
|
|
3
|
+
|
|
4
|
+
import type { ConnectorDefinition, ResourceName } from "../core/types";
|
|
5
|
+
|
|
6
|
+
import { PlatformRuntimeKey } from "./constants";
|
|
7
|
+
import { RuntimeConfigError } from "./error";
|
|
8
|
+
|
|
9
|
+
/** Table binding shape accepted in `AIRFOIL_TABLE_BINDINGS`. */
|
|
10
|
+
export type HostedTableBinding =
|
|
11
|
+
| string
|
|
12
|
+
| {
|
|
13
|
+
readonly name: string;
|
|
14
|
+
readonly partition?: Wings.PartitionValue.Json;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
/** Validated table binding ready for the Wings publisher. */
|
|
18
|
+
export type ResolvedTableBinding = {
|
|
19
|
+
readonly name: string;
|
|
20
|
+
readonly partitionValue?: Wings.PartitionValue.PartitionValue;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
/** One resolved binding for each resource in a connector manifest. */
|
|
24
|
+
export type ResolvedTableBindings<Connector extends ConnectorDefinition> = Readonly<
|
|
25
|
+
Record<ResourceName<Connector["resources"]>, ResolvedTableBinding>
|
|
26
|
+
>;
|
|
27
|
+
|
|
28
|
+
const RawBindingsSchema = Schema.fromJsonString(Schema.Record(Schema.String, Schema.Unknown));
|
|
29
|
+
|
|
30
|
+
// JSON.parse keeps the last duplicate key, so check before decoding.
|
|
31
|
+
const duplicateTopLevelKey = (json: string): string | undefined => {
|
|
32
|
+
const keys = new Set<string>();
|
|
33
|
+
let depth = 0;
|
|
34
|
+
let expectKey = false;
|
|
35
|
+
for (let index = 0; index < json.length; index += 1) {
|
|
36
|
+
const char = json[index];
|
|
37
|
+
if (char === '"') {
|
|
38
|
+
const start = index;
|
|
39
|
+
for (index += 1; index < json.length; index += 1) {
|
|
40
|
+
if (json[index] === "\\") {
|
|
41
|
+
index += 1;
|
|
42
|
+
} else if (json[index] === '"') {
|
|
43
|
+
break;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
if (depth === 1 && expectKey) {
|
|
47
|
+
const key = JSON.parse(json.slice(start, index + 1)) as string;
|
|
48
|
+
if (keys.has(key)) return key;
|
|
49
|
+
keys.add(key);
|
|
50
|
+
expectKey = false;
|
|
51
|
+
}
|
|
52
|
+
continue;
|
|
53
|
+
}
|
|
54
|
+
if (char === "{") {
|
|
55
|
+
depth += 1;
|
|
56
|
+
if (depth === 1) expectKey = true;
|
|
57
|
+
} else if (char === "}") {
|
|
58
|
+
depth -= 1;
|
|
59
|
+
} else if (char === "," && depth === 1) {
|
|
60
|
+
expectKey = true;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
return undefined;
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
const asError = (cause: unknown): Error =>
|
|
67
|
+
cause instanceof Error ? cause : new Error(String(cause));
|
|
68
|
+
|
|
69
|
+
const fail = (code: "TABLE_BINDINGS_INVALID" | "TABLE_BINDINGS_MISMATCH", message: string) =>
|
|
70
|
+
new RuntimeConfigError({ code, message, cause: new Error(message) });
|
|
71
|
+
|
|
72
|
+
const isRecord = (value: unknown): value is Record<string, unknown> =>
|
|
73
|
+
typeof value === "object" && value !== null && !Array.isArray(value);
|
|
74
|
+
|
|
75
|
+
const hasExactKeys = (value: Record<string, unknown>, allowed: ReadonlyArray<string>) => {
|
|
76
|
+
const keys = Object.keys(value);
|
|
77
|
+
return (
|
|
78
|
+
keys.length === allowed.filter((key) => key in value).length &&
|
|
79
|
+
keys.every((key) => allowed.includes(key))
|
|
80
|
+
);
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
const nonEmptyName = (value: unknown): string => {
|
|
84
|
+
if (typeof value !== "string" || value.length === 0 || value.trim() !== value) {
|
|
85
|
+
throw fail("TABLE_BINDINGS_INVALID", "Table binding names must be non-empty strings");
|
|
86
|
+
}
|
|
87
|
+
return value;
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
const resolveBinding = (input: unknown): Effect.Effect<ResolvedTableBinding, RuntimeConfigError> =>
|
|
91
|
+
Effect.gen(function* () {
|
|
92
|
+
const binding = yield* Effect.try({
|
|
93
|
+
try: () => {
|
|
94
|
+
if (typeof input === "string") return { name: nonEmptyName(input) } as const;
|
|
95
|
+
if (!isRecord(input) || !hasExactKeys(input, ["name", "partition"])) {
|
|
96
|
+
throw fail(
|
|
97
|
+
"TABLE_BINDINGS_INVALID",
|
|
98
|
+
"Table bindings must be a name string or an object containing name and optional partition",
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
return { name: nonEmptyName(input.name), partition: input.partition } as const;
|
|
102
|
+
},
|
|
103
|
+
catch: (cause) =>
|
|
104
|
+
cause instanceof RuntimeConfigError
|
|
105
|
+
? cause
|
|
106
|
+
: fail("TABLE_BINDINGS_INVALID", "Hosted table binding is invalid"),
|
|
107
|
+
});
|
|
108
|
+
if (!("partition" in binding) || binding.partition === undefined) {
|
|
109
|
+
return { name: binding.name };
|
|
110
|
+
}
|
|
111
|
+
const partitionValue = yield* Wings.PartitionValue.decodeJson(binding.partition).pipe(
|
|
112
|
+
Effect.mapError(
|
|
113
|
+
(cause) =>
|
|
114
|
+
new RuntimeConfigError({
|
|
115
|
+
code: "TABLE_BINDINGS_INVALID",
|
|
116
|
+
message: "Hosted table binding has an invalid partition value",
|
|
117
|
+
cause,
|
|
118
|
+
}),
|
|
119
|
+
),
|
|
120
|
+
);
|
|
121
|
+
return { name: binding.name, partitionValue };
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
/** Loads and validates the complete hosted Wings table map for a connector. */
|
|
125
|
+
export const loadTableBindings = <const Connector extends ConnectorDefinition>(
|
|
126
|
+
connector: Connector,
|
|
127
|
+
): Effect.Effect<ResolvedTableBindings<Connector>, RuntimeConfigError> =>
|
|
128
|
+
Effect.gen(function* () {
|
|
129
|
+
const raw = yield* Config.nonEmptyString(PlatformRuntimeKey.tableBindings).pipe(
|
|
130
|
+
Effect.mapError(
|
|
131
|
+
(cause) =>
|
|
132
|
+
new RuntimeConfigError({
|
|
133
|
+
code: "TABLE_BINDINGS_MISSING",
|
|
134
|
+
message: `${PlatformRuntimeKey.tableBindings} is required in hosted mode`,
|
|
135
|
+
cause: asError(cause),
|
|
136
|
+
}),
|
|
137
|
+
),
|
|
138
|
+
);
|
|
139
|
+
const duplicate = yield* Effect.try({
|
|
140
|
+
try: () => duplicateTopLevelKey(raw),
|
|
141
|
+
catch: (cause) =>
|
|
142
|
+
new RuntimeConfigError({
|
|
143
|
+
code: "TABLE_BINDINGS_INVALID",
|
|
144
|
+
message: "Hosted table bindings are not valid JSON",
|
|
145
|
+
cause: asError(cause),
|
|
146
|
+
}),
|
|
147
|
+
});
|
|
148
|
+
if (duplicate !== undefined) {
|
|
149
|
+
return yield* Effect.fail(
|
|
150
|
+
fail("TABLE_BINDINGS_INVALID", "Hosted table bindings contain a duplicate resource"),
|
|
151
|
+
);
|
|
152
|
+
}
|
|
153
|
+
const decoded = yield* Schema.decodeUnknownEffect(RawBindingsSchema)(raw).pipe(
|
|
154
|
+
Effect.mapError(
|
|
155
|
+
(cause) =>
|
|
156
|
+
new RuntimeConfigError({
|
|
157
|
+
code: "TABLE_BINDINGS_INVALID",
|
|
158
|
+
message: "Hosted table bindings are not valid JSON",
|
|
159
|
+
cause: asError(cause),
|
|
160
|
+
}),
|
|
161
|
+
),
|
|
162
|
+
);
|
|
163
|
+
// Starting with a partial map would leave some manifest resources unwired.
|
|
164
|
+
const expected = new Set(connector.resources.map((resource) => resource.name));
|
|
165
|
+
const actual = Object.keys(decoded);
|
|
166
|
+
const missing = [...expected].filter((name) => !(name in decoded));
|
|
167
|
+
const unknown = actual.filter((name) => !expected.has(name));
|
|
168
|
+
if (missing.length > 0 || unknown.length > 0) {
|
|
169
|
+
return yield* Effect.fail(
|
|
170
|
+
fail(
|
|
171
|
+
"TABLE_BINDINGS_MISMATCH",
|
|
172
|
+
`Hosted table bindings do not match connector resources (missing: ${missing.join(", ") || "none"}; unknown resources: ${unknown.length})`,
|
|
173
|
+
),
|
|
174
|
+
);
|
|
175
|
+
}
|
|
176
|
+
const entries = yield* Effect.forEach(actual, (name) =>
|
|
177
|
+
resolveBinding(decoded[name]).pipe(Effect.map((binding) => [name, binding] as const)),
|
|
178
|
+
);
|
|
179
|
+
return Object.fromEntries(entries) as ResolvedTableBindings<Connector>;
|
|
180
|
+
});
|
package/src/telemetry.ts
CHANGED
|
@@ -21,7 +21,11 @@ export const Attr = {
|
|
|
21
21
|
resourceName: "airfoil.resource.name",
|
|
22
22
|
resourceSource: "airfoil.resource.source",
|
|
23
23
|
batchMutations: "airfoil.batch.mutations",
|
|
24
|
+
batchOutcome: "airfoil.batch.outcome",
|
|
24
25
|
webhookPath: "airfoil.webhook.path",
|
|
26
|
+
webhookOutcome: "airfoil.webhook.outcome",
|
|
27
|
+
syncState: "airfoil.connector.sync.state",
|
|
28
|
+
apiRetryReason: "airfoil.connector.api.retry.reason",
|
|
25
29
|
publisherSuccess: "airfoil.publisher.success",
|
|
26
30
|
stateKey: "airfoil.state.key",
|
|
27
31
|
apiPath: "airfoil.api.path",
|
|
@@ -31,6 +35,13 @@ export const Attr = {
|
|
|
31
35
|
errorDetails: "airfoil.error.details",
|
|
32
36
|
} as const;
|
|
33
37
|
|
|
38
|
+
/** Resource attributes injected by the hosted operator through standard OTel environment keys. */
|
|
39
|
+
export const ResourceAttr = {
|
|
40
|
+
serviceInstanceId: "service.instance.id",
|
|
41
|
+
connectorRevision: "airfoil.connector.revision",
|
|
42
|
+
teamId: "airfoil.team.id",
|
|
43
|
+
} as const;
|
|
44
|
+
|
|
34
45
|
/** Span event names for high-cardinality reproduction details. */
|
|
35
46
|
export const EventName = {
|
|
36
47
|
batchCheckpoint: "airfoil.batch.checkpoint",
|
|
@@ -165,6 +176,7 @@ export type OtlpTracingOptions = {
|
|
|
165
176
|
readonly redactedHeaders?: ReadonlyArray<string | RegExp>;
|
|
166
177
|
};
|
|
167
178
|
|
|
179
|
+
/** OTLP metric export settings. */
|
|
168
180
|
export type OtlpMetricsOptions = {
|
|
169
181
|
readonly exportInterval?: Duration.Input;
|
|
170
182
|
};
|
|
@@ -251,6 +263,7 @@ export const layerOtlpTracing = (options: OtlpTracingOptions = {}) =>
|
|
|
251
263
|
}),
|
|
252
264
|
);
|
|
253
265
|
|
|
266
|
+
/** Exports connector metrics over OTLP when telemetry is enabled. */
|
|
254
267
|
export const layerOtlpMetrics = (options: OtlpMetricsOptions = {}) =>
|
|
255
268
|
Layer.unwrap(
|
|
256
269
|
Effect.gen(function* () {
|
|
@@ -270,9 +283,11 @@ export const layerOtlpMetrics = (options: OtlpMetricsOptions = {}) =>
|
|
|
270
283
|
}),
|
|
271
284
|
);
|
|
272
285
|
|
|
286
|
+
/** Enables OTLP traces and metrics with shared environment configuration. */
|
|
273
287
|
export const layerOtlp = (options: OtlpTracingOptions & OtlpMetricsOptions = {}) =>
|
|
274
288
|
Layer.mergeAll(layerOtlpTracing(options), layerOtlpMetrics(options));
|
|
275
289
|
|
|
290
|
+
/** Periodically logs the in-memory metric snapshot for local debugging. */
|
|
276
291
|
export const layerMetricsConsoleDump = (interval: Duration.Input = "30 seconds") =>
|
|
277
292
|
Layer.effectDiscard(
|
|
278
293
|
Effect.gen(function* () {
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"constants-BRhIX_rz.js","names":[],"sources":["../src/runtime-config/constants.ts"],"sourcesContent":["import { HashSet } from \"effect\";\n\n/** Shared runtime keys owned by Airfoil rather than an individual connector. */\nexport const PlatformRuntimeKey = {\n configPath: \"AIRFOIL_CONFIG_PATH\",\n connectorInstanceId: \"AIRFOIL_CONNECTOR_INSTANCE_ID\",\n stateTable: \"AIRFOIL_STATE_TABLE\",\n postgresConnectionString: \"POSTGRES_CONNECTION_STRING\",\n wingsHost: \"WINGS_HOST\",\n wingsNamespace: \"WINGS_NAMESPACE\",\n otelEnabled: \"OTEL_ENABLED\",\n otelExporterOtlpEndpoint: \"OTEL_EXPORTER_OTLP_ENDPOINT\",\n otelExporterOtlpHeaders: \"OTEL_EXPORTER_OTLP_HEADERS\",\n otelServiceName: \"OTEL_SERVICE_NAME\",\n otelServiceVersion: \"OTEL_SERVICE_VERSION\",\n otelResourceAttributes: \"OTEL_RESOURCE_ATTRIBUTES\",\n} as const;\n\n/** Defaults for optional platform-owned runtime configuration. */\nexport const PlatformRuntimeDefault = {\n stateTable: \"_airfoil_connectors_state\",\n} as const;\n\nconst platformRuntimeKeys = HashSet.fromIterable(Object.values(PlatformRuntimeKey));\n\n/** @internal */\nexport const isPlatformRuntimeKey = (key: string): boolean => HashSet.has(platformRuntimeKeys, key);\n"],"mappings":";;;;AAGA,MAAa,qBAAqB;CAChC,YAAY;CACZ,qBAAqB;CACrB,YAAY;CACZ,0BAA0B;CAC1B,WAAW;CACX,gBAAgB;CAChB,aAAa;CACb,0BAA0B;CAC1B,yBAAyB;CACzB,iBAAiB;CACjB,oBAAoB;CACpB,wBAAwB;CACzB;;AAGD,MAAa,yBAAyB,EACpC,YAAY,6BACb;AAED,MAAM,sBAAsB,QAAQ,aAAa,OAAO,OAAO,mBAAmB,CAAC;;AAGnF,MAAa,wBAAwB,QAAyB,QAAQ,IAAI,qBAAqB,IAAI"}
|
package/dist/index-o1-Z3n7V.d.ts
DELETED
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
import { FileSystem } from "effect";
|
|
2
|
-
import * as effect_Types0 from "effect/Types";
|
|
3
|
-
import * as effect_Cause0 from "effect/Cause";
|
|
4
|
-
import * as effect_Layer0 from "effect/Layer";
|
|
5
|
-
|
|
6
|
-
//#region src/runtime-config/error.d.ts
|
|
7
|
-
type RuntimeConfigErrorCode = "CONFIG_PATH_MISSING" | "CONFIG_FILE_UNREADABLE" | "CONFIG_JSON_INVALID";
|
|
8
|
-
declare const RuntimeConfigError_base: new <A extends Record<string, any> = {}>(args: effect_Types0.VoidIfEmpty<{ readonly [P in keyof A as P extends "_tag" ? never : P]: A[P] }>) => effect_Cause0.YieldableError & {
|
|
9
|
-
readonly _tag: "RuntimeConfigError";
|
|
10
|
-
} & Readonly<A>;
|
|
11
|
-
/** Expected hosted-config failure with a stable code and diagnostic cause. */
|
|
12
|
-
declare class RuntimeConfigError extends RuntimeConfigError_base<{
|
|
13
|
-
readonly code: RuntimeConfigErrorCode;
|
|
14
|
-
readonly message: string;
|
|
15
|
-
readonly cause: Error;
|
|
16
|
-
}> {}
|
|
17
|
-
//#endregion
|
|
18
|
-
//#region src/runtime-config/constants.d.ts
|
|
19
|
-
/** Shared runtime keys owned by Airfoil rather than an individual connector. */
|
|
20
|
-
declare const PlatformRuntimeKey: {
|
|
21
|
-
readonly configPath: "AIRFOIL_CONFIG_PATH";
|
|
22
|
-
readonly connectorInstanceId: "AIRFOIL_CONNECTOR_INSTANCE_ID";
|
|
23
|
-
readonly stateTable: "AIRFOIL_STATE_TABLE";
|
|
24
|
-
readonly postgresConnectionString: "POSTGRES_CONNECTION_STRING";
|
|
25
|
-
readonly wingsHost: "WINGS_HOST";
|
|
26
|
-
readonly wingsNamespace: "WINGS_NAMESPACE";
|
|
27
|
-
readonly otelEnabled: "OTEL_ENABLED";
|
|
28
|
-
readonly otelExporterOtlpEndpoint: "OTEL_EXPORTER_OTLP_ENDPOINT";
|
|
29
|
-
readonly otelExporterOtlpHeaders: "OTEL_EXPORTER_OTLP_HEADERS";
|
|
30
|
-
readonly otelServiceName: "OTEL_SERVICE_NAME";
|
|
31
|
-
readonly otelServiceVersion: "OTEL_SERVICE_VERSION";
|
|
32
|
-
readonly otelResourceAttributes: "OTEL_RESOURCE_ATTRIBUTES";
|
|
33
|
-
};
|
|
34
|
-
/** Defaults for optional platform-owned runtime configuration. */
|
|
35
|
-
declare const PlatformRuntimeDefault: {
|
|
36
|
-
readonly stateTable: "_airfoil_connectors_state";
|
|
37
|
-
};
|
|
38
|
-
//#endregion
|
|
39
|
-
//#region src/runtime-config/layer.d.ts
|
|
40
|
-
/**
|
|
41
|
-
* Loads the required JSON file and lets environment values override it per key.
|
|
42
|
-
* Connector field validation remains in the connector's Effect Config.
|
|
43
|
-
*
|
|
44
|
-
* @example
|
|
45
|
-
* ```ts
|
|
46
|
-
* program.pipe(Effect.provide(layerHosted()));
|
|
47
|
-
* ```
|
|
48
|
-
*/
|
|
49
|
-
declare const layerHosted: () => effect_Layer0.Layer<never, RuntimeConfigError, FileSystem.FileSystem>;
|
|
50
|
-
declare namespace index_d_exports {
|
|
51
|
-
export { PlatformRuntimeDefault, PlatformRuntimeKey, RuntimeConfigError, RuntimeConfigErrorCode, layerHosted };
|
|
52
|
-
}
|
|
53
|
-
//#endregion
|
|
54
|
-
export { RuntimeConfigError as a, PlatformRuntimeKey as i, layerHosted as n, RuntimeConfigErrorCode as o, PlatformRuntimeDefault as r, index_d_exports as t };
|
|
55
|
-
//# sourceMappingURL=index-o1-Z3n7V.d.ts.map
|
package/dist/metrics-CWPy2Aqc.js
DELETED
|
@@ -1,96 +0,0 @@
|
|
|
1
|
-
import { t as __export } from "./chunk-Bp6m_JJh.js";
|
|
2
|
-
import { DateTime, Effect, Metric, Option } from "effect";
|
|
3
|
-
|
|
4
|
-
//#region src/metrics.ts
|
|
5
|
-
var metrics_exports = /* @__PURE__ */ __export({
|
|
6
|
-
apiRetriesTotal: () => apiRetriesTotal,
|
|
7
|
-
batchSize: () => batchSize,
|
|
8
|
-
batchesTotal: () => batchesTotal,
|
|
9
|
-
entitiesDeletedTotal: () => entitiesDeletedTotal,
|
|
10
|
-
entitiesUpsertedTotal: () => entitiesUpsertedTotal,
|
|
11
|
-
lastSuccessTimestamp: () => lastSuccessTimestamp,
|
|
12
|
-
publishDuration: () => publishDuration,
|
|
13
|
-
recordApiRetry: () => recordApiRetry,
|
|
14
|
-
recordWebhookRequest: () => recordWebhookRequest,
|
|
15
|
-
setLastSuccessTimestamp: () => setLastSuccessTimestamp,
|
|
16
|
-
setSyncState: () => setSyncState,
|
|
17
|
-
setWebhookQueueDepth: () => setWebhookQueueDepth,
|
|
18
|
-
syncState: () => syncState,
|
|
19
|
-
webhookQueueDepth: () => webhookQueueDepth,
|
|
20
|
-
webhookRequestsTotal: () => webhookRequestsTotal
|
|
21
|
-
});
|
|
22
|
-
const entitiesUpsertedTotal = Metric.counter("airfoil_connector_entities_upserted_total", {
|
|
23
|
-
description: "Total entity upsert mutations published by connector sources",
|
|
24
|
-
incremental: true
|
|
25
|
-
});
|
|
26
|
-
const entitiesDeletedTotal = Metric.counter("airfoil_connector_entities_deleted_total", {
|
|
27
|
-
description: "Total entity delete mutations published by connector sources",
|
|
28
|
-
incremental: true
|
|
29
|
-
});
|
|
30
|
-
const batchesTotal = Metric.counter("airfoil_connector_batches_total", {
|
|
31
|
-
description: "Total resource batches published by connector sources",
|
|
32
|
-
incremental: true
|
|
33
|
-
});
|
|
34
|
-
const batchSize = Metric.histogram("airfoil_connector_batch_size", {
|
|
35
|
-
description: "Distribution of resource mutation batch sizes",
|
|
36
|
-
boundaries: [
|
|
37
|
-
1,
|
|
38
|
-
5,
|
|
39
|
-
10,
|
|
40
|
-
25,
|
|
41
|
-
50,
|
|
42
|
-
100,
|
|
43
|
-
250,
|
|
44
|
-
500,
|
|
45
|
-
1e3
|
|
46
|
-
]
|
|
47
|
-
});
|
|
48
|
-
const publishDuration = Metric.timer("airfoil_connector_publish_duration_ms", {
|
|
49
|
-
description: "Publisher call duration in milliseconds",
|
|
50
|
-
boundaries: [
|
|
51
|
-
1,
|
|
52
|
-
5,
|
|
53
|
-
10,
|
|
54
|
-
25,
|
|
55
|
-
50,
|
|
56
|
-
100,
|
|
57
|
-
250,
|
|
58
|
-
500,
|
|
59
|
-
1e3,
|
|
60
|
-
2500,
|
|
61
|
-
5e3,
|
|
62
|
-
1e4
|
|
63
|
-
]
|
|
64
|
-
});
|
|
65
|
-
const webhookRequestsTotal = Metric.counter("airfoil_connector_webhook_requests_total", {
|
|
66
|
-
description: "Total webhook HTTP requests handled by connector routes",
|
|
67
|
-
incremental: true
|
|
68
|
-
});
|
|
69
|
-
const webhookQueueDepth = Metric.gauge("airfoil_connector_webhook_queue_depth", { description: "Current queued webhook batches waiting for publish" });
|
|
70
|
-
const syncState = Metric.gauge("airfoil_connector_sync_state", { description: "Connector resource sync state as a Prometheus state-set gauge" });
|
|
71
|
-
const lastSuccessTimestamp = Metric.gauge("airfoil_connector_last_success_timestamp_seconds", { description: "Unix timestamp of the last successful durable source checkpoint" });
|
|
72
|
-
const apiRetriesTotal = Metric.counter("airfoil_connector_api_retries_total", {
|
|
73
|
-
description: "Total provider API retries performed by the connector",
|
|
74
|
-
incremental: true
|
|
75
|
-
});
|
|
76
|
-
const recordWebhookRequest = (attrs) => Metric.update(Metric.withAttributes(webhookRequestsTotal, attrs), 1);
|
|
77
|
-
const setWebhookQueueDepth = (connector, depth) => Metric.update(Metric.withAttributes(webhookQueueDepth, { connector }), depth);
|
|
78
|
-
const setLastSuccessTimestamp = (attrs, lastSuccessAt) => {
|
|
79
|
-
const timestamp = lastSuccessAt === void 0 ? 0 : DateTime.make(lastSuccessAt).pipe(Option.map((dateTime) => DateTime.toEpochMillis(dateTime) / 1e3), Option.getOrElse(() => 0));
|
|
80
|
-
return Metric.update(Metric.withAttributes(lastSuccessTimestamp, attrs), timestamp);
|
|
81
|
-
};
|
|
82
|
-
const recordApiRetry = (attrs) => Metric.update(Metric.withAttributes(apiRetriesTotal, attrs), 1);
|
|
83
|
-
const syncStates = [
|
|
84
|
-
"pending",
|
|
85
|
-
"backfilling",
|
|
86
|
-
"live",
|
|
87
|
-
"error"
|
|
88
|
-
];
|
|
89
|
-
const setSyncState = (attrs, current) => Effect.forEach(syncStates, (state) => Metric.update(Metric.withAttributes(syncState, {
|
|
90
|
-
...attrs,
|
|
91
|
-
state
|
|
92
|
-
}), state === current ? 1 : 0), { discard: true });
|
|
93
|
-
|
|
94
|
-
//#endregion
|
|
95
|
-
export { entitiesUpsertedTotal as a, publishDuration as c, setLastSuccessTimestamp as d, setSyncState as f, webhookRequestsTotal as g, webhookQueueDepth as h, entitiesDeletedTotal as i, recordApiRetry as l, syncState as m, batchSize as n, lastSuccessTimestamp as o, setWebhookQueueDepth as p, batchesTotal as r, metrics_exports as s, apiRetriesTotal as t, recordWebhookRequest as u };
|
|
96
|
-
//# sourceMappingURL=metrics-CWPy2Aqc.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"metrics-CWPy2Aqc.js","names":[],"sources":["../src/metrics.ts"],"sourcesContent":["import { DateTime, Effect, Metric, Option } from \"effect\";\n\nimport type { SyncState } from \"./core/types\";\n\nexport type BatchSource = \"backfill\" | \"changes\" | \"webhook\";\nexport type BatchOutcome = \"accepted\" | \"rejected\" | \"error\";\nexport type ApiRetryReason = \"transport\" | \"timeout\" | \"rate_limit\" | \"server_error\";\nexport type WebhookOutcome =\n | \"ok\"\n | \"read_error\"\n | \"invalid_json\"\n | \"invalid_payload\"\n | \"rejected\"\n | \"handler_error\";\n\nexport type ResourceMetricAttrs = {\n readonly connector: string;\n readonly resource: string;\n readonly source: BatchSource;\n};\n\nexport type WebhookMetricAttrs = {\n readonly connector: string;\n readonly path: string;\n readonly outcome: WebhookOutcome;\n};\n\nexport type SyncStateAttrs = {\n readonly connector: string;\n readonly resource: string;\n};\n\nexport type LastSuccessMetricAttrs = {\n readonly connector: string;\n readonly resource: string;\n readonly source: \"backfill\" | \"changes\";\n};\n\nexport type ApiRetryMetricAttrs = {\n readonly connector: string;\n readonly reason: ApiRetryReason;\n};\n\nexport const entitiesUpsertedTotal = Metric.counter(\"airfoil_connector_entities_upserted_total\", {\n description: \"Total entity upsert mutations published by connector sources\",\n incremental: true,\n});\n\nexport const entitiesDeletedTotal = Metric.counter(\"airfoil_connector_entities_deleted_total\", {\n description: \"Total entity delete mutations published by connector sources\",\n incremental: true,\n});\n\nexport const batchesTotal = Metric.counter(\"airfoil_connector_batches_total\", {\n description: \"Total resource batches published by connector sources\",\n incremental: true,\n});\n\nexport const batchSize = Metric.histogram(\"airfoil_connector_batch_size\", {\n description: \"Distribution of resource mutation batch sizes\",\n boundaries: [1, 5, 10, 25, 50, 100, 250, 500, 1000],\n});\n\nexport const publishDuration = Metric.timer(\"airfoil_connector_publish_duration_ms\", {\n description: \"Publisher call duration in milliseconds\",\n boundaries: [1, 5, 10, 25, 50, 100, 250, 500, 1000, 2500, 5000, 10000],\n});\n\nexport const webhookRequestsTotal = Metric.counter(\"airfoil_connector_webhook_requests_total\", {\n description: \"Total webhook HTTP requests handled by connector routes\",\n incremental: true,\n});\n\nexport const webhookQueueDepth = Metric.gauge(\"airfoil_connector_webhook_queue_depth\", {\n description: \"Current queued webhook batches waiting for publish\",\n});\n\nexport const syncState = Metric.gauge(\"airfoil_connector_sync_state\", {\n description: \"Connector resource sync state as a Prometheus state-set gauge\",\n});\n\nexport const lastSuccessTimestamp = Metric.gauge(\n \"airfoil_connector_last_success_timestamp_seconds\",\n {\n description: \"Unix timestamp of the last successful durable source checkpoint\",\n },\n);\n\nexport const apiRetriesTotal = Metric.counter(\"airfoil_connector_api_retries_total\", {\n description: \"Total provider API retries performed by the connector\",\n incremental: true,\n});\n\nexport const recordWebhookRequest = (attrs: WebhookMetricAttrs) =>\n Metric.update(Metric.withAttributes(webhookRequestsTotal, attrs), 1);\n\nexport const setWebhookQueueDepth = (connector: string, depth: number) =>\n Metric.update(Metric.withAttributes(webhookQueueDepth, { connector }), depth);\n\nexport const setLastSuccessTimestamp = (attrs: LastSuccessMetricAttrs, lastSuccessAt?: string) => {\n const timestamp =\n lastSuccessAt === undefined\n ? 0\n : DateTime.make(lastSuccessAt).pipe(\n Option.map((dateTime) => DateTime.toEpochMillis(dateTime) / 1000),\n Option.getOrElse(() => 0),\n );\n return Metric.update(Metric.withAttributes(lastSuccessTimestamp, attrs), timestamp);\n};\n\nexport const recordApiRetry = (attrs: ApiRetryMetricAttrs) =>\n Metric.update(Metric.withAttributes(apiRetriesTotal, attrs), 1);\n\nconst syncStates = [\"pending\", \"backfilling\", \"live\", \"error\"] as const;\n\nexport const setSyncState = (attrs: SyncStateAttrs, current: SyncState) =>\n Effect.forEach(\n syncStates,\n (state) =>\n Metric.update(\n Metric.withAttributes(syncState, {\n ...attrs,\n state,\n }),\n state === current ? 1 : 0,\n ),\n { discard: true },\n );\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;AA2CA,MAAa,wBAAwB,OAAO,QAAQ,6CAA6C;CAC/F,aAAa;CACb,aAAa;CACd,CAAC;AAEF,MAAa,uBAAuB,OAAO,QAAQ,4CAA4C;CAC7F,aAAa;CACb,aAAa;CACd,CAAC;AAEF,MAAa,eAAe,OAAO,QAAQ,mCAAmC;CAC5E,aAAa;CACb,aAAa;CACd,CAAC;AAEF,MAAa,YAAY,OAAO,UAAU,gCAAgC;CACxE,aAAa;CACb,YAAY;EAAC;EAAG;EAAG;EAAI;EAAI;EAAI;EAAK;EAAK;EAAK;EAAK;CACpD,CAAC;AAEF,MAAa,kBAAkB,OAAO,MAAM,yCAAyC;CACnF,aAAa;CACb,YAAY;EAAC;EAAG;EAAG;EAAI;EAAI;EAAI;EAAK;EAAK;EAAK;EAAM;EAAM;EAAM;EAAM;CACvE,CAAC;AAEF,MAAa,uBAAuB,OAAO,QAAQ,4CAA4C;CAC7F,aAAa;CACb,aAAa;CACd,CAAC;AAEF,MAAa,oBAAoB,OAAO,MAAM,yCAAyC,EACrF,aAAa,sDACd,CAAC;AAEF,MAAa,YAAY,OAAO,MAAM,gCAAgC,EACpE,aAAa,iEACd,CAAC;AAEF,MAAa,uBAAuB,OAAO,MACzC,oDACA,EACE,aAAa,mEACd,CACF;AAED,MAAa,kBAAkB,OAAO,QAAQ,uCAAuC;CACnF,aAAa;CACb,aAAa;CACd,CAAC;AAEF,MAAa,wBAAwB,UACnC,OAAO,OAAO,OAAO,eAAe,sBAAsB,MAAM,EAAE,EAAE;AAEtE,MAAa,wBAAwB,WAAmB,UACtD,OAAO,OAAO,OAAO,eAAe,mBAAmB,EAAE,WAAW,CAAC,EAAE,MAAM;AAE/E,MAAa,2BAA2B,OAA+B,kBAA2B;CAChG,MAAM,YACJ,kBAAkB,SACd,IACA,SAAS,KAAK,cAAc,CAAC,KAC3B,OAAO,KAAK,aAAa,SAAS,cAAc,SAAS,GAAG,IAAK,EACjE,OAAO,gBAAgB,EAAE,CAC1B;AACP,QAAO,OAAO,OAAO,OAAO,eAAe,sBAAsB,MAAM,EAAE,UAAU;;AAGrF,MAAa,kBAAkB,UAC7B,OAAO,OAAO,OAAO,eAAe,iBAAiB,MAAM,EAAE,EAAE;AAEjE,MAAM,aAAa;CAAC;CAAW;CAAe;CAAQ;CAAQ;AAE9D,MAAa,gBAAgB,OAAuB,YAClD,OAAO,QACL,aACC,UACC,OAAO,OACL,OAAO,eAAe,WAAW;CAC/B,GAAG;CACH;CACD,CAAC,EACF,UAAU,UAAU,IAAI,EACzB,EACH,EAAE,SAAS,MAAM,CAClB"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"publisher-BYT3RLVO.js","names":["service: PublisherService","upserts: Rows[]","deletes: Rows[]"],"sources":["../src/publisher/console.ts","../src/publisher/wings.ts","../src/publisher/index.ts"],"sourcesContent":["import { Effect, Layer } from \"effect\";\n\nimport { Publisher } from \"./service\";\n\nexport const layerConsole = Layer.succeed(Publisher)({\n publish: ({ resource, source, batch }) =>\n Effect.gen(function* () {\n yield* Effect.logInfo(`[publisher] -> Source: ${source} | Resource: ${resource}`).pipe(\n Effect.annotateLogs({\n mutations: batch.mutations.length,\n cursor: batch.cursor,\n source,\n }),\n );\n\n return { status: \"accepted\" as const, resource };\n }),\n});\n","import * as Wings from \"@useairfoil/wings\";\nimport { Effect, Layer } from \"effect\";\n\nimport type { ConnectorDefinition, ResourceDefinition } from \"../core/types\";\n\nimport { ConnectorError } from \"../errors\";\nimport { SpanName } from \"../telemetry\";\nimport { Publisher, type PublishAck, type PublisherService } from \"./service\";\n\ntype Rows = Record<string, unknown>;\n\nexport type WingsTableMapping = {\n readonly name: string;\n readonly partitionValue?: Wings.PartitionValue.PartitionValue;\n};\n\nexport type WingsPublisherConfig = {\n readonly connector: ConnectorDefinition;\n readonly tables: Record<string, string | WingsTableMapping>;\n};\n\ntype PublisherEntry = {\n readonly resource: ResourceDefinition;\n readonly table: Wings.Cluster.Table.Table;\n readonly publisher: Wings.WingsClient.Publisher;\n readonly partitionValue?: Wings.PartitionValue.PartitionValue;\n readonly keyField: Wings.Cluster.ArrowType.Field;\n readonly versionField: Wings.Cluster.ArrowType.Field;\n};\n\nconst normalizeMapping = (mapping: string | WingsTableMapping): WingsTableMapping =>\n typeof mapping === \"string\" ? { name: mapping } : mapping;\n\nconst buildRecordBatch = (rows: ReadonlyArray<Rows>) => {\n const table = Wings.Arrow.tableFromJSON(Array.from(rows));\n const [batch] = table.batches;\n return batch\n ? Effect.succeed(batch)\n : Effect.fail(new ConnectorError({ message: \"No rows to publish\" }));\n};\n\nconst validateTableMapping = (options: {\n readonly resource: ResourceDefinition;\n readonly table: Wings.Cluster.Table.Table;\n readonly partitionValue?: Wings.PartitionValue.PartitionValue;\n}) =>\n Effect.try({\n try: () => {\n const keyField = Wings.TableUtils.getKeyField(options.table);\n const versionField = Wings.TableUtils.getVersionField(options.table);\n const partitionField = Wings.TableUtils.getPartitionField(options.table);\n\n if (keyField.name !== options.resource.key) {\n throw new Error(\n `Resource ${options.resource.name} key '${options.resource.key}' does not match Wings table key '${keyField.name}'`,\n );\n }\n if (versionField.name !== options.resource.version) {\n throw new Error(\n `Resource ${options.resource.name} version '${options.resource.version}' does not match Wings table version '${versionField.name}'`,\n );\n }\n if (options.resource.partition?.required === true && !options.partitionValue) {\n throw new Error(`Resource ${options.resource.name} requires a partition value`);\n }\n if (partitionField && !options.partitionValue) {\n throw new Error(\n `Wings table ${options.table.name} is partitioned but no partition value was provided`,\n );\n }\n if (!partitionField && options.partitionValue) {\n throw new Error(\n `Wings table ${options.table.name} is not partitioned but a partition value was provided`,\n );\n }\n\n return { keyField, versionField };\n },\n catch: (cause) =>\n new ConnectorError({\n message: `Invalid Wings table mapping for ${options.resource.name}`,\n cause,\n }),\n });\n\nconst accepted = (entry: PublisherEntry): PublishAck => ({\n status: \"accepted\",\n resource: entry.resource.name,\n partition: entry.partitionValue,\n});\n\nconst rejected = (entry: PublisherEntry, reason: string, rejectedRows: number): PublishAck => ({\n status: \"rejected\",\n resource: entry.resource.name,\n reason,\n rejectedRows,\n partition: entry.partitionValue,\n});\n\nexport const layerWings = (\n config: WingsPublisherConfig,\n): Layer.Layer<Publisher, ConnectorError, Wings.WingsClient.WingsClient> =>\n Layer.effect(Publisher)(\n Effect.gen(function* () {\n const entries = new Map<string, PublisherEntry>();\n const clusterClient = yield* Wings.WingsClient.clusterClient;\n\n for (const resource of config.connector.resources) {\n const mappingInput = config.tables[resource.name];\n if (!mappingInput) {\n return yield* Effect.fail(\n new ConnectorError({ message: `Missing table for ${resource.name}` }),\n );\n }\n\n const mapping = normalizeMapping(mappingInput);\n const table = yield* clusterClient.getTable({ name: mapping.name }).pipe(\n Effect.mapError(\n (error) =>\n new ConnectorError({\n message: `Failed to resolve Wings table for ${resource.name}`,\n cause: error,\n }),\n ),\n );\n const { keyField, versionField } = yield* validateTableMapping({\n resource,\n table,\n partitionValue: mapping.partitionValue,\n });\n\n const publisher = yield* Wings.WingsClient.publisher({\n table,\n partitionValue: mapping.partitionValue,\n }).pipe(\n Effect.mapError((error) => new ConnectorError({ message: error.message, cause: error })),\n );\n\n entries.set(resource.name, {\n resource,\n table,\n publisher,\n partitionValue: mapping.partitionValue,\n keyField,\n versionField,\n });\n }\n\n const pushRows = (\n entry: PublisherEntry,\n operation: \"upsert\" | \"delete\",\n rows: ReadonlyArray<Rows>,\n ) =>\n Effect.gen(function* () {\n if (rows.length === 0) return accepted(entry);\n const batch = yield* buildRecordBatch(rows);\n return yield* entry.publisher\n .push({ operation, batch, partitionValue: entry.partitionValue })\n .pipe(\n Effect.matchEffect({\n onFailure: (error) => Effect.succeed(rejected(entry, error.message, rows.length)),\n onSuccess: () => Effect.succeed(accepted(entry)),\n }),\n );\n });\n\n const service: PublisherService = {\n publish: Effect.fn(SpanName.publish, { kind: \"producer\" })(function* ({\n resource,\n source: _source,\n batch,\n }) {\n const entry = entries.get(resource);\n if (!entry) {\n return yield* Effect.fail(\n new ConnectorError({ message: `Unknown resource ${resource}` }),\n );\n }\n\n if (batch.mutations.length === 0) return accepted(entry);\n\n const upserts: Rows[] = [];\n const deletes: Rows[] = [];\n for (const mutation of batch.mutations) {\n if (mutation.op === \"upsert\") {\n upserts.push(mutation.row as Rows);\n } else {\n deletes.push({\n [entry.keyField.name]: mutation.key,\n [entry.versionField.name]: mutation.version,\n });\n }\n }\n\n const upsertAck = yield* pushRows(entry, \"upsert\", upserts);\n if (upsertAck.status === \"rejected\") return upsertAck;\n\n return yield* pushRows(entry, \"delete\", deletes);\n }),\n };\n\n return Publisher.of(service);\n }),\n );\n","export type { ResourceBatch } from \"../core/types\";\nexport type { PublishAck, PublishOptions, PublisherService, PublishSource } from \"./service\";\nexport type { WingsPublisherConfig } from \"./wings\";\n\nexport { layerConsole } from \"./console\";\nexport { Publisher } from \"./service\";\nexport { layerWings } from \"./wings\";\n"],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"runtime-config-BpB-d_bF.js","names":["providerFromFile: Effect.Effect<\n ConfigProvider.ConfigProvider,\n RuntimeConfigError,\n FileSystem.FileSystem\n>"],"sources":["../src/runtime-config/error.ts","../src/runtime-config/layer.ts","../src/runtime-config/index.ts"],"sourcesContent":["import { Data } from \"effect\";\n\nexport type RuntimeConfigErrorCode =\n | \"CONFIG_PATH_MISSING\"\n | \"CONFIG_FILE_UNREADABLE\"\n | \"CONFIG_JSON_INVALID\";\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 { Config, ConfigProvider, Effect, FileSystem, Schema } from \"effect\";\n\nimport { PlatformRuntimeKey } from \"./constants\";\nimport { RuntimeConfigError } from \"./error\";\n\nconst providerFromFile: Effect.Effect<\n ConfigProvider.ConfigProvider,\n RuntimeConfigError,\n FileSystem.FileSystem\n> = Effect.gen(function* () {\n const configPath = yield* Config.nonEmptyString(PlatformRuntimeKey.configPath).pipe(\n Effect.mapError(\n (error) =>\n new RuntimeConfigError({\n code: \"CONFIG_PATH_MISSING\",\n message: `${PlatformRuntimeKey.configPath} is required in hosted mode`,\n cause: error.cause,\n }),\n ),\n );\n const fileSystem = yield* FileSystem.FileSystem;\n const contents = yield* fileSystem.readFileString(configPath).pipe(\n Effect.mapError(\n (cause) =>\n new RuntimeConfigError({\n code: \"CONFIG_FILE_UNREADABLE\",\n message: \"Connector config file could not be read\",\n cause,\n }),\n ),\n );\n const document = yield* Schema.decodeUnknownEffect(Schema.fromJsonString(Schema.Unknown))(\n contents,\n ).pipe(\n Effect.mapError(\n (error) =>\n new RuntimeConfigError({\n code: \"CONFIG_JSON_INVALID\",\n message: \"Connector config is not valid JSON\",\n cause: error,\n }),\n ),\n );\n return ConfigProvider.fromUnknown(document, { preserveEmptyStrings: true });\n});\n\n/**\n * Loads the required JSON file and lets environment values override it per key.\n * Connector field validation remains in the connector's Effect Config.\n *\n * @example\n * ```ts\n * program.pipe(Effect.provide(layerHosted()));\n * ```\n */\nexport const layerHosted = () => ConfigProvider.layerAdd(providerFromFile);\n","export { RuntimeConfigError, type RuntimeConfigErrorCode } from \"./error\";\nexport { PlatformRuntimeDefault, PlatformRuntimeKey } from \"./constants\";\nexport { layerHosted } from \"./layer\";\n"],"mappings":";;;;;;AAQA,IAAa,qBAAb,cAAwC,KAAK,YAAY,qBAAqB,CAI3E;;;;ACPH,MAAMA,mBAIF,OAAO,IAAI,aAAa;CAC1B,MAAM,aAAa,OAAO,OAAO,eAAe,mBAAmB,WAAW,CAAC,KAC7E,OAAO,UACJ,UACC,IAAI,mBAAmB;EACrB,MAAM;EACN,SAAS,GAAG,mBAAmB,WAAW;EAC1C,OAAO,MAAM;EACd,CAAC,CACL,CACF;CAED,MAAM,WAAW,QADE,OAAO,WAAW,YACF,eAAe,WAAW,CAAC,KAC5D,OAAO,UACJ,UACC,IAAI,mBAAmB;EACrB,MAAM;EACN,SAAS;EACT;EACD,CAAC,CACL,CACF;CACD,MAAM,WAAW,OAAO,OAAO,oBAAoB,OAAO,eAAe,OAAO,QAAQ,CAAC,CACvF,SACD,CAAC,KACA,OAAO,UACJ,UACC,IAAI,mBAAmB;EACrB,MAAM;EACN,SAAS;EACT,OAAO;EACR,CAAC,CACL,CACF;AACD,QAAO,eAAe,YAAY,UAAU,EAAE,sBAAsB,MAAM,CAAC;EAC3E;;;;;;;;;;AAWF,MAAa,oBAAoB,eAAe,SAAS,iBAAiB"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"server-CXTQ7odl.js","names":["Metrics.batchesTotal","Metrics.publishDuration","Metrics.entitiesUpsertedTotal","Metrics.entitiesDeletedTotal","Metrics.batchSize","payload","collected: Array<QueuedWebhookBatch>","Metrics.recordWebhookRequest","Metrics.setWebhookQueueDepth"],"sources":["../src/publisher/instrumented.ts","../src/webhook/server.ts"],"sourcesContent":["import { Effect, Metric } from \"effect\";\n\nimport type { ResourceBatch } from \"../core/types\";\n\nimport { ConnectorError } from \"../errors\";\nimport * as Metrics from \"../metrics\";\nimport { Attr, EventAttr, EventName, addCurrentSpanEvent } from \"../telemetry\";\nimport { Publisher, type PublishSource } from \"./service\";\n\nexport const publishBatch = Effect.fnUntraced(function* (options: {\n readonly connector?: string;\n readonly resource: string;\n readonly source: PublishSource;\n readonly batch: ResourceBatch;\n}) {\n const metric = {\n connector: options.connector ?? \"unknown\",\n resource: options.resource,\n source: options.source,\n };\n const upserts = options.batch.mutations.filter((mutation) => mutation.op === \"upsert\").length;\n const deletes = options.batch.mutations.length - upserts;\n\n const publisher = yield* Publisher;\n const [duration, ack] = yield* Effect.timed(\n publisher.publish({\n resource: options.resource,\n source: options.source,\n batch: options.batch,\n }),\n ).pipe(\n Effect.catchCause((cause) =>\n Metric.update(\n Metric.withAttributes(Metrics.batchesTotal, { ...metric, outcome: \"error\" }),\n 1,\n ).pipe(Effect.andThen(Effect.failCause(cause))),\n ),\n );\n\n yield* Metric.update(Metric.withAttributes(Metrics.publishDuration, metric), duration);\n yield* Metric.update(\n Metric.withAttributes(Metrics.batchesTotal, { ...metric, outcome: ack.status }),\n 1,\n );\n\n yield* Effect.annotateCurrentSpan({ [Attr.publisherSuccess]: ack.status === \"accepted\" });\n if (ack.status === \"rejected\") {\n yield* Effect.annotateCurrentSpan({ [Attr.errorPhase]: \"publish\" });\n return yield* Effect.fail(\n new ConnectorError({\n message: `Publisher rejected batch for ${options.resource}: ${ack.reason}`,\n }),\n );\n }\n\n yield* Metric.update(Metric.withAttributes(Metrics.entitiesUpsertedTotal, metric), upserts);\n yield* Metric.update(Metric.withAttributes(Metrics.entitiesDeletedTotal, metric), deletes);\n yield* Metric.update(\n Metric.withAttributes(Metrics.batchSize, metric),\n options.batch.mutations.length,\n );\n\n if (options.batch.cursor !== undefined) {\n yield* addCurrentSpanEvent(EventName.batchCheckpoint, {\n [EventAttr.batchCursor]: options.batch.cursor,\n });\n }\n});\n","import { Cause, Context, Effect, Queue, Schema } from \"effect\";\nimport { HttpRouter, HttpServerRequest, HttpServerResponse } from \"effect/unstable/http\";\n\nimport type {\n ResourceBatch,\n ResourceDefinition,\n ResourceMutation,\n WebhookHandler,\n WebhookRoute,\n WebhookRouteContext,\n} from \"../core/types\";\n\nimport { ConnectorError } from \"../errors\";\nimport * as Metrics from \"../metrics\";\nimport { publishBatch } from \"../publisher/instrumented\";\nimport { Attr, SpanName, annotateError } from \"../telemetry\";\n\nexport type QueuedWebhookBatch = {\n readonly resource: string;\n readonly batch: ResourceBatch;\n};\n\nexport class WebhookQueue extends Context.Service<\n WebhookQueue,\n {\n readonly queue: Queue.Queue<QueuedWebhookBatch>;\n }\n>()(\"@useairfoil/connector-kit/WebhookQueue\") {}\n\nconst resourceBatch = <Row extends object>(\n mutations: ReadonlyArray<ResourceMutation<Row>>,\n): ResourceBatch<Row> => ({\n mutations,\n});\n\nconst decodeAndHandleWebhook = <Row extends object, Payload>(\n resourceName: string,\n webhook: WebhookHandler<Row, Payload>,\n payload: unknown,\n) =>\n Schema.decodeUnknownEffect(webhook.schema)(payload).pipe(\n Effect.mapError(\n (cause) =>\n new ConnectorError({\n message: `Invalid webhook payload for resource ${resourceName}`,\n cause,\n }),\n ),\n Effect.flatMap((decoded) => webhook.handler({ payload: decoded })),\n );\n\nconst makeRouteContext = (\n request: HttpServerRequest.HttpServerRequest,\n rawBody: Uint8Array,\n payload: unknown,\n batches: Array<QueuedWebhookBatch>,\n): WebhookRouteContext<ReadonlyArray<ResourceDefinition>, unknown> => ({\n request,\n rawBody,\n payload,\n to: (resource, payload) =>\n Effect.gen(function* () {\n if (!resource.webhook) {\n return yield* Effect.fail(\n new ConnectorError({\n message: `Resource ${resource.name} does not define a webhook handler`,\n }),\n );\n }\n\n const mutations = yield* decodeAndHandleWebhook(resource.name, resource.webhook, payload);\n batches.push({\n resource: resource.name,\n batch: resourceBatch(mutations),\n });\n }),\n});\n\nconst compactBatches = (batches: ReadonlyArray<QueuedWebhookBatch>) => {\n const grouped = new Map<string, ResourceMutation[]>();\n for (const item of batches) {\n const current = grouped.get(item.resource) ?? [];\n grouped.set(item.resource, [...current, ...item.batch.mutations]);\n }\n return Array.from(grouped.entries()).map(([resource, mutations]) => ({\n resource,\n batch: resourceBatch(mutations),\n }));\n};\n\nconst publishBatches = (\n batches: ReadonlyArray<QueuedWebhookBatch>,\n options: { readonly connectorName: string },\n) =>\n Effect.gen(function* () {\n yield* Effect.forEach(\n batches,\n (batch) =>\n publishBatch({\n connector: options.connectorName,\n resource: batch.resource,\n source: \"webhook\",\n batch: batch.batch,\n }),\n { concurrency: \"unbounded\" },\n );\n });\n\nconst badRequest = (message: string) =>\n HttpServerResponse.jsonUnsafe({ ok: false, error: message }, { status: 400 });\n\nconst readRawBody = (request: HttpServerRequest.HttpServerRequest) =>\n request.arrayBuffer.pipe(Effect.map((buffer) => new Uint8Array(buffer)));\n\nconst parseJson = (rawBody: Uint8Array) =>\n Effect.try({\n try: () => JSON.parse(new TextDecoder().decode(rawBody)),\n catch: (cause) => new ConnectorError({ message: \"Invalid webhook JSON\", cause }),\n });\n\nconst makeHandler = (route: WebhookRoute, options: { readonly connectorName: string }) =>\n Effect.gen(function* () {\n const request = yield* HttpServerRequest.HttpServerRequest;\n const collected: Array<QueuedWebhookBatch> = [];\n\n const rawBodyResult = yield* readRawBody(request).pipe(\n Effect.match({\n onFailure: () => ({ _tag: \"Error\" as const, message: \"Failed to read webhook body\" }),\n onSuccess: (value) => ({ _tag: \"Success\" as const, value }),\n }),\n );\n if (rawBodyResult._tag === \"Error\") {\n yield* Metrics.recordWebhookRequest({\n connector: options.connectorName,\n path: String(route.path),\n outcome: \"read_error\",\n });\n return badRequest(rawBodyResult.message);\n }\n\n const jsonResult = yield* parseJson(rawBodyResult.value).pipe(\n Effect.match({\n onFailure: (error) => ({ _tag: \"Error\" as const, message: error.message }),\n onSuccess: (value) => ({ _tag: \"Success\" as const, value }),\n }),\n );\n if (jsonResult._tag === \"Error\") {\n yield* Metrics.recordWebhookRequest({\n connector: options.connectorName,\n path: String(route.path),\n outcome: \"invalid_json\",\n });\n return badRequest(jsonResult.message);\n }\n\n const payloadResult = yield* Schema.decodeUnknownEffect(route.schema)(jsonResult.value).pipe(\n Effect.match({\n onFailure: () => ({ _tag: \"Error\" as const, message: \"Invalid webhook payload\" }),\n onSuccess: (value) => ({ _tag: \"Success\" as const, value }),\n }),\n );\n if (payloadResult._tag === \"Error\") {\n yield* Metrics.recordWebhookRequest({\n connector: options.connectorName,\n path: String(route.path),\n outcome: \"invalid_payload\",\n });\n return badRequest(payloadResult.message);\n }\n\n const response = yield* Effect.withSpan(\n route\n .handler(makeRouteContext(request, rawBodyResult.value, payloadResult.value, collected))\n .pipe(Effect.tapError((error) => annotateError(\"webhook_handle\", error))),\n SpanName.webhookHandle,\n { attributes: { [Attr.webhookPath]: route.path } },\n );\n\n const batches = compactBatches(collected);\n\n if (route.ackMode === \"after-enqueue\") {\n const queue = yield* WebhookQueue;\n yield* Queue.offerAll(queue.queue, batches);\n const depth = yield* Queue.size(queue.queue);\n yield* Metrics.setWebhookQueueDepth(options.connectorName, depth);\n yield* Metrics.recordWebhookRequest({\n connector: options.connectorName,\n path: String(route.path),\n outcome: response.status >= 400 ? \"rejected\" : \"ok\",\n });\n return response;\n }\n\n yield* Effect.withSpan(\n publishBatches(batches, { connectorName: options.connectorName }).pipe(\n Effect.tapError((error) => annotateError(\"webhook_publish\", error)),\n ),\n SpanName.webhookHandle,\n { attributes: { [Attr.webhookPath]: route.path } },\n );\n\n yield* Metrics.recordWebhookRequest({\n connector: options.connectorName,\n path: String(route.path),\n outcome: response.status >= 400 ? \"rejected\" : \"ok\",\n });\n return response;\n }).pipe(\n Effect.catchCause((cause) =>\n Metrics.recordWebhookRequest({\n connector: options.connectorName,\n path: String(route.path),\n outcome: \"handler_error\",\n }).pipe(\n Effect.andThen(Effect.logWarning(`Webhook handler error: ${Cause.pretty(cause)}`)),\n Effect.andThen(\n Effect.succeed(\n HttpServerResponse.jsonUnsafe(\n { ok: false, error: \"Webhook handler failed\" },\n { status: 500 },\n ),\n ),\n ),\n ),\n ),\n );\n\nexport const router = (\n routes: ReadonlyArray<WebhookRoute>,\n options: { readonly connectorName: string },\n) =>\n HttpRouter.addAll(\n routes.map((route) => HttpRouter.route(\"POST\", route.path, makeHandler(route, options))),\n );\n"],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"service-DuFiQwid.js","names":["defaultRedactedHeaders: ReadonlyArray<string | RegExp>"],"sources":["../src/telemetry.ts","../src/publisher/service.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 webhookPath: \"airfoil.webhook.path\",\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/** 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\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\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\nexport const layerOtlp = (options: OtlpTracingOptions & OtlpMetricsOptions = {}) =>\n Layer.mergeAll(layerOtlpTracing(options), layerOtlpMetrics(options));\n\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","import type * as Wings from \"@useairfoil/wings\";\nimport type * as Effect from \"effect/Effect\";\n\nimport { Context } from \"effect\";\n\nimport type { ResourceBatch } from \"../core/types\";\nimport type { ConnectorError } from \"../errors\";\n\nexport type PublishSource = \"backfill\" | \"changes\" | \"webhook\";\n\nexport type PublishAck =\n | {\n readonly status: \"accepted\";\n readonly resource: string;\n readonly partition?: Wings.PartitionValue.PartitionValue;\n }\n | {\n readonly status: \"rejected\";\n readonly resource: string;\n readonly reason: string;\n readonly rejectedRows?: number;\n readonly partition?: Wings.PartitionValue.PartitionValue;\n };\n\nexport type PublishOptions = {\n readonly resource: string;\n readonly source: PublishSource;\n readonly batch: ResourceBatch;\n};\n\nexport interface PublisherService {\n readonly publish: (options: PublishOptions) => Effect.Effect<PublishAck, ConnectorError>;\n}\n\nexport class Publisher extends Context.Service<Publisher, PublisherService>()(\n \"@useairfoil/connector-kit/Publisher\",\n) {}\n"],"mappings":"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"}
|