@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
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { t as __export } from "./chunk-Bp6m_JJh.js";
|
|
2
2
|
import { t as ConnectorError } from "./errors-DHWylqRZ.js";
|
|
3
|
-
import {
|
|
3
|
+
import { i as SpanName } from "./telemetry-j_nxwYfh.js";
|
|
4
|
+
import { t as Publisher } from "./service-DiP-XETo.js";
|
|
5
|
+
import { t as loadTableBindings } from "./table-bindings-Dp1DhTeS.js";
|
|
4
6
|
import { Effect, Layer } from "effect";
|
|
5
7
|
import * as Wings from "@useairfoil/wings";
|
|
6
8
|
|
|
@@ -28,12 +30,10 @@ const validateTableMapping = (options) => Effect.try({
|
|
|
28
30
|
try: () => {
|
|
29
31
|
const keyField = Wings.TableUtils.getKeyField(options.table);
|
|
30
32
|
const versionField = Wings.TableUtils.getVersionField(options.table);
|
|
31
|
-
const partitionField = Wings.TableUtils.getPartitionField(options.table);
|
|
32
33
|
if (keyField.name !== options.resource.key) throw new Error(`Resource ${options.resource.name} key '${options.resource.key}' does not match Wings table key '${keyField.name}'`);
|
|
33
34
|
if (versionField.name !== options.resource.version) throw new Error(`Resource ${options.resource.name} version '${options.resource.version}' does not match Wings table version '${versionField.name}'`);
|
|
34
35
|
if (options.resource.partition?.required === true && !options.partitionValue) throw new Error(`Resource ${options.resource.name} requires a partition value`);
|
|
35
|
-
|
|
36
|
-
if (!partitionField && options.partitionValue) throw new Error(`Wings table ${options.table.name} is not partitioned but a partition value was provided`);
|
|
36
|
+
Wings.TableUtils.validatePartitionValueUnsafe(options.table, options.partitionValue);
|
|
37
37
|
return {
|
|
38
38
|
keyField,
|
|
39
39
|
versionField
|
|
@@ -56,6 +56,7 @@ const rejected = (entry, reason, rejectedRows) => ({
|
|
|
56
56
|
rejectedRows,
|
|
57
57
|
partition: entry.partitionValue
|
|
58
58
|
});
|
|
59
|
+
/** Builds a Wings publisher from explicit table mappings. */
|
|
59
60
|
const layerWings = (config) => Layer.effect(Publisher)(Effect.gen(function* () {
|
|
60
61
|
const entries = /* @__PURE__ */ new Map();
|
|
61
62
|
const clusterClient = yield* Wings.WingsClient.clusterClient;
|
|
@@ -117,15 +118,21 @@ const layerWings = (config) => Layer.effect(Publisher)(Effect.gen(function* () {
|
|
|
117
118
|
}) };
|
|
118
119
|
return Publisher.of(service);
|
|
119
120
|
}));
|
|
121
|
+
/** Builds a Wings publisher from the complete hosted `AIRFOIL_TABLE_BINDINGS` document. */
|
|
122
|
+
const layerWingsConfig = (connector) => Layer.unwrap(loadTableBindings(connector).pipe(Effect.map((tables) => layerWings({
|
|
123
|
+
connector,
|
|
124
|
+
tables
|
|
125
|
+
}))));
|
|
120
126
|
|
|
121
127
|
//#endregion
|
|
122
128
|
//#region src/publisher/index.ts
|
|
123
129
|
var publisher_exports = /* @__PURE__ */ __export({
|
|
124
130
|
Publisher: () => Publisher,
|
|
125
131
|
layerConsole: () => layerConsole,
|
|
126
|
-
layerWings: () => layerWings
|
|
132
|
+
layerWings: () => layerWings,
|
|
133
|
+
layerWingsConfig: () => layerWingsConfig
|
|
127
134
|
});
|
|
128
135
|
|
|
129
136
|
//#endregion
|
|
130
|
-
export { layerWings as n,
|
|
131
|
-
//# sourceMappingURL=publisher-
|
|
137
|
+
export { layerConsole as i, layerWings as n, layerWingsConfig as r, publisher_exports as t };
|
|
138
|
+
//# sourceMappingURL=publisher-BdWJOfer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"publisher-BdWJOfer.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 { loadTableBindings } from \"../runtime-config/table-bindings\";\nimport { SpanName } from \"../telemetry\";\nimport { Publisher, type PublishAck, type PublisherService } from \"./service\";\n\ntype Rows = Record<string, unknown>;\n\n/** Programmatic mapping between a connector resource and a Wings table. */\nexport type WingsTableMapping = {\n readonly name: string;\n readonly partitionValue?: Wings.PartitionValue.PartitionValue;\n};\n\n/** Direct Wings publisher configuration, mainly for tests and local runtimes. */\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\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 Wings.TableUtils.validatePartitionValueUnsafe(options.table, options.partitionValue);\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\n/** Builds a Wings publisher from explicit table mappings. */\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\n/** Builds a Wings publisher from the complete hosted `AIRFOIL_TABLE_BINDINGS` document. */\nexport const layerWingsConfig = (connector: ConnectorDefinition) =>\n Layer.unwrap(\n loadTableBindings(connector).pipe(Effect.map((tables) => layerWings({ connector, tables }))),\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, layerWingsConfig } from \"./wings\";\n"],"mappings":"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"}
|
|
@@ -1,2 +1,4 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
1
|
+
import "../index-DKoRwyFn.js";
|
|
2
|
+
import "../types-jVD5Ys43.js";
|
|
3
|
+
import { a as loadTableBindings, c as PlatformRuntimeKey, d as RuntimeConfigError, f as RuntimeConfigErrorCode, i as ResolvedTableBindings, l as httpPort, n as HostedTableBinding, o as layerHosted, r as ResolvedTableBinding, s as PlatformRuntimeDefault, u as wingsClient } from "../index-ZSuiZX5H.js";
|
|
4
|
+
export { HostedTableBinding, PlatformRuntimeDefault, PlatformRuntimeKey, ResolvedTableBinding, ResolvedTableBindings, RuntimeConfigError, RuntimeConfigErrorCode, httpPort, layerHosted, loadTableBindings, wingsClient };
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { n as PlatformRuntimeKey, t as PlatformRuntimeDefault } from "../constants-
|
|
2
|
-
import { n as
|
|
1
|
+
import { n as PlatformRuntimeKey, t as PlatformRuntimeDefault } from "../constants-D113Ut1x.js";
|
|
2
|
+
import { n as RuntimeConfigError, t as loadTableBindings } from "../table-bindings-Dp1DhTeS.js";
|
|
3
|
+
import { i as wingsClient, n as layerHosted, r as httpPort } from "../runtime-config-IbyCVpei.js";
|
|
3
4
|
|
|
4
|
-
export { PlatformRuntimeDefault, PlatformRuntimeKey, RuntimeConfigError, layerHosted };
|
|
5
|
+
export { PlatformRuntimeDefault, PlatformRuntimeKey, RuntimeConfigError, httpPort, layerHosted, loadTableBindings, wingsClient };
|
|
@@ -1,10 +1,16 @@
|
|
|
1
1
|
import { t as __export } from "./chunk-Bp6m_JJh.js";
|
|
2
|
-
import { n as PlatformRuntimeKey, t as PlatformRuntimeDefault } from "./constants-
|
|
3
|
-
import {
|
|
2
|
+
import { n as PlatformRuntimeKey, t as PlatformRuntimeDefault } from "./constants-D113Ut1x.js";
|
|
3
|
+
import { n as RuntimeConfigError, t as loadTableBindings } from "./table-bindings-Dp1DhTeS.js";
|
|
4
|
+
import { Config, ConfigProvider, Effect, FileSystem, Schema } from "effect";
|
|
4
5
|
|
|
5
|
-
//#region src/runtime-config/
|
|
6
|
-
/**
|
|
7
|
-
|
|
6
|
+
//#region src/runtime-config/config.ts
|
|
7
|
+
/** Shared HTTP port used by hosted and sandbox connector entrypoints. */
|
|
8
|
+
const httpPort = Config.port(PlatformRuntimeKey.httpPort).pipe(Config.withDefault(PlatformRuntimeDefault.httpPort));
|
|
9
|
+
/** Shared Wings client configuration supplied by the hosting platform. */
|
|
10
|
+
const wingsClient = {
|
|
11
|
+
host: Config.nonEmptyString(PlatformRuntimeKey.wingsHost),
|
|
12
|
+
namespace: Config.nonEmptyString(PlatformRuntimeKey.wingsNamespace)
|
|
13
|
+
};
|
|
8
14
|
|
|
9
15
|
//#endregion
|
|
10
16
|
//#region src/runtime-config/layer.ts
|
|
@@ -43,9 +49,12 @@ var runtime_config_exports = /* @__PURE__ */ __export({
|
|
|
43
49
|
PlatformRuntimeDefault: () => PlatformRuntimeDefault,
|
|
44
50
|
PlatformRuntimeKey: () => PlatformRuntimeKey,
|
|
45
51
|
RuntimeConfigError: () => RuntimeConfigError,
|
|
46
|
-
|
|
52
|
+
httpPort: () => httpPort,
|
|
53
|
+
layerHosted: () => layerHosted,
|
|
54
|
+
loadTableBindings: () => loadTableBindings,
|
|
55
|
+
wingsClient: () => wingsClient
|
|
47
56
|
});
|
|
48
57
|
|
|
49
58
|
//#endregion
|
|
50
|
-
export { layerHosted as n,
|
|
51
|
-
//# sourceMappingURL=runtime-config-
|
|
59
|
+
export { wingsClient as i, layerHosted as n, httpPort as r, runtime_config_exports as t };
|
|
60
|
+
//# sourceMappingURL=runtime-config-IbyCVpei.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"runtime-config-IbyCVpei.js","names":["providerFromFile: Effect.Effect<\n ConfigProvider.ConfigProvider,\n RuntimeConfigError,\n FileSystem.FileSystem\n>"],"sources":["../src/runtime-config/config.ts","../src/runtime-config/layer.ts","../src/runtime-config/index.ts"],"sourcesContent":["import type { WingsClientOptions } from \"@useairfoil/wings/wings-client\";\n\nimport { Config } from \"effect\";\n\nimport { PlatformRuntimeDefault, PlatformRuntimeKey } from \"./constants\";\n\n/** Shared HTTP port used by hosted and sandbox connector entrypoints. */\nexport const httpPort = Config.port(PlatformRuntimeKey.httpPort).pipe(\n Config.withDefault(PlatformRuntimeDefault.httpPort),\n);\n\n/** Shared Wings client configuration supplied by the hosting platform. */\nexport const wingsClient = {\n host: Config.nonEmptyString(PlatformRuntimeKey.wingsHost),\n namespace: Config.nonEmptyString(PlatformRuntimeKey.wingsNamespace),\n} satisfies Config.Wrap<WingsClientOptions>;\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 { httpPort, wingsClient } from \"./config\";\nexport { PlatformRuntimeDefault, PlatformRuntimeKey } from \"./constants\";\nexport { layerHosted } from \"./layer\";\nexport {\n type HostedTableBinding,\n type ResolvedTableBinding,\n type ResolvedTableBindings,\n loadTableBindings,\n} from \"./table-bindings\";\n"],"mappings":";;;;;;;AAOA,MAAa,WAAW,OAAO,KAAK,mBAAmB,SAAS,CAAC,KAC/D,OAAO,YAAY,uBAAuB,SAAS,CACpD;;AAGD,MAAa,cAAc;CACzB,MAAM,OAAO,eAAe,mBAAmB,UAAU;CACzD,WAAW,OAAO,eAAe,mBAAmB,eAAe;CACpE;;;;ACVD,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,6 +1,7 @@
|
|
|
1
1
|
import { t as ConnectorError } from "./errors-DHWylqRZ.js";
|
|
2
|
-
import { a as
|
|
3
|
-
import { a as
|
|
2
|
+
import { a as addCurrentSpanEvent, i as SpanName, n as EventAttr, o as annotateError, r as EventName, t as Attr } from "./telemetry-j_nxwYfh.js";
|
|
3
|
+
import { _ as withResourceAttributes, a as entitiesUpserted, c as publishDuration, i as entitiesDeleted, n as batchSize, p as setWebhookQueueDepth, r as batches, u as recordWebhookRequest } from "./metrics-inkCAxKq.js";
|
|
4
|
+
import { t as Publisher } from "./service-DiP-XETo.js";
|
|
4
5
|
import { Cause, Context, Effect, Metric, Queue, Schema } from "effect";
|
|
5
6
|
import { HttpRouter, HttpServerRequest, HttpServerResponse } from "effect/unstable/http";
|
|
6
7
|
|
|
@@ -11,30 +12,29 @@ const publishBatch = Effect.fnUntraced(function* (options) {
|
|
|
11
12
|
resource: options.resource,
|
|
12
13
|
source: options.source
|
|
13
14
|
};
|
|
15
|
+
const metricAttributes = withResourceAttributes(metric);
|
|
14
16
|
const upserts = options.batch.mutations.filter((mutation) => mutation.op === "upsert").length;
|
|
15
17
|
const deletes = options.batch.mutations.length - upserts;
|
|
16
|
-
const
|
|
17
|
-
const [duration, ack] = yield* Effect.timed(publisher.publish({
|
|
18
|
+
const ack = yield* (yield* Publisher).publish({
|
|
18
19
|
resource: options.resource,
|
|
19
20
|
source: options.source,
|
|
20
21
|
batch: options.batch
|
|
21
|
-
})
|
|
22
|
-
...
|
|
23
|
-
|
|
22
|
+
}).pipe(Effect.trackDuration(Metric.withAttributes(publishDuration, metricAttributes)), Effect.catchCause((cause) => Metric.update(Metric.withAttributes(batches, {
|
|
23
|
+
...metricAttributes,
|
|
24
|
+
[Attr.batchOutcome]: "error"
|
|
24
25
|
}), 1).pipe(Effect.andThen(Effect.failCause(cause)))));
|
|
25
|
-
yield* Metric.update(Metric.withAttributes(
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
outcome: ack.status
|
|
26
|
+
yield* Metric.update(Metric.withAttributes(batches, {
|
|
27
|
+
...metricAttributes,
|
|
28
|
+
[Attr.batchOutcome]: ack.status
|
|
29
29
|
}), 1);
|
|
30
30
|
yield* Effect.annotateCurrentSpan({ [Attr.publisherSuccess]: ack.status === "accepted" });
|
|
31
31
|
if (ack.status === "rejected") {
|
|
32
32
|
yield* Effect.annotateCurrentSpan({ [Attr.errorPhase]: "publish" });
|
|
33
33
|
return yield* Effect.fail(new ConnectorError({ message: `Publisher rejected batch for ${options.resource}: ${ack.reason}` }));
|
|
34
34
|
}
|
|
35
|
-
yield* Metric.update(Metric.withAttributes(
|
|
36
|
-
yield* Metric.update(Metric.withAttributes(
|
|
37
|
-
yield* Metric.update(Metric.withAttributes(batchSize,
|
|
35
|
+
yield* Metric.update(Metric.withAttributes(entitiesUpserted, metricAttributes), upserts);
|
|
36
|
+
yield* Metric.update(Metric.withAttributes(entitiesDeleted, metricAttributes), deletes);
|
|
37
|
+
yield* Metric.update(Metric.withAttributes(batchSize, metricAttributes), options.batch.mutations.length);
|
|
38
38
|
if (options.batch.cursor !== void 0) yield* addCurrentSpanEvent(EventName.batchCheckpoint, { [EventAttr.batchCursor]: options.batch.cursor });
|
|
39
39
|
});
|
|
40
40
|
|
|
@@ -46,22 +46,22 @@ const decodeAndHandleWebhook = (resourceName, webhook, payload) => Schema.decode
|
|
|
46
46
|
message: `Invalid webhook payload for resource ${resourceName}`,
|
|
47
47
|
cause
|
|
48
48
|
})), Effect.flatMap((decoded) => webhook.handler({ payload: decoded })));
|
|
49
|
-
const makeRouteContext = (request, rawBody, payload, batches) => ({
|
|
49
|
+
const makeRouteContext = (request, rawBody, payload, batches$1) => ({
|
|
50
50
|
request,
|
|
51
51
|
rawBody,
|
|
52
52
|
payload,
|
|
53
53
|
to: (resource, payload$1) => Effect.gen(function* () {
|
|
54
54
|
if (!resource.webhook) return yield* Effect.fail(new ConnectorError({ message: `Resource ${resource.name} does not define a webhook handler` }));
|
|
55
55
|
const mutations = yield* decodeAndHandleWebhook(resource.name, resource.webhook, payload$1);
|
|
56
|
-
batches.push({
|
|
56
|
+
batches$1.push({
|
|
57
57
|
resource: resource.name,
|
|
58
58
|
batch: resourceBatch(mutations)
|
|
59
59
|
});
|
|
60
60
|
})
|
|
61
61
|
});
|
|
62
|
-
const compactBatches = (batches) => {
|
|
62
|
+
const compactBatches = (batches$1) => {
|
|
63
63
|
const grouped = /* @__PURE__ */ new Map();
|
|
64
|
-
for (const item of batches) {
|
|
64
|
+
for (const item of batches$1) {
|
|
65
65
|
const current = grouped.get(item.resource) ?? [];
|
|
66
66
|
grouped.set(item.resource, [...current, ...item.batch.mutations]);
|
|
67
67
|
}
|
|
@@ -70,8 +70,8 @@ const compactBatches = (batches) => {
|
|
|
70
70
|
batch: resourceBatch(mutations)
|
|
71
71
|
}));
|
|
72
72
|
};
|
|
73
|
-
const publishBatches = (batches, options) => Effect.gen(function* () {
|
|
74
|
-
yield* Effect.forEach(batches, (batch) => publishBatch({
|
|
73
|
+
const publishBatches = (batches$1, options) => Effect.gen(function* () {
|
|
74
|
+
yield* Effect.forEach(batches$1, (batch) => publishBatch({
|
|
75
75
|
connector: options.connectorName,
|
|
76
76
|
resource: batch.resource,
|
|
77
77
|
source: "webhook",
|
|
@@ -148,10 +148,10 @@ const makeHandler = (route, options) => Effect.gen(function* () {
|
|
|
148
148
|
return badRequest(payloadResult.message);
|
|
149
149
|
}
|
|
150
150
|
const response = yield* Effect.withSpan(route.handler(makeRouteContext(request, rawBodyResult.value, payloadResult.value, collected)).pipe(Effect.tapError((error) => annotateError("webhook_handle", error))), SpanName.webhookHandle, { attributes: { [Attr.webhookPath]: route.path } });
|
|
151
|
-
const batches = compactBatches(collected);
|
|
151
|
+
const batches$1 = compactBatches(collected);
|
|
152
152
|
if (route.ackMode === "after-enqueue") {
|
|
153
153
|
const queue = yield* WebhookQueue;
|
|
154
|
-
yield* Queue.offerAll(queue.queue, batches);
|
|
154
|
+
yield* Queue.offerAll(queue.queue, batches$1);
|
|
155
155
|
const depth = yield* Queue.size(queue.queue);
|
|
156
156
|
yield* setWebhookQueueDepth(options.connectorName, depth);
|
|
157
157
|
yield* recordWebhookRequest({
|
|
@@ -161,7 +161,7 @@ const makeHandler = (route, options) => Effect.gen(function* () {
|
|
|
161
161
|
});
|
|
162
162
|
return response;
|
|
163
163
|
}
|
|
164
|
-
yield* Effect.withSpan(publishBatches(batches, { connectorName: options.connectorName }).pipe(Effect.tapError((error) => annotateError("webhook_publish", error))), SpanName.webhookHandle, { attributes: { [Attr.webhookPath]: route.path } });
|
|
164
|
+
yield* Effect.withSpan(publishBatches(batches$1, { connectorName: options.connectorName }).pipe(Effect.tapError((error) => annotateError("webhook_publish", error))), SpanName.webhookHandle, { attributes: { [Attr.webhookPath]: route.path } });
|
|
165
165
|
yield* recordWebhookRequest({
|
|
166
166
|
connector: options.connectorName,
|
|
167
167
|
path: String(route.path),
|
|
@@ -180,4 +180,4 @@ const router = (routes, options) => HttpRouter.addAll(routes.map((route) => Http
|
|
|
180
180
|
|
|
181
181
|
//#endregion
|
|
182
182
|
export { router as n, publishBatch as r, WebhookQueue as t };
|
|
183
|
-
//# sourceMappingURL=server-
|
|
183
|
+
//# sourceMappingURL=server-D-eLX3yN.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server-D-eLX3yN.js","names":["Metrics.withResourceAttributes","Metrics.publishDuration","Metrics.batches","Metrics.entitiesUpserted","Metrics.entitiesDeleted","Metrics.batchSize","payload","batches","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 metricAttributes = Metrics.withResourceAttributes(metric);\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 ack = yield* publisher\n .publish({\n resource: options.resource,\n source: options.source,\n batch: options.batch,\n })\n .pipe(\n Effect.trackDuration(Metric.withAttributes(Metrics.publishDuration, metricAttributes)),\n Effect.catchCause((cause) =>\n Metric.update(\n Metric.withAttributes(Metrics.batches, {\n ...metricAttributes,\n [Attr.batchOutcome]: \"error\",\n }),\n 1,\n ).pipe(Effect.andThen(Effect.failCause(cause))),\n ),\n );\n\n yield* Metric.update(\n Metric.withAttributes(Metrics.batches, {\n ...metricAttributes,\n [Attr.batchOutcome]: ack.status,\n }),\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.entitiesUpserted, metricAttributes), upserts);\n yield* Metric.update(Metric.withAttributes(Metrics.entitiesDeleted, metricAttributes), deletes);\n yield* Metric.update(\n Metric.withAttributes(Metrics.batchSize, metricAttributes),\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,5 +1,5 @@
|
|
|
1
|
-
import { t as ConnectorError } from "./index-
|
|
2
|
-
import { c as ResourceBatch } from "./types-
|
|
1
|
+
import { t as ConnectorError } from "./index-DKoRwyFn.js";
|
|
2
|
+
import { c as ResourceBatch } from "./types-jVD5Ys43.js";
|
|
3
3
|
import { Context } from "effect";
|
|
4
4
|
import * as Wings from "@useairfoil/wings";
|
|
5
5
|
import * as Effect$1 from "effect/Effect";
|
|
@@ -29,4 +29,4 @@ declare const Publisher_base: Context.ServiceClass<Publisher, "@useairfoil/conne
|
|
|
29
29
|
declare class Publisher extends Publisher_base {}
|
|
30
30
|
//#endregion
|
|
31
31
|
export { PublisherService as a, Publisher as i, PublishOptions as n, PublishSource as r, PublishAck as t };
|
|
32
|
-
//# sourceMappingURL=service-
|
|
32
|
+
//# sourceMappingURL=service-CUjGhe-7.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"service-DiP-XETo.js","names":[],"sources":["../src/publisher/service.ts"],"sourcesContent":["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":";;;AAkCA,IAAa,YAAb,cAA+B,QAAQ,SAAsC,CAC3E,sCACD,CAAC"}
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import "../index-
|
|
2
|
-
import { a as StateStoreService, i as StateStore, n as layerSql, r as layerMemory } from "../index-
|
|
1
|
+
import "../index-DKoRwyFn.js";
|
|
2
|
+
import { a as StateStoreService, i as StateStore, n as layerSql, r as layerMemory } from "../index-DYpEMJf-.js";
|
|
3
3
|
export { StateStore, StateStoreService, layerMemory, layerSql };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import "../errors-DHWylqRZ.js";
|
|
2
|
-
import "../constants-
|
|
3
|
-
import { n as layerSql, o as StateStore, r as layerMemory } from "../state-store-
|
|
2
|
+
import "../constants-D113Ut1x.js";
|
|
3
|
+
import { n as layerSql, o as StateStore, r as layerMemory } from "../state-store-BdYulQOY.js";
|
|
4
4
|
|
|
5
5
|
export { StateStore, layerMemory, layerSql };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { t as __export } from "./chunk-Bp6m_JJh.js";
|
|
2
2
|
import { t as ConnectorError } from "./errors-DHWylqRZ.js";
|
|
3
|
-
import { n as PlatformRuntimeKey, t as PlatformRuntimeDefault } from "./constants-
|
|
3
|
+
import { n as PlatformRuntimeKey, t as PlatformRuntimeDefault } from "./constants-D113Ut1x.js";
|
|
4
4
|
import { Config, Context, DateTime, Effect, Layer, Option, Schema } from "effect";
|
|
5
5
|
import { KeyValueStore } from "effect/unstable/persistence";
|
|
6
6
|
import { SqlClient } from "effect/unstable/sql";
|
|
@@ -195,4 +195,4 @@ var state_store_exports = /* @__PURE__ */ __export({
|
|
|
195
195
|
|
|
196
196
|
//#endregion
|
|
197
197
|
export { ResourceStateSchema as a, ResourceErrorSchema as i, layerSql as n, StateStore as o, layerMemory as r, state_store_exports as t };
|
|
198
|
-
//# sourceMappingURL=state-store-
|
|
198
|
+
//# sourceMappingURL=state-store-BdYulQOY.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"state-store-yl6LiTQ0.js","names":["layerKeyValueStore: Layer.Layer<StateStore, never, KeyValueStore.KeyValueStore>","layerMemory: Layer.Layer<StateStore>"],"sources":["../src/state-store/service.ts","../src/state-store/keys.ts","../src/state-store/schema.ts","../src/state-store/layer-key-value-store.ts","../src/state-store/layer-memory.ts","../src/state-store/layer-sql.ts","../src/state-store/index.ts"],"sourcesContent":["import { Context, Effect } from \"effect\";\n\nimport type {\n PersistedBackfillState,\n PersistedChangesState,\n PersistedResourceState,\n StateOperation,\n StateSource,\n} from \"./schema\";\n\nimport { ConnectorError } from \"../errors\";\n\n/** Connector-facing durable state operations, independent of the storage backend. */\nexport interface StateStoreService {\n readonly getResourceState: (\n resource: string,\n ) => Effect.Effect<PersistedResourceState | undefined, ConnectorError>;\n readonly setBackfillState: (\n resource: string,\n state: PersistedBackfillState,\n ) => Effect.Effect<void, ConnectorError>;\n readonly setChangesState: (\n resource: string,\n state: PersistedChangesState,\n ) => Effect.Effect<void, ConnectorError>;\n readonly setResourceError: (\n resource: string,\n source: StateSource,\n operation: StateOperation,\n ) => Effect.Effect<void, ConnectorError>;\n readonly clearResourceError: (\n resource: string,\n source: StateSource,\n ) => Effect.Effect<void, ConnectorError>;\n}\n\n/**\n * Effect service used by ingestion and status logic for connector checkpoints.\n * Production provides `layerSql`; sandboxes and tests may provide `layerMemory`.\n *\n * @example\n * ```ts\n * const program = Effect.gen(function* () {\n * const state = yield* StateStore;\n * yield* state.setChangesState(\"orders\", { cursor: \"next-page\" });\n * });\n * ```\n */\nexport class StateStore extends Context.Service<StateStore, StateStoreService>()(\n \"@useairfoil/connector-kit/StateStore\",\n) {}\n","import type { StateSource } from \"./schema\";\n\ntype StateComponent = StateSource | `error:${StateSource}`;\n\nexport const resourceComponentKey = (resource: string, component: StateComponent): string =>\n `resource:${resource}:${component}`;\n\nexport const resourceErrorKey = (resource: string, source: StateSource): string =>\n resourceComponentKey(resource, `error:${source}`);\n\n/**\n * Prefix for all state owned by one connector instance. The platform-owned ID\n * stays visible so operators can inspect and clean up shared-table rows directly.\n *\n * @example\n * ```ts\n * connectorInstanceKeyPrefix(\"team-a/orders\");\n * // \"connector-instance:team-a/orders:\"\n * ```\n */\nexport const connectorInstanceKeyPrefix = (connectorInstanceId: string): string =>\n `connector-instance:${connectorInstanceId}:`;\n","import { Schema } from \"effect\";\n\nconst CursorValueSchema = Schema.Union([Schema.String, Schema.Number]);\n\n/** Durable progress for a finite historical backfill. */\nexport const BackfillStateSchema = Schema.Struct({\n cutoff: CursorValueSchema,\n pageCursor: Schema.optional(CursorValueSchema),\n completed: Schema.Boolean,\n lastSuccessAt: Schema.optional(Schema.String),\n});\n\n/** Durable cursor for an ongoing incremental changes stream. */\nexport const ChangesStateSchema = Schema.Struct({\n cursor: CursorValueSchema,\n lastSuccessAt: Schema.optional(Schema.String),\n});\n\nexport const StateSourceSchema = Schema.Literals([\"backfill\", \"changes\"]);\n\nexport const StateOperationSchema = Schema.Literals([\"fetch\", \"publish\", \"checkpoint\"]);\n\nexport const StateErrorCodeSchema = Schema.Literals([\n \"fetch_failed\",\n \"publish_failed\",\n \"checkpoint_failed\",\n]);\n\n/** SDK-owned error context safe to persist and expose through status. */\nexport const ResourceErrorContextSchema = Schema.Struct({\n source: StateSourceSchema,\n operation: StateOperationSchema,\n code: StateErrorCodeSchema,\n message: Schema.String,\n});\n\n/** Stable public error summary persisted for status reporting; never a raw cause. */\nexport const ResourceErrorSchema = Schema.Struct({\n ...ResourceErrorContextSchema.fields,\n at: Schema.String,\n});\n\n/** Complete persisted state assembled from independently stored components. */\nexport const ResourceStateSchema = Schema.Struct({\n backfill: Schema.optional(BackfillStateSchema),\n changes: Schema.optional(ChangesStateSchema),\n lastError: Schema.optional(ResourceErrorSchema),\n});\n\nexport type PersistedBackfillState = Schema.Schema.Type<typeof BackfillStateSchema>;\nexport type PersistedChangesState = Schema.Schema.Type<typeof ChangesStateSchema>;\nexport type StateSource = Schema.Schema.Type<typeof StateSourceSchema>;\nexport type StateOperation = Schema.Schema.Type<typeof StateOperationSchema>;\nexport type StateErrorCode = Schema.Schema.Type<typeof StateErrorCodeSchema>;\nexport type ResourceErrorContext = Schema.Schema.Type<typeof ResourceErrorContextSchema>;\nexport type PersistedResourceError = Schema.Schema.Type<typeof ResourceErrorSchema>;\nexport type PersistedResourceState = Schema.Schema.Type<typeof ResourceStateSchema>;\n","import { DateTime, Effect, Layer, Option, Schema } from \"effect\";\nimport { KeyValueStore } from \"effect/unstable/persistence\";\n\nimport { ConnectorError } from \"../errors\";\nimport { resourceComponentKey, resourceErrorKey } from \"./keys\";\nimport {\n BackfillStateSchema,\n ChangesStateSchema,\n ResourceErrorSchema,\n type PersistedBackfillState,\n type PersistedChangesState,\n type PersistedResourceError,\n type ResourceErrorContext,\n type StateOperation,\n type StateSource,\n} from \"./schema\";\nimport { StateStore } from \"./service\";\n\nconst mapStoreError = (operation: \"read\" | \"write\" | \"clear\", resource: string) =>\n Effect.mapError(\n (cause: KeyValueStore.KeyValueStoreError | Schema.SchemaError) =>\n new ConnectorError({\n message: `Failed to ${operation} state for resource ${resource}`,\n cause,\n }),\n );\n\nconst resourceErrorContext = (\n source: StateSource,\n operation: StateOperation,\n): ResourceErrorContext => {\n const sourceName = source === \"backfill\" ? \"Backfill\" : \"Changes\";\n switch (operation) {\n case \"fetch\":\n return { source, operation, code: \"fetch_failed\", message: `${sourceName} fetch failed` };\n case \"publish\":\n return { source, operation, code: \"publish_failed\", message: `${sourceName} publish failed` };\n case \"checkpoint\":\n return {\n source,\n operation,\n code: \"checkpoint_failed\",\n message: `${sourceName} checkpoint failed`,\n };\n }\n};\n\nconst latestError = (\n backfill: Option.Option<PersistedResourceError>,\n changes: Option.Option<PersistedResourceError>,\n): Option.Option<PersistedResourceError> => {\n if (Option.isNone(backfill)) return changes;\n if (Option.isNone(changes)) return backfill;\n return backfill.value.at >= changes.value.at ? backfill : changes;\n};\n\n/**\n * Implements StateStore over any Effect KeyValueStore. `toSchemaStore` owns\n * serialization and validates persisted values when they are read and written.\n * The caller owns instance scoping; `layerSql` supplies the production prefix.\n */\nexport const layerKeyValueStore: Layer.Layer<StateStore, never, KeyValueStore.KeyValueStore> =\n Layer.effect(StateStore)(\n Effect.gen(function* () {\n const keyValueStore = yield* KeyValueStore.KeyValueStore;\n const backfillStore = KeyValueStore.toSchemaStore(keyValueStore, BackfillStateSchema);\n const changesStore = KeyValueStore.toSchemaStore(keyValueStore, ChangesStateSchema);\n const errorStore = KeyValueStore.toSchemaStore(keyValueStore, ResourceErrorSchema);\n\n const getResourceState = (resource: string) =>\n // Components use separate keys so a cursor update cannot overwrite the\n // independently managed error or backfill state.\n Effect.all(\n {\n backfill: backfillStore.get(resourceComponentKey(resource, \"backfill\")),\n changes: changesStore.get(resourceComponentKey(resource, \"changes\")),\n backfillError: errorStore.get(resourceErrorKey(resource, \"backfill\")),\n changesError: errorStore.get(resourceErrorKey(resource, \"changes\")),\n },\n { concurrency: \"unbounded\" },\n ).pipe(\n Effect.map(({ backfill, changes, backfillError, changesError }) => {\n const lastError = latestError(backfillError, changesError);\n if (Option.isNone(backfill) && Option.isNone(changes) && Option.isNone(lastError)) {\n return undefined;\n }\n return {\n ...(Option.isSome(backfill) ? { backfill: backfill.value } : {}),\n ...(Option.isSome(changes) ? { changes: changes.value } : {}),\n ...(Option.isSome(lastError) ? { lastError: lastError.value } : {}),\n };\n }),\n mapStoreError(\"read\", resource),\n );\n\n const setBackfillState = (resource: string, state: PersistedBackfillState) =>\n backfillStore\n .set(resourceComponentKey(resource, \"backfill\"), state)\n .pipe(mapStoreError(\"write\", resource));\n\n const setChangesState = (resource: string, state: PersistedChangesState) =>\n changesStore\n .set(resourceComponentKey(resource, \"changes\"), state)\n .pipe(mapStoreError(\"write\", resource));\n\n const setResourceError = (resource: string, source: StateSource, operation: StateOperation) =>\n DateTime.now.pipe(\n Effect.map(DateTime.formatIso),\n Effect.flatMap((at) => {\n const error = resourceErrorContext(source, operation);\n return errorStore.set(resourceErrorKey(resource, source), {\n ...error,\n at,\n });\n }),\n mapStoreError(\"write\", resource),\n );\n\n const clearResourceError = (resource: string, source: StateSource) =>\n errorStore\n .remove(resourceErrorKey(resource, source))\n .pipe(mapStoreError(\"clear\", resource));\n\n return StateStore.of({\n getResourceState,\n setBackfillState,\n setChangesState,\n setResourceError,\n clearResourceError,\n });\n }),\n );\n","import { Layer } from \"effect\";\nimport { KeyValueStore } from \"effect/unstable/persistence\";\n\nimport type { StateStore } from \"./service\";\n\nimport { layerKeyValueStore } from \"./layer-key-value-store\";\n\n/** Process-local state for tests and explicit local sandbox commands. */\nexport const layerMemory: Layer.Layer<StateStore> = layerKeyValueStore.pipe(\n Layer.provide(KeyValueStore.layerMemory),\n);\n","import { Config, Effect, Layer } from \"effect\";\nimport { KeyValueStore } from \"effect/unstable/persistence\";\nimport { SqlClient } from \"effect/unstable/sql\";\n\nimport type { StateStore } from \"./service\";\n\nimport { PlatformRuntimeDefault, PlatformRuntimeKey } from \"../runtime-config/constants\";\nimport { connectorInstanceKeyPrefix } from \"./keys\";\nimport { layerKeyValueStore } from \"./layer-key-value-store\";\n\nconst scopedKeyValueStore = (connectorInstanceId: string) =>\n Layer.effect(KeyValueStore.KeyValueStore)(\n KeyValueStore.KeyValueStore.pipe(\n Effect.map(KeyValueStore.prefix(connectorInstanceKeyPrefix(connectorInstanceId))),\n ),\n );\n\n/**\n * Shared SQL state using the immutable connector instance ID supplied by\n * platform-owned Effect configuration. The table defaults to\n * `_airfoil_connectors_state` and can be overridden with `AIRFOIL_STATE_TABLE`.\n * Effect's SQL KeyValueStore owns the table, while `KeyValueStore.prefix`\n * provides application-level isolation between connector instances in that\n * table. The resulting layer exposes only StateStore, never the unscoped\n * KeyValueStore.\n *\n * @example\n * ```ts\n * const StateStoreLive = layerSql().pipe(Layer.provide(PostgresLive));\n * ```\n */\nexport const layerSql = (): Layer.Layer<StateStore, Config.ConfigError, SqlClient.SqlClient> =>\n Layer.unwrap(\n Config.all({\n connectorInstanceId: Config.nonEmptyString(PlatformRuntimeKey.connectorInstanceId),\n table: Config.nonEmptyString(PlatformRuntimeKey.stateTable).pipe(\n Config.withDefault(PlatformRuntimeDefault.stateTable),\n ),\n }).pipe(\n Effect.map(({ connectorInstanceId, table }) =>\n layerKeyValueStore.pipe(\n Layer.provide(\n scopedKeyValueStore(connectorInstanceId).pipe(\n Layer.provide(KeyValueStore.layerSql({ table })),\n ),\n ),\n ),\n ),\n ),\n );\n","export { StateStore, type StateStoreService } from \"./service\";\nexport { layerMemory } from \"./layer-memory\";\nexport { layerSql } from \"./layer-sql\";\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"state-store-BdYulQOY.js","names":["layerKeyValueStore: Layer.Layer<StateStore, never, KeyValueStore.KeyValueStore>","layerMemory: Layer.Layer<StateStore>"],"sources":["../src/state-store/service.ts","../src/state-store/keys.ts","../src/state-store/schema.ts","../src/state-store/layer-key-value-store.ts","../src/state-store/layer-memory.ts","../src/state-store/layer-sql.ts","../src/state-store/index.ts"],"sourcesContent":["import { Context, Effect } from \"effect\";\n\nimport type {\n PersistedBackfillState,\n PersistedChangesState,\n PersistedResourceState,\n StateOperation,\n StateSource,\n} from \"./schema\";\n\nimport { ConnectorError } from \"../errors\";\n\n/** Connector-facing durable state operations, independent of the storage backend. */\nexport interface StateStoreService {\n readonly getResourceState: (\n resource: string,\n ) => Effect.Effect<PersistedResourceState | undefined, ConnectorError>;\n readonly setBackfillState: (\n resource: string,\n state: PersistedBackfillState,\n ) => Effect.Effect<void, ConnectorError>;\n readonly setChangesState: (\n resource: string,\n state: PersistedChangesState,\n ) => Effect.Effect<void, ConnectorError>;\n readonly setResourceError: (\n resource: string,\n source: StateSource,\n operation: StateOperation,\n ) => Effect.Effect<void, ConnectorError>;\n readonly clearResourceError: (\n resource: string,\n source: StateSource,\n ) => Effect.Effect<void, ConnectorError>;\n}\n\n/**\n * Effect service used by ingestion and status logic for connector checkpoints.\n * Production provides `layerSql`; sandboxes and tests may provide `layerMemory`.\n *\n * @example\n * ```ts\n * const program = Effect.gen(function* () {\n * const state = yield* StateStore;\n * yield* state.setChangesState(\"orders\", { cursor: \"next-page\" });\n * });\n * ```\n */\nexport class StateStore extends Context.Service<StateStore, StateStoreService>()(\n \"@useairfoil/connector-kit/StateStore\",\n) {}\n","import type { StateSource } from \"./schema\";\n\ntype StateComponent = StateSource | `error:${StateSource}`;\n\nexport const resourceComponentKey = (resource: string, component: StateComponent): string =>\n `resource:${resource}:${component}`;\n\nexport const resourceErrorKey = (resource: string, source: StateSource): string =>\n resourceComponentKey(resource, `error:${source}`);\n\n/**\n * Prefix for all state owned by one connector instance. The platform-owned ID\n * stays visible so operators can inspect and clean up shared-table rows directly.\n *\n * @example\n * ```ts\n * connectorInstanceKeyPrefix(\"team-a/orders\");\n * // \"connector-instance:team-a/orders:\"\n * ```\n */\nexport const connectorInstanceKeyPrefix = (connectorInstanceId: string): string =>\n `connector-instance:${connectorInstanceId}:`;\n","import { Schema } from \"effect\";\n\nconst CursorValueSchema = Schema.Union([Schema.String, Schema.Number]);\n\n/** Durable progress for a finite historical backfill. */\nexport const BackfillStateSchema = Schema.Struct({\n cutoff: CursorValueSchema,\n pageCursor: Schema.optional(CursorValueSchema),\n completed: Schema.Boolean,\n lastSuccessAt: Schema.optional(Schema.String),\n});\n\n/** Durable cursor for an ongoing incremental changes stream. */\nexport const ChangesStateSchema = Schema.Struct({\n cursor: CursorValueSchema,\n lastSuccessAt: Schema.optional(Schema.String),\n});\n\nexport const StateSourceSchema = Schema.Literals([\"backfill\", \"changes\"]);\n\nexport const StateOperationSchema = Schema.Literals([\"fetch\", \"publish\", \"checkpoint\"]);\n\nexport const StateErrorCodeSchema = Schema.Literals([\n \"fetch_failed\",\n \"publish_failed\",\n \"checkpoint_failed\",\n]);\n\n/** SDK-owned error context safe to persist and expose through status. */\nexport const ResourceErrorContextSchema = Schema.Struct({\n source: StateSourceSchema,\n operation: StateOperationSchema,\n code: StateErrorCodeSchema,\n message: Schema.String,\n});\n\n/** Stable public error summary persisted for status reporting; never a raw cause. */\nexport const ResourceErrorSchema = Schema.Struct({\n ...ResourceErrorContextSchema.fields,\n at: Schema.String,\n});\n\n/** Complete persisted state assembled from independently stored components. */\nexport const ResourceStateSchema = Schema.Struct({\n backfill: Schema.optional(BackfillStateSchema),\n changes: Schema.optional(ChangesStateSchema),\n lastError: Schema.optional(ResourceErrorSchema),\n});\n\nexport type PersistedBackfillState = Schema.Schema.Type<typeof BackfillStateSchema>;\nexport type PersistedChangesState = Schema.Schema.Type<typeof ChangesStateSchema>;\nexport type StateSource = Schema.Schema.Type<typeof StateSourceSchema>;\nexport type StateOperation = Schema.Schema.Type<typeof StateOperationSchema>;\nexport type StateErrorCode = Schema.Schema.Type<typeof StateErrorCodeSchema>;\nexport type ResourceErrorContext = Schema.Schema.Type<typeof ResourceErrorContextSchema>;\nexport type PersistedResourceError = Schema.Schema.Type<typeof ResourceErrorSchema>;\nexport type PersistedResourceState = Schema.Schema.Type<typeof ResourceStateSchema>;\n","import { DateTime, Effect, Layer, Option, Schema } from \"effect\";\nimport { KeyValueStore } from \"effect/unstable/persistence\";\n\nimport { ConnectorError } from \"../errors\";\nimport { resourceComponentKey, resourceErrorKey } from \"./keys\";\nimport {\n BackfillStateSchema,\n ChangesStateSchema,\n ResourceErrorSchema,\n type PersistedBackfillState,\n type PersistedChangesState,\n type PersistedResourceError,\n type ResourceErrorContext,\n type StateOperation,\n type StateSource,\n} from \"./schema\";\nimport { StateStore } from \"./service\";\n\nconst mapStoreError = (operation: \"read\" | \"write\" | \"clear\", resource: string) =>\n Effect.mapError(\n (cause: KeyValueStore.KeyValueStoreError | Schema.SchemaError) =>\n new ConnectorError({\n message: `Failed to ${operation} state for resource ${resource}`,\n cause,\n }),\n );\n\nconst resourceErrorContext = (\n source: StateSource,\n operation: StateOperation,\n): ResourceErrorContext => {\n const sourceName = source === \"backfill\" ? \"Backfill\" : \"Changes\";\n switch (operation) {\n case \"fetch\":\n return { source, operation, code: \"fetch_failed\", message: `${sourceName} fetch failed` };\n case \"publish\":\n return { source, operation, code: \"publish_failed\", message: `${sourceName} publish failed` };\n case \"checkpoint\":\n return {\n source,\n operation,\n code: \"checkpoint_failed\",\n message: `${sourceName} checkpoint failed`,\n };\n }\n};\n\nconst latestError = (\n backfill: Option.Option<PersistedResourceError>,\n changes: Option.Option<PersistedResourceError>,\n): Option.Option<PersistedResourceError> => {\n if (Option.isNone(backfill)) return changes;\n if (Option.isNone(changes)) return backfill;\n return backfill.value.at >= changes.value.at ? backfill : changes;\n};\n\n/**\n * Implements StateStore over any Effect KeyValueStore. `toSchemaStore` owns\n * serialization and validates persisted values when they are read and written.\n * The caller owns instance scoping; `layerSql` supplies the production prefix.\n */\nexport const layerKeyValueStore: Layer.Layer<StateStore, never, KeyValueStore.KeyValueStore> =\n Layer.effect(StateStore)(\n Effect.gen(function* () {\n const keyValueStore = yield* KeyValueStore.KeyValueStore;\n const backfillStore = KeyValueStore.toSchemaStore(keyValueStore, BackfillStateSchema);\n const changesStore = KeyValueStore.toSchemaStore(keyValueStore, ChangesStateSchema);\n const errorStore = KeyValueStore.toSchemaStore(keyValueStore, ResourceErrorSchema);\n\n const getResourceState = (resource: string) =>\n // Components use separate keys so a cursor update cannot overwrite the\n // independently managed error or backfill state.\n Effect.all(\n {\n backfill: backfillStore.get(resourceComponentKey(resource, \"backfill\")),\n changes: changesStore.get(resourceComponentKey(resource, \"changes\")),\n backfillError: errorStore.get(resourceErrorKey(resource, \"backfill\")),\n changesError: errorStore.get(resourceErrorKey(resource, \"changes\")),\n },\n { concurrency: \"unbounded\" },\n ).pipe(\n Effect.map(({ backfill, changes, backfillError, changesError }) => {\n const lastError = latestError(backfillError, changesError);\n if (Option.isNone(backfill) && Option.isNone(changes) && Option.isNone(lastError)) {\n return undefined;\n }\n return {\n ...(Option.isSome(backfill) ? { backfill: backfill.value } : {}),\n ...(Option.isSome(changes) ? { changes: changes.value } : {}),\n ...(Option.isSome(lastError) ? { lastError: lastError.value } : {}),\n };\n }),\n mapStoreError(\"read\", resource),\n );\n\n const setBackfillState = (resource: string, state: PersistedBackfillState) =>\n backfillStore\n .set(resourceComponentKey(resource, \"backfill\"), state)\n .pipe(mapStoreError(\"write\", resource));\n\n const setChangesState = (resource: string, state: PersistedChangesState) =>\n changesStore\n .set(resourceComponentKey(resource, \"changes\"), state)\n .pipe(mapStoreError(\"write\", resource));\n\n const setResourceError = (resource: string, source: StateSource, operation: StateOperation) =>\n DateTime.now.pipe(\n Effect.map(DateTime.formatIso),\n Effect.flatMap((at) => {\n const error = resourceErrorContext(source, operation);\n return errorStore.set(resourceErrorKey(resource, source), {\n ...error,\n at,\n });\n }),\n mapStoreError(\"write\", resource),\n );\n\n const clearResourceError = (resource: string, source: StateSource) =>\n errorStore\n .remove(resourceErrorKey(resource, source))\n .pipe(mapStoreError(\"clear\", resource));\n\n return StateStore.of({\n getResourceState,\n setBackfillState,\n setChangesState,\n setResourceError,\n clearResourceError,\n });\n }),\n );\n","import { Layer } from \"effect\";\nimport { KeyValueStore } from \"effect/unstable/persistence\";\n\nimport type { StateStore } from \"./service\";\n\nimport { layerKeyValueStore } from \"./layer-key-value-store\";\n\n/** Process-local state for tests and explicit local sandbox commands. */\nexport const layerMemory: Layer.Layer<StateStore> = layerKeyValueStore.pipe(\n Layer.provide(KeyValueStore.layerMemory),\n);\n","import { Config, Effect, Layer } from \"effect\";\nimport { KeyValueStore } from \"effect/unstable/persistence\";\nimport { SqlClient } from \"effect/unstable/sql\";\n\nimport type { StateStore } from \"./service\";\n\nimport { PlatformRuntimeDefault, PlatformRuntimeKey } from \"../runtime-config/constants\";\nimport { connectorInstanceKeyPrefix } from \"./keys\";\nimport { layerKeyValueStore } from \"./layer-key-value-store\";\n\nconst scopedKeyValueStore = (connectorInstanceId: string) =>\n Layer.effect(KeyValueStore.KeyValueStore)(\n KeyValueStore.KeyValueStore.pipe(\n Effect.map(KeyValueStore.prefix(connectorInstanceKeyPrefix(connectorInstanceId))),\n ),\n );\n\n/**\n * Shared SQL state using the immutable connector instance ID supplied by\n * platform-owned Effect configuration. The table defaults to\n * `_airfoil_connectors_state` and can be overridden with `AIRFOIL_STATE_TABLE`.\n * Effect's SQL KeyValueStore owns the table, while `KeyValueStore.prefix`\n * provides application-level isolation between connector instances in that\n * table. The resulting layer exposes only StateStore, never the unscoped\n * KeyValueStore.\n *\n * @example\n * ```ts\n * const StateStoreLive = layerSql().pipe(Layer.provide(PostgresLive));\n * ```\n */\nexport const layerSql = (): Layer.Layer<StateStore, Config.ConfigError, SqlClient.SqlClient> =>\n Layer.unwrap(\n Config.all({\n connectorInstanceId: Config.nonEmptyString(PlatformRuntimeKey.connectorInstanceId),\n table: Config.nonEmptyString(PlatformRuntimeKey.stateTable).pipe(\n Config.withDefault(PlatformRuntimeDefault.stateTable),\n ),\n }).pipe(\n Effect.map(({ connectorInstanceId, table }) =>\n layerKeyValueStore.pipe(\n Layer.provide(\n scopedKeyValueStore(connectorInstanceId).pipe(\n Layer.provide(KeyValueStore.layerSql({ table })),\n ),\n ),\n ),\n ),\n ),\n );\n","export { StateStore, type StateStoreService } from \"./service\";\nexport { layerMemory } from \"./layer-memory\";\nexport { layerSql } from \"./layer-sql\";\n"],"mappings":"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"}
|
package/dist/status/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import "../index-
|
|
2
|
-
import "../types-
|
|
3
|
-
import "../index-
|
|
4
|
-
import { a as ResourceStatusSchema, c as StatusUnavailableResponse, d as SyncStateSchema, f as statusRoute, i as ResourceStatus, l as StatusUnavailableResponseSchema, n as ResourceError, o as StatusResponse, r as ResourceErrorSchema, s as StatusResponseSchema, u as SyncState } from "../index-
|
|
1
|
+
import "../index-DKoRwyFn.js";
|
|
2
|
+
import "../types-jVD5Ys43.js";
|
|
3
|
+
import "../index-DYpEMJf-.js";
|
|
4
|
+
import { a as ResourceStatusSchema, c as StatusUnavailableResponse, d as SyncStateSchema, f as statusRoute, i as ResourceStatus, l as StatusUnavailableResponseSchema, n as ResourceError, o as StatusResponse, r as ResourceErrorSchema, s as StatusResponseSchema, u as SyncState } from "../index-VpHaZXZw.js";
|
|
5
5
|
export { ResourceError, ResourceErrorSchema, ResourceStatus, ResourceStatusSchema, StatusResponse, StatusResponseSchema, StatusUnavailableResponse, StatusUnavailableResponseSchema, SyncState, SyncStateSchema, statusRoute };
|
package/dist/status/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import "../errors-DHWylqRZ.js";
|
|
2
|
-
import "../constants-
|
|
3
|
-
import "../state-store-
|
|
4
|
-
import { a as StatusUnavailableResponseSchema, i as StatusResponseSchema, n as ResourceErrorSchema, o as SyncStateSchema, r as ResourceStatusSchema, s as statusRoute } from "../status-
|
|
2
|
+
import "../constants-D113Ut1x.js";
|
|
3
|
+
import "../state-store-BdYulQOY.js";
|
|
4
|
+
import { a as StatusUnavailableResponseSchema, i as StatusResponseSchema, n as ResourceErrorSchema, o as SyncStateSchema, r as ResourceStatusSchema, s as statusRoute } from "../status-BTTd7AUR.js";
|
|
5
5
|
|
|
6
6
|
export { ResourceErrorSchema, ResourceStatusSchema, StatusResponseSchema, StatusUnavailableResponseSchema, SyncStateSchema, statusRoute };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { t as __export } from "./chunk-Bp6m_JJh.js";
|
|
2
|
-
import { a as ResourceStateSchema, i as ResourceErrorSchema, o as StateStore } from "./state-store-
|
|
2
|
+
import { a as ResourceStateSchema, i as ResourceErrorSchema, o as StateStore } from "./state-store-BdYulQOY.js";
|
|
3
3
|
import { Cause, Effect, Schema } from "effect";
|
|
4
4
|
import { HttpRouter, HttpServerResponse } from "effect/unstable/http";
|
|
5
5
|
|
|
@@ -81,4 +81,4 @@ var status_exports = /* @__PURE__ */ __export({
|
|
|
81
81
|
|
|
82
82
|
//#endregion
|
|
83
83
|
export { StatusUnavailableResponseSchema as a, deriveSyncState as c, StatusResponseSchema as i, normalizeCursor as l, ResourceErrorSchema$1 as n, SyncStateSchema as o, ResourceStatusSchema as r, statusRoute as s, status_exports as t };
|
|
84
|
-
//# sourceMappingURL=status-
|
|
84
|
+
//# sourceMappingURL=status-BTTd7AUR.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"status-
|
|
1
|
+
{"version":3,"file":"status-BTTd7AUR.js","names":["ResourceErrorSchema","StateResourceErrorSchema"],"sources":["../src/state-store/state.ts","../src/status/route.ts","../src/status/types.ts","../src/status/index.ts"],"sourcesContent":["import type { Cursor, ResourceDefinition, ResourceState, SyncState } from \"../core/types\";\n\n/** Normalizes a cursor value to its persisted/wire representation (never a Date). */\nexport const normalizeCursor = (value: Cursor.Value): string | number =>\n value instanceof Date ? value.toISOString() : value;\n\n/** Derives connector-facing sync state from durable state and resource capabilities. */\nexport const deriveSyncState = (\n resource: ResourceDefinition,\n state: ResourceState | undefined,\n): SyncState => {\n if (state?.lastError) return \"error\";\n if (!resource.backfill && !resource.changes) return \"live\";\n if (!state) return \"pending\";\n if (resource.backfill && (!state.backfill || !state.backfill.completed)) return \"backfilling\";\n return \"live\";\n};\n","import { Cause, Effect } from \"effect\";\nimport { HttpRouter, HttpServerResponse } from \"effect/unstable/http\";\n\nimport type { ConnectorDefinition } from \"../core/types\";\nimport type { ResourceStatus, StatusUnavailableResponse } from \"./types\";\n\nimport { StateStore } from \"../state-store\";\nimport { deriveSyncState, normalizeCursor } from \"../state-store/state\";\n\nexport const statusRoute = (\n connector: ConnectorDefinition,\n path: HttpRouter.PathInput = \"/status\",\n) =>\n HttpRouter.add(\n \"GET\",\n path,\n Effect.gen(function* () {\n const store = yield* StateStore;\n\n const resources = yield* Effect.forEach(\n connector.resources,\n (resource) =>\n store.getResourceState(resource.name).pipe(\n Effect.map(\n (state): ResourceStatus => ({\n name: resource.name,\n state: deriveSyncState(resource, state),\n ...(state?.backfill\n ? {\n backfill: {\n completed: state.backfill.completed,\n cutoff: normalizeCursor(state.backfill.cutoff),\n ...(state.backfill.pageCursor !== undefined\n ? { pageCursor: normalizeCursor(state.backfill.pageCursor) }\n : {}),\n ...(state.backfill.lastSuccessAt !== undefined\n ? { lastSuccessAt: state.backfill.lastSuccessAt }\n : {}),\n },\n }\n : {}),\n ...(state?.changes\n ? {\n changes: {\n cursor: normalizeCursor(state.changes.cursor),\n ...(state.changes.lastSuccessAt !== undefined\n ? { lastSuccessAt: state.changes.lastSuccessAt }\n : {}),\n },\n }\n : {}),\n ...(state?.lastError ? { lastError: state.lastError } : {}),\n }),\n ),\n ),\n { concurrency: \"unbounded\" },\n );\n\n return HttpServerResponse.jsonUnsafe({ connector: connector.name, resources });\n }).pipe(\n Effect.catchCause((cause) =>\n Effect.logWarning(`Status route failed: ${Cause.pretty(cause)}`).pipe(\n Effect.andThen(\n Effect.succeed(\n HttpServerResponse.jsonUnsafe(\n {\n ok: false,\n error: \"Failed to compute status\",\n } satisfies StatusUnavailableResponse,\n { status: 500 },\n ),\n ),\n ),\n ),\n ),\n ),\n );\n","import { Schema } from \"effect\";\n\nimport {\n ResourceErrorSchema as StateResourceErrorSchema,\n ResourceStateSchema,\n} from \"../state-store/schema\";\n\nexport const SyncStateSchema = Schema.Literals([\"pending\", \"backfilling\", \"live\", \"error\"]);\nexport type SyncState = Schema.Schema.Type<typeof SyncStateSchema>;\n\n/** Safe source failure returned by the status endpoint. */\nexport const ResourceErrorSchema = StateResourceErrorSchema;\nexport type ResourceError = Schema.Schema.Type<typeof ResourceErrorSchema>;\n\nexport const ResourceStatusSchema = Schema.Struct({\n name: Schema.String,\n state: SyncStateSchema,\n ...ResourceStateSchema.fields,\n});\nexport type ResourceStatus = Schema.Schema.Type<typeof ResourceStatusSchema>;\n\nexport const StatusResponseSchema = Schema.Struct({\n connector: Schema.String,\n resources: Schema.Array(ResourceStatusSchema),\n});\nexport type StatusResponse = Schema.Schema.Type<typeof StatusResponseSchema>;\n\n/** Returned when durable resource status cannot be read. */\nexport const StatusUnavailableResponseSchema = Schema.Struct({\n ok: Schema.Literal(false),\n error: Schema.String,\n});\nexport type StatusUnavailableResponse = Schema.Schema.Type<typeof StatusUnavailableResponseSchema>;\n","export * from \"./route\";\nexport * from \"./types\";\n"],"mappings":";;;;;;;AAGA,MAAa,mBAAmB,UAC9B,iBAAiB,OAAO,MAAM,aAAa,GAAG;;AAGhD,MAAa,mBACX,UACA,UACc;AACd,KAAI,OAAO,UAAW,QAAO;AAC7B,KAAI,CAAC,SAAS,YAAY,CAAC,SAAS,QAAS,QAAO;AACpD,KAAI,CAAC,MAAO,QAAO;AACnB,KAAI,SAAS,aAAa,CAAC,MAAM,YAAY,CAAC,MAAM,SAAS,WAAY,QAAO;AAChF,QAAO;;;;;ACNT,MAAa,eACX,WACA,OAA6B,cAE7B,WAAW,IACT,OACA,MACA,OAAO,IAAI,aAAa;CACtB,MAAM,QAAQ,OAAO;CAErB,MAAM,YAAY,OAAO,OAAO,QAC9B,UAAU,YACT,aACC,MAAM,iBAAiB,SAAS,KAAK,CAAC,KACpC,OAAO,KACJ,WAA2B;EAC1B,MAAM,SAAS;EACf,OAAO,gBAAgB,UAAU,MAAM;EACvC,GAAI,OAAO,WACP,EACE,UAAU;GACR,WAAW,MAAM,SAAS;GAC1B,QAAQ,gBAAgB,MAAM,SAAS,OAAO;GAC9C,GAAI,MAAM,SAAS,eAAe,SAC9B,EAAE,YAAY,gBAAgB,MAAM,SAAS,WAAW,EAAE,GAC1D,EAAE;GACN,GAAI,MAAM,SAAS,kBAAkB,SACjC,EAAE,eAAe,MAAM,SAAS,eAAe,GAC/C,EAAE;GACP,EACF,GACD,EAAE;EACN,GAAI,OAAO,UACP,EACE,SAAS;GACP,QAAQ,gBAAgB,MAAM,QAAQ,OAAO;GAC7C,GAAI,MAAM,QAAQ,kBAAkB,SAChC,EAAE,eAAe,MAAM,QAAQ,eAAe,GAC9C,EAAE;GACP,EACF,GACD,EAAE;EACN,GAAI,OAAO,YAAY,EAAE,WAAW,MAAM,WAAW,GAAG,EAAE;EAC3D,EACF,CACF,EACH,EAAE,aAAa,aAAa,CAC7B;AAED,QAAO,mBAAmB,WAAW;EAAE,WAAW,UAAU;EAAM;EAAW,CAAC;EAC9E,CAAC,KACD,OAAO,YAAY,UACjB,OAAO,WAAW,wBAAwB,MAAM,OAAO,MAAM,GAAG,CAAC,KAC/D,OAAO,QACL,OAAO,QACL,mBAAmB,WACjB;CACE,IAAI;CACJ,OAAO;CACR,EACD,EAAE,QAAQ,KAAK,CAChB,CACF,CACF,CACF,CACF,CACF,CACF;;;;ACrEH,MAAa,kBAAkB,OAAO,SAAS;CAAC;CAAW;CAAe;CAAQ;CAAQ,CAAC;;AAI3F,MAAaA,wBAAsBC;AAGnC,MAAa,uBAAuB,OAAO,OAAO;CAChD,MAAM,OAAO;CACb,OAAO;CACP,GAAG,oBAAoB;CACxB,CAAC;AAGF,MAAa,uBAAuB,OAAO,OAAO;CAChD,WAAW,OAAO;CAClB,WAAW,OAAO,MAAM,qBAAqB;CAC9C,CAAC;;AAIF,MAAa,kCAAkC,OAAO,OAAO;CAC3D,IAAI,OAAO,QAAQ,MAAM;CACzB,OAAO,OAAO;CACf,CAAC"}
|