@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.
- package/LICENSE.md +105 -0
- package/README.md +39 -1
- package/__assets__/package-og.svg +14 -0
- package/dist/analytics/index.d.mts +148 -0
- package/dist/analytics/index.d.ts +148 -0
- package/dist/analytics/index.mjs +2 -0
- package/dist/images/index.d.mts +338 -0
- package/dist/images/index.d.ts +338 -0
- package/dist/images/index.mjs +3 -0
- package/dist/kv/index.d.mts +177 -0
- package/dist/kv/index.d.ts +177 -0
- package/dist/kv/index.mjs +1 -0
- package/dist/packem_shared/AnalyticsSqlError-CGTdsi4H.mjs +42 -0
- package/dist/packem_shared/R2SqlError-DlDd_SrE.mjs +67 -0
- package/dist/packem_shared/SelectBuilder-DHaXZwn_.mjs +167 -0
- package/dist/packem_shared/SetOperation-RDHcxccj.mjs +80 -0
- package/dist/packem_shared/Sql-DceGtcUd.mjs +68 -0
- package/dist/packem_shared/WindowExpression-Cg9s2xcr.mjs +44 -0
- package/dist/packem_shared/WindowFunction-DA3pGC3N.mjs +82 -0
- package/dist/packem_shared/asc-Cur-xO8v.mjs +16 -0
- package/dist/packem_shared/buildImageDeliveryUrl-D1sVfIOP.mjs +30 -0
- package/dist/packem_shared/buildSignedImageUrl-Otdgc_jO.mjs +113 -0
- package/dist/packem_shared/concurrent-Dj5sOibv.mjs +23 -0
- package/dist/packem_shared/createAnalytics-CEEI69o9.mjs +57 -0
- package/dist/packem_shared/createContextVectors-BSizpmu5.mjs +140 -0
- package/dist/packem_shared/createImages-CJrvqX0u.mjs +80 -0
- package/dist/packem_shared/createKv-DTiSt216.mjs +141 -0
- package/dist/packem_shared/createPipelines-CfyJ6VGu.mjs +10 -0
- package/dist/packem_shared/createVectorAdminIntrospector-BJUOM6VW.mjs +51 -0
- package/dist/packem_shared/createVectors-LSpGoKCd.mjs +91 -0
- package/dist/pipelines/index.d.mts +41 -0
- package/dist/pipelines/index.d.ts +41 -0
- package/dist/pipelines/index.mjs +1 -0
- package/dist/r2sql/index.d.mts +383 -0
- package/dist/r2sql/index.d.ts +383 -0
- package/dist/r2sql/index.mjs +7 -0
- package/dist/vectors/index.d.mts +285 -0
- package/dist/vectors/index.d.ts +285 -0
- package/dist/vectors/index.mjs +3 -0
- package/package.json +54 -4
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Structural types for the Analytics Engine write path.
|
|
3
|
+
*
|
|
4
|
+
* The real binding is workers-types' `AnalyticsEngineDataset`. We mirror it
|
|
5
|
+
* **structurally** (`AnalyticsEngineDatasetLike`) so plain-object test doubles
|
|
6
|
+
* satisfy the contract without pulling workerd into a unit test — the same
|
|
7
|
+
* approach `@lunora/d1` takes with `D1DatabaseLike`
|
|
8
|
+
* (`packages/d1/src/d1-client.ts`).
|
|
9
|
+
*/
|
|
10
|
+
/**
|
|
11
|
+
* One Analytics Engine data point, mirroring the positional shape
|
|
12
|
+
* `writeDataPoint` accepts. AE stores up to 20 string `blobs`, up to 20 numeric
|
|
13
|
+
* `doubles`, and exactly **one** `index` (the high-cardinality sampling key) per
|
|
14
|
+
* data point — the SQL API later exposes them as `blob1..blob20`,
|
|
15
|
+
* `double1..double20`, and `index1`.
|
|
16
|
+
*/
|
|
17
|
+
interface AnalyticsEngineDataPoint {
|
|
18
|
+
/** String columns, mapped positionally to `blob1..blob20`. */
|
|
19
|
+
blobs?: (ArrayBuffer | null | string)[];
|
|
20
|
+
/** Numeric columns, mapped positionally to `double1..double20`. */
|
|
21
|
+
doubles?: number[];
|
|
22
|
+
/** Sampling key, exposed as `index1`. AE accepts at most one. */
|
|
23
|
+
indexes?: (ArrayBuffer | string)[];
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Minimal structural projection of workers-types' `AnalyticsEngineDataset`,
|
|
27
|
+
* kept loose enough for a plain-object fake in unit tests. `writeDataPoint` is
|
|
28
|
+
* fire-and-forget: it returns `void` and never throws on the hot path.
|
|
29
|
+
*/
|
|
30
|
+
interface AnalyticsEngineDatasetLike {
|
|
31
|
+
writeDataPoint: (event: AnalyticsEngineDataPoint) => void;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Named-field event handed to {@link AnalyticsClient.track}. Each field is
|
|
35
|
+
* mapped to a positional AE column and the mapping recorded in a returned
|
|
36
|
+
* {@link TrackSchema}, so the read side can reconstruct named columns from the
|
|
37
|
+
* SQL API's positional `blobN`/`doubleN`/`index1` output.
|
|
38
|
+
*/
|
|
39
|
+
interface TrackEvent {
|
|
40
|
+
/** String dimensions → `blobs` (`blob1..blob20`), in object key order. */
|
|
41
|
+
dimensions?: Record<string, string>;
|
|
42
|
+
/** Single high-cardinality sampling key → `index1`. */
|
|
43
|
+
index?: string;
|
|
44
|
+
/** Numeric metrics → `doubles` (`double1..double20`), in object key order. */
|
|
45
|
+
metrics?: Record<string, number>;
|
|
46
|
+
}
|
|
47
|
+
/** One named field's position in the AE positional layout. */
|
|
48
|
+
interface TrackColumn {
|
|
49
|
+
/** Positional AE column it maps to (`blob3`, `double1`, `index1`). */
|
|
50
|
+
column: string;
|
|
51
|
+
/** Field name from the {@link TrackEvent}. */
|
|
52
|
+
field: string;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* The field→column mapping {@link AnalyticsClient.track} records for one event
|
|
56
|
+
* shape, so the read side can project the SQL API's positional columns back to
|
|
57
|
+
* named fields. `name` is the logical event name; the column arrays are in the
|
|
58
|
+
* same order the dimensions/metrics were written.
|
|
59
|
+
*/
|
|
60
|
+
interface TrackSchema {
|
|
61
|
+
dimensions: TrackColumn[];
|
|
62
|
+
index: TrackColumn | null;
|
|
63
|
+
metrics: TrackColumn[];
|
|
64
|
+
name: string;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* The write-side client bound to `ctx.analytics` (the generated context imports
|
|
68
|
+
* this exact type as `import("@lunora/bindings/analytics").AnalyticsClient`). Telemetry
|
|
69
|
+
* is fire-and-forget and sampled — never read a data point back in-handler.
|
|
70
|
+
*/
|
|
71
|
+
interface AnalyticsClient {
|
|
72
|
+
/**
|
|
73
|
+
* Ergonomic named-field write: maps `{ dimensions, metrics, index }` to the
|
|
74
|
+
* positional layout, writes it, and returns the {@link TrackSchema} mapping
|
|
75
|
+
* (the logical `name` is recorded as the first blob, `blob1`).
|
|
76
|
+
*/
|
|
77
|
+
track: (name: string, event?: TrackEvent) => TrackSchema;
|
|
78
|
+
/**
|
|
79
|
+
* Write a raw positional data point. Enforces AE's per-data-point count
|
|
80
|
+
* caps (≤20 blobs, ≤20 doubles, ≤1 index) and byte budget (combined blobs
|
|
81
|
+
* ≤16 KiB, index ≤96 bytes, measured as UTF-8); overflow throws so a misuse
|
|
82
|
+
* surfaces in dev rather than being silently rejected by the platform.
|
|
83
|
+
*/
|
|
84
|
+
writeDataPoint: (event: AnalyticsEngineDataPoint) => void;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Wrap an Analytics Engine dataset binding in the write-side
|
|
88
|
+
* {@link AnalyticsClient} bound to `ctx.analytics`.
|
|
89
|
+
*
|
|
90
|
+
* The binding is `env.ANALYTICS` (the self-describing
|
|
91
|
+
* `analytics_engine_datasets` binding the config layer reconciles). Writes are
|
|
92
|
+
* fire-and-forget and sampled — there is no return value to read in-handler.
|
|
93
|
+
*
|
|
94
|
+
* `writeDataPoint` enforces AE's per-data-point caps eagerly — both the count
|
|
95
|
+
* caps (≤20 blobs, ≤20 doubles, ≤1 index) and the byte budget (combined blobs
|
|
96
|
+
* ≤16 KiB, index ≤96 bytes, measured as UTF-8) — so a misuse throws in dev
|
|
97
|
+
* instead of being silently rejected by the platform at the edge. `track` is
|
|
98
|
+
* the ergonomic named-field path: it maps a
|
|
99
|
+
* `{ dimensions, metrics, index }` object to the positional layout and returns
|
|
100
|
+
* the field→column mapping the read side uses to reconstruct named columns.
|
|
101
|
+
*/
|
|
102
|
+
declare const createAnalytics: (binding: AnalyticsEngineDatasetLike) => AnalyticsClient;
|
|
103
|
+
/** Configuration for an {@link AnalyticsSqlClient}. */
|
|
104
|
+
interface AnalyticsSqlConfig {
|
|
105
|
+
/** Cloudflare account id that owns the dataset. */
|
|
106
|
+
accountId: string;
|
|
107
|
+
/** API token with Analytics Engine read scope. A secret — never a binding. */
|
|
108
|
+
apiToken: string;
|
|
109
|
+
/**
|
|
110
|
+
* `fetch` implementation. Defaults to the global `fetch`; injected in tests
|
|
111
|
+
* so the SQL path never touches the network.
|
|
112
|
+
*/
|
|
113
|
+
fetch?: typeof globalThis.fetch;
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* One column descriptor in a SQL-API response's `meta` array: the column `name`
|
|
117
|
+
* and the AE storage `type` (`String`, `Float64`, `DateTime`, …).
|
|
118
|
+
*/
|
|
119
|
+
interface AnalyticsSqlColumnMeta {
|
|
120
|
+
name: string;
|
|
121
|
+
type: string;
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Parsed SQL-API result. AE returns `{ meta, data, rows, rows_before_limit_at_least }`;
|
|
125
|
+
* we surface `columns` (from `meta`), the `rows` array of column→value records,
|
|
126
|
+
* and the total `rowCount`.
|
|
127
|
+
*/
|
|
128
|
+
interface AnalyticsSqlResult {
|
|
129
|
+
columns: AnalyticsSqlColumnMeta[];
|
|
130
|
+
rowCount: number;
|
|
131
|
+
rows: Record<string, unknown>[];
|
|
132
|
+
}
|
|
133
|
+
/** Thrown when the SQL API responds with a non-2xx status; carries the status + body for the caller to surface. */
|
|
134
|
+
declare class AnalyticsSqlError extends Error {
|
|
135
|
+
readonly status: number;
|
|
136
|
+
constructor(status: number, body: string);
|
|
137
|
+
}
|
|
138
|
+
/** The read client: a single `query(sql)` over the AE SQL API. */
|
|
139
|
+
interface AnalyticsSqlClient {
|
|
140
|
+
query: (sql: string) => Promise<AnalyticsSqlResult>;
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Build an {@link AnalyticsSqlClient}. Each `query` POSTs the raw SQL text to
|
|
144
|
+
* the account's `analytics_engine/sql` endpoint with the bearer token, then
|
|
145
|
+
* normalises AE's `{ meta, data, rows }` body into {@link AnalyticsSqlResult}.
|
|
146
|
+
*/
|
|
147
|
+
declare const createAnalyticsSqlClient: (config: AnalyticsSqlConfig) => AnalyticsSqlClient;
|
|
148
|
+
export { type AnalyticsClient, type AnalyticsEngineDataPoint, type AnalyticsEngineDatasetLike, type AnalyticsSqlClient, type AnalyticsSqlColumnMeta, type AnalyticsSqlConfig, AnalyticsSqlError, type AnalyticsSqlResult, type TrackColumn, type TrackEvent, type TrackSchema, createAnalytics, createAnalyticsSqlClient };
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Structural types for the Analytics Engine write path.
|
|
3
|
+
*
|
|
4
|
+
* The real binding is workers-types' `AnalyticsEngineDataset`. We mirror it
|
|
5
|
+
* **structurally** (`AnalyticsEngineDatasetLike`) so plain-object test doubles
|
|
6
|
+
* satisfy the contract without pulling workerd into a unit test — the same
|
|
7
|
+
* approach `@lunora/d1` takes with `D1DatabaseLike`
|
|
8
|
+
* (`packages/d1/src/d1-client.ts`).
|
|
9
|
+
*/
|
|
10
|
+
/**
|
|
11
|
+
* One Analytics Engine data point, mirroring the positional shape
|
|
12
|
+
* `writeDataPoint` accepts. AE stores up to 20 string `blobs`, up to 20 numeric
|
|
13
|
+
* `doubles`, and exactly **one** `index` (the high-cardinality sampling key) per
|
|
14
|
+
* data point — the SQL API later exposes them as `blob1..blob20`,
|
|
15
|
+
* `double1..double20`, and `index1`.
|
|
16
|
+
*/
|
|
17
|
+
interface AnalyticsEngineDataPoint {
|
|
18
|
+
/** String columns, mapped positionally to `blob1..blob20`. */
|
|
19
|
+
blobs?: (ArrayBuffer | null | string)[];
|
|
20
|
+
/** Numeric columns, mapped positionally to `double1..double20`. */
|
|
21
|
+
doubles?: number[];
|
|
22
|
+
/** Sampling key, exposed as `index1`. AE accepts at most one. */
|
|
23
|
+
indexes?: (ArrayBuffer | string)[];
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Minimal structural projection of workers-types' `AnalyticsEngineDataset`,
|
|
27
|
+
* kept loose enough for a plain-object fake in unit tests. `writeDataPoint` is
|
|
28
|
+
* fire-and-forget: it returns `void` and never throws on the hot path.
|
|
29
|
+
*/
|
|
30
|
+
interface AnalyticsEngineDatasetLike {
|
|
31
|
+
writeDataPoint: (event: AnalyticsEngineDataPoint) => void;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Named-field event handed to {@link AnalyticsClient.track}. Each field is
|
|
35
|
+
* mapped to a positional AE column and the mapping recorded in a returned
|
|
36
|
+
* {@link TrackSchema}, so the read side can reconstruct named columns from the
|
|
37
|
+
* SQL API's positional `blobN`/`doubleN`/`index1` output.
|
|
38
|
+
*/
|
|
39
|
+
interface TrackEvent {
|
|
40
|
+
/** String dimensions → `blobs` (`blob1..blob20`), in object key order. */
|
|
41
|
+
dimensions?: Record<string, string>;
|
|
42
|
+
/** Single high-cardinality sampling key → `index1`. */
|
|
43
|
+
index?: string;
|
|
44
|
+
/** Numeric metrics → `doubles` (`double1..double20`), in object key order. */
|
|
45
|
+
metrics?: Record<string, number>;
|
|
46
|
+
}
|
|
47
|
+
/** One named field's position in the AE positional layout. */
|
|
48
|
+
interface TrackColumn {
|
|
49
|
+
/** Positional AE column it maps to (`blob3`, `double1`, `index1`). */
|
|
50
|
+
column: string;
|
|
51
|
+
/** Field name from the {@link TrackEvent}. */
|
|
52
|
+
field: string;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* The field→column mapping {@link AnalyticsClient.track} records for one event
|
|
56
|
+
* shape, so the read side can project the SQL API's positional columns back to
|
|
57
|
+
* named fields. `name` is the logical event name; the column arrays are in the
|
|
58
|
+
* same order the dimensions/metrics were written.
|
|
59
|
+
*/
|
|
60
|
+
interface TrackSchema {
|
|
61
|
+
dimensions: TrackColumn[];
|
|
62
|
+
index: TrackColumn | null;
|
|
63
|
+
metrics: TrackColumn[];
|
|
64
|
+
name: string;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* The write-side client bound to `ctx.analytics` (the generated context imports
|
|
68
|
+
* this exact type as `import("@lunora/bindings/analytics").AnalyticsClient`). Telemetry
|
|
69
|
+
* is fire-and-forget and sampled — never read a data point back in-handler.
|
|
70
|
+
*/
|
|
71
|
+
interface AnalyticsClient {
|
|
72
|
+
/**
|
|
73
|
+
* Ergonomic named-field write: maps `{ dimensions, metrics, index }` to the
|
|
74
|
+
* positional layout, writes it, and returns the {@link TrackSchema} mapping
|
|
75
|
+
* (the logical `name` is recorded as the first blob, `blob1`).
|
|
76
|
+
*/
|
|
77
|
+
track: (name: string, event?: TrackEvent) => TrackSchema;
|
|
78
|
+
/**
|
|
79
|
+
* Write a raw positional data point. Enforces AE's per-data-point count
|
|
80
|
+
* caps (≤20 blobs, ≤20 doubles, ≤1 index) and byte budget (combined blobs
|
|
81
|
+
* ≤16 KiB, index ≤96 bytes, measured as UTF-8); overflow throws so a misuse
|
|
82
|
+
* surfaces in dev rather than being silently rejected by the platform.
|
|
83
|
+
*/
|
|
84
|
+
writeDataPoint: (event: AnalyticsEngineDataPoint) => void;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Wrap an Analytics Engine dataset binding in the write-side
|
|
88
|
+
* {@link AnalyticsClient} bound to `ctx.analytics`.
|
|
89
|
+
*
|
|
90
|
+
* The binding is `env.ANALYTICS` (the self-describing
|
|
91
|
+
* `analytics_engine_datasets` binding the config layer reconciles). Writes are
|
|
92
|
+
* fire-and-forget and sampled — there is no return value to read in-handler.
|
|
93
|
+
*
|
|
94
|
+
* `writeDataPoint` enforces AE's per-data-point caps eagerly — both the count
|
|
95
|
+
* caps (≤20 blobs, ≤20 doubles, ≤1 index) and the byte budget (combined blobs
|
|
96
|
+
* ≤16 KiB, index ≤96 bytes, measured as UTF-8) — so a misuse throws in dev
|
|
97
|
+
* instead of being silently rejected by the platform at the edge. `track` is
|
|
98
|
+
* the ergonomic named-field path: it maps a
|
|
99
|
+
* `{ dimensions, metrics, index }` object to the positional layout and returns
|
|
100
|
+
* the field→column mapping the read side uses to reconstruct named columns.
|
|
101
|
+
*/
|
|
102
|
+
declare const createAnalytics: (binding: AnalyticsEngineDatasetLike) => AnalyticsClient;
|
|
103
|
+
/** Configuration for an {@link AnalyticsSqlClient}. */
|
|
104
|
+
interface AnalyticsSqlConfig {
|
|
105
|
+
/** Cloudflare account id that owns the dataset. */
|
|
106
|
+
accountId: string;
|
|
107
|
+
/** API token with Analytics Engine read scope. A secret — never a binding. */
|
|
108
|
+
apiToken: string;
|
|
109
|
+
/**
|
|
110
|
+
* `fetch` implementation. Defaults to the global `fetch`; injected in tests
|
|
111
|
+
* so the SQL path never touches the network.
|
|
112
|
+
*/
|
|
113
|
+
fetch?: typeof globalThis.fetch;
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* One column descriptor in a SQL-API response's `meta` array: the column `name`
|
|
117
|
+
* and the AE storage `type` (`String`, `Float64`, `DateTime`, …).
|
|
118
|
+
*/
|
|
119
|
+
interface AnalyticsSqlColumnMeta {
|
|
120
|
+
name: string;
|
|
121
|
+
type: string;
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Parsed SQL-API result. AE returns `{ meta, data, rows, rows_before_limit_at_least }`;
|
|
125
|
+
* we surface `columns` (from `meta`), the `rows` array of column→value records,
|
|
126
|
+
* and the total `rowCount`.
|
|
127
|
+
*/
|
|
128
|
+
interface AnalyticsSqlResult {
|
|
129
|
+
columns: AnalyticsSqlColumnMeta[];
|
|
130
|
+
rowCount: number;
|
|
131
|
+
rows: Record<string, unknown>[];
|
|
132
|
+
}
|
|
133
|
+
/** Thrown when the SQL API responds with a non-2xx status; carries the status + body for the caller to surface. */
|
|
134
|
+
declare class AnalyticsSqlError extends Error {
|
|
135
|
+
readonly status: number;
|
|
136
|
+
constructor(status: number, body: string);
|
|
137
|
+
}
|
|
138
|
+
/** The read client: a single `query(sql)` over the AE SQL API. */
|
|
139
|
+
interface AnalyticsSqlClient {
|
|
140
|
+
query: (sql: string) => Promise<AnalyticsSqlResult>;
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Build an {@link AnalyticsSqlClient}. Each `query` POSTs the raw SQL text to
|
|
144
|
+
* the account's `analytics_engine/sql` endpoint with the bearer token, then
|
|
145
|
+
* normalises AE's `{ meta, data, rows }` body into {@link AnalyticsSqlResult}.
|
|
146
|
+
*/
|
|
147
|
+
declare const createAnalyticsSqlClient: (config: AnalyticsSqlConfig) => AnalyticsSqlClient;
|
|
148
|
+
export { type AnalyticsClient, type AnalyticsEngineDataPoint, type AnalyticsEngineDatasetLike, type AnalyticsSqlClient, type AnalyticsSqlColumnMeta, type AnalyticsSqlConfig, AnalyticsSqlError, type AnalyticsSqlResult, type TrackColumn, type TrackEvent, type TrackSchema, createAnalytics, createAnalyticsSqlClient };
|
|
@@ -0,0 +1,338 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Structural projections of the Cloudflare **Images** binding (`env.IMAGES`).
|
|
3
|
+
*
|
|
4
|
+
* Declared structurally — the same pattern `@lunora/storage` uses for
|
|
5
|
+
* `R2BucketLike` — so a unit test can pass a plain object double and the real
|
|
6
|
+
* `ImagesBinding` from `@cloudflare/workers-types` satisfies the same shape. We
|
|
7
|
+
* project only the slice of the chain we actually call
|
|
8
|
+
* (`input(stream).transform(opts).output(opts)` + `info(stream)`), not the full
|
|
9
|
+
* hosted-images CRUD surface.
|
|
10
|
+
*
|
|
11
|
+
* TODO(workers-types): the 2026-06-16 optimization features — the `aspect-crop`
|
|
12
|
+
* / `scale-up` fit modes and the `upscale` param — are modeled here by hand
|
|
13
|
+
* because `@cloudflare/workers-types` (through 4.20260616.1) does not type them
|
|
14
|
+
* yet. Re-check on the next `@cloudflare/workers-types` bump: once `ImageTransform`
|
|
15
|
+
* carries `fit: "aspect-crop" | "scale-up"` and `upscale`, drop our hand-rolled
|
|
16
|
+
* additions and lean on the upstream type.
|
|
17
|
+
*/
|
|
18
|
+
/**
|
|
19
|
+
* Porter-Duff compositing operation controlling how an overlay is blended onto
|
|
20
|
+
* the image beneath it. Mirrors the binding's `ImageCompositeMode`.
|
|
21
|
+
*
|
|
22
|
+
* - `over` — foreground drawn on top of the backdrop (default).
|
|
23
|
+
* - `in` — foreground shown only where the backdrop is opaque.
|
|
24
|
+
* - `atop` — foreground drawn on top, clipped to the backdrop's shape.
|
|
25
|
+
* - `out` — foreground shown only where the backdrop is transparent.
|
|
26
|
+
* - `xor` — foreground and backdrop visible only where the other is not.
|
|
27
|
+
* - `lighter` — foreground and backdrop channels added (brightening).
|
|
28
|
+
*/
|
|
29
|
+
type ImageCompositeMode = "atop" | "in" | "lighter" | "out" | "over" | "xor";
|
|
30
|
+
/**
|
|
31
|
+
* One overlay in a {@link TransformOptions.draw} list — the **URL-form** overlay
|
|
32
|
+
* (the `cf.image.draw` / `/cdn-cgi/image` shape), where the overlay image is
|
|
33
|
+
* referenced by absolute `url`. For the **binding** path use {@link ImageOverlay}
|
|
34
|
+
* instead, which carries the overlay bytes as a stream.
|
|
35
|
+
*
|
|
36
|
+
* `width`/`height` accept either an integer (pixels) or a decimal in `(0, 1]`
|
|
37
|
+
* interpreted as a fraction of the base image's corresponding dimension.
|
|
38
|
+
*/
|
|
39
|
+
interface DrawOverlay {
|
|
40
|
+
/** Offset, in pixels, from the bottom edge. */
|
|
41
|
+
bottom?: number;
|
|
42
|
+
/** Blend mode for compositing this overlay onto the image. Default `over`. */
|
|
43
|
+
composite?: ImageCompositeMode;
|
|
44
|
+
/** Overlay height — pixels (integer) or a `0–1` fraction of the base height. */
|
|
45
|
+
height?: number;
|
|
46
|
+
/** Offset, in pixels, from the left edge. */
|
|
47
|
+
left?: number;
|
|
48
|
+
/** Overlay opacity, `0.0` (transparent) – `1.0` (opaque). */
|
|
49
|
+
opacity?: number;
|
|
50
|
+
/** Tile the overlay across the base image: `true`, or a single axis `"x"`/`"y"`. */
|
|
51
|
+
repeat?: "x" | "y" | boolean;
|
|
52
|
+
/** Offset, in pixels, from the right edge. */
|
|
53
|
+
right?: number;
|
|
54
|
+
/** Offset, in pixels, from the top edge. */
|
|
55
|
+
top?: number;
|
|
56
|
+
/** Absolute URL of the overlay image. */
|
|
57
|
+
url: string;
|
|
58
|
+
/** Overlay width — pixels (integer) or a `0–1` fraction of the base width. */
|
|
59
|
+
width?: number;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Transform parameters threaded into `binding.input(stream).transform(...)`.
|
|
63
|
+
* A structural subset of the real `ImageTransform`; the keys here are the
|
|
64
|
+
* resize/format/optimize knobs apps reach for. Unknown extra keys on the real
|
|
65
|
+
* binding are still accepted because the binding owns the authoritative type.
|
|
66
|
+
*/
|
|
67
|
+
interface TransformOptions {
|
|
68
|
+
/** Background color (CSS color) painted under transparent images. */
|
|
69
|
+
background?: string;
|
|
70
|
+
/** Gaussian blur radius (1–250). */
|
|
71
|
+
blur?: number;
|
|
72
|
+
/** Brightness multiplier (1 = unchanged). */
|
|
73
|
+
brightness?: number;
|
|
74
|
+
/** Contrast multiplier (1 = unchanged). */
|
|
75
|
+
contrast?: number;
|
|
76
|
+
/**
|
|
77
|
+
* URL-form overlays composited over the result, in paint order (last entry on
|
|
78
|
+
* top). Consumed by the URL builders ({@link DrawOverlay} references each
|
|
79
|
+
* overlay by `url`); the binding path applies overlays via the `overlays`
|
|
80
|
+
* argument to `Images.transform` instead, so this key is ignored there.
|
|
81
|
+
*/
|
|
82
|
+
draw?: DrawOverlay[];
|
|
83
|
+
/**
|
|
84
|
+
* Resize mode. Affects how `width`/`height` are interpreted.
|
|
85
|
+
*
|
|
86
|
+
* - `scale-down` — contain, but never enlarges.
|
|
87
|
+
* - `contain` — fit within the box, preserving aspect ratio.
|
|
88
|
+
* - `cover` — fill the box, cropping overflow.
|
|
89
|
+
* - `crop` — shrink-and-crop to fit, but never enlarges.
|
|
90
|
+
* - `aspect-crop` — crop to the target aspect ratio, but never enlarges.
|
|
91
|
+
* - `pad` — fit within the box, padding the remainder with `background`.
|
|
92
|
+
* - `squeeze` — stretch to the exact box, distorting aspect ratio.
|
|
93
|
+
* - `scale-up` — enlarge to show the whole image, but never downscales.
|
|
94
|
+
*/
|
|
95
|
+
fit?: "aspect-crop" | "contain" | "cover" | "crop" | "pad" | "scale-down" | "scale-up" | "squeeze";
|
|
96
|
+
/** Mirror the image horizontally, vertically, or both. */
|
|
97
|
+
flip?: "h" | "hv" | "v";
|
|
98
|
+
/** Gamma multiplier (1 = unchanged). */
|
|
99
|
+
gamma?: number;
|
|
100
|
+
/** Crop anchor when `fit: "cover"`/`"crop"`. */
|
|
101
|
+
gravity?: "auto" | "bottom" | "center" | "entropy" | "face" | "left" | "right" | "top" | {
|
|
102
|
+
mode: "box-center" | "remainder";
|
|
103
|
+
x?: number;
|
|
104
|
+
y?: number;
|
|
105
|
+
};
|
|
106
|
+
/** Target height in pixels (integer). Clamped to the configured ceiling. */
|
|
107
|
+
height?: number;
|
|
108
|
+
/** Rotate by a fixed multiple of 90 degrees. `width`/`height` refer to axes after rotation. */
|
|
109
|
+
rotate?: 0 | 90 | 180 | 270;
|
|
110
|
+
/** Saturation multiplier (0 = greyscale, 1 = unchanged). */
|
|
111
|
+
saturation?: number;
|
|
112
|
+
/** AI segmentation — set non-`foreground` pixels transparent. */
|
|
113
|
+
segment?: "foreground";
|
|
114
|
+
/** Sharpen strength (0–10). */
|
|
115
|
+
sharpen?: number;
|
|
116
|
+
/**
|
|
117
|
+
* Algorithm used when a transform enlarges the image (e.g. `fit: "scale-up"`).
|
|
118
|
+
*
|
|
119
|
+
* - `interpolate` — bicubic interpolation (default), may soften detail.
|
|
120
|
+
* - `generate` — AI upscaling for sharper, more detailed enlargements.
|
|
121
|
+
*/
|
|
122
|
+
upscale?: "generate" | "interpolate";
|
|
123
|
+
/** Target width in pixels (integer). Clamped to the configured ceiling. */
|
|
124
|
+
width?: number;
|
|
125
|
+
}
|
|
126
|
+
/** The output image formats Lunora permits (the binding allowlist plus `json` info). */
|
|
127
|
+
type ImageOutputFormat = "image/avif" | "image/gif" | "image/jpeg" | "image/png" | "image/webp";
|
|
128
|
+
/** Options for the terminal `output(...)` call. */
|
|
129
|
+
interface OutputOptions {
|
|
130
|
+
/** Encode animated source frames into the output (WebP/GIF/AVIF). */
|
|
131
|
+
anim?: boolean;
|
|
132
|
+
/** Background color (CSS color) for formats without an alpha channel. */
|
|
133
|
+
background?: string;
|
|
134
|
+
/** Output MIME type. Validated against {@link ImageOutputFormat}. Default `image/webp`. */
|
|
135
|
+
format?: ImageOutputFormat;
|
|
136
|
+
/** Encoder quality 1–100 (lossy formats). */
|
|
137
|
+
quality?: number;
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* The result of `binding.input(...).transform(...).output(...)`. Mirrors the
|
|
141
|
+
* real `ImageTransformationResult` — a `Response`, the content type, and the
|
|
142
|
+
* raw byte stream.
|
|
143
|
+
*/
|
|
144
|
+
interface ImageTransformationResultLike {
|
|
145
|
+
contentType: () => string;
|
|
146
|
+
image: () => ReadableStream<Uint8Array>;
|
|
147
|
+
response: () => Response;
|
|
148
|
+
}
|
|
149
|
+
/** Metadata returned by `binding.info(...)` — format, and (for raster images) dimensions + size. */
|
|
150
|
+
type ImageInfoLike = {
|
|
151
|
+
fileSize: number;
|
|
152
|
+
format: string;
|
|
153
|
+
height: number;
|
|
154
|
+
width: number;
|
|
155
|
+
} | {
|
|
156
|
+
format: string;
|
|
157
|
+
};
|
|
158
|
+
/**
|
|
159
|
+
* Binding-side overlay options for `transformer.draw(image, options)` — the
|
|
160
|
+
* blend/position/opacity knobs. Unlike {@link DrawOverlay} there is no `url`
|
|
161
|
+
* (the overlay bytes are passed as the stream) and no `width`/`height` (the
|
|
162
|
+
* overlay is pre-sized via its own transform); mirrors `ImageDrawOptions`.
|
|
163
|
+
*/
|
|
164
|
+
interface ImageDrawOptions {
|
|
165
|
+
/** Offset, in pixels, from the bottom edge. */
|
|
166
|
+
bottom?: number;
|
|
167
|
+
/** Blend mode for compositing this overlay onto the image. Default `over`. */
|
|
168
|
+
composite?: ImageCompositeMode;
|
|
169
|
+
/** Offset, in pixels, from the left edge. */
|
|
170
|
+
left?: number;
|
|
171
|
+
/** Overlay opacity, `0.0` (transparent) – `1.0` (opaque). */
|
|
172
|
+
opacity?: number;
|
|
173
|
+
/** Tile the overlay across the base image: `true`, or a single axis `"x"`/`"y"`. */
|
|
174
|
+
repeat?: "x" | "y" | boolean;
|
|
175
|
+
/** Offset, in pixels, from the right edge. */
|
|
176
|
+
right?: number;
|
|
177
|
+
/** Offset, in pixels, from the top edge. */
|
|
178
|
+
top?: number;
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* One overlay applied through the **binding** path (`Images.transform`'s
|
|
182
|
+
* `overlays` argument). The overlay bytes come from `image` (any {@link ImageInput});
|
|
183
|
+
* an optional `transform` pre-sizes/reformats the overlay before it is drawn.
|
|
184
|
+
*/
|
|
185
|
+
interface ImageOverlay extends ImageDrawOptions {
|
|
186
|
+
/** The overlay image bytes — a stream, buffer, `Blob`, or R2 object body. */
|
|
187
|
+
image: ImageInput;
|
|
188
|
+
/** Optional transform applied to the overlay before compositing (e.g. resize). */
|
|
189
|
+
transform?: TransformOptions;
|
|
190
|
+
}
|
|
191
|
+
/** One link in the transform chain: apply more transforms, draw an overlay, or finalize with `output`. */
|
|
192
|
+
interface ImageTransformerLike {
|
|
193
|
+
draw: (image: ImageTransformerLike | ReadableStream<Uint8Array>, options?: ImageDrawOptions) => ImageTransformerLike;
|
|
194
|
+
output: (options: OutputOptions) => Promise<ImageTransformationResultLike>;
|
|
195
|
+
transform: (transform: TransformOptions) => ImageTransformerLike;
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
198
|
+
* Minimal projection of `ImagesBinding`. Declared structurally so unit tests can
|
|
199
|
+
* pass a plain object double; the real `env.IMAGES` binding satisfies the same
|
|
200
|
+
* shape. Only the input/transform/output chain and `info` are projected — the
|
|
201
|
+
* hosted-images CRUD surface is out of scope.
|
|
202
|
+
*/
|
|
203
|
+
interface ImagesBindingLike {
|
|
204
|
+
info: (stream: ReadableStream<Uint8Array>) => Promise<ImageInfoLike>;
|
|
205
|
+
input: (stream: ReadableStream<Uint8Array>) => ImageTransformerLike;
|
|
206
|
+
}
|
|
207
|
+
/**
|
|
208
|
+
* An R2 object body (as returned by `ctx.storage.download(key)`) — first-class
|
|
209
|
+
* transform input. Only `.body` is read, so a structural projection is enough.
|
|
210
|
+
*/
|
|
211
|
+
interface R2ObjectBodyLike {
|
|
212
|
+
body: ReadableStream | null;
|
|
213
|
+
}
|
|
214
|
+
/** Anything `transform`/`info` accept as input bytes. R2 bodies are unwrapped to their stream. */
|
|
215
|
+
type ImageInput = ArrayBuffer | Blob | R2ObjectBodyLike | ReadableStream | Uint8Array;
|
|
216
|
+
interface LunoraImagesOptions {
|
|
217
|
+
/** The Cloudflare Images binding (`env.IMAGES`). */
|
|
218
|
+
binding: ImagesBindingLike;
|
|
219
|
+
/** Maximum pixel value any single `width`/`height` may request. Default 10000. */
|
|
220
|
+
maxDimension?: number;
|
|
221
|
+
}
|
|
222
|
+
/**
|
|
223
|
+
* The action-only Images client wired onto `ctx.images`.
|
|
224
|
+
*
|
|
225
|
+
* The binding-backed `transform`/`info` calls are non-deterministic network /
|
|
226
|
+
* compute I/O, so this lives on **ActionCtx only** — the same seam as `ctx.ai`.
|
|
227
|
+
* The pure URL/signed-URL builders are exported as free functions (see
|
|
228
|
+
* `index.ts`) and are safe to call from any handler.
|
|
229
|
+
*/
|
|
230
|
+
interface Images {
|
|
231
|
+
/**
|
|
232
|
+
* Probe an image for its format and (for raster formats) dimensions + byte
|
|
233
|
+
* size, without running a transform. Wraps `binding.info(...)`.
|
|
234
|
+
*/
|
|
235
|
+
info: (input: ImageInput) => Promise<ImageInfoLike>;
|
|
236
|
+
/**
|
|
237
|
+
* Resize / reformat / optimize `input`, returning the transformed result
|
|
238
|
+
* (a `Response`, content type, and byte stream). Accepts a raw stream/buffer,
|
|
239
|
+
* a `Blob`, or an R2 object body straight from `ctx.storage.download(key)`.
|
|
240
|
+
*
|
|
241
|
+
* `transform` dimensions are clamped to the configured ceiling and the output
|
|
242
|
+
* `format` is validated against the allowlist, so a hostile request can't
|
|
243
|
+
* mint a multi-gigapixel canvas or an unexpected content type.
|
|
244
|
+
*
|
|
245
|
+
* `overlays` are composited over the result in order (last on top) via the
|
|
246
|
+
* binding's `draw` step — each overlay's bytes come from its own `image`, with
|
|
247
|
+
* optional per-overlay `transform` (resize/reformat) and blend/position options.
|
|
248
|
+
*/
|
|
249
|
+
transform: (input: ImageInput, transform?: TransformOptions, output?: OutputOptions, overlays?: ImageOverlay[]) => Promise<ImageTransformationResultLike>;
|
|
250
|
+
}
|
|
251
|
+
/**
|
|
252
|
+
* Build the action-only {@link Images} client over a Cloudflare Images binding.
|
|
253
|
+
*
|
|
254
|
+
* ```ts
|
|
255
|
+
* const images = createImages({ binding: env.IMAGES });
|
|
256
|
+
* const result = await images.transform(
|
|
257
|
+
* await ctx.storage.download("uploads/avatar.png"),
|
|
258
|
+
* { width: 256, height: 256, fit: "cover" },
|
|
259
|
+
* { format: "image/webp", quality: 82 },
|
|
260
|
+
* );
|
|
261
|
+
* ```
|
|
262
|
+
*/
|
|
263
|
+
declare const createImages: (options: LunoraImagesOptions) => Images;
|
|
264
|
+
interface ImageDeliveryUrlOptions {
|
|
265
|
+
/** Delivery / transform origin, e.g. `https://cdn.acme.test`. */
|
|
266
|
+
baseUrl: string;
|
|
267
|
+
/**
|
|
268
|
+
* Hosted-Images image id. When set, the **delivery-variant** form is built
|
|
269
|
+
* (`<baseUrl>/<imageId>/<variant>`) and `transform`/`key` are ignored.
|
|
270
|
+
*/
|
|
271
|
+
imageId?: string;
|
|
272
|
+
/**
|
|
273
|
+
* Source image — an absolute URL or an origin-relative key. Used by the
|
|
274
|
+
* `/cdn-cgi/image/...` transform form. Ignored when `imageId` is set.
|
|
275
|
+
*/
|
|
276
|
+
key?: string;
|
|
277
|
+
/** Transform options for the `/cdn-cgi/image/<options>/<source>` form. */
|
|
278
|
+
transform?: TransformOptions;
|
|
279
|
+
/** Named delivery variant (e.g. `public`, `thumbnail`). Used with `imageId`; default `public`. */
|
|
280
|
+
variant?: string;
|
|
281
|
+
}
|
|
282
|
+
/**
|
|
283
|
+
* Build a Cloudflare Images delivery / transform URL.
|
|
284
|
+
*
|
|
285
|
+
* - With `imageId`: `<baseUrl>/<imageId>/<variant>` (hosted delivery variant).
|
|
286
|
+
* - With `key`: `<baseUrl>/cdn-cgi/image/<options>/<source>` (URL-based transform).
|
|
287
|
+
*
|
|
288
|
+
* Pure and deterministic — usable from any handler.
|
|
289
|
+
*/
|
|
290
|
+
declare const buildImageDeliveryUrl: (options: ImageDeliveryUrlOptions) => string;
|
|
291
|
+
interface SignedImageUrlOptions {
|
|
292
|
+
/** Delivery / Worker origin the signed URL points at (e.g. `https://cdn.acme.test`). */
|
|
293
|
+
baseUrl: string;
|
|
294
|
+
/** Seconds the URL stays valid. Default 3600; capped at 7 days. */
|
|
295
|
+
expiresInSeconds?: number;
|
|
296
|
+
/** Image key/id resolved by the Worker route (the pathname). */
|
|
297
|
+
key: string;
|
|
298
|
+
/** HMAC secret. MUST NOT be shared across tenants. */
|
|
299
|
+
secret: string;
|
|
300
|
+
/**
|
|
301
|
+
* Transform requested by this URL. Bound into the signature, so a client
|
|
302
|
+
* can't alter the render without invalidating it. The Worker should apply
|
|
303
|
+
* exactly this (verified) transform via `ctx.images.transform(...)`.
|
|
304
|
+
*/
|
|
305
|
+
transform?: TransformOptions;
|
|
306
|
+
}
|
|
307
|
+
/**
|
|
308
|
+
* Mint a Worker-signed image URL: `baseUrl` joined to `key`, plus `exp` (unix
|
|
309
|
+
* seconds), the serialized `t` (transform), and `sig` (base64url HMAC). The
|
|
310
|
+
* canonical binds host + key + expiry + transform.
|
|
311
|
+
*
|
|
312
|
+
* The Worker handling the route should call {@link verifySignedImageUrl} to
|
|
313
|
+
* validate before serving / transforming.
|
|
314
|
+
*/
|
|
315
|
+
declare const buildSignedImageUrl: (options: SignedImageUrlOptions) => Promise<string>;
|
|
316
|
+
interface VerifyImageResult {
|
|
317
|
+
/** The verified image key (route pathname). */
|
|
318
|
+
key?: string;
|
|
319
|
+
/**
|
|
320
|
+
* Internal-only failure reason for server logs/diagnostics. **Do not echo to
|
|
321
|
+
* clients** — a precise reason ("expired" vs "bad_signature") is a signing
|
|
322
|
+
* oracle. Public responses should expose only `valid`.
|
|
323
|
+
*/
|
|
324
|
+
reason?: "bad_signature" | "expired" | "malformed";
|
|
325
|
+
/** The raw, verified transform string (the `t` query value), when present. */
|
|
326
|
+
transform?: string;
|
|
327
|
+
valid: boolean;
|
|
328
|
+
}
|
|
329
|
+
/**
|
|
330
|
+
* Verify a {@link buildSignedImageUrl} output. By default the signature is
|
|
331
|
+
* canonicalized against the inbound `url.host`; pass `expectedHost` for a
|
|
332
|
+
* CDN/host-rewrite topology where the Worker sees a different host than the one
|
|
333
|
+
* the URL was minted for.
|
|
334
|
+
*/
|
|
335
|
+
declare const verifySignedImageUrl: (input: string | URL, secret: string, options?: {
|
|
336
|
+
expectedHost?: string;
|
|
337
|
+
}) => Promise<VerifyImageResult>;
|
|
338
|
+
export { type DrawOverlay, type ImageCompositeMode, type ImageDeliveryUrlOptions, type ImageDrawOptions, type ImageInfoLike, type ImageInput, type ImageOutputFormat, type ImageOverlay, type ImageTransformationResultLike, type ImageTransformerLike, type Images, type ImagesBindingLike, type LunoraImagesOptions, type OutputOptions, type R2ObjectBodyLike, type SignedImageUrlOptions, type TransformOptions, type VerifyImageResult, buildImageDeliveryUrl, buildSignedImageUrl, createImages, verifySignedImageUrl };
|