@nuxodin/item 0.6.14 → 0.6.15

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nuxodin/item",
3
- "version": "0.6.14",
3
+ "version": "0.6.15",
4
4
  "description": "Reactive state library",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -78,6 +78,11 @@ export function schemaFromField(row) {
78
78
  prop.type = "integer";
79
79
  prop.minimum = 1901;
80
80
  prop.maximum = 2155;
81
+ } else if (type === "vector") {
82
+ // the length belongs to the application, not the schema — see to-field.js
83
+ prop.type = "array";
84
+ prop.items = { type: "number" };
85
+ prop["x-vector"] = true;
81
86
  } else if (type === "json") prop.type = "object";
82
87
  else if (type === "enum") {
83
88
  prop.type = "string";
@@ -34,7 +34,7 @@ function setEqual(a, b) {
34
34
  function secondaryIndexKind(prop) {
35
35
  if (prop["x-index"] === "unique") return "UNIQUE KEY";
36
36
  if (prop["x-index"] === "fulltext") return "FULLTEXT KEY";
37
- if (prop["x-index"] === true) return "KEY";
37
+ if (prop["x-index"] === true) return prop["x-vector"] ? "VECTOR INDEX" : "KEY";
38
38
  return null;
39
39
  }
40
40
 
@@ -233,6 +233,8 @@ async function indexStatements(query, table, nextFields, currFields, { patch = f
233
233
  const currentKind = current
234
234
  ? current.indexType === "FULLTEXT"
235
235
  ? "FULLTEXT KEY"
236
+ : current.indexType === "VECTOR"
237
+ ? "VECTOR INDEX"
236
238
  : current.nonUnique
237
239
  ? "KEY"
238
240
  : "UNIQUE KEY"
@@ -15,6 +15,9 @@ export function toFieldDef(name, prop, { required = false } = {}) {
15
15
  if (schemaType === "boolean") type = "TINYINT(1)";
16
16
  else if (schemaType === "integer") ({ type, unsigned } = integerType(prop));
17
17
  else if (schemaType === "number") type = prop.multipleOf ? "DECIMAL" : "DOUBLE";
18
+ // `x-vector: true` leaves the length to the application (MariaDB needs one: it starts at 1 and
19
+ // widens with MODIFY, padding existing vectors with zeros); a number fixes it.
20
+ else if (prop["x-vector"]) type = `VECTOR(${prop["x-vector"] === true ? 1 : prop["x-vector"]})`;
18
21
  else if (schemaType === "object" || schemaType === "array") type = "JSON";
19
22
  else type = stringType(prop);
20
23
 
@@ -43,6 +43,10 @@ export function schemaFromField(row) {
43
43
  } else if (type.startsWith('time') || dataType.startsWith('time')) {
44
44
  prop.type = 'string'
45
45
  prop.format = 'time'
46
+ } else if (type === 'vector') {
47
+ prop.type = 'array'
48
+ prop.items = { type: 'number' }
49
+ prop['x-vector'] = true
46
50
  } else if (type === 'json' || type === 'jsonb') prop.type = 'object'
47
51
  else prop.type = 'string'
48
52
 
@@ -33,6 +33,8 @@ function preserveDefault(prop, currProp) {
33
33
  }
34
34
 
35
35
  function secondaryIndexKind(table, name, prop) {
36
+ // hnsw needs a fixed length and an operator class; the application builds vector indexes
37
+ if (prop['x-vector']) return null
36
38
  if (prop['x-index'] === 'unique') return 'UNIQUE INDEX'
37
39
  if (prop['x-index'] === true) return 'INDEX'
38
40
  if (prop['x-index'] === 'fulltext')
@@ -171,6 +173,10 @@ export async function schemaToDb(schema, query, { force = false, patch = false }
171
173
  }
172
174
  }
173
175
 
176
+ // x-vector columns are pgvector's type; the extension has to exist before they do
177
+ const vectors = nextTables.some(t => tableData(schema.properties[t]).fields.some(([, f]) => f['x-vector']))
178
+ if (vectors && stmts.length) stmts.unshift('CREATE EXTENSION IF NOT EXISTS vector;')
179
+
174
180
  for (const stmt of stmts) {
175
181
  console.log(stmt)
176
182
  await query(stmt)
@@ -17,6 +17,7 @@ export function typeSql(prop) {
17
17
  if (t === 'boolean') return 'BOOLEAN'
18
18
  if (t === 'integer') return integerType(prop)
19
19
  if (t === 'number') return prop.multipleOf ? 'NUMERIC' : 'DOUBLE PRECISION'
20
+ if (prop['x-vector']) return prop['x-vector'] === true ? 'vector' : `vector(${prop['x-vector']})` // pgvector
20
21
  if (t === 'object' || t === 'array') return 'JSONB'
21
22
  if (prop.format in formats) return formats[prop.format]
22
23
  if (prop.contentEncoding === 'base64') return 'BYTEA'
@@ -32,6 +32,7 @@ export function schemaFromField(row) {
32
32
  } else if (type === 'date') { prop.type = 'string'; prop.format = 'date'
33
33
  } else if (type === 'time') { prop.type = 'string'; prop.format = 'time'
34
34
  } else if (type === 'datetime' || type === 'timestamp') { prop.type = 'string'; prop.format = 'date-time'
35
+ } else if (type === 'vector') { prop.type = 'array'; prop.items = { type: 'number' }; prop['x-vector'] = true
35
36
  } else if (type === 'json') { prop.type = 'object'
36
37
  } else {
37
38
  prop.type = 'string'
@@ -47,6 +47,7 @@ function createBody(table, fields, primaries) {
47
47
  }
48
48
 
49
49
  function indexKind(table, name, prop) {
50
+ if (prop['x-vector']) return null // sqlite-vec scans; a B-tree over vectors helps nothing
50
51
  if (prop['x-index'] === 'unique') return 'UNIQUE INDEX'
51
52
  if (prop['x-index'] === true) return 'INDEX'
52
53
  if (prop['x-index'] === 'fulltext')
@@ -15,6 +15,7 @@ export function toFieldDef(name, prop) {
15
15
  if (t === 'boolean') type = 'BOOLEAN'
16
16
  else if (t === 'integer') type = 'INTEGER'
17
17
  else if (t === 'number') type = 'REAL'
18
+ else if (prop['x-vector']) type = 'VECTOR' // blobs keep their bytes; the name round-trips
18
19
  else if (t === 'object' || t === 'array') type = 'TEXT' // JSON as text
19
20
  else {
20
21
  // Same reasoning as BOOLEAN: the declared name is the only carrier of the format, and