sails-sqlite 0.2.5 → 0.2.6

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.
@@ -17,6 +17,16 @@ module.exports = function buildSqliteWhereClause(whereClause, WLModel, meta) {
17
17
  return { clause: '', bindings: [] }
18
18
  }
19
19
 
20
+ // Helper to convert values for SQLite binding
21
+ // SQLite/better-sqlite3 only accepts: numbers, strings, bigints, buffers, and null
22
+ // Booleans must be converted to integers (0/1)
23
+ function toSqliteValue(val) {
24
+ if (typeof val === 'boolean') {
25
+ return val ? 1 : 0
26
+ }
27
+ return val
28
+ }
29
+
20
30
  const bindings = []
21
31
 
22
32
  // Recursively build WHERE clause
@@ -77,36 +87,36 @@ module.exports = function buildSqliteWhereClause(whereClause, WLModel, meta) {
77
87
  if (Array.isArray(operatorValue) && operatorValue.length > 0) {
78
88
  const placeholders = operatorValue.map(() => '?').join(', ')
79
89
  conditions.push(`${columnName} IN (${placeholders})`)
80
- bindings.push(...operatorValue)
90
+ bindings.push(...operatorValue.map(toSqliteValue))
81
91
  }
82
92
  break
83
93
  case 'nin':
84
94
  if (Array.isArray(operatorValue) && operatorValue.length > 0) {
85
95
  const placeholders = operatorValue.map(() => '?').join(', ')
86
96
  conditions.push(`${columnName} NOT IN (${placeholders})`)
87
- bindings.push(...operatorValue)
97
+ bindings.push(...operatorValue.map(toSqliteValue))
88
98
  }
89
99
  break
90
100
  case '>':
91
101
  conditions.push(`${columnName} > ?`)
92
- bindings.push(operatorValue)
102
+ bindings.push(toSqliteValue(operatorValue))
93
103
  break
94
104
  case '>=':
95
105
  conditions.push(`${columnName} >= ?`)
96
- bindings.push(operatorValue)
106
+ bindings.push(toSqliteValue(operatorValue))
97
107
  break
98
108
  case '<':
99
109
  conditions.push(`${columnName} < ?`)
100
- bindings.push(operatorValue)
110
+ bindings.push(toSqliteValue(operatorValue))
101
111
  break
102
112
  case '<=':
103
113
  conditions.push(`${columnName} <= ?`)
104
- bindings.push(operatorValue)
114
+ bindings.push(toSqliteValue(operatorValue))
105
115
  break
106
116
  case '!=':
107
117
  case 'ne':
108
118
  conditions.push(`${columnName} != ?`)
109
- bindings.push(operatorValue)
119
+ bindings.push(toSqliteValue(operatorValue))
110
120
  break
111
121
  case 'like':
112
122
  if (meta && meta.makeLikeModifierCaseInsensitive === true) {
@@ -114,7 +124,7 @@ module.exports = function buildSqliteWhereClause(whereClause, WLModel, meta) {
114
124
  } else {
115
125
  conditions.push(`${columnName} LIKE ?`)
116
126
  }
117
- bindings.push(operatorValue)
127
+ bindings.push(toSqliteValue(operatorValue))
118
128
  break
119
129
  case 'contains':
120
130
  conditions.push(`${columnName} LIKE ?`)
@@ -130,13 +140,13 @@ module.exports = function buildSqliteWhereClause(whereClause, WLModel, meta) {
130
140
  break
131
141
  default:
132
142
  conditions.push(`${columnName} = ?`)
133
- bindings.push(operatorValue)
143
+ bindings.push(toSqliteValue(operatorValue))
134
144
  }
135
145
  })
136
146
  } else {
137
147
  // Simple equality
138
148
  conditions.push(`${columnName} = ?`)
139
- bindings.push(value)
149
+ bindings.push(toSqliteValue(value))
140
150
  }
141
151
  })
142
152
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sails-sqlite",
3
- "version": "0.2.5",
3
+ "version": "0.2.6",
4
4
  "description": "SQLite adapter for Sails/Waterline",
5
5
  "main": "lib",
6
6
  "directories": {
@@ -0,0 +1,345 @@
1
+ const { test, describe, before, after } = require('node:test')
2
+ const assert = require('node:assert')
3
+ const path = require('node:path')
4
+ const fs = require('node:fs')
5
+
6
+ // Import the adapter
7
+ const adapter = require('../lib/index.js')
8
+
9
+ describe('Boolean bindings support', () => {
10
+ let testDbPath
11
+ let datastore
12
+ let models
13
+
14
+ before(async () => {
15
+ testDbPath = path.join(__dirname, `test-boolean-${Date.now()}.sqlite`)
16
+ datastore = {
17
+ identity: 'testDatastore',
18
+ adapter: 'sails-sqlite',
19
+ url: testDbPath
20
+ }
21
+
22
+ models = {
23
+ comment: {
24
+ identity: 'comment',
25
+ tableName: 'comments',
26
+ primaryKey: 'id',
27
+ definition: {
28
+ id: {
29
+ type: 'number',
30
+ autoIncrement: true,
31
+ columnName: 'id'
32
+ },
33
+ text: {
34
+ type: 'string',
35
+ required: true,
36
+ columnName: 'text'
37
+ },
38
+ isResolved: {
39
+ type: 'boolean',
40
+ defaultsTo: false,
41
+ columnName: 'isResolved'
42
+ },
43
+ isPublic: {
44
+ type: 'boolean',
45
+ defaultsTo: true,
46
+ columnName: 'isPublic'
47
+ }
48
+ },
49
+ attributes: {
50
+ id: {
51
+ type: 'number',
52
+ autoIncrement: true,
53
+ columnName: 'id'
54
+ },
55
+ text: {
56
+ type: 'string',
57
+ required: true,
58
+ columnName: 'text'
59
+ },
60
+ isResolved: {
61
+ type: 'boolean',
62
+ defaultsTo: false,
63
+ columnName: 'isResolved'
64
+ },
65
+ isPublic: {
66
+ type: 'boolean',
67
+ defaultsTo: true,
68
+ columnName: 'isPublic'
69
+ }
70
+ }
71
+ }
72
+ }
73
+
74
+ // Register datastore
75
+ await new Promise((resolve, reject) => {
76
+ adapter.registerDatastore(datastore, models, (err) => {
77
+ if (err) return reject(err)
78
+ resolve()
79
+ })
80
+ })
81
+
82
+ // Create table schema
83
+ const tableDef = {
84
+ id: {
85
+ type: 'number',
86
+ primaryKey: true,
87
+ autoIncrement: true,
88
+ required: true
89
+ },
90
+ text: {
91
+ type: 'string',
92
+ required: true
93
+ },
94
+ isResolved: {
95
+ type: 'boolean',
96
+ defaultsTo: false
97
+ },
98
+ isPublic: {
99
+ type: 'boolean',
100
+ defaultsTo: true
101
+ }
102
+ }
103
+
104
+ await new Promise((resolve, reject) => {
105
+ adapter.define('testDatastore', 'comments', tableDef, (err) => {
106
+ if (err) return reject(err)
107
+ resolve()
108
+ })
109
+ })
110
+
111
+ // Create test data
112
+ const testRecords = [
113
+ { text: 'Resolved public comment', isResolved: true, isPublic: true },
114
+ { text: 'Resolved private comment', isResolved: true, isPublic: false },
115
+ { text: 'Unresolved public comment', isResolved: false, isPublic: true },
116
+ { text: 'Unresolved private comment', isResolved: false, isPublic: false }
117
+ ]
118
+
119
+ for (const record of testRecords) {
120
+ await new Promise((resolve, reject) => {
121
+ adapter.create(
122
+ 'testDatastore',
123
+ { using: 'comments', newRecord: record, meta: {} },
124
+ (err) => {
125
+ if (err) return reject(err)
126
+ resolve()
127
+ }
128
+ )
129
+ })
130
+ }
131
+ })
132
+
133
+ after(async () => {
134
+ // Teardown datastore
135
+ await new Promise((resolve, reject) => {
136
+ adapter.teardown('testDatastore', (err) => {
137
+ if (err) return reject(err)
138
+ resolve()
139
+ })
140
+ })
141
+
142
+ // Clean up test database
143
+ if (fs.existsSync(testDbPath)) {
144
+ try {
145
+ fs.unlinkSync(testDbPath)
146
+ } catch (err) {
147
+ // Ignore cleanup errors
148
+ }
149
+ }
150
+ })
151
+
152
+ test('should find records with boolean false in WHERE clause', async () => {
153
+ const findQuery = {
154
+ using: 'comments',
155
+ criteria: {
156
+ where: { isResolved: false }
157
+ }
158
+ }
159
+
160
+ const results = await new Promise((resolve, reject) => {
161
+ adapter.find('testDatastore', findQuery, (err, result) => {
162
+ if (err) return reject(err)
163
+ resolve(result)
164
+ })
165
+ })
166
+
167
+ assert(Array.isArray(results), 'Results should be an array')
168
+ assert.equal(results.length, 2, 'Should find 2 unresolved comments')
169
+ results.forEach((record) => {
170
+ assert.equal(record.isResolved, 0, 'isResolved should be 0 (false)')
171
+ })
172
+ })
173
+
174
+ test('should find records with boolean true in WHERE clause', async () => {
175
+ const findQuery = {
176
+ using: 'comments',
177
+ criteria: {
178
+ where: { isResolved: true }
179
+ }
180
+ }
181
+
182
+ const results = await new Promise((resolve, reject) => {
183
+ adapter.find('testDatastore', findQuery, (err, result) => {
184
+ if (err) return reject(err)
185
+ resolve(result)
186
+ })
187
+ })
188
+
189
+ assert(Array.isArray(results), 'Results should be an array')
190
+ assert.equal(results.length, 2, 'Should find 2 resolved comments')
191
+ results.forEach((record) => {
192
+ assert.equal(record.isResolved, 1, 'isResolved should be 1 (true)')
193
+ })
194
+ })
195
+
196
+ test('should find records with multiple boolean conditions', async () => {
197
+ const findQuery = {
198
+ using: 'comments',
199
+ criteria: {
200
+ where: { isResolved: false, isPublic: true }
201
+ }
202
+ }
203
+
204
+ const results = await new Promise((resolve, reject) => {
205
+ adapter.find('testDatastore', findQuery, (err, result) => {
206
+ if (err) return reject(err)
207
+ resolve(result)
208
+ })
209
+ })
210
+
211
+ assert(Array.isArray(results), 'Results should be an array')
212
+ assert.equal(results.length, 1, 'Should find 1 unresolved public comment')
213
+ assert.equal(results[0].text, 'Unresolved public comment')
214
+ })
215
+
216
+ test('should find records with boolean in OR conditions', async () => {
217
+ const findQuery = {
218
+ using: 'comments',
219
+ criteria: {
220
+ where: {
221
+ or: [{ isResolved: true }, { isPublic: false }]
222
+ }
223
+ }
224
+ }
225
+
226
+ const results = await new Promise((resolve, reject) => {
227
+ adapter.find('testDatastore', findQuery, (err, result) => {
228
+ if (err) return reject(err)
229
+ resolve(result)
230
+ })
231
+ })
232
+
233
+ assert(Array.isArray(results), 'Results should be an array')
234
+ // Should find: 2 resolved + 1 unresolved private (3 total, but 1 overlap)
235
+ assert.equal(results.length, 3, 'Should find 3 comments')
236
+ })
237
+
238
+ test('should update records using boolean in WHERE clause', async () => {
239
+ const updateQuery = {
240
+ using: 'comments',
241
+ criteria: {
242
+ where: { isResolved: false, isPublic: false }
243
+ },
244
+ valuesToSet: { text: 'Updated unresolved private comment' },
245
+ meta: { fetch: true }
246
+ }
247
+
248
+ const results = await new Promise((resolve, reject) => {
249
+ adapter.update('testDatastore', updateQuery, (err, result) => {
250
+ if (err) return reject(err)
251
+ resolve(result)
252
+ })
253
+ })
254
+
255
+ assert(Array.isArray(results), 'Results should be an array')
256
+ assert.equal(results.length, 1, 'Should update 1 record')
257
+ assert.equal(results[0].text, 'Updated unresolved private comment')
258
+ })
259
+
260
+ test('should count records using boolean in WHERE clause', async () => {
261
+ const countQuery = {
262
+ using: 'comments',
263
+ criteria: {
264
+ where: { isPublic: true }
265
+ }
266
+ }
267
+
268
+ const result = await new Promise((resolve, reject) => {
269
+ adapter.count('testDatastore', countQuery, (err, count) => {
270
+ if (err) return reject(err)
271
+ resolve(count)
272
+ })
273
+ })
274
+
275
+ assert.equal(result, 2, 'Should count 2 public comments')
276
+ })
277
+
278
+ test('should destroy records using boolean in WHERE clause', async () => {
279
+ // First create a record to destroy
280
+ await new Promise((resolve, reject) => {
281
+ adapter.create(
282
+ 'testDatastore',
283
+ {
284
+ using: 'comments',
285
+ newRecord: {
286
+ text: 'To be deleted',
287
+ isResolved: true,
288
+ isPublic: true
289
+ },
290
+ meta: {}
291
+ },
292
+ (err) => {
293
+ if (err) return reject(err)
294
+ resolve()
295
+ }
296
+ )
297
+ })
298
+
299
+ // Count before destroy
300
+ const countBefore = await new Promise((resolve, reject) => {
301
+ adapter.count(
302
+ 'testDatastore',
303
+ { using: 'comments', criteria: { where: { isResolved: true } } },
304
+ (err, count) => {
305
+ if (err) return reject(err)
306
+ resolve(count)
307
+ }
308
+ )
309
+ })
310
+
311
+ // Destroy all resolved comments
312
+ const destroyQuery = {
313
+ using: 'comments',
314
+ criteria: {
315
+ where: { isResolved: true }
316
+ }
317
+ }
318
+
319
+ await new Promise((resolve, reject) => {
320
+ adapter.destroy('testDatastore', destroyQuery, (err) => {
321
+ if (err) return reject(err)
322
+ resolve()
323
+ })
324
+ })
325
+
326
+ // Count after destroy
327
+ const countAfter = await new Promise((resolve, reject) => {
328
+ adapter.count(
329
+ 'testDatastore',
330
+ { using: 'comments', criteria: { where: { isResolved: true } } },
331
+ (err, count) => {
332
+ if (err) return reject(err)
333
+ resolve(count)
334
+ }
335
+ )
336
+ })
337
+
338
+ assert(countBefore > 0, 'Should have resolved comments before destroy')
339
+ assert.equal(
340
+ countAfter,
341
+ 0,
342
+ 'Should have no resolved comments after destroy'
343
+ )
344
+ })
345
+ })
package/tests/index.js CHANGED
@@ -6,7 +6,7 @@ const fs = require('node:fs')
6
6
 
7
7
  // __dirname is automatically available in CommonJS
8
8
 
9
- const testFiles = ['transaction.test.js']
9
+ const testFiles = ['transaction.test.js', 'boolean-bindings.test.js']
10
10
 
11
11
  function cleanupTestDatabases() {
12
12
  try {