@lunora/bindings 0.0.0 → 1.0.0-alpha.2

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.
Files changed (40) hide show
  1. package/LICENSE.md +105 -0
  2. package/README.md +39 -1
  3. package/__assets__/package-og.svg +14 -0
  4. package/dist/analytics/index.d.mts +148 -0
  5. package/dist/analytics/index.d.ts +148 -0
  6. package/dist/analytics/index.mjs +2 -0
  7. package/dist/images/index.d.mts +338 -0
  8. package/dist/images/index.d.ts +338 -0
  9. package/dist/images/index.mjs +3 -0
  10. package/dist/kv/index.d.mts +177 -0
  11. package/dist/kv/index.d.ts +177 -0
  12. package/dist/kv/index.mjs +1 -0
  13. package/dist/packem_shared/AnalyticsSqlError-CGTdsi4H.mjs +42 -0
  14. package/dist/packem_shared/R2SqlError-DlDd_SrE.mjs +67 -0
  15. package/dist/packem_shared/SelectBuilder-DHaXZwn_.mjs +167 -0
  16. package/dist/packem_shared/SetOperation-RDHcxccj.mjs +80 -0
  17. package/dist/packem_shared/Sql-DceGtcUd.mjs +68 -0
  18. package/dist/packem_shared/WindowExpression-Cg9s2xcr.mjs +44 -0
  19. package/dist/packem_shared/WindowFunction-DA3pGC3N.mjs +82 -0
  20. package/dist/packem_shared/asc-Cur-xO8v.mjs +16 -0
  21. package/dist/packem_shared/buildImageDeliveryUrl-D1sVfIOP.mjs +30 -0
  22. package/dist/packem_shared/buildSignedImageUrl-Otdgc_jO.mjs +113 -0
  23. package/dist/packem_shared/concurrent-Dj5sOibv.mjs +23 -0
  24. package/dist/packem_shared/createAnalytics-CEEI69o9.mjs +57 -0
  25. package/dist/packem_shared/createContextVectors-BSizpmu5.mjs +140 -0
  26. package/dist/packem_shared/createImages-CJrvqX0u.mjs +80 -0
  27. package/dist/packem_shared/createKv-DTiSt216.mjs +141 -0
  28. package/dist/packem_shared/createPipelines-CfyJ6VGu.mjs +10 -0
  29. package/dist/packem_shared/createVectorAdminIntrospector-BJUOM6VW.mjs +51 -0
  30. package/dist/packem_shared/createVectors-LSpGoKCd.mjs +91 -0
  31. package/dist/pipelines/index.d.mts +41 -0
  32. package/dist/pipelines/index.d.ts +41 -0
  33. package/dist/pipelines/index.mjs +1 -0
  34. package/dist/r2sql/index.d.mts +383 -0
  35. package/dist/r2sql/index.d.ts +383 -0
  36. package/dist/r2sql/index.mjs +7 -0
  37. package/dist/vectors/index.d.mts +285 -0
  38. package/dist/vectors/index.d.ts +285 -0
  39. package/dist/vectors/index.mjs +3 -0
  40. package/package.json +54 -4
@@ -0,0 +1,10 @@
1
+ const createPipelines = (options) => {
2
+ const { binding } = options;
3
+ return {
4
+ send: async (records) => {
5
+ await binding.send(Array.isArray(records) ? records : [records]);
6
+ }
7
+ };
8
+ };
9
+
10
+ export { createPipelines };
@@ -0,0 +1,51 @@
1
+ const MAX_TOP_K = 20;
2
+ const DEFAULT_TOP_K = 10;
3
+ const createVectorAdminIntrospector = (options) => {
4
+ const { embedders, indexes, registry } = options;
5
+ const listIndexes = async () => Promise.all(
6
+ registry.map(async (entry) => {
7
+ const binding = indexes[entry.name];
8
+ if (binding?.describe === void 0) {
9
+ return { ...entry };
10
+ }
11
+ try {
12
+ const details = await binding.describe();
13
+ return {
14
+ ...entry,
15
+ dimensions: entry.dimensions ?? details.dimensions,
16
+ processedUpToMutation: details.processedUpToMutation,
17
+ vectorsCount: details.vectorsCount
18
+ };
19
+ } catch {
20
+ return { ...entry };
21
+ }
22
+ })
23
+ );
24
+ const hasEmbedders = embedders !== void 0 && Object.keys(embedders).length > 0;
25
+ if (!hasEmbedders) {
26
+ return { listIndexes };
27
+ }
28
+ const queryIndex = async ({ name, text, topK }) => {
29
+ const binding = indexes[name];
30
+ if (binding === void 0) {
31
+ throw new Error(`@lunora/bindings/vectors: no Vectorize binding registered for index "${name}"`);
32
+ }
33
+ const embed = embedders[name];
34
+ if (embed === void 0) {
35
+ throw new Error(`@lunora/bindings/vectors: no embedder registered for index "${name}" — it lists read-only`);
36
+ }
37
+ const vector = await embed(text);
38
+ const result = await binding.query(vector, {
39
+ returnMetadata: "all",
40
+ topK: Math.min(topK ?? DEFAULT_TOP_K, MAX_TOP_K)
41
+ });
42
+ return {
43
+ matches: result.matches.map((match) => {
44
+ return { id: match.id, metadata: match.metadata, score: match.score };
45
+ })
46
+ };
47
+ };
48
+ return { listIndexes, queryIndex };
49
+ };
50
+
51
+ export { createVectorAdminIntrospector };
@@ -0,0 +1,91 @@
1
+ import { c as concurrentMap, U as UPSERT_EMBED_CONCURRENCY } from './concurrent-Dj5sOibv.mjs';
2
+
3
+ const resolveIndex = (indexes, name) => {
4
+ const index = indexes[name];
5
+ if (!index) {
6
+ throw new Error(`@lunora/bindings/vectors: no index registered for "${name}". Known indexes: ${Object.keys(indexes).join(", ") || "(none)"}`);
7
+ }
8
+ return index;
9
+ };
10
+ const toVector = async (input) => {
11
+ const values = await input.embed(input.input);
12
+ return {
13
+ id: input.id,
14
+ metadata: input.metadata,
15
+ namespace: input.namespace,
16
+ values
17
+ };
18
+ };
19
+ const MAX_TOP_K = 100;
20
+ const MAX_TOP_K_WITH_VALUES = 20;
21
+ const MAX_ID_BATCH = 1e3;
22
+ const MAX_UPSERT_BATCH = 1e3;
23
+ const createVectors = (options) => {
24
+ if (Object.keys(options.indexes).length === 0) {
25
+ throw new Error("@lunora/bindings/vectors: at least one index binding is required");
26
+ }
27
+ const upsert = async (indexName, input) => {
28
+ const index = resolveIndex(options.indexes, indexName);
29
+ const vector = await toVector(input);
30
+ return index.upsert([vector]);
31
+ };
32
+ const upsertMany = async (indexName, inputs) => {
33
+ const index = resolveIndex(options.indexes, indexName);
34
+ if (inputs.length > MAX_UPSERT_BATCH) {
35
+ throw new RangeError(
36
+ `@lunora/bindings/vectors: upsertMany batch exceeds ${String(MAX_UPSERT_BATCH)} (got ${String(inputs.length)}) — split across calls`
37
+ );
38
+ }
39
+ const vectors = await concurrentMap(inputs, UPSERT_EMBED_CONCURRENCY, toVector);
40
+ return index.upsert(vectors);
41
+ };
42
+ const query = async (indexName, input) => {
43
+ const index = resolveIndex(options.indexes, indexName);
44
+ const wantsHeavyPayload = input.returnValues === true || input.returnMetadata === "all";
45
+ const topKCeiling = wantsHeavyPayload ? MAX_TOP_K_WITH_VALUES : MAX_TOP_K;
46
+ if (input.topK !== void 0 && (!Number.isInteger(input.topK) || input.topK < 1 || input.topK > topKCeiling)) {
47
+ const reason = wantsHeavyPayload ? ' (lowered to 20 because returnValues/returnMetadata:"all" is set)' : "";
48
+ throw new RangeError(`@lunora/bindings/vectors: topK must be an integer in [1, ${String(topKCeiling)}]${reason} (got ${String(input.topK)})`);
49
+ }
50
+ let values;
51
+ if (input.vector && input.vector.length > 0) {
52
+ values = input.vector;
53
+ } else {
54
+ if (!input.embed || input.input === void 0) {
55
+ throw new Error("@lunora/bindings/vectors: query requires either `vector` or both `input` and `embed`");
56
+ }
57
+ values = await input.embed(input.input);
58
+ }
59
+ return index.query(values, {
60
+ filter: input.filter,
61
+ namespace: input.namespace,
62
+ returnMetadata: input.returnMetadata,
63
+ returnValues: input.returnValues,
64
+ topK: input.topK
65
+ });
66
+ };
67
+ const getByIds = async (indexName, ids) => {
68
+ const index = resolveIndex(options.indexes, indexName);
69
+ if (ids.length > MAX_ID_BATCH) {
70
+ throw new RangeError(`@lunora/bindings/vectors: getByIds accepts at most ${String(MAX_ID_BATCH)} ids (got ${String(ids.length)})`);
71
+ }
72
+ return index.getByIds(ids);
73
+ };
74
+ const deleteByIds = async (indexName, ids) => {
75
+ const index = resolveIndex(options.indexes, indexName);
76
+ if (ids.length > MAX_ID_BATCH) {
77
+ throw new RangeError(`@lunora/bindings/vectors: deleteByIds accepts at most ${String(MAX_ID_BATCH)} ids (got ${String(ids.length)})`);
78
+ }
79
+ return index.deleteByIds(ids);
80
+ };
81
+ const describe = async (indexName) => {
82
+ const index = resolveIndex(options.indexes, indexName);
83
+ if (!index.describe) {
84
+ throw new Error(`@lunora/bindings/vectors: binding for "${indexName}" does not implement describe()`);
85
+ }
86
+ return index.describe();
87
+ };
88
+ return { deleteByIds, describe, getByIds, query, upsert, upsertMany };
89
+ };
90
+
91
+ export { createVectors as default };
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Structural types for the Cloudflare Pipelines write path (R2-backed streaming
3
+ * ingestion). Pipelines is the other "emit data to a sink" surface alongside
4
+ * Analytics Engine — telemetry/events out, no in-handler read-back. The binding
5
+ * is mirrored structurally (`*Like`) so a plain-object fake satisfies it in unit
6
+ * tests, like `@lunora/bindings/analytics`'s `AnalyticsEngineDatasetLike`.
7
+ */
8
+ /** One Pipelines record — a JSON object matching the stream's schema. */
9
+ type PipelineRecord = Record<string, unknown>;
10
+ /**
11
+ * Minimal structural projection of workers-types' `Pipeline&lt;T>` binding. The
12
+ * real binding's `send` takes an array of records and resolves once accepted.
13
+ */
14
+ interface PipelineBindingLike<T extends PipelineRecord = PipelineRecord> {
15
+ send: (records: T[]) => Promise<void>;
16
+ }
17
+ /**
18
+ * The write-side client bound to `ctx.pipelines` (the generated context imports
19
+ * this exact type as `import("@lunora/bindings/pipelines").PipelineClient`).
20
+ * Ingestion is durable, batched, and fire-and-forget — never read a record back
21
+ * in-handler.
22
+ */
23
+ interface PipelineClient<T extends PipelineRecord = PipelineRecord> {
24
+ /** Ingest one record or an array of records into the R2-backed sink. */
25
+ send: (records: T | T[]) => Promise<void>;
26
+ }
27
+ /**
28
+ * Wrap a Cloudflare Pipelines binding in the write-side {@link PipelineClient}
29
+ * bound to `ctx.pipelines`. The binding is `env.PIPELINES` (the `pipelines`
30
+ * binding the config layer recognizes; the remote pipeline name is minted with
31
+ * `wrangler pipelines create`).
32
+ *
33
+ * Ingestion is durable and batched: `send` accepts one record or an array and
34
+ * resolves once Cloudflare has accepted them for delivery to the R2-backed sink.
35
+ * There is no in-handler read-back — this is a fire-and-forget egress path, so
36
+ * it belongs on ActionCtx only (external I/O), mirroring `ctx.images`.
37
+ */
38
+ declare const createPipelines: <T extends PipelineRecord = PipelineRecord>(options: {
39
+ binding: PipelineBindingLike<T>;
40
+ }) => PipelineClient<T>;
41
+ export { type PipelineBindingLike, type PipelineClient, type PipelineRecord, createPipelines };
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Structural types for the Cloudflare Pipelines write path (R2-backed streaming
3
+ * ingestion). Pipelines is the other "emit data to a sink" surface alongside
4
+ * Analytics Engine — telemetry/events out, no in-handler read-back. The binding
5
+ * is mirrored structurally (`*Like`) so a plain-object fake satisfies it in unit
6
+ * tests, like `@lunora/bindings/analytics`'s `AnalyticsEngineDatasetLike`.
7
+ */
8
+ /** One Pipelines record — a JSON object matching the stream's schema. */
9
+ type PipelineRecord = Record<string, unknown>;
10
+ /**
11
+ * Minimal structural projection of workers-types' `Pipeline&lt;T>` binding. The
12
+ * real binding's `send` takes an array of records and resolves once accepted.
13
+ */
14
+ interface PipelineBindingLike<T extends PipelineRecord = PipelineRecord> {
15
+ send: (records: T[]) => Promise<void>;
16
+ }
17
+ /**
18
+ * The write-side client bound to `ctx.pipelines` (the generated context imports
19
+ * this exact type as `import("@lunora/bindings/pipelines").PipelineClient`).
20
+ * Ingestion is durable, batched, and fire-and-forget — never read a record back
21
+ * in-handler.
22
+ */
23
+ interface PipelineClient<T extends PipelineRecord = PipelineRecord> {
24
+ /** Ingest one record or an array of records into the R2-backed sink. */
25
+ send: (records: T | T[]) => Promise<void>;
26
+ }
27
+ /**
28
+ * Wrap a Cloudflare Pipelines binding in the write-side {@link PipelineClient}
29
+ * bound to `ctx.pipelines`. The binding is `env.PIPELINES` (the `pipelines`
30
+ * binding the config layer recognizes; the remote pipeline name is minted with
31
+ * `wrangler pipelines create`).
32
+ *
33
+ * Ingestion is durable and batched: `send` accepts one record or an array and
34
+ * resolves once Cloudflare has accepted them for delivery to the R2-backed sink.
35
+ * There is no in-handler read-back — this is a fire-and-forget egress path, so
36
+ * it belongs on ActionCtx only (external I/O), mirroring `ctx.images`.
37
+ */
38
+ declare const createPipelines: <T extends PipelineRecord = PipelineRecord>(options: {
39
+ binding: PipelineBindingLike<T>;
40
+ }) => PipelineClient<T>;
41
+ export { type PipelineBindingLike, type PipelineClient, type PipelineRecord, createPipelines };
@@ -0,0 +1 @@
1
+ export { createPipelines } from '../packem_shared/createPipelines-CfyJ6VGu.mjs';
@@ -0,0 +1,383 @@
1
+ /**
2
+ * A composed SQL fragment. Carries the finished `text`; `toString()` returns it
3
+ * so a fragment can be dropped straight into a template or `String(...)`.
4
+ */
5
+ declare class Sql {
6
+ readonly text: string;
7
+ constructor(text: string);
8
+ toString(): string;
9
+ }
10
+ /** True when `value` is a {@link Sql} fragment (an already-trusted, pre-escaped span). */
11
+ declare const isSql: (value: unknown) => value is Sql;
12
+ /**
13
+ * Wrap an already-trusted string as a {@link Sql} fragment so {@link sql}
14
+ * splices it **verbatim** (no escaping). Use ONLY for SQL you constructed
15
+ * yourself (identifiers, keywords, sub-fragments) — never for user input.
16
+ */
17
+ declare const raw: (text: string) => Sql;
18
+ /** Resolve a `string | Sql` to its raw text. A bare string is taken as trusted SQL (callers pass identifiers/fragments here). */
19
+ declare const toText: (value: Sql | string) => string;
20
+ /**
21
+ * Render a JS value as an R2 SQL literal:
22
+ *
23
+ * - `null` / `undefined` → `NULL`
24
+ * - `boolean` → `true` / `false`
25
+ * - finite `number` / `bigint` → the numeric text (non-finite throws — `NaN`/`Infinity` have no SQL literal)
26
+ * - `Date` → an RFC3339 string literal (R2 SQL's `timestamp` form)
27
+ * - `string` → a single-quoted, escaped literal
28
+ * - `Array` → a parenthesised, comma-separated list of literals (for `IN (...)`)
29
+ *
30
+ * Anything else (object, symbol, function) throws — there is no safe SQL literal
31
+ * for it, and silently coercing would risk a malformed or injectable statement.
32
+ */
33
+ declare const lit: (value: unknown) => string;
34
+ /**
35
+ * Tagged template producing a safe {@link Sql} fragment. Each interpolation is
36
+ * escaped with {@link lit} unless it is already a {@link Sql} (spliced
37
+ * verbatim), so user values can never break out of their literal.
38
+ */
39
+ declare const sql: (strings: TemplateStringsArray, ...values: unknown[]) => Sql;
40
+ /** Join SQL fragments/strings with `separator` into one {@link Sql} (e.g. `AND`-ed conditions). */
41
+ declare const joinSql: (parts: ReadonlyArray<Sql | string>, separator: string) => Sql;
42
+ /** A single `ORDER BY` term: a bare column/expression (ASC) or one tagged via {@link asc} / {@link desc}. */
43
+ type OrderTerm = Sql | string | {
44
+ dir: "ASC" | "DESC";
45
+ expr: Sql | string;
46
+ };
47
+ /** Tag an order term ascending — `asc("total")` → `total ASC`. */
48
+ declare const asc: (expr: Sql | string) => OrderTerm;
49
+ /** Tag an order term descending — `desc("total")` → `total DESC`. */
50
+ declare const desc: (expr: Sql | string) => OrderTerm;
51
+ /** Render one {@link OrderTerm} to SQL. */
52
+ declare const renderOrderTerm: (term: OrderTerm) => string;
53
+ /**
54
+ * Public types for `@lunora/bindings/r2sql`.
55
+ *
56
+ * R2 SQL is Cloudflare's serverless, distributed query engine over **Apache
57
+ * Iceberg** tables in [R2 Data Catalog](https://developers.cloudflare.com/r2/data-catalog/).
58
+ * It has **no Workers binding** — every query is an HTTPS round-trip to the REST
59
+ * endpoint (`POST …/r2-sql/query/{bucket}`). So, like Hyperdrive's `ctx.sql`, the
60
+ * client is **non-deterministic external I/O**: it is wired onto `ActionCtx`
61
+ * only (see the `r2sql_outside_action` advisor lint) and its reads are NOT
62
+ * tracked by Lunora live queries.
63
+ *
64
+ * Everything here is deliberately structural (no hard dependency on
65
+ * `@cloudflare/workers-types`) so unit tests can inject a plain `fetch` double
66
+ * and never touch the network — mirroring `AnalyticsSqlConfig` in
67
+ * `@lunora/bindings/analytics`.
68
+ */
69
+ /**
70
+ * Configuration for a {@link import("./client").R2SqlClient | R2SqlClient}.
71
+ *
72
+ * `apiToken` is a **secret** — a Cloudflare API token scoped to R2 SQL (read),
73
+ * R2 Data Catalog, and R2 storage. It is never a binding and must never be
74
+ * auto-scaffolded with a real value; the caller provides it from
75
+ * env/`.dev.vars`.
76
+ */
77
+ interface R2SqlConfig {
78
+ /** Cloudflare account id that owns the bucket/catalog. */
79
+ accountId: string;
80
+ /** API token with R2 SQL read + R2 Data Catalog + R2 storage scope. A secret — never a binding. */
81
+ apiToken: string;
82
+ /** The R2 bucket (warehouse) whose Data Catalog the queries run against. */
83
+ bucket: string;
84
+ /**
85
+ * Override the REST base URL. Defaults to Cloudflare's public R2 SQL host
86
+ * (`https://api.sql.cloudflarestorage.com/api/v1/accounts`). Injected in
87
+ * tests, or pointed at a regional/preview host.
88
+ */
89
+ endpoint?: string;
90
+ /**
91
+ * `fetch` implementation. Defaults to the global `fetch`; injected in tests
92
+ * so a query never touches the network.
93
+ */
94
+ fetch?: typeof globalThis.fetch;
95
+ }
96
+ /**
97
+ * One column descriptor in a result's schema: the column `name` and, when the
98
+ * engine reports it, the Iceberg storage `type` (`integer`, `string`,
99
+ * `timestamp`, …). R2 SQL does not always echo a schema block, so `type` is
100
+ * optional and `columns` may be derived from the first row's keys.
101
+ */
102
+ interface R2SqlColumn {
103
+ name: string;
104
+ type?: string;
105
+ }
106
+ /**
107
+ * A parsed R2 SQL result. R2 SQL returns a Cloudflare envelope
108
+ * (`{ success, result, errors }`); we surface the `rows` (the `result` array of
109
+ * column→value records), the inferred/echoed `columns`, and the `rowCount`.
110
+ *
111
+ * `Row` defaults to an open record; supply it (`from&lt;MyRow>(…)` /
112
+ * `query&lt;MyRow>(…)`) to get typed result fields — R2 SQL tables live in Iceberg,
113
+ * not `defineSchema`, so the row type is caller-declared rather than inferred.
114
+ */
115
+ interface R2SqlResult<Row = Record<string, unknown>> {
116
+ /** Column descriptors, echoed by the engine or inferred from the first row. */
117
+ columns: R2SqlColumn[];
118
+ /** Total rows returned. */
119
+ rowCount: number;
120
+ /** The result rows. */
121
+ rows: Row[];
122
+ }
123
+ /** Options for {@link import("./client").R2SqlClient.explain | explain}. */
124
+ interface R2SqlExplainOptions {
125
+ /**
126
+ * `"json"` runs `EXPLAIN FORMAT JSON` (structured plan); `"text"` (default)
127
+ * runs a plain `EXPLAIN`.
128
+ */
129
+ format?: "json" | "text";
130
+ }
131
+ /**
132
+ * Executes a finished SQL string against R2 SQL and returns the parsed result.
133
+ * Deliberately non-generic (the row type is a caller-side concern) — the typed
134
+ * `run()` / `query()` boundaries cast the open result to the declared row.
135
+ */
136
+ type QueryExecutor = (statement: string) => Promise<R2SqlResult>;
137
+ /** Anything that compiles to a statement and can run — a {@link import("./builder").SelectBuilder | SelectBuilder} or {@link import("./set-operation").SetOperation | SetOperation}. */
138
+ interface Queryable<Row = Record<string, unknown>> {
139
+ /**
140
+ * True when this query carries its own `ORDER BY`/`LIMIT`, so a set
141
+ * operation must parenthesise it (R2 SQL rejects a bare `LIMIT` before a set
142
+ * operator). Read structurally by the set-operation renderer to avoid an
143
+ * import cycle.
144
+ */
145
+ readonly needsWrapForSetOperation?: boolean;
146
+ run: () => Promise<R2SqlResult<Row>>;
147
+ toSQL: () => string;
148
+ }
149
+ /** A `WHERE`/`HAVING`/`QUALIFY`/`ON` condition: trusted SQL text or a {@link Sql} fragment (use the `sql` tag to bind values safely). */
150
+ type Condition = Sql | string;
151
+ /** One member of a {@link SetOperation}: the leading query has no operator; each subsequent one carries the operator that joins it. */
152
+ interface SetMember {
153
+ operator?: string;
154
+ query: Queryable<unknown>;
155
+ }
156
+ /**
157
+ * A composition of queries via set operations. Chain more operations, or apply a
158
+ * single `ORDER BY` / `LIMIT` to the combined result.
159
+ */
160
+ declare class SetOperation<Row = Record<string, unknown>> implements Queryable<Row> {
161
+ /**
162
+ * Always `true`: a nested set operation must be parenthesised when it is a
163
+ * member of another set operation, or mixed operators mis-associate — e.g.
164
+ * `a.union(b.except(c))` must render `a UNION (b EXCEPT c)`, not the flat
165
+ * `a UNION b EXCEPT c`.
166
+ */
167
+ readonly needsWrapForSetOperation = true;
168
+ private readonly exec;
169
+ private readonly members;
170
+ private readonly orderByItems;
171
+ private limitValue?;
172
+ constructor(exec: QueryExecutor, members: SetMember[]);
173
+ /** Append `UNION other`. */
174
+ union(other: Queryable<unknown>): this;
175
+ /** Append `UNION ALL other`. */
176
+ unionAll(other: Queryable<unknown>): this;
177
+ /** Append `INTERSECT other`. */
178
+ intersect(other: Queryable<unknown>): this;
179
+ /** Append `EXCEPT other`. */
180
+ except(other: Queryable<unknown>): this;
181
+ /** `ORDER BY` applied to the combined result. */
182
+ orderBy(...terms: OrderTerm[]): this;
183
+ /** `LIMIT` applied to the combined result. */
184
+ limit(n: number): this;
185
+ /** Re-type the combined result rows. */
186
+ returns<NextRow>(): SetOperation<NextRow>;
187
+ /** Render the combined statement. */
188
+ toSQL(): string;
189
+ /** Execute the combined query and return the typed result. */
190
+ run(): Promise<R2SqlResult<Row>>;
191
+ private add;
192
+ }
193
+ /**
194
+ * A fluent `SELECT` over one Iceberg table (`namespace.table`), generic over the
195
+ * caller-declared `Row` result type.
196
+ */
197
+ declare class SelectBuilder<Row = Record<string, unknown>> implements Queryable<Row> {
198
+ private readonly exec;
199
+ private readonly table;
200
+ private readonly selectItems;
201
+ private distinctFlag;
202
+ private readonly distinctOnItems;
203
+ private readonly joins;
204
+ private readonly whereConditions;
205
+ private readonly groupByItems;
206
+ private readonly havingConditions;
207
+ private qualifyCondition?;
208
+ private readonly orderByItems;
209
+ private limitValue?;
210
+ constructor(exec: QueryExecutor, table: string);
211
+ /** The `SELECT` list. Omit/empty for `SELECT *`. Items are columns, expressions, or aliased window fragments (`fn.rowNumber().over(...).as("rk")`). */
212
+ select(...items: (Sql | string)[]): this;
213
+ /** `SELECT DISTINCT` — unique rows. */
214
+ distinct(): this;
215
+ /** `DISTINCT ON (cols)` — the first row per distinct combination, ordered by {@link orderBy}. */
216
+ distinctOn(...columns: (Sql | string)[]): this;
217
+ /** `INNER JOIN table ON condition`. */
218
+ innerJoin(table: string, on: Condition): this;
219
+ /** `LEFT JOIN table ON condition`. */
220
+ leftJoin(table: string, on: Condition): this;
221
+ /** `RIGHT JOIN table ON condition`. */
222
+ rightJoin(table: string, on: Condition): this;
223
+ /** `FULL OUTER JOIN table ON condition`. */
224
+ fullJoin(table: string, on: Condition): this;
225
+ /** `CROSS JOIN table` (no `ON`). */
226
+ crossJoin(table: string): this;
227
+ /** Add `WHERE` condition(s). Multiple calls (and multiple args) are `AND`-ed. Bind values with the `sql` tag. */
228
+ where(...conditions: Condition[]): this;
229
+ /** `GROUP BY` column(s)/expression(s). */
230
+ groupBy(...columns: (Sql | string)[]): this;
231
+ /** Add `HAVING` condition(s) over aggregates; multiple are `AND`-ed. */
232
+ having(...conditions: Condition[]): this;
233
+ /**
234
+ * `QUALIFY` — filter on a window function without a subquery, e.g.
235
+ * `.qualify(fn.rowNumber().over({ partitionBy: "region", orderBy: desc("total") }).lte(3))`.
236
+ */
237
+ qualify(condition: Condition): this;
238
+ /** `ORDER BY` term(s) — bare strings (ASC) or {@link import("./order").asc | asc}/{@link import("./order").desc | desc} tags. */
239
+ orderBy(...terms: OrderTerm[]): this;
240
+ /** `LIMIT n` (R2 SQL: 1–10,000, default 500). */
241
+ limit(n: number): this;
242
+ /** Re-type the result rows without changing the query (the builder carries no schema of its own). */
243
+ returns<NextRow>(): SelectBuilder<NextRow>;
244
+ /** `this UNION other` — all rows from both, duplicates removed. */
245
+ union(other: Queryable<unknown>): SetOperation<Row>;
246
+ /** `this UNION ALL other` — all rows from both, duplicates kept. */
247
+ unionAll(other: Queryable<unknown>): SetOperation<Row>;
248
+ /** `this INTERSECT other` — rows present in both. */
249
+ intersect(other: Queryable<unknown>): SetOperation<Row>;
250
+ /** `this EXCEPT other` — rows in `this` but not `other`. */
251
+ except(other: Queryable<unknown>): SetOperation<Row>;
252
+ /** True when this query carries its own `ORDER BY`/`LIMIT` — so a set operation must parenthesise it. */
253
+ get needsWrapForSetOperation(): boolean;
254
+ /** Render the `SELECT` statement (no trailing semicolon). */
255
+ toSQL(): string;
256
+ /** Execute the query and return the typed result. */
257
+ run(): Promise<R2SqlResult<Row>>;
258
+ /** The `SELECT [DISTINCT [ON (...)]]` head. */
259
+ private renderHead;
260
+ private addJoin;
261
+ private setOperation;
262
+ }
263
+ /**
264
+ * Thrown when R2 SQL responds with a non-2xx status, an `success: false`
265
+ * envelope, or an unparseable body; carries the HTTP `status` and the raw body
266
+ * for the caller to surface.
267
+ */
268
+ declare class R2SqlError extends Error {
269
+ readonly status: number;
270
+ constructor(status: number, body: string);
271
+ }
272
+ /**
273
+ * The typed R2 SQL surface bound to `ctx.r2sql` on **`ActionCtx` only**. This is
274
+ * the exact type the generated ctx imports as
275
+ * `import("@lunora/bindings/r2sql").R2SqlClient` — keep the name and shape stable.
276
+ */
277
+ interface R2SqlClient {
278
+ /** Run `DESCRIBE namespace.table` — column names and Iceberg types. */
279
+ describe: (table: string) => Promise<R2SqlResult>;
280
+ /** Run `EXPLAIN [FORMAT JSON] sql` — the execution plan, without running the query. */
281
+ explain: (statement: Sql | string, options?: R2SqlExplainOptions) => Promise<R2SqlResult>;
282
+ /** Start a chainable `SELECT` over `table` (`namespace.table`), generic over the caller-declared `Row`. */
283
+ from: <Row = Record<string, unknown>>(table: string) => SelectBuilder<Row>;
284
+ /** Run a raw SQL statement (the escape hatch). Use the `sql` tag to bind values safely. */
285
+ query: <Row = Record<string, unknown>>(statement: Sql | string) => Promise<R2SqlResult<Row>>;
286
+ /** Run `SHOW DATABASES` — the available namespaces. */
287
+ showDatabases: () => Promise<R2SqlResult>;
288
+ /** Run `SHOW TABLES IN namespace` — the tables in a namespace. */
289
+ showTables: (namespace: string) => Promise<R2SqlResult>;
290
+ }
291
+ /**
292
+ * Build an {@link R2SqlClient}. Each query POSTs to the bucket's
293
+ * `r2-sql/query/{bucket}` endpoint with the bearer token, then normalises the
294
+ * envelope into {@link R2SqlResult}.
295
+ */
296
+ declare const createR2Sql: (config: R2SqlConfig) => R2SqlClient;
297
+ /**
298
+ * A windowed expression. Extends {@link Sql}, so it is usable anywhere a raw
299
+ * fragment is; `.as(alias)` makes a `SELECT` item, and the comparison helpers
300
+ * (`.lte`, `.gt`, `.between`, …) make `QUALIFY` conditions.
301
+ */
302
+ declare class WindowExpression extends Sql {
303
+ /** Alias the expression — `... AS alias` — for use in a `SELECT` list. */
304
+ as(alias: string): Sql;
305
+ /** `expr BETWEEN low AND high`. */
306
+ between(low: unknown, high: unknown): Sql;
307
+ /** `expr = value`. */
308
+ eq(value: unknown): Sql;
309
+ /** `expr > value`. */
310
+ gt(value: unknown): Sql;
311
+ /** `expr >= value`. */
312
+ gte(value: unknown): Sql;
313
+ /** `expr &lt; value`. */
314
+ lt(value: unknown): Sql;
315
+ /** `expr &lt;= value`. */
316
+ lte(value: unknown): Sql;
317
+ private compare;
318
+ }
319
+ /** The `OVER (...)` window specification. */
320
+ interface OverSpec {
321
+ /**
322
+ * A raw frame clause, e.g. `"ROWS BETWEEN 2 PRECEDING AND CURRENT ROW"`.
323
+ * Spliced verbatim — it is keyword-only SQL, not a value.
324
+ */
325
+ frame?: string;
326
+ /** `ORDER BY` term(s) within the window. */
327
+ orderBy?: OrderTerm | OrderTerm[];
328
+ /** `PARTITION BY` column(s)/expression(s). */
329
+ partitionBy?: Sql | string | (Sql | string)[];
330
+ }
331
+ /**
332
+ * A window function awaiting its `OVER (...)`. Call {@link WindowFunction.over |
333
+ * .over} to bind a window and get a {@link WindowExpression}.
334
+ */
335
+ declare class WindowFunction {
336
+ private readonly callText;
337
+ constructor(callText: string);
338
+ /** Attach the window frame, yielding a {@link WindowExpression}. */
339
+ over(spec?: OverSpec): WindowExpression;
340
+ }
341
+ /**
342
+ * Window-function builders. Each returns a {@link WindowFunction}; chain
343
+ * `.over(...)` to bind the window.
344
+ *
345
+ * Ranking: `rowNumber`, `rank`, `denseRank`, `percentRank`, `cumeDist`,
346
+ * `ntile`. Offset/value: `lag`, `lead`, `firstValue`, `lastValue`, `nthValue`.
347
+ * Aggregates used as windows: `sum`, `avg`, `count`, `min`, `max`.
348
+ */
349
+ declare const fn: {
350
+ /** `AVG(column) OVER (...)`. */
351
+ avg: (column: Sql | string) => WindowFunction;
352
+ /** `COUNT(column) OVER (...)` — omit the column for `COUNT(*)`. */
353
+ count: (column?: Sql | string) => WindowFunction;
354
+ /** `CUME_DIST() OVER (...)`. */
355
+ cumeDist: () => WindowFunction;
356
+ /** `DENSE_RANK() OVER (...)`. */
357
+ denseRank: () => WindowFunction;
358
+ /** `FIRST_VALUE(column) OVER (...)`. */
359
+ firstValue: (column: Sql | string) => WindowFunction;
360
+ /** `LAG(column[, offset[, default]]) OVER (...)`. */
361
+ lag: (column: Sql | string, offset?: number, fallback?: unknown) => WindowFunction;
362
+ /** `LAST_VALUE(column) OVER (...)`. */
363
+ lastValue: (column: Sql | string) => WindowFunction;
364
+ /** `LEAD(column[, offset[, default]]) OVER (...)`. */
365
+ lead: (column: Sql | string, offset?: number, fallback?: unknown) => WindowFunction;
366
+ /** `MAX(column) OVER (...)`. */
367
+ max: (column: Sql | string) => WindowFunction;
368
+ /** `MIN(column) OVER (...)`. */
369
+ min: (column: Sql | string) => WindowFunction;
370
+ /** `NTH_VALUE(column, n) OVER (...)`. */
371
+ nthValue: (column: Sql | string, n: number) => WindowFunction;
372
+ /** `NTILE(buckets) OVER (...)`. */
373
+ ntile: (buckets: number) => WindowFunction;
374
+ /** `PERCENT_RANK() OVER (...)`. */
375
+ percentRank: () => WindowFunction;
376
+ /** `RANK() OVER (...)`. */
377
+ rank: () => WindowFunction;
378
+ /** `ROW_NUMBER() OVER (...)`. */
379
+ rowNumber: () => WindowFunction;
380
+ /** `SUM(column) OVER (...)`. */
381
+ sum: (column: Sql | string) => WindowFunction;
382
+ };
383
+ export { type Condition, type OrderTerm, type OverSpec, type QueryExecutor, type Queryable, type R2SqlClient, type R2SqlColumn, type R2SqlConfig, R2SqlError, type R2SqlExplainOptions, type R2SqlResult, SelectBuilder, SetOperation, Sql, WindowExpression, WindowFunction, asc, createR2Sql, desc, fn, isSql, joinSql, lit, raw, renderOrderTerm, sql, toText };