@lunora/bindings 0.0.0 → 1.0.0-alpha.10

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 (41) hide show
  1. package/LICENSE.md +111 -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 +271 -0
  11. package/dist/kv/index.d.ts +271 -0
  12. package/dist/kv/index.mjs +2 -0
  13. package/dist/packem_shared/AnalyticsSqlError-C2nz3jpH.mjs +41 -0
  14. package/dist/packem_shared/R2SqlError-drPKSCZ3.mjs +65 -0
  15. package/dist/packem_shared/SelectBuilder-BOqJQHEv.mjs +168 -0
  16. package/dist/packem_shared/SetOperation-DmPgUL8W.mjs +81 -0
  17. package/dist/packem_shared/Sql-B3zq2YGx.mjs +74 -0
  18. package/dist/packem_shared/WindowExpression-BT_uA6g1.mjs +44 -0
  19. package/dist/packem_shared/WindowFunction-DrnuZUF6.mjs +82 -0
  20. package/dist/packem_shared/asc-DZbQCxh1.mjs +16 -0
  21. package/dist/packem_shared/buildImageDeliveryUrl-qZ7XbqTL.mjs +35 -0
  22. package/dist/packem_shared/buildSignedImageUrl-DNUFfyGP.mjs +130 -0
  23. package/dist/packem_shared/concurrent-CkCEVwqP.mjs +39 -0
  24. package/dist/packem_shared/createAnalytics-CEEI69o9.mjs +57 -0
  25. package/dist/packem_shared/createContextVectors-DwZtnPeC.mjs +140 -0
  26. package/dist/packem_shared/createImages-BzRnsz3H.mjs +85 -0
  27. package/dist/packem_shared/createKv-C8Iyu5hD.mjs +145 -0
  28. package/dist/packem_shared/createKvIntrospector-Byk4GfsY.mjs +77 -0
  29. package/dist/packem_shared/createPipelines-CfyJ6VGu.mjs +10 -0
  30. package/dist/packem_shared/createVectorAdminIntrospector-DuSvcBa5.mjs +53 -0
  31. package/dist/packem_shared/createVectors-CTSrctiK.mjs +95 -0
  32. package/dist/pipelines/index.d.mts +41 -0
  33. package/dist/pipelines/index.d.ts +41 -0
  34. package/dist/pipelines/index.mjs +1 -0
  35. package/dist/r2sql/index.d.mts +383 -0
  36. package/dist/r2sql/index.d.ts +383 -0
  37. package/dist/r2sql/index.mjs +7 -0
  38. package/dist/vectors/index.d.mts +285 -0
  39. package/dist/vectors/index.d.ts +285 -0
  40. package/dist/vectors/index.mjs +3 -0
  41. package/package.json +57 -4
@@ -0,0 +1,85 @@
1
+ import { LunoraError } from '@lunora/errors';
2
+
3
+ const ALLOWED_OUTPUT_FORMATS = /* @__PURE__ */ new Set(["image/avif", "image/gif", "image/jpeg", "image/png", "image/webp"]);
4
+ const DEFAULT_MAX_DIMENSION = 1e4;
5
+ const DEFAULT_OUTPUT_FORMAT = "image/webp";
6
+ const isR2ObjectBody = (input) => {
7
+ if (typeof input !== "object") {
8
+ return false;
9
+ }
10
+ const object = input;
11
+ return "body" in object && !(input instanceof ArrayBuffer) && !(input instanceof Uint8Array);
12
+ };
13
+ const toStream = (input) => {
14
+ if (input instanceof ReadableStream) {
15
+ return input;
16
+ }
17
+ if (isR2ObjectBody(input)) {
18
+ if (input.body === null) {
19
+ throw new LunoraError("INTERNAL", "@lunora/bindings/images: R2 object body is null (object missing or already consumed)");
20
+ }
21
+ return input.body;
22
+ }
23
+ if (input instanceof Blob) {
24
+ return input.stream();
25
+ }
26
+ const bytes = input instanceof Uint8Array ? input : new Uint8Array(input);
27
+ return new ReadableStream({
28
+ start(controller) {
29
+ controller.enqueue(bytes);
30
+ controller.close();
31
+ }
32
+ });
33
+ };
34
+ const sanitizeTransform = (transform, maxDimension) => {
35
+ if (transform === void 0) {
36
+ return {};
37
+ }
38
+ const clampDimension = (value) => {
39
+ if (!Number.isFinite(value) || value <= 0) {
40
+ throw new TypeError("@lunora/bindings/images: width/height must be a positive finite number");
41
+ }
42
+ return Math.min(Math.floor(value), maxDimension);
43
+ };
44
+ const rest = { ...transform };
45
+ delete rest.draw;
46
+ return {
47
+ ...rest,
48
+ ...transform.width === void 0 ? {} : { width: clampDimension(transform.width) },
49
+ ...transform.height === void 0 ? {} : { height: clampDimension(transform.height) }
50
+ };
51
+ };
52
+ const splitOverlay = (overlay) => {
53
+ const { image, transform, ...drawOptions } = overlay;
54
+ return { drawOptions, image, transform };
55
+ };
56
+ const resolveOutput = (output) => {
57
+ const format = output?.format ?? DEFAULT_OUTPUT_FORMAT;
58
+ if (!ALLOWED_OUTPUT_FORMATS.has(format)) {
59
+ throw new LunoraError(
60
+ "INTERNAL",
61
+ `@lunora/bindings/images: unsupported output format "${format}" (allowed: ${[...ALLOWED_OUTPUT_FORMATS].join(", ")})`
62
+ );
63
+ }
64
+ return { ...output, format };
65
+ };
66
+ const createImages = (options) => {
67
+ const { binding } = options;
68
+ const maxDimension = options.maxDimension ?? DEFAULT_MAX_DIMENSION;
69
+ return {
70
+ info: async (input) => binding.info(toStream(input)),
71
+ transform: async (input, transform, output, overlays) => {
72
+ const safeTransform = sanitizeTransform(transform, maxDimension);
73
+ const outputOptions = resolveOutput(output);
74
+ let transformer = binding.input(toStream(input)).transform(safeTransform);
75
+ for (const overlay of overlays ?? []) {
76
+ const { drawOptions, image, transform: overlayTransform } = splitOverlay(overlay);
77
+ const overlayImage = overlayTransform === void 0 ? toStream(image) : binding.input(toStream(image)).transform(sanitizeTransform(overlayTransform, maxDimension));
78
+ transformer = transformer.draw(overlayImage, drawOptions);
79
+ }
80
+ return transformer.output(outputOptions);
81
+ }
82
+ };
83
+ };
84
+
85
+ export { createImages };
@@ -0,0 +1,145 @@
1
+ import { LunoraError } from '@lunora/errors';
2
+
3
+ const MAX_KEY_LENGTH = 512;
4
+ const MAX_LIST_LIMIT = 1e3;
5
+ const TEXT_ENCODER = new TextEncoder();
6
+ const byteLength = (value) => TEXT_ENCODER.encode(value).length;
7
+ const validateKey = (key) => {
8
+ if (typeof key !== "string" || key.length === 0) {
9
+ throw new TypeError("@lunora/bindings/kv: key must be a non-empty string");
10
+ }
11
+ if (byteLength(key) > MAX_KEY_LENGTH) {
12
+ throw new LunoraError("INTERNAL", `@lunora/bindings/kv: key exceeds ${String(MAX_KEY_LENGTH)}-byte limit`);
13
+ }
14
+ if (key.includes("\0")) {
15
+ throw new LunoraError("INTERNAL", "@lunora/bindings/kv: key contains NUL byte");
16
+ }
17
+ const segments = key.split("/");
18
+ for (const segment of segments) {
19
+ if (segment === "." || segment === "..") {
20
+ throw new LunoraError("INTERNAL", "@lunora/bindings/kv: key contains a `.`/`..` path component");
21
+ }
22
+ }
23
+ };
24
+ const validatePrefix = (prefix) => {
25
+ if (prefix.length === 0) {
26
+ return;
27
+ }
28
+ if (byteLength(prefix) > MAX_KEY_LENGTH) {
29
+ throw new LunoraError("INTERNAL", `@lunora/bindings/kv: prefix exceeds ${String(MAX_KEY_LENGTH)}-byte limit`);
30
+ }
31
+ if (prefix.includes("\0")) {
32
+ throw new LunoraError("INTERNAL", "@lunora/bindings/kv: prefix contains NUL byte");
33
+ }
34
+ const segments = prefix.split("/");
35
+ for (const segment of segments) {
36
+ if (segment === "." || segment === "..") {
37
+ throw new LunoraError("INTERNAL", "@lunora/bindings/kv: prefix contains a `.`/`..` path component");
38
+ }
39
+ }
40
+ };
41
+ const scopeKey = (prefix, key) => {
42
+ validateKey(prefix);
43
+ validateKey(key);
44
+ const trimmedPrefix = prefix.endsWith("/") ? prefix.slice(0, -1) : prefix;
45
+ const composed = `${trimmedPrefix}/${key}`;
46
+ if (byteLength(composed) > MAX_KEY_LENGTH) {
47
+ throw new LunoraError("INTERNAL", `@lunora/bindings/kv: scoped key exceeds ${String(MAX_KEY_LENGTH)}-byte limit`);
48
+ }
49
+ return composed;
50
+ };
51
+ const toPutOptions = (options) => {
52
+ const out = {};
53
+ if (options.expiration !== void 0 && options.expirationTtl !== void 0) {
54
+ throw new LunoraError("INTERNAL", "@lunora/bindings/kv: `expiration` and `expirationTtl` are mutually exclusive");
55
+ }
56
+ if (options.expiration !== void 0) {
57
+ out.expiration = options.expiration;
58
+ }
59
+ if (options.expirationTtl !== void 0) {
60
+ out.expirationTtl = options.expirationTtl;
61
+ }
62
+ if (options.metadata !== void 0) {
63
+ out.metadata = options.metadata;
64
+ }
65
+ return Object.keys(out).length > 0 ? out : void 0;
66
+ };
67
+ const createKv = (options) => {
68
+ if (!options.namespace) {
69
+ throw new TypeError("@lunora/bindings/kv: `namespace` is required");
70
+ }
71
+ const { keyPrefix, namespace } = options;
72
+ if (keyPrefix !== void 0) {
73
+ validateKey(keyPrefix);
74
+ }
75
+ const resolve = (key) => {
76
+ if (keyPrefix === void 0) {
77
+ validateKey(key);
78
+ return key;
79
+ }
80
+ return scopeKey(keyPrefix, key);
81
+ };
82
+ const stripPrefix = (name) => {
83
+ if (keyPrefix === void 0) {
84
+ return name;
85
+ }
86
+ const trimmed = keyPrefix.endsWith("/") ? keyPrefix : `${keyPrefix}/`;
87
+ return name.startsWith(trimmed) ? name.slice(trimmed.length) : name;
88
+ };
89
+ const get = async (key, getOptions = {}) => {
90
+ const value = await namespace.get(resolve(key), { cacheTtl: getOptions.cacheTtl, type: "json" });
91
+ return value ?? null;
92
+ };
93
+ const getRaw = async (key, getOptions = {}) => {
94
+ const value = await namespace.get(resolve(key), { cacheTtl: getOptions.cacheTtl, type: getOptions.type ?? "text" });
95
+ return value ?? null;
96
+ };
97
+ const getWithMetadata = async (key, getOptions = {}) => {
98
+ const result = await namespace.getWithMetadata(resolve(key), { cacheTtl: getOptions.cacheTtl, type: "json" });
99
+ return { metadata: result.metadata ?? null, value: result.value ?? null };
100
+ };
101
+ const put = async (key, value, putOptions = {}) => {
102
+ const body = putOptions.raw ? value : JSON.stringify(value);
103
+ await namespace.put(resolve(key), body, toPutOptions(putOptions));
104
+ };
105
+ const deleteKey = async (key) => {
106
+ await namespace.delete(resolve(key));
107
+ };
108
+ const list = async (listOptions = {}) => {
109
+ if (listOptions.limit !== void 0 && (!Number.isInteger(listOptions.limit) || listOptions.limit <= 0)) {
110
+ throw new TypeError("@lunora/bindings/kv: `limit` must be a positive integer");
111
+ }
112
+ let { prefix } = listOptions;
113
+ if (prefix !== void 0) {
114
+ validatePrefix(prefix);
115
+ }
116
+ if (keyPrefix !== void 0) {
117
+ const base = keyPrefix.endsWith("/") ? keyPrefix : `${keyPrefix}/`;
118
+ prefix = listOptions.prefix === void 0 ? base : `${base}${listOptions.prefix}`;
119
+ validatePrefix(prefix);
120
+ }
121
+ const limit = listOptions.limit === void 0 ? void 0 : Math.min(listOptions.limit, MAX_LIST_LIMIT);
122
+ const result = await namespace.list({ cursor: listOptions.cursor, limit, prefix });
123
+ const keys = result.keys.map((entry) => {
124
+ return {
125
+ ...entry,
126
+ name: stripPrefix(entry.name)
127
+ };
128
+ });
129
+ return {
130
+ cursor: result.list_complete ? void 0 : result.cursor,
131
+ keys,
132
+ listComplete: result.list_complete
133
+ };
134
+ };
135
+ return {
136
+ delete: deleteKey,
137
+ get,
138
+ getRaw,
139
+ getWithMetadata,
140
+ list,
141
+ put
142
+ };
143
+ };
144
+
145
+ export { createKv, scopeKey };
@@ -0,0 +1,77 @@
1
+ import { LunoraError } from '@lunora/errors';
2
+
3
+ const createKvIntrospector = (options) => {
4
+ const { namespaces } = options;
5
+ const resolveNamespace = (binding) => {
6
+ const ns = namespaces[binding];
7
+ if (!Object.hasOwn(namespaces, binding) || ns === void 0) {
8
+ throw new LunoraError("BAD_REQUEST", `@lunora/bindings/kv: no namespace registered under binding "${binding}"`);
9
+ }
10
+ return ns;
11
+ };
12
+ const listNamespaces = () => Promise.resolve(
13
+ Object.keys(namespaces).map((binding) => {
14
+ return { binding };
15
+ })
16
+ );
17
+ const listKeys = async (listOptions) => {
18
+ const ns = resolveNamespace(listOptions.namespace);
19
+ const result = await ns.list({ cursor: listOptions.cursor, limit: listOptions.limit, prefix: listOptions.prefix });
20
+ const keys = result.keys.map((entry) => {
21
+ return {
22
+ expiration: entry.expiration,
23
+ metadata: entry.metadata,
24
+ name: entry.name
25
+ };
26
+ });
27
+ return {
28
+ cursor: result.list_complete ? void 0 : result.cursor,
29
+ keys,
30
+ listComplete: result.list_complete
31
+ };
32
+ };
33
+ const getValue = async (getOptions) => {
34
+ const ns = resolveNamespace(getOptions.namespace);
35
+ const result = await ns.getWithMetadata(getOptions.key, "text");
36
+ return { metadata: result.metadata ?? null, value: result.value ?? null };
37
+ };
38
+ const putValue = async (putOptions) => {
39
+ const ns = resolveNamespace(putOptions.namespace);
40
+ await ns.put(putOptions.key, putOptions.value, {
41
+ expiration: putOptions.expiration,
42
+ expirationTtl: putOptions.expirationTtl,
43
+ metadata: putOptions.metadata
44
+ });
45
+ };
46
+ const deleteKey = async (deleteOptions) => {
47
+ const ns = resolveNamespace(deleteOptions.namespace);
48
+ await ns.delete(deleteOptions.key);
49
+ };
50
+ return {
51
+ deleteKey,
52
+ getValue,
53
+ listKeys,
54
+ listNamespaces,
55
+ putValue
56
+ };
57
+ };
58
+ const isKvNamespace = (value) => {
59
+ if (typeof value !== "object" || value === null) {
60
+ return false;
61
+ }
62
+ const candidate = value;
63
+ return typeof candidate.getWithMetadata === "function" && typeof candidate.list === "function" && typeof candidate.put === "function" && typeof candidate.delete === "function";
64
+ };
65
+ const createKvIntrospectorFromEnv = (env) => {
66
+ const namespaces = {};
67
+ if (typeof env === "object" && env !== null) {
68
+ for (const [binding, value] of Object.entries(env)) {
69
+ if (isKvNamespace(value)) {
70
+ namespaces[binding] = value;
71
+ }
72
+ }
73
+ }
74
+ return createKvIntrospector({ namespaces });
75
+ };
76
+
77
+ export { createKvIntrospector, createKvIntrospectorFromEnv };
@@ -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,53 @@
1
+ import { LunoraError } from '@lunora/errors';
2
+
3
+ const MAX_TOP_K = 20;
4
+ const DEFAULT_TOP_K = 10;
5
+ const createVectorAdminIntrospector = (options) => {
6
+ const { embedders, indexes, registry } = options;
7
+ const listIndexes = async () => Promise.all(
8
+ registry.map(async (entry) => {
9
+ const binding = indexes[entry.name];
10
+ if (binding?.describe === void 0) {
11
+ return { ...entry };
12
+ }
13
+ try {
14
+ const details = await binding.describe();
15
+ return {
16
+ ...entry,
17
+ dimensions: entry.dimensions ?? details.dimensions,
18
+ processedUpToMutation: details.processedUpToMutation,
19
+ vectorsCount: details.vectorsCount
20
+ };
21
+ } catch {
22
+ return { ...entry };
23
+ }
24
+ })
25
+ );
26
+ const hasEmbedders = embedders !== void 0 && Object.keys(embedders).length > 0;
27
+ if (!hasEmbedders) {
28
+ return { listIndexes };
29
+ }
30
+ const queryIndex = async ({ name, text, topK }) => {
31
+ const binding = indexes[name];
32
+ if (!Object.hasOwn(indexes, name) || binding === void 0) {
33
+ throw new LunoraError("INTERNAL", `@lunora/bindings/vectors: no Vectorize binding registered for index "${name}"`);
34
+ }
35
+ const embed = embedders[name];
36
+ if (!Object.hasOwn(embedders, name) || embed === void 0) {
37
+ throw new LunoraError("INTERNAL", `@lunora/bindings/vectors: no embedder registered for index "${name}" — it lists read-only`);
38
+ }
39
+ const vector = await embed(text);
40
+ const result = await binding.query(vector, {
41
+ returnMetadata: "all",
42
+ topK: Math.min(topK ?? DEFAULT_TOP_K, MAX_TOP_K)
43
+ });
44
+ return {
45
+ matches: result.matches.map((match) => {
46
+ return { id: match.id, metadata: match.metadata, score: match.score };
47
+ })
48
+ };
49
+ };
50
+ return { listIndexes, queryIndex };
51
+ };
52
+
53
+ export { createVectorAdminIntrospector };
@@ -0,0 +1,95 @@
1
+ import { LunoraError } from '@lunora/errors';
2
+ import { c as concurrentMap, U as UPSERT_EMBED_CONCURRENCY } from './concurrent-CkCEVwqP.mjs';
3
+
4
+ const resolveIndex = (indexes, name) => {
5
+ const index = indexes[name];
6
+ if (!Object.hasOwn(indexes, name) || index === void 0) {
7
+ throw new LunoraError(
8
+ "INTERNAL",
9
+ `@lunora/bindings/vectors: no index registered for "${name}". Known indexes: ${Object.keys(indexes).join(", ") || "(none)"}`
10
+ );
11
+ }
12
+ return index;
13
+ };
14
+ const toVector = async (input) => {
15
+ const values = await input.embed(input.input);
16
+ return {
17
+ id: input.id,
18
+ metadata: input.metadata,
19
+ namespace: input.namespace,
20
+ values
21
+ };
22
+ };
23
+ const MAX_TOP_K = 100;
24
+ const MAX_TOP_K_WITH_VALUES = 20;
25
+ const MAX_ID_BATCH = 1e3;
26
+ const MAX_UPSERT_BATCH = 1e3;
27
+ const createVectors = (options) => {
28
+ if (Object.keys(options.indexes).length === 0) {
29
+ throw new TypeError("@lunora/bindings/vectors: at least one index binding is required");
30
+ }
31
+ const upsert = async (indexName, input) => {
32
+ const index = resolveIndex(options.indexes, indexName);
33
+ const vector = await toVector(input);
34
+ return index.upsert([vector]);
35
+ };
36
+ const upsertMany = async (indexName, inputs) => {
37
+ const index = resolveIndex(options.indexes, indexName);
38
+ if (inputs.length > MAX_UPSERT_BATCH) {
39
+ throw new RangeError(
40
+ `@lunora/bindings/vectors: upsertMany batch exceeds ${String(MAX_UPSERT_BATCH)} (got ${String(inputs.length)}) — split across calls`
41
+ );
42
+ }
43
+ const vectors = await concurrentMap(inputs, UPSERT_EMBED_CONCURRENCY, toVector);
44
+ return index.upsert(vectors);
45
+ };
46
+ const query = async (indexName, input) => {
47
+ const index = resolveIndex(options.indexes, indexName);
48
+ const wantsHeavyPayload = input.returnValues === true || input.returnMetadata === "all";
49
+ const topKCeiling = wantsHeavyPayload ? MAX_TOP_K_WITH_VALUES : MAX_TOP_K;
50
+ if (input.topK !== void 0 && (!Number.isInteger(input.topK) || input.topK < 1 || input.topK > topKCeiling)) {
51
+ const reason = wantsHeavyPayload ? ' (lowered to 20 because returnValues/returnMetadata:"all" is set)' : "";
52
+ throw new RangeError(`@lunora/bindings/vectors: topK must be an integer in [1, ${String(topKCeiling)}]${reason} (got ${String(input.topK)})`);
53
+ }
54
+ let values;
55
+ if (input.vector && input.vector.length > 0) {
56
+ values = input.vector;
57
+ } else {
58
+ if (!input.embed || input.input === void 0) {
59
+ throw new TypeError("@lunora/bindings/vectors: query requires either `vector` or both `input` and `embed`");
60
+ }
61
+ values = await input.embed(input.input);
62
+ }
63
+ return index.query(values, {
64
+ filter: input.filter,
65
+ namespace: input.namespace,
66
+ returnMetadata: input.returnMetadata,
67
+ returnValues: input.returnValues,
68
+ topK: input.topK
69
+ });
70
+ };
71
+ const getByIds = async (indexName, ids) => {
72
+ const index = resolveIndex(options.indexes, indexName);
73
+ if (ids.length > MAX_ID_BATCH) {
74
+ throw new RangeError(`@lunora/bindings/vectors: getByIds accepts at most ${String(MAX_ID_BATCH)} ids (got ${String(ids.length)})`);
75
+ }
76
+ return index.getByIds(ids);
77
+ };
78
+ const deleteByIds = async (indexName, ids) => {
79
+ const index = resolveIndex(options.indexes, indexName);
80
+ if (ids.length > MAX_ID_BATCH) {
81
+ throw new RangeError(`@lunora/bindings/vectors: deleteByIds accepts at most ${String(MAX_ID_BATCH)} ids (got ${String(ids.length)})`);
82
+ }
83
+ return index.deleteByIds(ids);
84
+ };
85
+ const describe = async (indexName) => {
86
+ const index = resolveIndex(options.indexes, indexName);
87
+ if (!index.describe) {
88
+ throw new LunoraError("INTERNAL", `@lunora/bindings/vectors: binding for "${indexName}" does not implement describe()`);
89
+ }
90
+ return index.describe();
91
+ };
92
+ return { deleteByIds, describe, getByIds, query, upsert, upsertMany };
93
+ };
94
+
95
+ 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';