@pylonsync/sdk 0.4.19 → 0.4.21
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/dist/index.d.ts +14 -1
- package/package.json +1 -1
- package/src/index.ts +25 -2
- package/src/vector-field.test.ts +36 -0
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export type RouteMode = "static" | "server" | "live" | "ssr";
|
|
2
|
-
export type FieldType = "string" | "int" | "float" | "bool" | "datetime" | "richtext" | "json" | `id(${string})`;
|
|
2
|
+
export type FieldType = "string" | "int" | "float" | "bool" | "datetime" | "richtext" | "json" | `id(${string})` | `vector(${number})`;
|
|
3
3
|
/**
|
|
4
4
|
* CRDT container override for a field. Wire format is the kebab-case
|
|
5
5
|
* string each variant maps to (`"text"`, `"counter"`, `"movable-list"`,
|
|
@@ -243,6 +243,19 @@ export declare const field: {
|
|
|
243
243
|
*/
|
|
244
244
|
json: () => FieldBuilder;
|
|
245
245
|
id: (target: string) => FieldBuilder;
|
|
246
|
+
/**
|
|
247
|
+
* `field.vector(1536)` — a fixed-dimension embedding, written as
|
|
248
|
+
* `number[]` and stored as a packed float array (BLOB in SQLite,
|
|
249
|
+
* BYTEA in Postgres). Query it with `ctx.db.vectorSearch(entity,
|
|
250
|
+
* { field, vector, limit })` — exact nearest-neighbor, cosine by
|
|
251
|
+
* default.
|
|
252
|
+
*
|
|
253
|
+
* Vector fields are always server-only: a 1536-dim embedding is
|
|
254
|
+
* 6KB+ per row and belongs to server-side retrieval, not client
|
|
255
|
+
* replicas or HTTP responses. Inserts/updates validate the exact
|
|
256
|
+
* dimension count and reject NaN/Infinity elements.
|
|
257
|
+
*/
|
|
258
|
+
vector: (dims: number) => FieldBuilder;
|
|
246
259
|
/**
|
|
247
260
|
* `field.enum(["pending", "paid", "failed"])` — stored as a string with
|
|
248
261
|
* allowed-values metadata so codegen emits a precise literal union
|
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -16,7 +16,8 @@ export type FieldType =
|
|
|
16
16
|
| "datetime"
|
|
17
17
|
| "richtext"
|
|
18
18
|
| "json"
|
|
19
|
-
| `id(${string})
|
|
19
|
+
| `id(${string})`
|
|
20
|
+
| `vector(${number})`;
|
|
20
21
|
|
|
21
22
|
// ---------------------------------------------------------------------------
|
|
22
23
|
// Field builder
|
|
@@ -318,6 +319,26 @@ export const field = {
|
|
|
318
319
|
*/
|
|
319
320
|
json: () => createFieldBuilder("json"),
|
|
320
321
|
id: (target: string) => createFieldBuilder(`id(${target})`),
|
|
322
|
+
/**
|
|
323
|
+
* `field.vector(1536)` — a fixed-dimension embedding, written as
|
|
324
|
+
* `number[]` and stored as a packed float array (BLOB in SQLite,
|
|
325
|
+
* BYTEA in Postgres). Query it with `ctx.db.vectorSearch(entity,
|
|
326
|
+
* { field, vector, limit })` — exact nearest-neighbor, cosine by
|
|
327
|
+
* default.
|
|
328
|
+
*
|
|
329
|
+
* Vector fields are always server-only: a 1536-dim embedding is
|
|
330
|
+
* 6KB+ per row and belongs to server-side retrieval, not client
|
|
331
|
+
* replicas or HTTP responses. Inserts/updates validate the exact
|
|
332
|
+
* dimension count and reject NaN/Infinity elements.
|
|
333
|
+
*/
|
|
334
|
+
vector: (dims: number) => {
|
|
335
|
+
if (!Number.isInteger(dims) || dims < 1 || dims > 8192) {
|
|
336
|
+
throw new Error(
|
|
337
|
+
`field.vector(dims): dims must be an integer in 1..8192, got ${dims}`
|
|
338
|
+
);
|
|
339
|
+
}
|
|
340
|
+
return createFieldBuilder(`vector(${dims})`);
|
|
341
|
+
},
|
|
321
342
|
/**
|
|
322
343
|
* `field.enum(["pending", "paid", "failed"])` — stored as a string with
|
|
323
344
|
* allowed-values metadata so codegen emits a precise literal union
|
|
@@ -970,7 +991,9 @@ export function entitiesToManifest(
|
|
|
970
991
|
if (fb._def.crdt !== undefined) {
|
|
971
992
|
f.crdt = fb._def.crdt;
|
|
972
993
|
}
|
|
973
|
-
if (fb._def.serverOnly) {
|
|
994
|
+
if (fb._def.serverOnly || fb._def.type.startsWith("vector(")) {
|
|
995
|
+
// Vector fields are unconditionally server-only: multi-KB
|
|
996
|
+
// embeddings must never ride the sync stream or HTTP reads.
|
|
974
997
|
f.serverOnly = true;
|
|
975
998
|
}
|
|
976
999
|
if (fb._def.syncOmit) {
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `field.vector(dims)` manifest contract:
|
|
3
|
+
* - the wire type string is `vector(<dims>)`
|
|
4
|
+
* - vector fields serialize with `serverOnly: true` unconditionally
|
|
5
|
+
* (multi-KB embeddings must never enter sync or HTTP reads)
|
|
6
|
+
* - dims outside 1..8192 throw at schema-definition time
|
|
7
|
+
*/
|
|
8
|
+
import { expect, test } from "bun:test";
|
|
9
|
+
import { entitiesToManifest, field } from "./index";
|
|
10
|
+
|
|
11
|
+
test("field.vector serializes as vector(dims) with forced serverOnly", () => {
|
|
12
|
+
const [ent] = entitiesToManifest([
|
|
13
|
+
{
|
|
14
|
+
name: "Doc",
|
|
15
|
+
fields: {
|
|
16
|
+
title: field.string(),
|
|
17
|
+
embedding: field.vector(1536).optional(),
|
|
18
|
+
},
|
|
19
|
+
},
|
|
20
|
+
]);
|
|
21
|
+
const emb = ent.fields.find((f) => f.name === "embedding")!;
|
|
22
|
+
expect(emb.type).toBe("vector(1536)");
|
|
23
|
+
expect(emb.optional).toBe(true);
|
|
24
|
+
expect(emb.serverOnly).toBe(true);
|
|
25
|
+
// Non-vector fields don't inherit the forcing.
|
|
26
|
+
const title = ent.fields.find((f) => f.name === "title")!;
|
|
27
|
+
expect(title.serverOnly).toBeUndefined();
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
test("field.vector rejects out-of-range dims", () => {
|
|
31
|
+
expect(() => field.vector(0)).toThrow();
|
|
32
|
+
expect(() => field.vector(1.5)).toThrow();
|
|
33
|
+
expect(() => field.vector(8193)).toThrow();
|
|
34
|
+
expect(() => field.vector(8192)).not.toThrow();
|
|
35
|
+
expect(() => field.vector(1)).not.toThrow();
|
|
36
|
+
});
|