sails-sqlite 0.0.0 → 0.1.0

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 (44) hide show
  1. package/.github/FUNDING.yml +1 -0
  2. package/.github/workflows/prettier.yml +16 -0
  3. package/.github/workflows/test.yml +16 -0
  4. package/.husky/pre-commit +1 -0
  5. package/.prettierrc.js +5 -0
  6. package/CHANGELOG.md +161 -0
  7. package/LICENSE +21 -0
  8. package/README.md +247 -0
  9. package/lib/index.js +928 -0
  10. package/lib/private/build-std-adapter-method.js +65 -0
  11. package/lib/private/constants/connection.input.js +15 -0
  12. package/lib/private/constants/dry-orm.input.js +23 -0
  13. package/lib/private/constants/meta.input.js +14 -0
  14. package/lib/private/constants/not-unique.exit.js +16 -0
  15. package/lib/private/constants/query.input.js +15 -0
  16. package/lib/private/constants/table-name.input.js +12 -0
  17. package/lib/private/machines/avg-records.js +74 -0
  18. package/lib/private/machines/count-records.js +78 -0
  19. package/lib/private/machines/create-each-record.js +163 -0
  20. package/lib/private/machines/create-manager.js +174 -0
  21. package/lib/private/machines/create-record.js +126 -0
  22. package/lib/private/machines/define-physical-model.js +102 -0
  23. package/lib/private/machines/destroy-manager.js +87 -0
  24. package/lib/private/machines/destroy-records.js +101 -0
  25. package/lib/private/machines/drop-physical-model.js +51 -0
  26. package/lib/private/machines/find-records.js +120 -0
  27. package/lib/private/machines/get-connection.js +54 -0
  28. package/lib/private/machines/join.js +93 -0
  29. package/lib/private/machines/private/build-sqlite-where-clause.js +89 -0
  30. package/lib/private/machines/private/generate-join-sql-query.js +377 -0
  31. package/lib/private/machines/private/process-each-record.js +99 -0
  32. package/lib/private/machines/private/process-native-error.js +59 -0
  33. package/lib/private/machines/private/process-native-record.js +104 -0
  34. package/lib/private/machines/private/reify-values-to-set.js +83 -0
  35. package/lib/private/machines/release-connection.js +70 -0
  36. package/lib/private/machines/set-physical-sequence.js +77 -0
  37. package/lib/private/machines/sum-records.js +75 -0
  38. package/lib/private/machines/update-records.js +145 -0
  39. package/lib/private/machines/verify-model-def.js +38 -0
  40. package/package.json +53 -5
  41. package/tests/adapter.test.js +534 -0
  42. package/tests/datatypes.test.js +293 -0
  43. package/tests/index.js +88 -0
  44. package/tests/sequence.test.js +153 -0
@@ -0,0 +1,93 @@
1
+ const processEachRecord = require('./private/process-each-record')
2
+ const generateJoinSqlQuery = require('./private/generate-join-sql-query')
3
+
4
+ module.exports = {
5
+ friendlyName: 'Join',
6
+
7
+ description: 'Perform a join operation in SQLite using better-sqlite3.',
8
+
9
+ inputs: {
10
+ datastore: {
11
+ description: 'The datastore to use for the query.',
12
+ required: true,
13
+ example: '==='
14
+ },
15
+ models: {
16
+ description:
17
+ 'An object containing all of the model definitions that have been registered.',
18
+ required: true,
19
+ example: '==='
20
+ },
21
+ query: {
22
+ description: 'A normalized Waterline Stage Three Query.',
23
+ required: true,
24
+ example: '==='
25
+ }
26
+ },
27
+
28
+ exits: {
29
+ success: {
30
+ description: 'The query was run successfully.',
31
+ outputType: 'ref'
32
+ },
33
+ error: {
34
+ description: 'An error occurred while performing the query.'
35
+ }
36
+ },
37
+
38
+ fn: async function (inputs, exits) {
39
+ const { datastore, models, query } = inputs
40
+
41
+ try {
42
+ const { joins } = require('waterline-utils')
43
+
44
+ // Convert join criteria using waterline-utils
45
+ const joinCriteria = joins.convertJoinCriteria({
46
+ query,
47
+ getPk: (tableName) => {
48
+ // Find the model by tableName
49
+ let targetModel = null
50
+ for (const modelIdentity in models) {
51
+ if (models[modelIdentity].tableName === tableName) {
52
+ targetModel = models[modelIdentity]
53
+ break
54
+ }
55
+ }
56
+
57
+ if (!targetModel) {
58
+ throw new Error(`No model found with tableName: ${tableName}`)
59
+ }
60
+
61
+ const pkAttrName = targetModel.primaryKey
62
+ const pkDef = targetModel.attributes[pkAttrName]
63
+ return pkDef.columnName || pkAttrName
64
+ }
65
+ })
66
+
67
+ // Generate SQL query using our helper
68
+ const { sql, bindings } = generateJoinSqlQuery(
69
+ joinCriteria,
70
+ models,
71
+ query
72
+ )
73
+
74
+ // Execute the query using the database connection from the datastore
75
+ const db = datastore.manager
76
+ const stmt = db.prepare(sql)
77
+ const results = stmt.all(...bindings)
78
+
79
+ // Process results through the join utility
80
+ const processedResults = joins.processJoinResults({
81
+ query,
82
+ records: results,
83
+ orm: {
84
+ collections: models
85
+ }
86
+ })
87
+
88
+ return exits.success(processedResults)
89
+ } catch (error) {
90
+ return exits.error(error)
91
+ }
92
+ }
93
+ }
@@ -0,0 +1,89 @@
1
+ /**
2
+ * buildSqliteWhereClause()
3
+ *
4
+ * Build a SQLite WHERE clause from the specified S3Q `where` clause.
5
+ * > Note: The provided `where` clause is NOT mutated.
6
+ *
7
+ * @param {Object} whereClause [`where` clause from the criteria of a S3Q]
8
+ * @param {Object} WLModel
9
+ * @param {Object?} meta [`meta` query key from the s3q]
10
+ *
11
+ * @returns {String} [SQLite WHERE clause]
12
+ */
13
+ module.exports = function buildSqliteWhereClause(whereClause, WLModel, meta) {
14
+ // Handle empty `where` clause.
15
+ if (Object.keys(whereClause).length === 0) {
16
+ return ''
17
+ }
18
+
19
+ // Recursively build and return a transformed `where` clause for use with SQLite.
20
+ function recurse(branch, isRoot = true) {
21
+ const clauses = []
22
+ for (const [key, value] of Object.entries(branch)) {
23
+ if (key === 'and' || key === 'or') {
24
+ const subClauses = value.map((subBranch) => recurse(subBranch, false))
25
+ clauses.push(`(${subClauses.join(` ${key.toUpperCase()} `)})`)
26
+ } else {
27
+ clauses.push(buildConstraint(key, value, WLModel, meta))
28
+ }
29
+ }
30
+ return isRoot ? clauses.join(' AND ') : clauses.join(' AND ')
31
+ }
32
+
33
+ return recurse(whereClause)
34
+ }
35
+
36
+ function buildConstraint(columnName, constraint, WLModel, meta) {
37
+ if (typeof constraint !== 'object' || constraint === null) {
38
+ return `${columnName} = ${sqliteEscape(constraint)}`
39
+ }
40
+
41
+ const modifierKind = Object.keys(constraint)[0]
42
+ const modifier = constraint[modifierKind]
43
+
44
+ switch (modifierKind) {
45
+ case '<':
46
+ return `${columnName} < ${sqliteEscape(modifier)}`
47
+ case '<=':
48
+ return `${columnName} <= ${sqliteEscape(modifier)}`
49
+ case '>':
50
+ return `${columnName} > ${sqliteEscape(modifier)}`
51
+ case '>=':
52
+ return `${columnName} >= ${sqliteEscape(modifier)}`
53
+ case '!=':
54
+ return `${columnName} != ${sqliteEscape(modifier)}`
55
+ case 'nin':
56
+ return `${columnName} NOT IN (${modifier.map(sqliteEscape).join(', ')})`
57
+ case 'in':
58
+ return `${columnName} IN (${modifier.map(sqliteEscape).join(', ')})`
59
+ case 'like':
60
+ let likePattern = modifier
61
+ .replace(/^%/, '.*')
62
+ .replace(/([^\\])%/g, '$1.*')
63
+ .replace(/\\%/g, '%')
64
+ likePattern = `^${likePattern}$`
65
+ let clause = `${columnName} REGEXP '${likePattern}'`
66
+ if (meta && meta.makeLikeModifierCaseInsensitive === true) {
67
+ clause = `LOWER(${columnName}) REGEXP '${likePattern.toLowerCase()}'`
68
+ }
69
+ return clause
70
+ default:
71
+ throw new Error(
72
+ `Consistency violation: \`where\` clause modifier \`${modifierKind}\` is not valid! This should never happen-- a stage 3 query should have already been normalized in Waterline core.`
73
+ )
74
+ }
75
+ }
76
+
77
+ function sqliteEscape(value) {
78
+ if (typeof value === 'string') {
79
+ return `'${value.replace(/'/g, "''")}'`
80
+ }
81
+ if (typeof value === 'boolean') {
82
+ // Match the decimal format that SQLite stores (1.0, 0.0)
83
+ return value ? '1.0' : '0.0'
84
+ }
85
+ if (value === null) {
86
+ return 'NULL'
87
+ }
88
+ return value
89
+ }
@@ -0,0 +1,377 @@
1
+ /**
2
+ * Generate SQL query for join operations in SQLite
3
+ * This follows SQLite performance best practices for optimal query performance
4
+ */
5
+
6
+ module.exports = function generateJoinSqlQuery(joinCriteria, models, query) {
7
+ const _ = require('@sailshq/lodash')
8
+
9
+ try {
10
+ // Start building the SELECT clause
11
+ let sqlParts = {
12
+ select: [],
13
+ from: '',
14
+ joins: [],
15
+ where: [],
16
+ orderBy: [],
17
+ limit: '',
18
+ bindings: []
19
+ }
20
+
21
+ // Get the primary table info
22
+ const primaryTableName = query.using
23
+ const primaryModel = _.find(models, { tableName: primaryTableName })
24
+
25
+ if (!primaryModel) {
26
+ throw new Error(`Primary model not found for table: ${primaryTableName}`)
27
+ }
28
+
29
+ // Build SELECT clause - use explicit column names for better performance
30
+ if (query.criteria && query.criteria.select) {
31
+ sqlParts.select = query.criteria.select.map(
32
+ (col) => `${primaryTableName}.${col}`
33
+ )
34
+ } else {
35
+ // Select all columns from primary table with table prefix
36
+ const primaryCols = Object.keys(primaryModel.attributes).map((attr) => {
37
+ const colName = primaryModel.attributes[attr].columnName || attr
38
+ return `${primaryTableName}.${colName}`
39
+ })
40
+ sqlParts.select = primaryCols
41
+ }
42
+
43
+ // Add joined table columns
44
+ if (joinCriteria && joinCriteria.joins) {
45
+ joinCriteria.joins.forEach((join) => {
46
+ const joinModel = _.find(models, { tableName: join.child })
47
+ if (joinModel) {
48
+ const joinCols = Object.keys(joinModel.attributes).map((attr) => {
49
+ const colName = joinModel.attributes[attr].columnName || attr
50
+ return `${join.child}.${colName} as ${join.child}_${colName}`
51
+ })
52
+ sqlParts.select = sqlParts.select.concat(joinCols)
53
+ }
54
+ })
55
+ }
56
+
57
+ // FROM clause
58
+ sqlParts.from = primaryTableName
59
+
60
+ // JOIN clauses
61
+ if (joinCriteria && joinCriteria.joins) {
62
+ joinCriteria.joins.forEach((join) => {
63
+ let joinType = 'INNER JOIN' // Default to inner join
64
+
65
+ // Determine join type based on Waterline criteria
66
+ if (join.criteria && join.criteria.where) {
67
+ // This is a simplified check - you might need more sophisticated logic
68
+ joinType = 'LEFT JOIN'
69
+ }
70
+
71
+ // Build the JOIN clause with proper foreign key relationships
72
+ const joinClause = `${joinType} ${join.child} ON ${primaryTableName}.${join.parentKey} = ${join.child}.${join.childKey}`
73
+ sqlParts.joins.push(joinClause)
74
+ })
75
+ }
76
+
77
+ // WHERE clause - handle both primary and join criteria
78
+ const whereConditions = []
79
+
80
+ if (query.criteria && query.criteria.where) {
81
+ const primaryWhere = buildWhereClause(
82
+ query.criteria.where,
83
+ primaryTableName,
84
+ primaryModel
85
+ )
86
+ if (primaryWhere.clause) {
87
+ whereConditions.push(primaryWhere.clause)
88
+ sqlParts.bindings = sqlParts.bindings.concat(primaryWhere.bindings)
89
+ }
90
+ }
91
+
92
+ // Add join-specific where conditions
93
+ if (joinCriteria && joinCriteria.joins) {
94
+ joinCriteria.joins.forEach((join) => {
95
+ if (join.criteria && join.criteria.where) {
96
+ const joinModel = _.find(models, { tableName: join.child })
97
+ if (joinModel) {
98
+ const joinWhere = buildWhereClause(
99
+ join.criteria.where,
100
+ join.child,
101
+ joinModel
102
+ )
103
+ if (joinWhere.clause) {
104
+ whereConditions.push(joinWhere.clause)
105
+ sqlParts.bindings = sqlParts.bindings.concat(joinWhere.bindings)
106
+ }
107
+ }
108
+ }
109
+ })
110
+ }
111
+
112
+ sqlParts.where = whereConditions
113
+
114
+ // ORDER BY clause
115
+ if (query.criteria && query.criteria.sort) {
116
+ sqlParts.orderBy = query.criteria.sort.map((sortObj) => {
117
+ const key = Object.keys(sortObj)[0]
118
+ const direction = sortObj[key].toUpperCase()
119
+ return `${primaryTableName}.${key} ${direction}`
120
+ })
121
+ }
122
+
123
+ // LIMIT clause
124
+ if (query.criteria && typeof query.criteria.limit === 'number') {
125
+ sqlParts.limit = `LIMIT ${query.criteria.limit}`
126
+
127
+ if (typeof query.criteria.skip === 'number') {
128
+ sqlParts.limit += ` OFFSET ${query.criteria.skip}`
129
+ }
130
+ }
131
+
132
+ // Assemble the final SQL query
133
+ let sql = `SELECT ${sqlParts.select.join(', ')} FROM ${sqlParts.from}`
134
+
135
+ if (sqlParts.joins.length > 0) {
136
+ sql += ' ' + sqlParts.joins.join(' ')
137
+ }
138
+
139
+ if (sqlParts.where.length > 0) {
140
+ sql += ' WHERE ' + sqlParts.where.join(' AND ')
141
+ }
142
+
143
+ if (sqlParts.orderBy.length > 0) {
144
+ sql += ' ORDER BY ' + sqlParts.orderBy.join(', ')
145
+ }
146
+
147
+ if (sqlParts.limit) {
148
+ sql += ' ' + sqlParts.limit
149
+ }
150
+
151
+ return {
152
+ sql: sql,
153
+ bindings: sqlParts.bindings
154
+ }
155
+ } catch (error) {
156
+ throw new Error(`Error generating join SQL query: ${error.message}`)
157
+ }
158
+ }
159
+
160
+ /**
161
+ * Build WHERE clause for a given criteria object
162
+ * This handles parameterized queries for SQL injection protection
163
+ */
164
+ function buildWhereClause(whereObj, tableName, model) {
165
+ const conditions = []
166
+ const bindings = []
167
+
168
+ if (!whereObj || typeof whereObj !== 'object') {
169
+ return { clause: '', bindings: [] }
170
+ }
171
+
172
+ for (const [key, value] of Object.entries(whereObj)) {
173
+ if (key === 'and') {
174
+ // Handle AND conditions
175
+ if (Array.isArray(value)) {
176
+ const andConditions = []
177
+ value.forEach((condition) => {
178
+ const subWhere = buildWhereClause(condition, tableName, model)
179
+ if (subWhere.clause) {
180
+ andConditions.push(subWhere.clause)
181
+ bindings.push(...subWhere.bindings)
182
+ }
183
+ })
184
+ if (andConditions.length > 0) {
185
+ conditions.push(`(${andConditions.join(' AND ')})`)
186
+ }
187
+ }
188
+ } else if (key === 'or') {
189
+ // Handle OR conditions
190
+ if (Array.isArray(value)) {
191
+ const orConditions = []
192
+ value.forEach((condition) => {
193
+ const subWhere = buildWhereClause(condition, tableName, model)
194
+ if (subWhere.clause) {
195
+ orConditions.push(subWhere.clause)
196
+ bindings.push(...subWhere.bindings)
197
+ }
198
+ })
199
+ if (orConditions.length > 0) {
200
+ conditions.push(`(${orConditions.join(' OR ')})`)
201
+ }
202
+ }
203
+ } else {
204
+ // Handle regular field conditions
205
+ const columnName = model.attributes[key]
206
+ ? model.attributes[key].columnName || key
207
+ : key
208
+ const fullColumnName = `${tableName}.${columnName}`
209
+
210
+ if (typeof value === 'object' && value !== null) {
211
+ // Handle operators like >, <, !=, in, etc.
212
+ for (const [operator, operatorValue] of Object.entries(value)) {
213
+ switch (operator) {
214
+ case '>':
215
+ conditions.push(`${fullColumnName} > ?`)
216
+ bindings.push(operatorValue)
217
+ break
218
+ case '<':
219
+ conditions.push(`${fullColumnName} < ?`)
220
+ bindings.push(operatorValue)
221
+ break
222
+ case '>=':
223
+ conditions.push(`${fullColumnName} >= ?`)
224
+ bindings.push(operatorValue)
225
+ break
226
+ case '<=':
227
+ conditions.push(`${fullColumnName} <= ?`)
228
+ bindings.push(operatorValue)
229
+ break
230
+ case '!=':
231
+ case 'ne':
232
+ conditions.push(`${fullColumnName} != ?`)
233
+ bindings.push(operatorValue)
234
+ break
235
+ case 'in':
236
+ if (Array.isArray(operatorValue) && operatorValue.length > 0) {
237
+ const placeholders = operatorValue.map(() => '?').join(', ')
238
+ conditions.push(`${fullColumnName} IN (${placeholders})`)
239
+ bindings.push(...operatorValue)
240
+ }
241
+ break
242
+ case 'nin':
243
+ if (Array.isArray(operatorValue) && operatorValue.length > 0) {
244
+ const placeholders = operatorValue.map(() => '?').join(', ')
245
+ conditions.push(`${fullColumnName} NOT IN (${placeholders})`)
246
+ bindings.push(...operatorValue)
247
+ }
248
+ break
249
+ case 'like':
250
+ conditions.push(`${fullColumnName} LIKE ?`)
251
+ bindings.push(operatorValue)
252
+ break
253
+ case 'contains':
254
+ conditions.push(`${fullColumnName} LIKE ?`)
255
+ bindings.push(`%${operatorValue}%`)
256
+ break
257
+ case 'startsWith':
258
+ conditions.push(`${fullColumnName} LIKE ?`)
259
+ bindings.push(`${operatorValue}%`)
260
+ break
261
+ case 'endsWith':
262
+ conditions.push(`${fullColumnName} LIKE ?`)
263
+ bindings.push(`%${operatorValue}`)
264
+ break
265
+ default:
266
+ // Default to equality
267
+ conditions.push(`${fullColumnName} = ?`)
268
+ bindings.push(operatorValue)
269
+ }
270
+ }
271
+ } else {
272
+ // Simple equality
273
+ conditions.push(`${fullColumnName} = ?`)
274
+ bindings.push(value)
275
+ }
276
+ }
277
+ }
278
+
279
+ return {
280
+ clause: conditions.join(' AND '),
281
+ bindings: bindings
282
+ }
283
+ }
284
+
285
+ function generateSqlQuery(joinCriteria, models) {
286
+ const { parentStatement, joins } = joinCriteria
287
+ const tableName = parentStatement.from
288
+ const model = models[tableName]
289
+
290
+ let sql = `SELECT ${tableName}.*`
291
+ const bindings = []
292
+
293
+ // Add select clauses for joined tables
294
+ joins.forEach((join, index) => {
295
+ const joinModel = models[join.childCollectionIdentity]
296
+ const joinAlias = `t${index + 1}`
297
+ Object.keys(joinModel.definition).forEach((attr) => {
298
+ if (joinModel.definition[attr].columnName) {
299
+ sql += `, ${joinAlias}.${joinModel.definition[attr].columnName} AS ${joinAlias}_${attr}`
300
+ }
301
+ })
302
+ })
303
+
304
+ sql += ` FROM ${tableName}`
305
+
306
+ // Add join clauses
307
+ joins.forEach((join, index) => {
308
+ const joinType = join.type === 'INNER JOIN' ? 'INNER JOIN' : 'LEFT JOIN'
309
+ const joinAlias = `t${index + 1}`
310
+ sql += ` ${joinType} ${join.childCollectionIdentity} AS ${joinAlias} ON `
311
+
312
+ const joinConditions = []
313
+ Object.keys(join.on).forEach((key) => {
314
+ const parentField = model.definition[key].columnName || key
315
+ const childField =
316
+ models[join.childCollectionIdentity].definition[join.on[key]]
317
+ .columnName || join.on[key]
318
+ joinConditions.push(
319
+ `${tableName}.${parentField} = ${joinAlias}.${childField}`
320
+ )
321
+ })
322
+ sql += joinConditions.join(' AND ')
323
+ })
324
+
325
+ // Add where clause
326
+ if (parentStatement.where && Object.keys(parentStatement.where).length > 0) {
327
+ sql += ' WHERE '
328
+ const whereClauses = []
329
+ Object.entries(parentStatement.where).forEach(([key, value]) => {
330
+ if (typeof value === 'object' && value !== null) {
331
+ Object.entries(value).forEach(([operator, operand]) => {
332
+ switch (operator) {
333
+ case 'in':
334
+ whereClauses.push(
335
+ `${tableName}.${key} IN (${operand.map(() => '?').join(', ')})`
336
+ )
337
+ bindings.push(...operand)
338
+ break
339
+ case 'like':
340
+ whereClauses.push(`${tableName}.${key} LIKE ?`)
341
+ bindings.push(operand)
342
+ break
343
+ // Add more operators as needed
344
+ }
345
+ })
346
+ } else {
347
+ whereClauses.push(`${tableName}.${key} = ?`)
348
+ bindings.push(value)
349
+ }
350
+ })
351
+ sql += whereClauses.join(' AND ')
352
+ }
353
+
354
+ // Add order by clause
355
+ if (parentStatement.sort && parentStatement.sort.length > 0) {
356
+ sql +=
357
+ ' ORDER BY ' +
358
+ parentStatement.sort
359
+ .map((sortClause) => {
360
+ const direction = sortClause.dir === 'desc' ? 'DESC' : 'ASC'
361
+ return `${tableName}.${sortClause.attrName} ${direction}`
362
+ })
363
+ .join(', ')
364
+ }
365
+
366
+ // Add limit and skip
367
+ if (parentStatement.limit) {
368
+ sql += ' LIMIT ?'
369
+ bindings.push(parentStatement.limit)
370
+ }
371
+ if (parentStatement.skip) {
372
+ sql += ' OFFSET ?'
373
+ bindings.push(parentStatement.skip)
374
+ }
375
+
376
+ return { sql, bindings }
377
+ }
@@ -0,0 +1,99 @@
1
+ /**
2
+ * Process Each Record
3
+ *
4
+ * Process an array of records, transforming them from their raw SQLite format
5
+ * to the format expected by Waterline. This follows performance best practices.
6
+ */
7
+
8
+ const { eachRecordDeep } = require('waterline-utils')
9
+
10
+ module.exports = function processEachRecord(options) {
11
+ // Validate options
12
+ if (!options || typeof options !== 'object') {
13
+ throw new Error(
14
+ 'Invalid options argument. Options must contain: records, identity, and orm.'
15
+ )
16
+ }
17
+
18
+ const { records, identity, orm } = options
19
+
20
+ if (!Array.isArray(records)) {
21
+ throw new Error(
22
+ 'Invalid option used in options argument. Missing or invalid records.'
23
+ )
24
+ }
25
+ if (typeof identity !== 'string') {
26
+ throw new Error(
27
+ 'Invalid option used in options argument. Missing or invalid identity.'
28
+ )
29
+ }
30
+ if (typeof orm !== 'object') {
31
+ throw new Error(
32
+ 'Invalid option used in options argument. Missing or invalid orm.'
33
+ )
34
+ }
35
+
36
+ // Key the collections by identity instead of column name
37
+ const collections = Object.fromEntries(
38
+ Object.entries(orm.collections).map(([key, val]) => [val.identity, val])
39
+ )
40
+
41
+ // Update the orm object with the keyed collections
42
+ orm.collections = collections
43
+
44
+ // Process each record
45
+ eachRecordDeep(
46
+ records,
47
+ (record, WLModel) => {
48
+ for (const [attrName, attrDef] of Object.entries(WLModel.definition)) {
49
+ const columnName = attrDef.columnName || attrName
50
+
51
+ if (columnName in record) {
52
+ switch (attrDef.type) {
53
+ case 'boolean':
54
+ // SQLite stores booleans as integers, so we need to convert them
55
+ if (typeof record[columnName] !== 'boolean') {
56
+ record[columnName] = record[columnName] === 1
57
+ }
58
+ break
59
+
60
+ case 'json':
61
+ // SQLite stores JSON as text, so we need to parse it
62
+ if (record[columnName] !== null) {
63
+ try {
64
+ record[columnName] = JSON.parse(record[columnName])
65
+ } catch (e) {
66
+ console.warn(
67
+ `Failed to parse JSON for attribute ${attrName}:`,
68
+ e
69
+ )
70
+ }
71
+ }
72
+ break
73
+
74
+ case 'number':
75
+ // Ensure numbers are actually numbers
76
+ record[columnName] = Number(record[columnName])
77
+ break
78
+
79
+ case 'date':
80
+ case 'datetime':
81
+ // SQLite doesn't have a native date type, so we need to parse it
82
+ if (
83
+ record[columnName] &&
84
+ typeof record[columnName] === 'string'
85
+ ) {
86
+ record[columnName] = new Date(record[columnName])
87
+ }
88
+ break
89
+
90
+ // Add more type conversions as needed
91
+ }
92
+ }
93
+ }
94
+ },
95
+ true,
96
+ identity,
97
+ orm
98
+ )
99
+ }