@lunora/bindings 1.0.0-alpha.11 → 1.0.0-alpha.13

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 (45) hide show
  1. package/dist/analytics/index.mjs +1 -2
  2. package/dist/images/index.mjs +1 -3
  3. package/dist/kv/index.mjs +1 -2
  4. package/dist/packem_shared/AnalyticsSqlError-B-JcsUhX.mjs +1 -0
  5. package/dist/packem_shared/R2SqlError-Uaz0RJTR.mjs +1 -0
  6. package/dist/packem_shared/SelectBuilder-WMBQ2b4q.mjs +1 -0
  7. package/dist/packem_shared/SetOperation-BrzMB1Tb.mjs +1 -0
  8. package/dist/packem_shared/Sql-CwhZl1Q5.mjs +1 -0
  9. package/dist/packem_shared/WindowExpression-CxCuEy8V.mjs +1 -0
  10. package/dist/packem_shared/WindowFunction-rzl4CzYE.mjs +1 -0
  11. package/dist/packem_shared/asc-BsbdtIQU.mjs +1 -0
  12. package/dist/packem_shared/buildImageDeliveryUrl-eARoj3MG.mjs +1 -0
  13. package/dist/packem_shared/buildSignedImageUrl-Bpu3Hs42.mjs +4 -0
  14. package/dist/packem_shared/concurrent-DMFQCILU.mjs +1 -0
  15. package/dist/packem_shared/createAnalytics-HRPCzT1M.mjs +1 -0
  16. package/dist/packem_shared/createContextVectors-HVMMJ-88.mjs +6 -0
  17. package/dist/packem_shared/createImages-TW6ircG2.mjs +1 -0
  18. package/dist/packem_shared/createKv-CspEaB58.mjs +1 -0
  19. package/dist/packem_shared/createKvIntrospector-Bz0nqUjD.mjs +1 -0
  20. package/dist/packem_shared/createPipelines-n0Bf_p8Y.mjs +1 -0
  21. package/dist/packem_shared/createVectorAdminIntrospector-B9Yt09yj.mjs +1 -0
  22. package/dist/packem_shared/createVectors-jb8fAY6R.mjs +1 -0
  23. package/dist/pipelines/index.mjs +1 -1
  24. package/dist/r2sql/index.mjs +1 -7
  25. package/dist/vectors/index.mjs +1 -3
  26. package/package.json +2 -2
  27. package/dist/packem_shared/AnalyticsSqlError-C2nz3jpH.mjs +0 -41
  28. package/dist/packem_shared/R2SqlError-drPKSCZ3.mjs +0 -65
  29. package/dist/packem_shared/SelectBuilder-BOqJQHEv.mjs +0 -168
  30. package/dist/packem_shared/SetOperation-DmPgUL8W.mjs +0 -81
  31. package/dist/packem_shared/Sql-B3zq2YGx.mjs +0 -74
  32. package/dist/packem_shared/WindowExpression-BT_uA6g1.mjs +0 -44
  33. package/dist/packem_shared/WindowFunction-DrnuZUF6.mjs +0 -82
  34. package/dist/packem_shared/asc-DZbQCxh1.mjs +0 -16
  35. package/dist/packem_shared/buildImageDeliveryUrl-qZ7XbqTL.mjs +0 -35
  36. package/dist/packem_shared/buildSignedImageUrl-DNUFfyGP.mjs +0 -130
  37. package/dist/packem_shared/concurrent-CkCEVwqP.mjs +0 -39
  38. package/dist/packem_shared/createAnalytics-CEEI69o9.mjs +0 -57
  39. package/dist/packem_shared/createContextVectors-DwZtnPeC.mjs +0 -140
  40. package/dist/packem_shared/createImages-BzRnsz3H.mjs +0 -85
  41. package/dist/packem_shared/createKv-C8Iyu5hD.mjs +0 -145
  42. package/dist/packem_shared/createKvIntrospector-Byk4GfsY.mjs +0 -77
  43. package/dist/packem_shared/createPipelines-CfyJ6VGu.mjs +0 -10
  44. package/dist/packem_shared/createVectorAdminIntrospector-DuSvcBa5.mjs +0 -53
  45. package/dist/packem_shared/createVectors-CTSrctiK.mjs +0 -95
@@ -1,44 +0,0 @@
1
- import { Sql, lit } from './Sql-B3zq2YGx.mjs';
2
-
3
- const IDENTIFIER = /^[A-Z_][\w$]*$/i;
4
- const assertIdent = (name) => {
5
- if (!IDENTIFIER.test(name)) {
6
- throw new TypeError(`r2sql: invalid identifier ${JSON.stringify(name)} — expected a simple SQL name (letters, digits, underscore).`);
7
- }
8
- return name;
9
- };
10
- class WindowExpression extends Sql {
11
- /** Alias the expression — `... AS alias` — for use in a `SELECT` list. */
12
- as(alias) {
13
- return new Sql(`${this.text} AS ${assertIdent(alias)}`);
14
- }
15
- /** `expr BETWEEN low AND high`. */
16
- between(low, high) {
17
- return new Sql(`${this.text} BETWEEN ${lit(low)} AND ${lit(high)}`);
18
- }
19
- /** `expr = value`. */
20
- eq(value) {
21
- return this.compare("=", value);
22
- }
23
- /** `expr > value`. */
24
- gt(value) {
25
- return this.compare(">", value);
26
- }
27
- /** `expr >= value`. */
28
- gte(value) {
29
- return this.compare(">=", value);
30
- }
31
- /** `expr < value`. */
32
- lt(value) {
33
- return this.compare("<", value);
34
- }
35
- /** `expr &lt;= value`. */
36
- lte(value) {
37
- return this.compare("<=", value);
38
- }
39
- compare(operator, value) {
40
- return new Sql(`${this.text} ${operator} ${lit(value)}`);
41
- }
42
- }
43
-
44
- export { WindowExpression as default };
@@ -1,82 +0,0 @@
1
- import { renderOrderTerm } from './asc-DZbQCxh1.mjs';
2
- import { lit, toText } from './Sql-B3zq2YGx.mjs';
3
- import WindowExpression from './WindowExpression-BT_uA6g1.mjs';
4
-
5
- const toArray = (value) => {
6
- if (value === void 0) {
7
- return [];
8
- }
9
- return Array.isArray(value) ? value : [value];
10
- };
11
- const renderOver = (spec) => {
12
- const clauses = [];
13
- const partitions = toArray(spec.partitionBy).map((part) => toText(part));
14
- if (partitions.length > 0) {
15
- clauses.push(`PARTITION BY ${partitions.join(", ")}`);
16
- }
17
- const orders = toArray(spec.orderBy).map((order) => renderOrderTerm(order));
18
- if (orders.length > 0) {
19
- clauses.push(`ORDER BY ${orders.join(", ")}`);
20
- }
21
- if (spec.frame !== void 0 && spec.frame.length > 0) {
22
- clauses.push(spec.frame);
23
- }
24
- return `OVER (${clauses.join(" ")})`;
25
- };
26
- const offsetArguments = (column, offset, fallback) => {
27
- const parts = [toText(column)];
28
- if (offset !== void 0) {
29
- parts.push(lit(offset));
30
- }
31
- if (fallback !== void 0) {
32
- parts.push(lit(fallback));
33
- }
34
- return parts.join(", ");
35
- };
36
- class WindowFunction {
37
- callText;
38
- constructor(callText) {
39
- this.callText = callText;
40
- }
41
- /** Attach the window frame, yielding a {@link WindowExpression}. */
42
- over(spec = {}) {
43
- return new WindowExpression(`${this.callText} ${renderOver(spec)}`);
44
- }
45
- }
46
- const windowFunction = (callText) => new WindowFunction(callText);
47
- const fn = {
48
- /** `AVG(column) OVER (...)`. */
49
- avg: (column) => windowFunction(`AVG(${toText(column)})`),
50
- /** `COUNT(column) OVER (...)` — omit the column for `COUNT(*)`. */
51
- count: (column) => windowFunction(`COUNT(${column === void 0 ? "*" : toText(column)})`),
52
- /** `CUME_DIST() OVER (...)`. */
53
- cumeDist: () => windowFunction("CUME_DIST()"),
54
- /** `DENSE_RANK() OVER (...)`. */
55
- denseRank: () => windowFunction("DENSE_RANK()"),
56
- /** `FIRST_VALUE(column) OVER (...)`. */
57
- firstValue: (column) => windowFunction(`FIRST_VALUE(${toText(column)})`),
58
- /** `LAG(column[, offset[, default]]) OVER (...)`. */
59
- lag: (column, offset, fallback) => windowFunction(`LAG(${offsetArguments(column, offset, fallback)})`),
60
- /** `LAST_VALUE(column) OVER (...)`. */
61
- lastValue: (column) => windowFunction(`LAST_VALUE(${toText(column)})`),
62
- /** `LEAD(column[, offset[, default]]) OVER (...)`. */
63
- lead: (column, offset, fallback) => windowFunction(`LEAD(${offsetArguments(column, offset, fallback)})`),
64
- /** `MAX(column) OVER (...)`. */
65
- max: (column) => windowFunction(`MAX(${toText(column)})`),
66
- /** `MIN(column) OVER (...)`. */
67
- min: (column) => windowFunction(`MIN(${toText(column)})`),
68
- /** `NTH_VALUE(column, n) OVER (...)`. */
69
- nthValue: (column, n) => windowFunction(`NTH_VALUE(${toText(column)}, ${lit(n)})`),
70
- /** `NTILE(buckets) OVER (...)`. */
71
- ntile: (buckets) => windowFunction(`NTILE(${lit(buckets)})`),
72
- /** `PERCENT_RANK() OVER (...)`. */
73
- percentRank: () => windowFunction("PERCENT_RANK()"),
74
- /** `RANK() OVER (...)`. */
75
- rank: () => windowFunction("RANK()"),
76
- /** `ROW_NUMBER() OVER (...)`. */
77
- rowNumber: () => windowFunction("ROW_NUMBER()"),
78
- /** `SUM(column) OVER (...)`. */
79
- sum: (column) => windowFunction(`SUM(${toText(column)})`)
80
- };
81
-
82
- export { WindowFunction, fn };
@@ -1,16 +0,0 @@
1
- import { Sql, toText } from './Sql-B3zq2YGx.mjs';
2
-
3
- const asc = (expr) => {
4
- return { dir: "ASC", expr };
5
- };
6
- const desc = (expr) => {
7
- return { dir: "DESC", expr };
8
- };
9
- const renderOrderTerm = (term) => {
10
- if (typeof term === "string" || term instanceof Sql) {
11
- return toText(term);
12
- }
13
- return `${toText(term.expr)} ${term.dir}`;
14
- };
15
-
16
- export { asc, desc, renderOrderTerm };
@@ -1,35 +0,0 @@
1
- import { LunoraError } from '@lunora/errors';
2
-
3
- const ABSOLUTE_URL_RE = /^[a-z][a-z\d+\-.]*:\/\//i;
4
- const FORBIDDEN_VALUE_CHARS = [",", "=", "#", "?", "/"];
5
- const stripTrailingSlash = (value) => value.endsWith("/") ? value.slice(0, -1) : value;
6
- const stripLeadingSlash = (value) => value.startsWith("/") ? value.slice(1) : value;
7
- const serializeTransform = (transform) => Object.entries(transform).filter(([, value]) => value !== void 0 && (typeof value === "string" || typeof value === "number")).map(([key, value]) => {
8
- const serialized = String(value);
9
- const offendingChar = FORBIDDEN_VALUE_CHARS.find((char) => serialized.includes(char));
10
- if (offendingChar !== void 0) {
11
- throw new LunoraError(
12
- "INTERNAL",
13
- `@lunora/bindings/images: transform option \`${key}\` value \`${serialized}\` contains a \`${offendingChar}\`, which the /cdn-cgi/image/ option path cannot represent (\`,\`/\`=\` are the option/key-value separators; \`#\`/\`?\`/\`/\` are URL-structural). Percent-encode the value — for colors use \`%23RRGGBB\`, not \`#RRGGBB\` or \`rgb(r,g,b)\`.`
14
- );
15
- }
16
- return `${key}=${serialized}`;
17
- }).join(",");
18
- const encodeKey = (key) => key.split("/").map((segment) => encodeURIComponent(segment)).join("/");
19
- const buildImageDeliveryUrl = (options) => {
20
- const base = stripTrailingSlash(options.baseUrl);
21
- if (options.imageId !== void 0) {
22
- const variant = options.variant ?? "public";
23
- return `${base}/${encodeURIComponent(options.imageId)}/${encodeURIComponent(variant)}`;
24
- }
25
- if (options.key === void 0) {
26
- throw new LunoraError("INTERNAL", "@lunora/bindings/images: buildImageDeliveryUrl requires either `imageId` or `key`");
27
- }
28
- const optionString = options.transform === void 0 ? "" : serializeTransform(options.transform);
29
- const isAbsolute = ABSOLUTE_URL_RE.test(options.key);
30
- const source = isAbsolute ? options.key : `/${encodeKey(stripLeadingSlash(options.key))}`;
31
- const prefix = optionString === "" ? "/cdn-cgi/image" : `/cdn-cgi/image/${optionString}`;
32
- return `${base}${prefix}${source.startsWith("/") ? source : `/${source}`}`;
33
- };
34
-
35
- export { buildImageDeliveryUrl };
@@ -1,130 +0,0 @@
1
- const evictOldestEntry = (map, capacity) => {
2
- if (map.size < capacity) {
3
- return;
4
- }
5
- const oldest = map.keys().next().value;
6
- if (oldest !== void 0) {
7
- map.delete(oldest);
8
- }
9
- };
10
-
11
- const textEncoder = new TextEncoder();
12
- const MAX_SIGNED_URL_TTL_SECONDS = 7 * 24 * 60 * 60;
13
- const SCHEME_PREFIX_RE = /^[a-z][a-z0-9+\-.]*:\/\//i;
14
- const toBase64Url = (bytes) => {
15
- const binary = String.fromCodePoint(...bytes);
16
- return btoa(binary).replaceAll("+", "-").replaceAll("/", "_").replaceAll("=", "");
17
- };
18
- const fromBase64Url = (input) => {
19
- const padded = input.replaceAll("-", "+").replaceAll("_", "/") + "===".slice((input.length + 3) % 4);
20
- const binary = atob(padded);
21
- const bytes = new Uint8Array(binary.length);
22
- for (let index = 0; index < binary.length; index += 1) {
23
- bytes[index] = binary.codePointAt(index) ?? 0;
24
- }
25
- return bytes;
26
- };
27
- const KEY_CACHE_MAX = 64;
28
- const keyCache = /* @__PURE__ */ new Map();
29
- const importHmacKey = async (secret) => {
30
- const cached = keyCache.get(secret);
31
- if (cached) {
32
- return cached;
33
- }
34
- evictOldestEntry(keyCache, KEY_CACHE_MAX);
35
- const keyPromise = crypto.subtle.importKey("raw", textEncoder.encode(secret), { hash: "SHA-256", name: "HMAC" }, false, ["sign", "verify"]);
36
- keyCache.set(secret, keyPromise);
37
- return keyPromise;
38
- };
39
- const extractHost = (input) => {
40
- try {
41
- return new URL(input).host;
42
- } catch {
43
- const noScheme = input.replace(SCHEME_PREFIX_RE, "");
44
- return noScheme.split("/")[0] ?? "";
45
- }
46
- };
47
- const signCanonical = async (secret, canonical) => {
48
- const cryptoKey = await importHmacKey(secret);
49
- const signature = await crypto.subtle.sign("HMAC", cryptoKey, textEncoder.encode(canonical));
50
- return toBase64Url(new Uint8Array(signature));
51
- };
52
- const verifyCanonical = async (secret, canonical, sigBytes) => {
53
- const cryptoKey = await importHmacKey(secret);
54
- return crypto.subtle.verify("HMAC", cryptoKey, sigBytes, textEncoder.encode(canonical));
55
- };
56
-
57
- const LEADING_SLASH_RE = /^\//;
58
- const serializeTransform = (transform) => {
59
- if (transform === void 0) {
60
- return "";
61
- }
62
- return Object.entries(transform).filter(([, value]) => value !== void 0).toSorted(([a], [b]) => (a > b ? 1 : 0) - (a < b ? 1 : 0)).map(([key, value]) => `${key}=${typeof value === "object" ? JSON.stringify(value) : String(value)}`).join("&");
63
- };
64
- const canonicalize = (host, key, exp, transform) => `${host.toLowerCase()}
65
- ${key}
66
- ${String(exp)}
67
- ${transform}`;
68
- const encodeKey = (key) => key.split("/").map((segment) => encodeURIComponent(segment)).join("/");
69
- const buildSignedImageUrl = async (options) => {
70
- const expiresInSeconds = options.expiresInSeconds ?? 60 * 60;
71
- if (!Number.isFinite(expiresInSeconds) || expiresInSeconds <= 0) {
72
- throw new TypeError("@lunora/bindings/images: expiresInSeconds must be a positive finite number");
73
- }
74
- if (expiresInSeconds > MAX_SIGNED_URL_TTL_SECONDS) {
75
- throw new TypeError(`@lunora/bindings/images: expiresInSeconds must not exceed ${String(MAX_SIGNED_URL_TTL_SECONDS)} (7 days)`);
76
- }
77
- let basePath = "";
78
- try {
79
- basePath = new URL(options.baseUrl).pathname;
80
- } catch {
81
- }
82
- if (basePath !== "" && basePath !== "/") {
83
- throw new TypeError(
84
- `@lunora/bindings/images: baseUrl must not carry a path ("${basePath}") — the key is verified from the full URL pathname, so a subpath base would make every signed URL fail verification`
85
- );
86
- }
87
- const exp = Math.floor(Date.now() / 1e3) + expiresInSeconds;
88
- const host = extractHost(options.baseUrl);
89
- const transform = serializeTransform(options.transform);
90
- const normalizedKey = options.key.replace(LEADING_SLASH_RE, "");
91
- const sig = await signCanonical(options.secret, canonicalize(host, normalizedKey, exp, transform));
92
- const base = options.baseUrl.endsWith("/") ? options.baseUrl.slice(0, -1) : options.baseUrl;
93
- const safeKey = encodeKey(normalizedKey);
94
- const tParameter = transform === "" ? "" : `&t=${encodeURIComponent(transform)}`;
95
- return `${base}/${safeKey}?exp=${String(exp)}&sig=${sig}${tParameter}`;
96
- };
97
- const verifySignedImageUrl = async (input, secret, options) => {
98
- let url;
99
- try {
100
- url = input instanceof URL ? input : new URL(input);
101
- } catch {
102
- return { reason: "malformed", valid: false };
103
- }
104
- const expRaw = url.searchParams.get("exp");
105
- const exp = expRaw === null ? Number.NaN : Number(expRaw);
106
- const sig = url.searchParams.get("sig");
107
- const transform = url.searchParams.get("t") ?? "";
108
- if (!sig || !Number.isInteger(exp)) {
109
- return { reason: "malformed", valid: false };
110
- }
111
- if (exp < Math.floor(Date.now() / 1e3)) {
112
- return { reason: "expired", valid: false };
113
- }
114
- let key;
115
- let sigBytes;
116
- try {
117
- key = url.pathname.replace(LEADING_SLASH_RE, "").split("/").map((segment) => decodeURIComponent(segment)).join("/");
118
- sigBytes = fromBase64Url(sig);
119
- } catch {
120
- return { reason: "malformed", valid: false };
121
- }
122
- const host = options?.expectedHost === void 0 ? url.host : extractHost(options.expectedHost);
123
- const valid = await verifyCanonical(secret, canonicalize(host, key, exp, transform), sigBytes);
124
- if (!valid) {
125
- return { reason: "bad_signature", valid: false };
126
- }
127
- return { key, transform: transform === "" ? void 0 : transform, valid: true };
128
- };
129
-
130
- export { buildSignedImageUrl, verifySignedImageUrl };
@@ -1,39 +0,0 @@
1
- const UPSERT_EMBED_CONCURRENCY = 8;
2
- const concurrentMap = async (items, limit, function_) => {
3
- if (items.length === 0) {
4
- return [];
5
- }
6
- const effectiveLimit = Math.max(1, Math.min(limit, items.length));
7
- const results = Array.from({ length: items.length });
8
- let cursor = 0;
9
- let failed = false;
10
- let firstError;
11
- const workers = Array.from({ length: effectiveLimit }, async () => {
12
- for (; ; ) {
13
- if (failed) {
14
- return;
15
- }
16
- const index = cursor;
17
- cursor += 1;
18
- if (index >= items.length) {
19
- return;
20
- }
21
- try {
22
- results[index] = await function_(items[index], index);
23
- } catch (error) {
24
- if (!failed) {
25
- failed = true;
26
- firstError = error;
27
- }
28
- return;
29
- }
30
- }
31
- });
32
- await Promise.all(workers);
33
- if (failed) {
34
- throw firstError;
35
- }
36
- return results;
37
- };
38
-
39
- export { UPSERT_EMBED_CONCURRENCY as U, concurrentMap as c };
@@ -1,57 +0,0 @@
1
- const MAX_BLOBS = 20;
2
- const MAX_DOUBLES = 20;
3
- const MAX_INDEXES = 1;
4
- const MAX_BLOB_BYTES = 16 * 1024;
5
- const MAX_INDEX_BYTES = 96;
6
- const TEXT_ENCODER = new TextEncoder();
7
- const byteLengthOf = (value) => {
8
- if (value === null) {
9
- return 0;
10
- }
11
- if (typeof value === "string") {
12
- return TEXT_ENCODER.encode(value).length;
13
- }
14
- return value.byteLength;
15
- };
16
- const assertWithin = (kind, length, max) => {
17
- if (length > max) {
18
- throw new RangeError(`@lunora/bindings/analytics: a data point may carry at most ${String(max)} ${kind} (got ${String(length)}).`);
19
- }
20
- };
21
- const assertByteBudget = (kind, bytes, max) => {
22
- if (bytes > max) {
23
- throw new RangeError(`@lunora/bindings/analytics: a data point's ${kind} may total at most ${String(max)} bytes (got ${String(bytes)}).`);
24
- }
25
- };
26
- const createAnalytics = (binding) => {
27
- const writeDataPoint = (event) => {
28
- assertWithin("blobs", event.blobs?.length ?? 0, MAX_BLOBS);
29
- assertWithin("doubles", event.doubles?.length ?? 0, MAX_DOUBLES);
30
- assertWithin("indexes", event.indexes?.length ?? 0, MAX_INDEXES);
31
- const blobBytes = (event.blobs ?? []).reduce((total, blob) => total + byteLengthOf(blob), 0);
32
- assertByteBudget("blobs", blobBytes, MAX_BLOB_BYTES);
33
- for (const index of event.indexes ?? []) {
34
- assertByteBudget("index", byteLengthOf(index), MAX_INDEX_BYTES);
35
- }
36
- binding.writeDataPoint(event);
37
- };
38
- const track = (name, event = {}) => {
39
- const dimensionEntries = Object.entries(event.dimensions ?? {});
40
- const metricEntries = Object.entries(event.metrics ?? {});
41
- const blobs = [name, ...dimensionEntries.map(([, value]) => value)];
42
- const doubles = metricEntries.map(([, value]) => value);
43
- const indexes = event.index === void 0 ? [] : [event.index];
44
- writeDataPoint({ blobs, doubles, indexes });
45
- const dimensions = dimensionEntries.map(([field], offset) => {
46
- return { column: `blob${String(offset + 2)}`, field };
47
- });
48
- const metrics = metricEntries.map(([field], offset) => {
49
- return { column: `double${String(offset + 1)}`, field };
50
- });
51
- const index = event.index === void 0 ? null : { column: "index1", field: "index" };
52
- return { dimensions, index, metrics, name };
53
- };
54
- return { track, writeDataPoint };
55
- };
56
-
57
- export { createAnalytics };
@@ -1,140 +0,0 @@
1
- import { c as concurrentMap, U as UPSERT_EMBED_CONCURRENCY } from './concurrent-CkCEVwqP.mjs';
2
-
3
- const createContextVectors = (lunora) => {
4
- const upsert = async (indexName, input) => {
5
- await lunora.upsert(indexName, {
6
- embed: input.embed,
7
- id: input.id,
8
- input: input.input,
9
- metadata: input.metadata,
10
- namespace: input.namespace
11
- });
12
- };
13
- return {
14
- deleteByIds: async (indexName, ids) => {
15
- await lunora.deleteByIds(indexName, ids);
16
- },
17
- getByIds: async (indexName, ids) => {
18
- const records = await lunora.getByIds(indexName, ids);
19
- return records.map((record) => {
20
- return { id: record.id, metadata: record.metadata, values: record.values };
21
- });
22
- },
23
- query: async (indexName, input) => {
24
- const result = await lunora.query(indexName, {
25
- embed: input.embed,
26
- filter: input.filter,
27
- input: input.input,
28
- namespace: input.namespace,
29
- // Default to "indexed" rather than "all": returning every
30
- // metadata field by default leaks whatever was stored on the
31
- // vector (potentially cross-tenant if namespaces aren't wired).
32
- // Callers that need full metadata opt in explicitly via input.
33
- returnMetadata: input.returnMetadata ?? "indexed",
34
- topK: input.topK,
35
- vector: input.vector
36
- });
37
- return {
38
- count: result.count,
39
- matches: result.matches.map((match) => {
40
- return { id: match.id, metadata: match.metadata, score: match.score };
41
- })
42
- };
43
- },
44
- upsert,
45
- upsertNow: upsert
46
- };
47
- };
48
- const sharedNamespaceWarned = /* @__PURE__ */ new Set();
49
- const warnSharedNamespace = (indexName) => {
50
- if (sharedNamespaceWarned.has(indexName)) {
51
- return;
52
- }
53
- sharedNamespaceWarned.add(indexName);
54
- console.warn(
55
- `[@lunora/bindings/vectors] index "${indexName}" syncs vectors without a namespace — in a
56
- multi-tenant/sharded app this exposes one tenant's vectors (and any captured
57
- metadata) to every other tenant, since Vectorize indexes are account-global.
58
- Pass \`namespace\` (the shard/tenant key) on both write and query — query-side
59
- namespace filtering is mandatory for multi-tenant apps. Single-tenant apps that
60
- legitimately have no tenant key suppress this via { allowSharedNamespace: true }.`
61
- );
62
- };
63
- const pickMetadata = (row, fields) => {
64
- const result = {};
65
- for (const field of fields) {
66
- if (field in row) {
67
- result[field] = row[field];
68
- }
69
- }
70
- return result;
71
- };
72
- const createVectorSyncHook = (options) => {
73
- const { allowSharedNamespace, namespace, schema, vectors } = options;
74
- return async (event) => {
75
- const tableDefinition = schema.tables[event.table];
76
- const inlineIndexes = tableDefinition?.vectorIndexes ?? [];
77
- const standaloneIndexes = Object.entries(schema.vectorIndexes).filter(([, definition]) => definition.table === event.table);
78
- if (inlineIndexes.length === 0 && standaloneIndexes.length === 0) {
79
- return;
80
- }
81
- const allIndexNames = [...inlineIndexes.map((index) => index.name), ...standaloneIndexes.map(([name]) => name)];
82
- if (event.op === "delete") {
83
- await Promise.all(allIndexNames.map((name) => vectors.deleteByIds(name, [event.id])));
84
- return;
85
- }
86
- const row = event.doc;
87
- if (!row) {
88
- return;
89
- }
90
- const inlineWithValue = inlineIndexes.map((index) => {
91
- return { index, value: row[index.field] };
92
- });
93
- const inlineToUpsert = inlineWithValue.filter((entry) => entry.value !== void 0 && entry.value !== null);
94
- const inlineToClear = inlineWithValue.filter((entry) => entry.value === void 0 || entry.value === null);
95
- for (const { index, value } of inlineToUpsert) {
96
- if (typeof value !== "string") {
97
- throw new TypeError(
98
- `@lunora/bindings/vectors: inline index "${index.name}" expects a string source at "${index.field}" on table "${event.table}" (got ${typeof value}); use a standalone defineVectorIndex with a select() to derive text from non-string columns`
99
- );
100
- }
101
- }
102
- const operations = [
103
- ...inlineToClear.map((entry) => async () => {
104
- await vectors.deleteByIds(entry.index.name, [event.id]);
105
- }),
106
- ...inlineToUpsert.map((entry) => async () => {
107
- if (!allowSharedNamespace && namespace === void 0) {
108
- warnSharedNamespace(entry.index.name);
109
- }
110
- await vectors.upsert(entry.index.name, {
111
- embed: entry.index.embed,
112
- id: event.id,
113
- input: entry.value,
114
- metadata: entry.index.metadata ? pickMetadata(row, entry.index.metadata) : void 0,
115
- namespace
116
- });
117
- }),
118
- ...standaloneIndexes.map(([name, definition]) => async () => {
119
- if (!allowSharedNamespace && namespace === void 0) {
120
- warnSharedNamespace(name);
121
- }
122
- await vectors.upsert(name, {
123
- embed: definition.embed,
124
- id: event.id,
125
- input: definition.select(row),
126
- metadata: definition.metadata?.(row),
127
- namespace
128
- });
129
- })
130
- ];
131
- try {
132
- await concurrentMap(operations, UPSERT_EMBED_CONCURRENCY, async (operation) => operation());
133
- } catch (error) {
134
- await Promise.allSettled(allIndexNames.map((name) => vectors.deleteByIds(name, [event.id])));
135
- throw error;
136
- }
137
- };
138
- };
139
-
140
- export { createContextVectors, createVectorSyncHook };
@@ -1,85 +0,0 @@
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 };