@voltro/plugin-duckdb 0.84.0 → 0.85.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/dist/index.js CHANGED
@@ -1,36 +1,36 @@
1
- import { Effect as e, Option as t } from "effect";
2
- import { AnalyticsError as n } from "@voltro/runtime";
1
+ import { Effect as e } from "effect";
2
+ import { AnalyticsError as t } from "@voltro/runtime";
3
3
  //#region src/sink.ts
4
- var r = "duckdb", i = "voltro_events", a = null, o = () => a === null ? e.tryPromise({
4
+ var n = "duckdb", r = "voltro_events", i = null, a = () => i === null ? e.tryPromise({
5
5
  try: async () => {
6
6
  let e = await import("@duckdb/node-api");
7
- return a = e, e;
7
+ return i = e, e;
8
8
  },
9
- catch: (e) => new n({
10
- provider: r,
9
+ catch: (e) => new t({
10
+ provider: n,
11
11
  operation: "loadNativeModule",
12
12
  cause: "@duckdb/node-api could not be loaded — the native binary for this platform may be missing (it is an optionalDependency). " + (e instanceof Error ? `${e.name}: ${e.message}` : String(e))
13
13
  })
14
- }) : e.succeed(a), s = (e) => new a.DuckDBTimestampValue(BigInt(e.getTime()) * 1000n), c = (e) => `voltro_mirror_${e}`, l = /^[A-Za-z_][A-Za-z0-9_]*$/, u = (e, t, i) => {
15
- if (!l.test(t)) throw new n({
16
- provider: r,
14
+ }) : e.succeed(i), o = (e) => new i.DuckDBTimestampValue(BigInt(e.getTime()) * 1000n), s = (e) => `voltro_mirror_${e}`, c = /^[A-Za-z_][A-Za-z0-9_]*$/, l = (e, r, i) => {
15
+ if (!c.test(r)) throw new t({
16
+ provider: n,
17
17
  operation: e,
18
- cause: `invalid ${i} ${JSON.stringify(t)} — must match ${l.source} (letters, digits, underscore; no dots/quotes/spaces)`
18
+ cause: `invalid ${i} ${JSON.stringify(r)} — must match ${c.source} (letters, digits, underscore; no dots/quotes/spaces)`
19
19
  });
20
- return t;
21
- }, d = /['\\]/, f = (e) => {
20
+ return r;
21
+ }, u = /['\\]/, d = (e) => {
22
22
  for (let t = 0; t < e.length; t++) if (e.charCodeAt(t) < 32) return !0;
23
23
  return !1;
24
- }, p = (e, t) => {
25
- if (d.test(t) || f(t)) throw new n({
26
- provider: r,
24
+ }, f = (e, r) => {
25
+ if (u.test(r) || d(r)) throw new t({
26
+ provider: n,
27
27
  operation: e,
28
- cause: `invalid property key ${JSON.stringify(t)} — must not contain quotes, backslashes, or control characters`
28
+ cause: `invalid property key ${JSON.stringify(r)} — must not contain quotes, backslashes, or control characters`
29
29
  });
30
- return t;
31
- }, m = `
30
+ return r;
31
+ }, p = `
32
32
  CREATE SEQUENCE IF NOT EXISTS voltro_events_id_seq;
33
- CREATE TABLE IF NOT EXISTS ${i} (
33
+ CREATE TABLE IF NOT EXISTS ${r} (
34
34
  id BIGINT PRIMARY KEY DEFAULT nextval('voltro_events_id_seq'),
35
35
  name VARCHAR NOT NULL,
36
36
  subject_id VARCHAR,
@@ -38,79 +38,79 @@ var r = "duckdb", i = "voltro_events", a = null, o = () => a === null ? e.tryPro
38
38
  properties JSON NOT NULL DEFAULT '{}',
39
39
  occurred_at TIMESTAMP NOT NULL DEFAULT now()
40
40
  );
41
- `.trim(), h = (i) => (a) => a.pipe(e.catchSomeDefect((r) => r instanceof n ? t.some(e.fail(r)) : t.none()), e.mapError((e) => e instanceof n ? e : new n({
42
- provider: r,
43
- operation: i,
41
+ `.trim(), m = (r) => (i) => i.pipe(e.catchDefect((n) => n instanceof t ? e.fail(n) : e.die(n)), e.mapError((e) => e instanceof t ? e : new t({
42
+ provider: n,
43
+ operation: r,
44
44
  cause: e instanceof Error ? `${e.name}: ${e.message}` : String(e)
45
- }))), g = (t) => t.conn === null ? e.fail(new n({
46
- provider: r,
45
+ }))), h = (r) => r.conn === null ? e.fail(new t({
46
+ provider: n,
47
47
  operation: "ensureConn",
48
48
  cause: "connection not initialized — spec.initialize must run before queries"
49
- })) : e.succeed(t.conn), _ = (e, t) => `date_trunc('${t}', ${e})`, v = (e, t) => `json_extract_string(properties, '$.${p(e, t)}')`, y = (e, t) => `TRY_CAST(json_extract_string(properties, '$.${p(e, t)}') AS DOUBLE)`, b = (e, t) => t === "count" ? "COUNT(*)" : t === "unique" ? "COUNT(DISTINCT subject_id)" : "sum" in t ? `COALESCE(SUM(${y(e, t.sum)}), 0)` : `AVG(${y(e, t.avg)})`, x = (e, t) => t === "count" ? "COUNT(*)" : `COALESCE(SUM(${y(e, t.sum)}), 0)`, S = (e, t, n, r, i) => {
50
- let { VARCHAR: o, TIMESTAMP: c } = a, l = [], u = [], d = [];
51
- if (d.push("name = ?"), l.push(t), u.push(o), d.push("occurred_at >= ?"), l.push(s(n.from)), u.push(c), n.to !== void 0 && (d.push("occurred_at < ?"), l.push(s(n.to)), u.push(c)), i != null && (d.push("tenant_id = ?"), l.push(i), u.push(o)), r !== void 0) for (let [t, n] of Object.entries(r)) d.push(`${v(e, t)} = ?`), l.push(String(n)), u.push(o);
49
+ })) : e.succeed(r.conn), g = (e, t) => `date_trunc('${t}', ${e})`, _ = (e, t) => `json_extract_string(properties, '$.${f(e, t)}')`, v = (e, t) => `TRY_CAST(json_extract_string(properties, '$.${f(e, t)}') AS DOUBLE)`, y = (e, t) => t === "count" ? "COUNT(*)" : t === "unique" ? "COUNT(DISTINCT subject_id)" : "sum" in t ? `COALESCE(SUM(${v(e, t.sum)}), 0)` : `AVG(${v(e, t.avg)})`, b = (e, t) => t === "count" ? "COUNT(*)" : `COALESCE(SUM(${v(e, t.sum)}), 0)`, x = (e, t, n, r, a) => {
50
+ let { VARCHAR: s, TIMESTAMP: c } = i, l = [], u = [], d = [];
51
+ if (d.push("name = ?"), l.push(t), u.push(s), d.push("occurred_at >= ?"), l.push(o(n.from)), u.push(c), n.to !== void 0 && (d.push("occurred_at < ?"), l.push(o(n.to)), u.push(c)), a != null && (d.push("tenant_id = ?"), l.push(a), u.push(s)), r !== void 0) for (let [t, n] of Object.entries(r)) d.push(`${_(e, t)} = ?`), l.push(String(n)), u.push(s);
52
52
  return {
53
53
  sql: d.join(" AND "),
54
54
  params: l,
55
55
  types: u
56
56
  };
57
- }, C = (e, t, n, r, i) => [
57
+ }, S = (e, t, n, r, a) => [
58
58
  `INSERT INTO ${e} (id, data, version, is_deleted) VALUES (?, ?::JSON, ?::BIGINT, ?) ON CONFLICT (id) DO UPDATE SET data = EXCLUDED.data, version = EXCLUDED.version, is_deleted = EXCLUDED.is_deleted WHERE EXCLUDED.version > ${e}.version`,
59
59
  [
60
60
  t,
61
61
  n,
62
62
  String(r),
63
- i
63
+ a
64
64
  ],
65
65
  [
66
- a.VARCHAR,
67
- a.VARCHAR,
68
- a.VARCHAR,
69
- a.BOOLEAN
66
+ i.VARCHAR,
67
+ i.VARCHAR,
68
+ i.VARCHAR,
69
+ i.BOOLEAN
70
70
  ]
71
- ], w = (t, n, r) => {
72
- let i = /* @__PURE__ */ new Set(), o = (n, r) => e.gen(function* () {
73
- if (i.has(r)) return;
74
- u(n, r, "mirror table name");
75
- let a = yield* g(t), o = c(r);
71
+ ], C = (t, n, r) => {
72
+ let a = /* @__PURE__ */ new Set(), o = (n, r) => e.gen(function* () {
73
+ if (a.has(r)) return;
74
+ l(n, r, "mirror table name");
75
+ let i = yield* h(t), o = s(r);
76
76
  yield* e.tryPromise({
77
- try: () => a.run(`CREATE TABLE IF NOT EXISTS ${o} (id VARCHAR PRIMARY KEY, data JSON NOT NULL, version BIGINT NOT NULL DEFAULT 0, is_deleted BOOLEAN NOT NULL DEFAULT false)`),
77
+ try: () => i.run(`CREATE TABLE IF NOT EXISTS ${o} (id VARCHAR PRIMARY KEY, data JSON NOT NULL, version BIGINT NOT NULL DEFAULT 0, is_deleted BOOLEAN NOT NULL DEFAULT false)`),
78
78
  catch: (e) => e
79
- }), i.add(r);
79
+ }), a.add(r);
80
80
  });
81
81
  return {
82
82
  tables: n,
83
83
  primaryKey: r,
84
84
  upsert: ({ table: n, row: i, version: a }) => e.gen(function* () {
85
85
  yield* o("mirror.upsert", n);
86
- let s = yield* g(t), l = c(n), u = String(i[r]);
86
+ let c = yield* h(t), l = s(n), u = String(i[r]);
87
87
  yield* e.tryPromise({
88
- try: () => s.run(...C(l, u, JSON.stringify(i), a, !1)),
88
+ try: () => c.run(...S(l, u, JSON.stringify(i), a, !1)),
89
89
  catch: (e) => e
90
90
  });
91
- }).pipe(h("mirror.upsert")),
91
+ }).pipe(m("mirror.upsert")),
92
92
  remove: ({ table: n, primaryKeyValue: r, version: i }) => e.gen(function* () {
93
93
  yield* o("mirror.remove", n);
94
- let a = yield* g(t), s = c(n), l = String(r);
94
+ let a = yield* h(t), c = s(n), l = String(r);
95
95
  yield* e.tryPromise({
96
- try: () => a.run(...C(s, l, "{}", i, !0)),
96
+ try: () => a.run(...S(c, l, "{}", i, !0)),
97
97
  catch: (e) => e
98
98
  });
99
- }).pipe(h("mirror.remove")),
99
+ }).pipe(m("mirror.remove")),
100
100
  maxVersion: ({ table: n, primaryKeyValue: r }) => e.gen(function* () {
101
101
  yield* o("mirror.maxVersion", n);
102
- let i = yield* g(t), s = c(n), l = (yield* e.tryPromise({
103
- try: () => i.runAndReadAll(`SELECT version FROM ${s} WHERE id = ?`, [String(r)], [a.VARCHAR]),
102
+ let a = yield* h(t), c = s(n), l = (yield* e.tryPromise({
103
+ try: () => a.runAndReadAll(`SELECT version FROM ${c} WHERE id = ?`, [String(r)], [i.VARCHAR]),
104
104
  catch: (e) => e
105
105
  })).getRowObjectsJS()[0]?.version;
106
106
  return l == null ? null : Number(l);
107
- }).pipe(h("mirror.maxVersion"))
107
+ }).pipe(m("mirror.maxVersion"))
108
108
  };
109
- }, T = (t, n) => {
110
- let o = n.mirrorTables ?? [], c = o.length > 0 ? w(t, o, n.mirrorPrimaryKey ?? "id") : void 0;
109
+ }, w = (t, a) => {
110
+ let s = a.mirrorTables ?? [], c = s.length > 0 ? C(t, s, a.mirrorPrimaryKey ?? "id") : void 0;
111
111
  return {
112
112
  info: {
113
- provider: r,
113
+ provider: n,
114
114
  capabilities: {
115
115
  track: !0,
116
116
  aggregate: !0,
@@ -119,107 +119,107 @@ var r = "duckdb", i = "voltro_events", a = null, o = () => a === null ? e.tryPro
119
119
  mirror: c !== void 0
120
120
  },
121
121
  detail: {
122
- path: n.path ?? ":memory:",
123
- ...o.length > 0 ? { mirrorTables: o } : {}
122
+ path: a.path ?? ":memory:",
123
+ ...s.length > 0 ? { mirrorTables: s } : {}
124
124
  }
125
125
  },
126
126
  track: (n) => e.gen(function* () {
127
- let r = yield* g(t), { VARCHAR: o, TIMESTAMP: c } = a, l = n.occurredAt ?? /* @__PURE__ */ new Date(), u = JSON.stringify(n.properties), d = n.subjectId ?? null, f = n.tenantId ?? null;
127
+ let a = yield* h(t), { VARCHAR: s, TIMESTAMP: c } = i, l = n.occurredAt ?? /* @__PURE__ */ new Date(), u = JSON.stringify(n.properties), d = n.subjectId ?? null, f = n.tenantId ?? null;
128
128
  yield* e.tryPromise({
129
- try: () => r.run(`INSERT INTO ${i} (name, subject_id, tenant_id, properties, occurred_at) VALUES (?, ?, ?, ?::JSON, ?)`, [
129
+ try: () => a.run(`INSERT INTO ${r} (name, subject_id, tenant_id, properties, occurred_at) VALUES (?, ?, ?, ?::JSON, ?)`, [
130
130
  n.name,
131
131
  d,
132
132
  f,
133
133
  u,
134
- s(l)
134
+ o(l)
135
135
  ], [
136
- o,
137
- o,
138
- o,
139
- o,
136
+ s,
137
+ s,
138
+ s,
139
+ s,
140
140
  c
141
141
  ]),
142
142
  catch: (e) => e
143
143
  });
144
- }).pipe(h("track")),
144
+ }).pipe(m("track")),
145
145
  aggregate: (n) => e.gen(function* () {
146
- let r = yield* g(t), a = S("aggregate", n.event, n.range, n.filter, n.tenantId), o = `SELECT ${b("aggregate", n.metric)} AS value FROM ${i} WHERE ${a.sql}`, s = (yield* e.tryPromise({
147
- try: () => r.runAndReadAll(o, a.params, a.types),
146
+ let i = yield* h(t), a = x("aggregate", n.event, n.range, n.filter, n.tenantId), o = `SELECT ${y("aggregate", n.metric)} AS value FROM ${r} WHERE ${a.sql}`, s = (yield* e.tryPromise({
147
+ try: () => i.runAndReadAll(o, a.params, a.types),
148
148
  catch: (e) => e
149
149
  })).getRowObjectsJS()[0]?.value ?? 0;
150
150
  return typeof s == "number" ? s : Number(s ?? 0);
151
- }).pipe(h("aggregate")),
151
+ }).pipe(m("aggregate")),
152
152
  timeseries: (n) => e.gen(function* () {
153
- let r = yield* g(t), a = S("timeseries", n.event, n.range, n.filter, n.tenantId), o = _("occurred_at", n.bucket), s = `
154
- SELECT ${o} AS bucket_start, ${b("timeseries", n.metric)} AS value
155
- FROM ${i}
153
+ let i = yield* h(t), a = x("timeseries", n.event, n.range, n.filter, n.tenantId), o = g("occurred_at", n.bucket), s = `
154
+ SELECT ${o} AS bucket_start, ${y("timeseries", n.metric)} AS value
155
+ FROM ${r}
156
156
  WHERE ${a.sql}
157
157
  GROUP BY ${o}
158
158
  ORDER BY ${o} ASC
159
159
  `;
160
160
  return (yield* e.tryPromise({
161
- try: () => r.runAndReadAll(s, a.params, a.types),
161
+ try: () => i.runAndReadAll(s, a.params, a.types),
162
162
  catch: (e) => e
163
163
  })).getRowObjectsJS().map((e) => ({
164
164
  bucketStart: e.bucket_start instanceof Date ? e.bucket_start : new Date(e.bucket_start),
165
165
  value: typeof e.value == "number" ? e.value : Number(e.value ?? 0)
166
166
  }));
167
- }).pipe(h("timeseries")),
167
+ }).pipe(m("timeseries")),
168
168
  topN: (n) => e.gen(function* () {
169
- let r = yield* g(t), a = S("topN", n.event, n.range, n.filter, n.tenantId), o = Math.max(1, Math.floor(n.n)), s = v("topN", n.groupBy), c = `
170
- SELECT ${s} AS key, ${x("topN", n.metric)} AS value
171
- FROM ${i}
169
+ let i = yield* h(t), a = x("topN", n.event, n.range, n.filter, n.tenantId), o = Math.max(1, Math.floor(n.n)), s = _("topN", n.groupBy), c = `
170
+ SELECT ${s} AS key, ${b("topN", n.metric)} AS value
171
+ FROM ${r}
172
172
  WHERE ${a.sql}
173
173
  GROUP BY ${s}
174
174
  ORDER BY value DESC
175
175
  LIMIT ${o}
176
176
  `;
177
177
  return (yield* e.tryPromise({
178
- try: () => r.runAndReadAll(c, a.params, a.types),
178
+ try: () => i.runAndReadAll(c, a.params, a.types),
179
179
  catch: (e) => e
180
180
  })).getRowObjectsJS().filter((e) => e.key !== null).map((e) => ({
181
181
  key: e.key,
182
182
  value: typeof e.value == "number" ? e.value : Number(e.value ?? 0)
183
183
  }));
184
- }).pipe(h("topN")),
184
+ }).pipe(m("topN")),
185
185
  ...c === void 0 ? {} : { mirror: c }
186
186
  };
187
- }, E = (t, i) => e.gen(function* () {
188
- if (t.conn !== null) return;
189
- let a = yield* o(), s = yield* e.tryPromise({
187
+ }, T = (r, i) => e.gen(function* () {
188
+ if (r.conn !== null) return;
189
+ let o = yield* a(), s = yield* e.tryPromise({
190
190
  try: async () => {
191
- let e = await a.DuckDBInstance.create(i.path ?? ":memory:"), t = await e.connect();
192
- return await t.run(m), {
191
+ let e = await o.DuckDBInstance.create(i.path ?? ":memory:"), t = await e.connect();
192
+ return await t.run(p), {
193
193
  instance: e,
194
194
  connection: t
195
195
  };
196
196
  },
197
- catch: (e) => new n({
198
- provider: r,
197
+ catch: (e) => new t({
198
+ provider: n,
199
199
  operation: "initialize",
200
200
  cause: e instanceof Error ? `${e.name}: ${e.message}` : String(e)
201
201
  })
202
202
  });
203
- t.instance = s.instance, t.conn = s.connection;
204
- }), D = (t) => e.gen(function* () {
205
- let { conn: n, instance: i } = t;
206
- t.conn = null, t.instance = null, (n !== null || i !== null) && (yield* e.try(() => {
207
- n?.closeSync(), i?.closeSync();
208
- }).pipe(e.catchAll((t) => e.logWarning("duckdb analytics sink teardown failed", {
209
- provider: r,
203
+ r.instance = s.instance, r.conn = s.connection;
204
+ }), E = (t) => e.gen(function* () {
205
+ let { conn: r, instance: i } = t;
206
+ t.conn = null, t.instance = null, (r !== null || i !== null) && (yield* e.try(() => {
207
+ r?.closeSync(), i?.closeSync();
208
+ }).pipe(e.catch((t) => e.logWarning("duckdb analytics sink teardown failed", {
209
+ provider: n,
210
210
  err: t
211
211
  }))));
212
- }), O = (e = {}) => {
212
+ }), D = (e = {}) => {
213
213
  let t = {
214
214
  conn: null,
215
215
  instance: null
216
216
  };
217
217
  return {
218
218
  kind: "duckdb",
219
- build: (n) => T(t, e),
220
- initialize: () => E(t, e),
221
- dispose: () => D(t)
219
+ build: (n) => w(t, e),
220
+ initialize: () => T(t, e),
221
+ dispose: () => E(t)
222
222
  };
223
223
  };
224
224
  //#endregion
225
- export { O as duckdbAnalytics };
225
+ export { D as duckdbAnalytics };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@voltro/plugin-duckdb",
3
- "version": "0.84.0",
3
+ "version": "0.85.0",
4
4
  "description": "Embedded DuckDB AnalyticsSink. Runs DuckDB in-process for real OLAP performance without an external service. Events land in a DuckDB column-store table; aggregate / timeseries / topN queries execute vectorized.",
5
5
  "keywords": [
6
6
  "voltro",
@@ -33,13 +33,13 @@
33
33
  "node": ">=24.0.0"
34
34
  },
35
35
  "dependencies": {
36
- "@voltro/runtime": "0.84.0"
36
+ "@voltro/runtime": "0.85.0"
37
37
  },
38
38
  "optionalDependencies": {
39
39
  "@duckdb/node-api": "1.5.5-r.5"
40
40
  },
41
41
  "peerDependencies": {
42
- "effect": "^3.22.2"
42
+ "effect": "4.0.1"
43
43
  },
44
44
  "publishConfig": {
45
45
  "access": "public"