@voltro/plugin-tinybird 0.1.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.
@@ -0,0 +1,41 @@
1
+ import { AggregateQuery } from '@voltro/runtime';
2
+ import { AnalyticsBucket } from '@voltro/runtime';
3
+ import { AnalyticsEvent } from '@voltro/runtime';
4
+ import { AnalyticsSinkSpec } from '@voltro/runtime';
5
+ import { AnalyticsTopEntry } from '@voltro/runtime';
6
+ import { TimeseriesQuery } from '@voltro/runtime';
7
+ import { TopNQuery } from '@voltro/runtime';
8
+
9
+ export { AggregateQuery }
10
+
11
+ export { AnalyticsBucket }
12
+
13
+ export { AnalyticsEvent }
14
+
15
+ export { AnalyticsTopEntry }
16
+
17
+ export { TimeseriesQuery }
18
+
19
+ export declare const tinybirdAnalytics: (options: TinybirdAnalyticsOptions) => AnalyticsSinkSpec;
20
+
21
+ export declare interface TinybirdAnalyticsOptions {
22
+ readonly token: string;
23
+ /** Tinybird region: 'eu' | 'us-east' | 'us-west' | 'asia-southeast' / custom URL. */
24
+ readonly region?: string;
25
+ /** Custom API base URL (overrides `region`). Useful for self-hosted Tinybird or
26
+ * preview environments. */
27
+ readonly apiUrl?: string;
28
+ /** Datasource name. The plugin POSTs events to /v0/events?name=<datasource>.
29
+ * Default: 'voltro_events'. */
30
+ readonly datasource?: string;
31
+ /** Override pipe names. Defaults to the three canonical pipes. */
32
+ readonly pipes?: {
33
+ readonly aggregate?: string;
34
+ readonly timeseries?: string;
35
+ readonly topN?: string;
36
+ };
37
+ }
38
+
39
+ export { TopNQuery }
40
+
41
+ export { }
package/dist/index.js ADDED
@@ -0,0 +1,157 @@
1
+ import { Effect as e } from "effect";
2
+ import { AnalyticsError as t } from "@voltro/runtime";
3
+ //#region src/sink.ts
4
+ var n = "tinybird", r = (r) => e.mapError((e) => new t({
5
+ provider: n,
6
+ operation: r,
7
+ cause: e instanceof Error ? `${e.name}: ${e.message}` : String(e)
8
+ })), i = /^[A-Za-z_][A-Za-z0-9_]*$/, a = (e, r) => {
9
+ if (!i.test(e)) throw new t({
10
+ provider: n,
11
+ operation: "config",
12
+ cause: `invalid ${r} ${JSON.stringify(e)} — must match ${i.source} (letters, digits, underscore; no dots/slashes/quotes/spaces)`
13
+ });
14
+ return e;
15
+ }, o = /['\\]/, s = (e) => {
16
+ for (let t = 0; t < e.length; t++) if (e.charCodeAt(t) < 32) return !0;
17
+ return !1;
18
+ }, c = (e) => {
19
+ if (o.test(e) || s(e)) throw Error(`tinybird: invalid property key ${JSON.stringify(e)} — must not contain quotes, backslashes, or control characters`);
20
+ return e;
21
+ }, l = (e, t) => {
22
+ let n = [];
23
+ if (t != null && n.push(`tenant_id = '${t.replace(/'/g, "''")}'`), e) for (let [t, r] of Object.entries(e)) {
24
+ let e = String(r).replace(/'/g, "''");
25
+ n.push(`JSONExtractString(properties, '${c(t)}') = '${e}'`);
26
+ }
27
+ return n.length === 0 ? "" : ` AND ${n.join(" AND ")}`;
28
+ }, u = (e) => e === "count" ? "count()" : e === "unique" ? "uniqExact(subject_id)" : "sum" in e ? `sum(JSONExtractFloat(properties, '${c(e.sum)}'))` : `avg(JSONExtractFloat(properties, '${c(e.avg)}'))`, d = async (e, t, n) => {
29
+ let r = new URL(`${e.apiUrl}/v0/pipes/${t}.json`);
30
+ for (let [e, t] of Object.entries(n)) r.searchParams.set(e, String(t));
31
+ let i = await fetch(r.toString(), { headers: { authorization: `Bearer ${e.token}` } });
32
+ if (!i.ok) {
33
+ let e = await i.text().catch(() => "");
34
+ throw Error(`tinybird pipe ${t} returned HTTP ${i.status}: ${e}`);
35
+ }
36
+ return i.json();
37
+ }, f = (t) => ({
38
+ info: {
39
+ provider: n,
40
+ capabilities: {
41
+ track: !0,
42
+ aggregate: !0,
43
+ timeseries: !0,
44
+ topN: !0,
45
+ mirror: !1
46
+ },
47
+ detail: {
48
+ apiUrl: t.apiUrl,
49
+ datasource: t.datasource,
50
+ pipes: t.pipes
51
+ }
52
+ },
53
+ track: (n) => e.gen(function* () {
54
+ let r = `${t.apiUrl}/v0/events?name=${encodeURIComponent(t.datasource)}`, i = JSON.stringify({
55
+ name: n.name,
56
+ subject_id: n.subjectId ?? null,
57
+ tenant_id: n.tenantId ?? null,
58
+ properties: JSON.stringify(n.properties),
59
+ occurred_at: (n.occurredAt ?? /* @__PURE__ */ new Date()).toISOString()
60
+ }), a = yield* e.tryPromise({
61
+ try: () => fetch(r, {
62
+ method: "POST",
63
+ headers: {
64
+ authorization: `Bearer ${t.token}`,
65
+ "content-type": "application/json"
66
+ },
67
+ body: i
68
+ }),
69
+ catch: (e) => e
70
+ });
71
+ if (!a.ok) {
72
+ let t = yield* e.tryPromise({
73
+ try: () => a.text(),
74
+ catch: () => "no body"
75
+ });
76
+ return yield* e.fail(/* @__PURE__ */ Error(`tinybird events API returned HTTP ${a.status}: ${t}`));
77
+ }
78
+ }).pipe(r("track")),
79
+ aggregate: (n) => e.gen(function* () {
80
+ let { data: r } = yield* e.tryPromise({
81
+ try: () => d(t, t.pipes.aggregate, {
82
+ event: n.event,
83
+ from: n.range.from.toISOString(),
84
+ to: (n.range.to ?? /* @__PURE__ */ new Date()).toISOString(),
85
+ metric_expr: u(n.metric),
86
+ filter_sql: l(n.filter, n.tenantId)
87
+ }),
88
+ catch: (e) => e
89
+ }), i = r[0]?.value ?? 0;
90
+ return typeof i == "number" ? i : Number(i ?? 0);
91
+ }).pipe(r("aggregate")),
92
+ timeseries: (n) => e.gen(function* () {
93
+ let { data: r } = yield* e.tryPromise({
94
+ try: () => d(t, t.pipes.timeseries, {
95
+ event: n.event,
96
+ from: n.range.from.toISOString(),
97
+ to: (n.range.to ?? /* @__PURE__ */ new Date()).toISOString(),
98
+ metric_expr: u(n.metric),
99
+ filter_sql: l(n.filter, n.tenantId),
100
+ bucket_fn: p(n.bucket)
101
+ }),
102
+ catch: (e) => e
103
+ });
104
+ return r.map((e) => ({
105
+ bucketStart: new Date(e.bucket_start),
106
+ value: typeof e.value == "number" ? e.value : Number(e.value ?? 0)
107
+ }));
108
+ }).pipe(r("timeseries")),
109
+ topN: (n) => e.gen(function* () {
110
+ let { data: r } = yield* e.tryPromise({
111
+ try: () => d(t, t.pipes.topN, {
112
+ event: n.event,
113
+ from: n.range.from.toISOString(),
114
+ to: (n.range.to ?? /* @__PURE__ */ new Date()).toISOString(),
115
+ group_expr: `JSONExtractString(properties, '${c(n.groupBy)}')`,
116
+ metric_expr: n.metric === "count" ? "count()" : `sum(JSONExtractFloat(properties, '${c(n.metric.sum)}'))`,
117
+ filter_sql: l(n.filter, n.tenantId),
118
+ limit: Math.max(1, Math.floor(n.n))
119
+ }),
120
+ catch: (e) => e
121
+ });
122
+ return r.filter((e) => e.key !== null).map((e) => ({
123
+ key: e.key,
124
+ value: typeof e.value == "number" ? e.value : Number(e.value ?? 0)
125
+ }));
126
+ }).pipe(r("topN"))
127
+ }), p = (e) => {
128
+ switch (e) {
129
+ case "minute": return "toStartOfMinute";
130
+ case "hour": return "toStartOfHour";
131
+ case "day": return "toStartOfDay";
132
+ case "week": return "toStartOfWeek";
133
+ case "month": return "toStartOfMonth";
134
+ }
135
+ }, m = {
136
+ eu: "https://api.tinybird.co",
137
+ "us-east": "https://api.us-east.tinybird.co",
138
+ "us-west": "https://api.us-west.tinybird.co",
139
+ "asia-southeast": "https://api.tinybird.co"
140
+ }, h = (e) => {
141
+ let t = e.apiUrl ?? m[e.region ?? "eu"] ?? "https://api.tinybird.co", n = {
142
+ token: e.token,
143
+ apiUrl: t,
144
+ datasource: a(e.datasource ?? "voltro_events", "datasource"),
145
+ pipes: {
146
+ aggregate: a(e.pipes?.aggregate ?? "events_aggregate", "aggregate pipe name"),
147
+ timeseries: a(e.pipes?.timeseries ?? "events_timeseries", "timeseries pipe name"),
148
+ topN: a(e.pipes?.topN ?? "events_topn", "topN pipe name")
149
+ }
150
+ };
151
+ return {
152
+ kind: "tinybird",
153
+ build: (e) => f(n)
154
+ };
155
+ };
156
+ //#endregion
157
+ export { h as tinybirdAnalytics };
package/package.json ADDED
@@ -0,0 +1,43 @@
1
+ {
2
+ "name": "@voltro/plugin-tinybird",
3
+ "version": "0.1.0",
4
+ "description": "Tinybird AnalyticsSink. Hosted ClickHouse with the Tinybird Events API + Pipes for low-friction OLAP. Pay-as-you-go alternative to running your own ClickHouse.",
5
+ "keywords": [
6
+ "voltro",
7
+ "typescript",
8
+ "framework"
9
+ ],
10
+ "license": "SEE LICENSE IN LICENSE",
11
+ "homepage": "https://voltro.dev",
12
+ "bugs": {
13
+ "email": "support@voltro.dev"
14
+ },
15
+ "author": {
16
+ "name": "Voltro UG",
17
+ "url": "https://voltro.dev"
18
+ },
19
+ "type": "module",
20
+ "exports": {
21
+ ".": {
22
+ "types": "./dist/index.d.ts",
23
+ "import": "./dist/index.js",
24
+ "default": "./dist/index.js"
25
+ }
26
+ },
27
+ "main": "./dist/index.js",
28
+ "module": "./dist/index.js",
29
+ "types": "./dist/index.d.ts",
30
+ "sideEffects": false,
31
+ "engines": {
32
+ "node": ">=24.0.0"
33
+ },
34
+ "dependencies": {
35
+ "@voltro/runtime": "0.1.0"
36
+ },
37
+ "peerDependencies": {
38
+ "effect": "^3.21.4"
39
+ },
40
+ "publishConfig": {
41
+ "access": "public"
42
+ }
43
+ }