@nuxodin/item 0.6.7 → 0.6.8

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.7",
3
+ "version": "0.6.8",
4
4
  "description": "Reactive state library",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -15,19 +15,32 @@ const indexName = (table, name) => `idx_${table}_${name}`
15
15
  // SQLite: a single integer primary key must be declared inline as
16
16
  // `INTEGER PRIMARY KEY [AUTOINCREMENT]`; AUTOINCREMENT is invalid elsewhere.
17
17
  // Composite or non-integer keys use a table-level PRIMARY KEY constraint.
18
+ function soloIntPrimary(fields, primaries) {
19
+ if (primaries.length !== 1) return
20
+ const prop = fields.find(([n]) => n === primaries[0])?.[1]
21
+ return prop && ['integer', 'boolean'].includes(schemaType(prop)) ? primaries[0] : undefined
22
+ }
23
+
24
+ // What SQLite never emits cannot be a difference either: a fulltext index needs an FTS virtual
25
+ // table, and AUTOINCREMENT exists only on the rowid alias.
26
+ function emittable(prop, isSoloPk) {
27
+ const out = { ...prop }
28
+ if (out['x-index'] === 'fulltext') delete out['x-index']
29
+ if (!isSoloPk) delete out['x-autoincrement']
30
+ return out
31
+ }
32
+
18
33
  function createBody(fields, primaries) {
19
- const pkProp = fields.find(([n]) => n === primaries[0])?.[1]
20
- const soloInt = primaries.length === 1 && pkProp &&
21
- ['integer', 'boolean'].includes(schemaType(pkProp))
34
+ const solo = soloIntPrimary(fields, primaries)
22
35
  const cols = fields.map(([n, f]) => {
23
- const isPk = soloInt && n === primaries[0]
36
+ const isPk = n === solo
24
37
  // Only `INTEGER PRIMARY KEY` is the rowid alias, and AUTOINCREMENT is legal nowhere else —
25
38
  // so a boolean key keeps the integer spelling, which SQLite would otherwise reject.
26
39
  let def = toFieldDef(n, isPk ? { ...f, type: 'integer' } : f)
27
40
  if (isPk) def += f['x-autoincrement'] ? ' PRIMARY KEY AUTOINCREMENT' : ' PRIMARY KEY'
28
41
  return ' ' + def
29
42
  }).join(',\n')
30
- const pk = (!soloInt && primaries.length) ? `,\n PRIMARY KEY (${primaries.map(quoteId).join(', ')})` : ''
43
+ const pk = (!solo && primaries.length) ? `,\n PRIMARY KEY (${primaries.map(quoteId).join(', ')})` : ''
31
44
  return `${cols}${pk}`
32
45
  }
33
46
 
@@ -92,8 +105,9 @@ export async function schemaToDb(schema, query, { force = false, patch = false }
92
105
  const currFields = new Map(tableFields(current.properties[table]))
93
106
  const nextFieldNames = new Set(fields.map(([n]) => n))
94
107
  // A new plain column is an ADD COLUMN; only a new key column needs the rebuild.
108
+ const solo = soloIntPrimary(fields, primaries)
95
109
  const hasChanges = fields.some(([name, prop]) => currFields.has(name)
96
- ? fieldNeedsDdl(prop, currFields.get(name))
110
+ ? fieldNeedsDdl(emittable(prop, name === solo), currFields.get(name))
97
111
  : prop['x-index'] === 'primary' || prop['x-autoincrement'])
98
112
  const hasDrops = !patch && [...currFields.keys()].some(n => !nextFieldNames.has(n))
99
113
 
@@ -17,9 +17,11 @@ export function toFieldDef(name, prop) {
17
17
  else if (t === 'number') type = 'REAL'
18
18
  else if (t === 'object' || t === 'array') type = 'TEXT' // JSON as text
19
19
  else {
20
- if (prop.format === 'date') type = 'TEXT'
21
- else if (prop.format === 'time') type = 'TEXT'
22
- else if (prop.format === 'date-time') type = 'TEXT'
20
+ // Same reasoning as BOOLEAN: the declared name is the only carrier of the format, and
21
+ // schemaFromField reads these back. TEXT would round-trip to a plain string.
22
+ if (prop.format === 'date') type = 'DATE'
23
+ else if (prop.format === 'time') type = 'TIME'
24
+ else if (prop.format === 'date-time') type = 'DATETIME'
23
25
  else if (prop.contentEncoding === 'base64') type = 'BLOB'
24
26
  else if (prop.maxLength) type = `VARCHAR(${prop.maxLength})`
25
27
  else type = 'TEXT'