jsql-neo 3.0.0 → 3.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.
package/README.md CHANGED
@@ -1,514 +1,41 @@
1
- ```
2
-
3
-
4
- JJJJJJJJJJJ SSSSSSSSSSSSSSS QQQQQQQQQ LLLLLLLLLLL
5
- J:::::::::J SS:::::::::::::::S QQ:::::::::QQ L:::::::::L
6
- J:::::::::JS:::::SSSSSS::::::S QQ:::::::::::::QQ L:::::::::L
7
- JJ:::::::JJS:::::S SSSSSSSQ:::::::QQQ:::::::QLL:::::::LL
8
- J:::::J S:::::S Q::::::O Q::::::Q L:::::L nnnn nnnnnnnn eeeeeeeeeeee ooooooooooo
9
- J:::::J S:::::S Q:::::O Q:::::Q L:::::L n:::nn::::::::nn ee::::::::::::ee oo:::::::::::oo
10
- J:::::J S::::SSSS Q:::::O Q:::::Q L:::::L n::::::::::::::nn e::::::eeeee:::::eeo:::::::::::::::o
11
- J:::::j SS::::::SSSSS Q:::::O Q:::::Q L:::::L --------------- nn:::::::::::::::ne::::::e e:::::eo:::::ooooo:::::o
12
- J:::::J SSS::::::::SS Q:::::O Q:::::Q L:::::L -:::::::::::::- n:::::nnnn:::::ne:::::::eeeee::::::eo::::o o::::o
13
- JJJJJJJ J:::::J SSSSSS::::S Q:::::O Q:::::Q L:::::L --------------- n::::n n::::ne:::::::::::::::::e o::::o o::::o
14
- J:::::J J:::::J S:::::SQ:::::O QQQQ:::::Q L:::::L n::::n n::::ne::::::eeeeeeeeeee o::::o o::::o
15
- J::::::J J::::::J S:::::SQ::::::O Q::::::::Q L:::::L LLLLLL n::::n n::::ne:::::::e o::::o o::::o
16
- J:::::::JJJ:::::::J SSSSSSS S:::::SQ:::::::QQ::::::::QLL:::::::LLLLLLLLL:::::L n::::n n::::ne::::::::e o:::::ooooo:::::o
17
- JJ:::::::::::::JJ S::::::SSSSSS:::::S QQ::::::::::::::Q L::::::::::::::::::::::L n::::n n::::n e::::::::eeeeeeee o:::::::::::::::o
18
- JJ:::::::::JJ S:::::::::::::::SS QQ:::::::::::Q L::::::::::::::::::::::L n::::n n::::n ee:::::::::::::e oo:::::::::::oo
19
- JJJJJJJJJ SSSSSSSSSSSSSSS QQQQQQQQ::::QQLLLLLLLLLLLLLLLLLLLLLLLL nnnnnn nnnnnn eeeeeeeeeeeeee ooooooooooo
20
- Q:::::Q
21
- QQQQQQ
22
- ```
23
-
24
- # JSQL-NEO
25
-
26
- **Pure JavaScript Embedded Database — SQL 风格 API,零原生依赖,JSON 文件存储**
27
-
28
- [![npm version](https://img.shields.io/npm/v/jsql-neo.svg)](https://www.npmjs.com/package/jsql-neo)
29
- [![license](https://img.shields.io/npm/l/jsql-neo.svg)](https://github.com/vexify-org/jsql-neo/blob/main/LICENSE)
30
- [![node](https://img.shields.io/node/v/jsql-neo.svg)](https://nodejs.org/)
31
-
32
- > **v2.0.0** — B-Tree 索引、哈希 JOIN、WAL 日志、事务隔离、MySQL 风格错误码
33
-
34
- ---
35
-
36
- ## 快速开始
37
-
38
- ```bash
39
- npm install jsql-neo
40
- ```
41
-
42
- ```javascript
43
- const jsql = require('jsql-neo');
44
-
45
- // 内存模式
46
- const db = new jsql.Database(':memory:');
47
-
48
- // 文件模式(带 WAL + 文件锁)
49
- const db = new jsql.Database('mydb.json', {
50
- wal: true,
51
- fileLock: true,
52
- isolationLevel: 'REPEATABLE_READ',
53
- slowQueryThreshold: 100
54
- });
55
-
56
- // 创建表(自动为 primaryKey 和 unique 字段建 B-Tree 索引)
57
- db.createTable('users', {
58
- id: { type: 'integer', primaryKey: true, autoIncrement: true },
59
- name: { type: 'string', required: true, length: 50 },
60
- email: { type: 'string', unique: true },
61
- age: { type: 'integer', min: 0, max: 150 },
62
- createdAt: { type: 'datetime', default: 'CURRENT_TIMESTAMP' }
63
- });
64
-
65
- // 插入
66
- db.users.insert({ name: 'Alice', email: 'alice@example.com', age: 25 });
67
-
68
- // 主键查询(B-Tree 加速,O(log n))
69
- db.users.findById(1);
70
-
71
- // 链式查询
72
- db.users.where({ age: { $gte: 18 } })
73
- .orderBy('name')
74
- .limit(10)
75
- .get();
76
- ```
77
-
78
- ---
79
-
80
- ## v2.0.0 核心特性
81
-
82
- ### 🔴 B-Tree 索引引擎
83
-
84
- 自动为 `primaryKey` 和 `unique` 字段创建 B-Tree 索引,查询复杂度从 O(n) 降至 O(log n)。
85
-
86
- ```javascript
87
- // 自动 B-Tree(无需手动创建)
88
- db.createTable('users', {
89
- id: { type: 'integer', primaryKey: true, autoIncrement: true },
90
- email: { type: 'string', unique: true }
91
- });
92
- // → 自动为 id 和 email 字段建 B-Tree
93
-
94
- // 手动创建 B-Tree 索引
95
- db.users.createBTreeIndex('name');
96
-
97
- // 范围查询(B-Tree 加速)
98
- db.users.where({ age: { $gte: 18, $lte: 60 } }).get();
99
- ```
100
-
101
- ### 🔴 事务隔离
102
-
103
- 支持 `READ_COMMITTED` 和 `REPEATABLE_READ` 两级隔离。
104
-
105
- ```javascript
106
- // REPEATABLE_READ:快照隔离
107
- db.begin('REPEATABLE_READ');
108
- db.users.insert({ name: 'Tx', email: 'tx@test.com' });
109
- // ... 其他操作看到的是事务开始时的快照
110
- db.rollback(); // 回滚所有变更
111
-
112
- // READ_COMMITTED(默认)
113
- db.begin();
114
- db.users.insert({ name: 'Tx2', email: 'tx2@test.com' });
115
- db.commit();
116
-
117
- // 动态切换隔离级别
118
- db.setIsolationLevel('REPEATABLE_READ');
119
- ```
120
-
121
- ### 🔴 文件锁 + WAL
122
-
123
- ```javascript
124
- const db = new jsql.Database('mydb.json', {
125
- wal: true, // Write-Ahead Logging,崩溃恢复
126
- fileLock: true // 防止多进程同时写入
127
- });
128
- ```
129
-
130
- ### 🟡 哈希 JOIN 优化
131
-
132
- 数据量 > 100 行时自动使用哈希 JOIN(O(n+m)),小数据量自动退化为嵌套循环。
133
-
134
- ```javascript
135
- // JOIN 支持字符串表名(v2.0 新增)
136
- db.posts.where({})
137
- .join('users', 'userId', 'id', 'author')
138
- .get();
139
-
140
- // LEFT JOIN
141
- db.posts.where({})
142
- .leftJoin('users', 'userId', 'id', 'author')
143
- .get();
144
-
145
- // 手动指定 JOIN 策略
146
- db.posts.where({})
147
- .useHashJoin() // 强制哈希 JOIN
148
- .join('users', 'userId', 'id', 'author')
149
- .get();
150
- ```
151
-
152
- ### 🟡 外键约束
153
-
154
- ```javascript
155
- db.createTable('posts', {
156
- id: { type: 'integer', primaryKey: true, autoIncrement: true },
157
- title: { type: 'string' },
158
- userId: { type: 'integer', foreignKey: {
159
- table: 'users',
160
- field: 'id',
161
- onDelete: 'cascade' // cascade | set null | restrict
162
- } }
163
- });
164
-
165
- // 删除用户时自动级联删除其帖子
166
- db.users.remove({ id: 1 });
167
- ```
168
-
169
- ### 🟡 MySQL 风格错误码
170
-
171
- ```javascript
172
- try {
173
- db.users.insert({ email: 'alice@example.com' }); // 重复
174
- } catch (e) {
175
- e.code; // 1062 (ER_DUP_ENTRY)
176
- e.message; // "Duplicate entry 'alice@example.com' for key 'email'"
177
- e.toJSON(); // { error: true, code: 1062, message: '...', details: {...} }
178
- }
179
- ```
180
-
181
- 错误码速查:
182
-
183
- | 错误码 | 名称 | 含义 |
184
- |--------|------|------|
185
- | 1048 | ER_BAD_NULL_ERROR | 列不能为 NULL |
186
- | 1062 | ER_DUP_ENTRY | 唯一键冲突 |
187
- | 1216 | ER_NO_REFERENCED_ROW | 外键约束失败 |
188
- | 1292 | ER_TRUNCATED_WRONG_VALUE | 数据类型错误 |
189
- | 1406 | ER_DATA_TOO_LONG | 数据超长 |
190
- | 3819 | ER_CHECK_CONSTRAINT | CHECK 约束违反 |
191
- | 1568 | ER_TRANSACTION_ACTIVE | 事务已在进行中 |
192
- | 1569 | ER_NO_TRANSACTION | 无活跃事务 |
193
-
194
- ### 🟡 日期类型
195
-
196
- ```javascript
197
- db.createTable('events', {
198
- id: { type: 'integer', primaryKey: true, autoIncrement: true },
199
- eventDate: { type: 'date' }, // YYYY-MM-DD
200
- eventTime: { type: 'datetime' }, // YYYY-MM-DD HH:MM:SS
201
- timestamp: { type: 'timestamp' }, // Unix 时间戳
202
- duration: { type: 'time' } // HH:MM:SS
203
- });
204
- ```
1
+ # JSQL-NEO v3.1.0
205
2
 
206
- ### 🟢 慢查询日志
3
+ Rust-powered embedded database. Runs via **WASM** (zero native deps) or as a standalone **HTTP server**.
207
4
 
208
- ```javascript
209
- const db = new jsql.Database('mydb.json', { slowQueryThreshold: 50 });
210
- // 所有 > 50ms 的查询自动记录
5
+ ## Quick Start (WASM — no server needed)
211
6
 
212
- // 查看慢查询
213
- db.getSlowQueries(20); // 最近 20 条
7
+ ```js
8
+ const { JSQL } = require('jsql-neo');
9
+ const db = new JSQL();
10
+ await db.start();
214
11
 
215
- // 清空日志
216
- db.clearSlowQueries();
217
-
218
- // 动态调整阈值
219
- db.setSlowQueryThreshold(200);
220
- ```
221
-
222
- ---
223
-
224
- ## 完整 API
225
-
226
- ### Database
227
-
228
- ```javascript
229
- const db = new jsql.Database(filePath, options);
230
- ```
231
-
232
- | 选项 | 类型 | 默认值 | 说明 |
233
- |------|------|--------|------|
234
- | `autoSave` | boolean | true | 自动保存 |
235
- | `autoSaveInterval` | number | 0 | 自动保存间隔(ms) |
236
- | `pretty` | boolean | true | JSON 格式化 |
237
- | `encryptKey` | string | null | AES-256-CBC 加密密钥 |
238
- | `versioning` | boolean | false | 版本历史 |
239
- | `wal` | boolean | false | WAL 模式 |
240
- | `fileLock` | boolean | false | 文件锁 |
241
- | `isolationLevel` | string | 'READ_COMMITTED' | 事务隔离级别 |
242
- | `slowQueryThreshold` | number | 100 | 慢查询阈值(ms),0 禁用 |
243
-
244
- #### 表管理
245
-
246
- ```javascript
247
- db.createTable(name, schema) // 创建表
248
- db.dropTable(name) // 删除表
249
- db.hasTable(name) // 是否存在
250
- db.getTables() // 获取所有表名
251
- ```
252
-
253
- #### 事务
254
-
255
- ```javascript
256
- db.begin(isolationLevel?) // 开始事务
257
- db.commit() // 提交
258
- db.rollback() // 回滚
259
- db.inTransaction() // 是否在事务中
260
- db.getIsolationLevel() // 获取隔离级别
261
- db.setIsolationLevel(level) // 设置隔离级别
262
- ```
263
-
264
- #### 视图
265
-
266
- ```javascript
267
- db.createView('adults', db => db.users.where({ age: { $gte: 18 } }));
268
- db.dropView('adults');
269
- db.getViews();
270
- ```
271
-
272
- #### 触发器
273
-
274
- ```javascript
275
- db.createTrigger('log_insert', {
276
- event: 'insert',
277
- table: 'users',
278
- timing: 'after'
279
- }, (data) => { console.log('New user:', data); });
280
-
281
- db.dropTrigger('log_insert');
282
- db.getTriggers();
283
- ```
284
-
285
- #### 备份 / 恢复
286
-
287
- ```javascript
288
- db.backup('backup.json') // 备份到文件
289
- db.restore('backup.json') // 从文件恢复
290
- db.export() // 导出为 JSON 对象
291
- db.import(data) // 导入 JSON 对象
292
- ```
293
-
294
- #### 统计 & 监控
295
-
296
- ```javascript
297
- db.stats() // 数据库统计
298
- db.getSlowQueries() // 慢查询日志
299
- db.clearSlowQueries() // 清空慢查询日志
300
- db.setSlowQueryThreshold(ms) // 设置慢查询阈值
301
- ```
302
-
303
- #### 插件 & 迁移
304
-
305
- ```javascript
306
- db.use(plugin) // 注册插件
307
- db.addMigration({ version, name, up, down }) // 注册迁移
308
- db.migrate(version) // 执行迁移
309
- ```
310
-
311
- #### 变更监听
312
-
313
- ```javascript
314
- const unsubscribe = db.onChange(event => {
315
- console.log(event.type, event.table, event.data);
12
+ await db.createTable('users', {
13
+ name: { type: 'string' },
14
+ age: { type: 'integer' }
316
15
  });
317
16
 
318
- // 变更流(异步迭代器)
319
- const stream = db.createChangeStream();
320
- for await (const event of stream) { /* ... */ }
321
- stream.close();
322
- ```
323
-
324
- #### 多数据库
325
-
326
- ```javascript
327
- db.attach('other', otherDb) // 附加数据库
328
- db.detach('other') // 分离
329
- db.getAttached() // 查看已附加的数据库
330
- ```
331
-
332
- ### Table
333
-
334
- ```javascript
335
- // 插入
336
- table.insert(data) // 插入单行
337
- table.insertMany([data, ...]) // 批量插入
338
- table.upsert(data) // 存在则更新,否则插入
339
-
340
- // 查询
341
- table.find(query) // 条件查询
342
- table.findOne(query) // 查单条
343
- table.findById(id) // 主键查询(B-Tree 加速)
344
- table.findAll() // 全表查询
345
- table.count(query) // 计数
346
- table.where(conditions) // 返回 Query 链式构建器
347
-
348
- // 更新
349
- table.update(query, updates) // 条件更新
350
- table.updateById(id, updates) // 主键更新
351
-
352
- // 删除
353
- table.remove(query) // 条件删除
354
-
355
- // 索引
356
- table.createBTreeIndex(field) // 创建 B-Tree 索引
357
- table.createIndex(field) // 创建 Hash 索引
358
- table.dropIndex(field) // 删除索引
359
-
360
- // Schema
361
- table.addColumn(name, def) // 添加列
362
- table.dropColumn(name) // 删除列
363
- table.renameColumn(old, new) // 重命名列
364
-
365
- // 钩子
366
- table.on('beforeInsert', fn) // 注册钩子
367
- table.off('beforeInsert', fn) // 移除钩子
368
- ```
369
-
370
- ### Query
371
-
372
- ```javascript
373
- db.users.where({ age: { $gte: 18 } }) // 条件
374
- .select(['name', 'age']) // 字段选择
375
- .orderBy('name') // 排序
376
- .orderByDesc('age') // 降序
377
- .limit(10) // 限制行数
378
- .offset(20) // 偏移
379
- .distinct('age') // 去重
380
- .groupBy('city') // 分组
381
- .having({ count: { $gt: 5 } }) // 分组过滤
382
- .join('posts', 'id', 'userId') // INNER JOIN
383
- .leftJoin('posts', 'id', 'userId') // LEFT JOIN
384
- .rightJoin('posts', 'id', 'userId') // RIGHT JOIN
385
- .union(otherQuery) // UNION
386
- .window('rowNumber', 'name') // 窗口函数
387
- .case([{ when: { age: { $lt: 18 } }, then: 'minor' }], 'adult')
388
- .as('ageGroup') // CASE WHEN
389
- .cache(30000) // 查询缓存
390
- .explain() // 执行计划
391
- .toSQL() // 导出 SQL 字符串
392
- .useHashJoin() // 强制哈希 JOIN
393
- .useNestedLoop() // 强制嵌套循环
394
-
395
- // 执行
396
- .get() // 获取结果
397
- .first() // 第一条
398
- .count() // 计数
399
- .sum('age') // 求和
400
- .avg('age') // 平均
401
- .min('age') // 最小
402
- .max('age') // 最大
403
- .groupStats('age') // 分组统计
404
- .paginate(1, 20) // 分页
405
- .update({ name: 'New' }) // 条件更新
406
- .remove() // 条件删除
407
- .invalidateCache() // 清除缓存
408
- ```
409
-
410
- ### Schema 定义
411
-
412
- ```javascript
413
- {
414
- fieldName: {
415
- type: 'integer' | 'string' | 'number' | 'boolean' | 'array' | 'object' |
416
- 'date' | 'datetime' | 'timestamp' | 'time' | 'any',
417
- primaryKey: true,
418
- autoIncrement: true,
419
- required: true,
420
- unique: true,
421
- default: '默认值',
422
- length: 50, // 字符串最大长度
423
- min: 0, // 数值最小值
424
- max: 150, // 数值最大值
425
- check: v => v >= 18, // 自定义校验函数
426
- foreignKey: {
427
- table: 'users',
428
- field: 'id',
429
- onDelete: 'cascade', // cascade | set null | restrict
430
- onUpdate: 'restrict'
431
- },
432
- computed: row => `prefix_${row.id}` // 计算字段
433
- }
434
- }
17
+ const [id] = await db.insert('users', { name: 'Alice', age: 30 });
18
+ const user = await db.findById('users', id);
19
+ console.log(user);
435
20
  ```
436
21
 
437
- ### 操作符
22
+ ## HTTP Server
438
23
 
439
- | 操作符 | 说明 | 示例 |
440
- |--------|------|------|
441
- | `$eq` | 等于 | `{ age: { $eq: 25 } }` |
442
- | `$ne` | 不等于 | `{ age: { $ne: 25 } }` |
443
- | `$gt` | 大于 | `{ age: { $gt: 18 } }` |
444
- | `$gte` | 大于等于 | `{ age: { $gte: 18 } }` |
445
- | `$lt` | 小于 | `{ age: { $lt: 60 } }` |
446
- | `$lte` | 小于等于 | `{ age: { $lte: 60 } }` |
447
- | `$in` | 在列表中 | `{ status: { $in: ['a', 'b'] } }` |
448
- | `$nin` | 不在列表中 | `{ status: { $nin: ['x', 'y'] } }` |
449
- | `$like` | 模糊匹配 | `{ name: { $like: 'Ali' } }` |
450
- | `$regex` | 正则匹配 | `{ email: { $regex: /@gmail\.com$/ } }` |
451
- | `$between` | 区间 | `{ age: { $between: [18, 60] } }` |
452
- | `$null` | 是否为 null | `{ deletedAt: { $null: true } }` |
453
- | `$notNull` | 是否非 null | `{ name: { $notNull: true } }` |
454
- | `$or` | 或 | `{ $or: [{age: 18}, {age: 21}] }` |
455
- | `$and` | 且 | `{ $and: [{age: {$gte: 18}}, {age: {$lte: 60}}] }` |
456
- | `$inSub` | 子查询 IN | `{ $inSub: { query: q, field: 'id' } }` |
457
- | `$notInSub` | 子查询 NOT IN | `{ $notInSub: { query: q, field: 'id' } }` |
458
- | `$expr` | 表达式 | `{ $expr: row => row.a > row.b }` |
459
-
460
- ### EXPLAIN 输出示例
24
+ ```bash
25
+ # Start the server
26
+ JSQL_DATA_DIR=/tmp/jsql-neo npx jsql-neo
461
27
 
462
- ```javascript
463
- db.users.where({ id: 3 }).explain().get();
464
- // {
465
- // table: 'users',
466
- // totalRows: 5000,
467
- // filteredRows: 1,
468
- // selectivity: '0.02%',
469
- // hasBTree: true,
470
- // indexUsed: 'id',
471
- // btreeIndexes: ['id', 'email'],
472
- // estimatedCost: 0.05,
473
- // joins: []
474
- // }
28
+ # Connect from JS
29
+ const { HttpJSQL } = require('jsql-neo');
30
+ const db = new HttpJSQL({ host: '127.0.0.1', port: 6379 });
475
31
  ```
476
32
 
477
- ---
478
-
479
- ## 内存模式 vs 文件模式
480
-
481
- | 特性 | 内存模式 | 文件模式 |
482
- |------|----------|----------|
483
- | 初始化 | `new Database(':memory:')` | `new Database('db.json')` |
484
- | 持久化 | 无 | 自动保存 |
485
- | B-Tree 索引 | ✅ | ✅ |
486
- | 事务隔离 | ✅ | ✅ |
487
- | WAL | ❌(自动禁用) | ✅ |
488
- | 文件锁 | ❌(自动禁用) | ✅ |
489
- | 加密存储 | ❌ | ✅ |
490
- | 慢查询日志 | ✅ | ✅ |
491
-
492
- ---
493
-
494
- ## 与 MySQL 对比
495
-
496
- | 特性 | JSQL-NEO v2.0 | MySQL 8.0 |
497
- |------|---------------|-----------|
498
- | 索引 | B-Tree(O(log n)) | B-Tree(O(log n)) |
499
- | 事务隔离 | READ_COMMITTED / REPEATABLE_READ | 4 级 |
500
- | 并发控制 | 文件锁 | MVCC + 锁 |
501
- | 外键 | CASCADE / SET NULL / RESTRICT | ✅ |
502
- | 错误码 | 25+ MySQL 兼容 | 完整 |
503
- | JOIN | 哈希 / 嵌套循环 | 基于代价 |
504
- | 子查询 | ✅ | ✅ |
505
- | 窗口函数 | ROW_NUMBER / RANK / DENSE_RANK | 完整 |
506
- | 存储引擎 | JSON 文件 | InnoDB / MyISAM |
507
- | 零依赖 | ✅ | ❌ |
508
- | 嵌入 Node.js | ✅ | ❌(需驱动) |
509
-
510
- ---
511
-
512
- ## 许可
33
+ ## Features
513
34
 
514
- Apache License 2.0 © Vexify 2026
35
+ - In-memory or persistent (WAL + snapshot) storage
36
+ - B-Tree indexing + HashMap O(1) PK lookups
37
+ - Strings Pool for memory-efficient string storage
38
+ - Batch insert / update / delete
39
+ - Cursor-based pagination
40
+ - Transaction support
41
+ - WASM — zero native dependencies, runs in Node.js and browsers
Binary file
package/index.js CHANGED
@@ -1,18 +1,20 @@
1
1
  /**
2
- * JSQL-NEO v3.0.0 — Rust-Powered Embedded Database
2
+ * JSQL-NEO v3.1.0 — Rust-Powered Embedded Database (WASM + HTTP)
3
3
  *
4
4
  * @example
5
5
  * const jsql = require('jsql-neo');
6
- * const db = new jsql.JSQL({ port: 6379 });
6
+ * const db = new jsql.JSQL(); // WASM mode (no server needed)
7
7
  * await db.start();
8
8
  * await db.createTable('users', { name: { type: 'string' }, age: { type: 'integer' } });
9
- * await db.insert('users', { name: 'Alice', age: 30 });
9
+ * const ids = await db.insert('users', { name: 'Alice', age: 30 });
10
10
  * const user = await db.findById('users', 1);
11
11
  * await db.stop();
12
12
  */
13
13
 
14
+ const WasmClient = require('./lib/wasm_client');
15
+
14
16
  module.exports = {
15
- JSQL: require('./lib/client').JSQL,
17
+ JSQL: WasmClient.JSQL,
16
18
  Database: require('./lib/database'),
17
19
  Table: require('./lib/table'),
18
20
  Query: require('./lib/query'),
@@ -20,5 +22,6 @@ module.exports = {
20
22
  Cache: require('./lib/cache'),
21
23
  JSQL_Error: require('./lib/errors').JSQL_Error,
22
24
  ErrorCodes: require('./lib/errors').ErrorCodes,
23
- JSQLFormat: require('./lib/jsql_format')
25
+ JSQLFormat: require('./lib/jsql_format'),
26
+ HttpJSQL: require('./lib/client').JSQL,
24
27
  };
package/lib/client.js CHANGED
@@ -4,11 +4,9 @@ const path = require('path');
4
4
  const fs = require('fs');
5
5
 
6
6
  function binPath() {
7
- const platform = process.platform;
8
- const arch = process.arch;
9
7
  const dir = path.join(__dirname, '..', 'bin');
10
- const name = platform === 'win32' ? 'jsql-neo-server.exe' : 'jsql-neo-server';
11
- return path.join(dir, `${platform}-${arch}`, name);
8
+ const name = process.platform === 'win32' ? 'jsql-neo-server.exe' : 'jsql-neo-server';
9
+ return path.join(dir, name);
12
10
  }
13
11
 
14
12
  class JSQL {
package/lib/wasm.js ADDED
@@ -0,0 +1 @@
1
+ module.exports = require('./wasm_client');
@@ -0,0 +1,68 @@
1
+ const path = require('path');
2
+
3
+ // wasm-pack generated loader — initializes WASM on first require
4
+ const wasmBindings = require(path.join(__dirname, '..', 'wasm', 'jsql_neo_wasm.js'));
5
+
6
+ function safeJsonParse(str) {
7
+ try { return JSON.parse(str); } catch { return str; }
8
+ }
9
+
10
+ class JSQL {
11
+ async start() {
12
+ // WASM already initialized by the require above
13
+ }
14
+
15
+ async createTable(name, schema) {
16
+ const r = safeJsonParse(wasmBindings.jsql_create_table(name, JSON.stringify(schema)));
17
+ if (r && r.ok === false) throw new Error(r.error || 'create table failed');
18
+ return r;
19
+ }
20
+
21
+ async dropTable(name) {
22
+ const r = safeJsonParse(wasmBindings.jsql_drop_table(name));
23
+ if (r && r.ok === false) throw new Error(r.error || 'drop table failed');
24
+ return r;
25
+ }
26
+
27
+ async insert(table, data) {
28
+ const arr = Array.isArray(data) ? data : [data];
29
+ const r = safeJsonParse(wasmBindings.jsql_insert(table, JSON.stringify(arr)));
30
+ if (r && r.error) throw new Error(r.error);
31
+ return r;
32
+ }
33
+
34
+ async findById(table, id) {
35
+ const r = safeJsonParse(wasmBindings.jsql_find_by_id(table, BigInt(id)));
36
+ if (r && r.error) throw new Error(r.error);
37
+ return r;
38
+ }
39
+
40
+ async find(table, filter, opts = {}) {
41
+ const filterStr = filter ? JSON.stringify(filter) : '';
42
+ const { limit = 100, offset = 0 } = opts;
43
+ const r = safeJsonParse(wasmBindings.jsql_find(table, filterStr, limit, offset));
44
+ if (r && r.error) throw new Error(r.error);
45
+ return r;
46
+ }
47
+
48
+ async count(table) {
49
+ const r = parseInt(wasmBindings.jsql_count(table), 10);
50
+ return isNaN(r) ? 0 : r;
51
+ }
52
+
53
+ async updateById(table, id, data) {
54
+ const r = safeJsonParse(wasmBindings.jsql_update_by_id(table, BigInt(id), JSON.stringify(data)));
55
+ if (r && r.ok === false) throw new Error(r.error || 'update failed');
56
+ return r;
57
+ }
58
+
59
+ async removeById(table, id) {
60
+ const r = safeJsonParse(wasmBindings.jsql_remove_by_id(table, BigInt(id)));
61
+ if (r && r.ok === false) throw new Error(r.error || 'remove failed');
62
+ return r;
63
+ }
64
+
65
+ async stop() {}
66
+ }
67
+
68
+ module.exports = { JSQL };
package/package.json CHANGED
@@ -1,24 +1,26 @@
1
1
  {
2
2
  "name": "jsql-neo",
3
- "version": "3.0.0",
4
- "description": "JSQL-NEO — Rust-powered embedded database with REST API, B-Tree indexes, WAL, crash recovery",
3
+ "version": "3.1.0",
4
+ "description": "JSQL-NEO — Rust-powered embedded database with WASM, REST API, B-Tree indexes, WAL, crash recovery",
5
5
  "main": "index.js",
6
6
  "files": [
7
7
  "index.js",
8
8
  "lib/",
9
+ "bin/",
10
+ "wasm/",
9
11
  "postinstall.js",
10
12
  "README.md",
11
13
  "LICENSE"
12
14
  ],
13
15
  "scripts": {
14
- "postinstall": "node postinstall.js",
15
- "postinstall:build": "cd ../jsql-neo-server && cargo build --release"
16
+ "postinstall": "node postinstall.js"
16
17
  },
17
18
  "keywords": [
18
19
  "database",
19
20
  "sql",
20
21
  "embedded",
21
22
  "rust",
23
+ "wasm",
22
24
  "json",
23
25
  "javascript",
24
26
  "jsql",
package/postinstall.js CHANGED
@@ -1,50 +1,18 @@
1
1
  const { execSync } = require('child_process');
2
2
  const fs = require('fs');
3
3
  const path = require('path');
4
- const https = require('https');
5
- const os = require('os');
6
4
 
7
- const pkg = require('./package.json');
8
5
  const BIN_NAME = process.platform === 'win32' ? 'jsql-neo-server.exe' : 'jsql-neo-server';
9
- const PLATFORM = `${process.platform}-${process.arch}`;
6
+ const BP = path.join(__dirname, 'bin', BIN_NAME);
10
7
 
11
- function binDir() {
12
- return path.join(__dirname, 'bin', PLATFORM);
13
- }
14
-
15
- function binPath() {
16
- return path.join(binDir(), BIN_NAME);
17
- }
18
-
19
- async function download(url, dest) {
20
- return new Promise((resolve, reject) => {
21
- const file = fs.createWriteStream(dest);
22
- https.get(url, (res) => {
23
- if (res.statusCode >= 300 && res.headers.location) {
24
- file.close();
25
- fs.unlinkSync(dest);
26
- return download(res.headers.location, dest).then(resolve).catch(reject);
27
- }
28
- if (res.statusCode !== 200) {
29
- reject(new Error(`HTTP ${res.statusCode}`));
30
- return;
31
- }
32
- res.pipe(file);
33
- file.on('finish', () => { file.close(); fs.chmodSync(dest, 0o755); resolve(); });
34
- }).on('error', (e) => { fs.unlinkSync(dest); reject(e); });
35
- });
36
- }
37
-
38
- async function main() {
39
- const bp = binPath();
40
- if (fs.existsSync(bp)) {
41
- console.log(`[jsql-neo] binary already exists at ${bp}`);
8
+ function main() {
9
+ if (fs.existsSync(BP)) {
10
+ fs.chmodSync(BP, 0o755);
11
+ console.log(`[jsql-neo] binary ready: ${BP}`);
42
12
  return;
43
13
  }
44
14
 
45
- fs.mkdirSync(binDir(), { recursive: true });
46
-
47
- // Try to build from cargo source
15
+ // Build from source
48
16
  const serverDir = path.join(__dirname, '..', 'jsql-neo-server');
49
17
  if (fs.existsSync(path.join(serverDir, 'Cargo.toml'))) {
50
18
  console.log('[jsql-neo] building Rust server from source...');
@@ -52,8 +20,10 @@ async function main() {
52
20
  execSync('cargo build --release', { cwd: serverDir, stdio: 'inherit' });
53
21
  const src = path.join(serverDir, 'target', 'release', BIN_NAME);
54
22
  if (fs.existsSync(src)) {
55
- fs.copyFileSync(src, bp);
56
- console.log(`[jsql-neo] binary built: ${bp}`);
23
+ fs.mkdirSync(path.join(__dirname, 'bin'), { recursive: true });
24
+ fs.copyFileSync(src, BP);
25
+ fs.chmodSync(BP, 0o755);
26
+ console.log(`[jsql-neo] binary built: ${BP}`);
57
27
  return;
58
28
  }
59
29
  } catch (e) {
@@ -61,23 +31,8 @@ async function main() {
61
31
  }
62
32
  }
63
33
 
64
- // Fallback: download from GitHub releases
65
- const version = pkg.version;
66
- const url = `https://github.com/vexify/jsql-neo/releases/download/v${version}/${BIN_NAME}-${PLATFORM}`;
67
- console.log(`[jsql-neo] downloading binary from ${url}...`);
68
- try {
69
- await download(url, bp);
70
- console.log(`[jsql-neo] binary downloaded: ${bp}`);
71
- } catch (e) {
72
- console.warn(`[jsql-neo] download failed: ${e.message}`);
73
- console.warn('[jsql-neo] falling back to JS-only mode');
74
- // Create a stub that errors with a helpful message
75
- fs.writeFileSync(bp, '#!/bin/sh\necho "jsql-neo-server binary not installed"\nexit 1\n');
76
- fs.chmodSync(bp, 0o755);
77
- }
34
+ console.warn(`[jsql-neo] binary not found at ${BP}`);
35
+ console.warn('[jsql-neo] install Rust from https://rustup.rs and run: npm run build');
78
36
  }
79
37
 
80
- main().catch(e => {
81
- console.error('[jsql-neo] postinstall failed:', e.message);
82
- process.exit(1);
83
- });
38
+ main();
@@ -0,0 +1,304 @@
1
+ /* @ts-self-types="./jsql_neo_wasm.d.ts" */
2
+
3
+ /**
4
+ * @param {string} table
5
+ * @returns {string}
6
+ */
7
+ function jsql_count(table) {
8
+ let deferred2_0;
9
+ let deferred2_1;
10
+ try {
11
+ const ptr0 = passStringToWasm0(table, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
12
+ const len0 = WASM_VECTOR_LEN;
13
+ const ret = wasm.jsql_count(ptr0, len0);
14
+ deferred2_0 = ret[0];
15
+ deferred2_1 = ret[1];
16
+ return getStringFromWasm0(ret[0], ret[1]);
17
+ } finally {
18
+ wasm.__wbindgen_free(deferred2_0, deferred2_1, 1);
19
+ }
20
+ }
21
+ exports.jsql_count = jsql_count;
22
+
23
+ /**
24
+ * @param {string} name
25
+ * @param {string} schema_json
26
+ * @returns {string}
27
+ */
28
+ function jsql_create_table(name, schema_json) {
29
+ let deferred3_0;
30
+ let deferred3_1;
31
+ try {
32
+ const ptr0 = passStringToWasm0(name, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
33
+ const len0 = WASM_VECTOR_LEN;
34
+ const ptr1 = passStringToWasm0(schema_json, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
35
+ const len1 = WASM_VECTOR_LEN;
36
+ const ret = wasm.jsql_create_table(ptr0, len0, ptr1, len1);
37
+ deferred3_0 = ret[0];
38
+ deferred3_1 = ret[1];
39
+ return getStringFromWasm0(ret[0], ret[1]);
40
+ } finally {
41
+ wasm.__wbindgen_free(deferred3_0, deferred3_1, 1);
42
+ }
43
+ }
44
+ exports.jsql_create_table = jsql_create_table;
45
+
46
+ /**
47
+ * @param {string} name
48
+ * @returns {string}
49
+ */
50
+ function jsql_drop_table(name) {
51
+ let deferred2_0;
52
+ let deferred2_1;
53
+ try {
54
+ const ptr0 = passStringToWasm0(name, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
55
+ const len0 = WASM_VECTOR_LEN;
56
+ const ret = wasm.jsql_drop_table(ptr0, len0);
57
+ deferred2_0 = ret[0];
58
+ deferred2_1 = ret[1];
59
+ return getStringFromWasm0(ret[0], ret[1]);
60
+ } finally {
61
+ wasm.__wbindgen_free(deferred2_0, deferred2_1, 1);
62
+ }
63
+ }
64
+ exports.jsql_drop_table = jsql_drop_table;
65
+
66
+ /**
67
+ * @param {string} table
68
+ * @param {string} filter_json
69
+ * @param {number} limit
70
+ * @param {number} offset
71
+ * @returns {string}
72
+ */
73
+ function jsql_find(table, filter_json, limit, offset) {
74
+ let deferred3_0;
75
+ let deferred3_1;
76
+ try {
77
+ const ptr0 = passStringToWasm0(table, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
78
+ const len0 = WASM_VECTOR_LEN;
79
+ const ptr1 = passStringToWasm0(filter_json, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
80
+ const len1 = WASM_VECTOR_LEN;
81
+ const ret = wasm.jsql_find(ptr0, len0, ptr1, len1, limit, offset);
82
+ deferred3_0 = ret[0];
83
+ deferred3_1 = ret[1];
84
+ return getStringFromWasm0(ret[0], ret[1]);
85
+ } finally {
86
+ wasm.__wbindgen_free(deferred3_0, deferred3_1, 1);
87
+ }
88
+ }
89
+ exports.jsql_find = jsql_find;
90
+
91
+ /**
92
+ * @param {string} table
93
+ * @param {bigint} id
94
+ * @returns {string}
95
+ */
96
+ function jsql_find_by_id(table, id) {
97
+ let deferred2_0;
98
+ let deferred2_1;
99
+ try {
100
+ const ptr0 = passStringToWasm0(table, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
101
+ const len0 = WASM_VECTOR_LEN;
102
+ const ret = wasm.jsql_find_by_id(ptr0, len0, id);
103
+ deferred2_0 = ret[0];
104
+ deferred2_1 = ret[1];
105
+ return getStringFromWasm0(ret[0], ret[1]);
106
+ } finally {
107
+ wasm.__wbindgen_free(deferred2_0, deferred2_1, 1);
108
+ }
109
+ }
110
+ exports.jsql_find_by_id = jsql_find_by_id;
111
+
112
+ /**
113
+ * @param {string} table
114
+ * @param {string} data_json
115
+ * @returns {string}
116
+ */
117
+ function jsql_insert(table, data_json) {
118
+ let deferred3_0;
119
+ let deferred3_1;
120
+ try {
121
+ const ptr0 = passStringToWasm0(table, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
122
+ const len0 = WASM_VECTOR_LEN;
123
+ const ptr1 = passStringToWasm0(data_json, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
124
+ const len1 = WASM_VECTOR_LEN;
125
+ const ret = wasm.jsql_insert(ptr0, len0, ptr1, len1);
126
+ deferred3_0 = ret[0];
127
+ deferred3_1 = ret[1];
128
+ return getStringFromWasm0(ret[0], ret[1]);
129
+ } finally {
130
+ wasm.__wbindgen_free(deferred3_0, deferred3_1, 1);
131
+ }
132
+ }
133
+ exports.jsql_insert = jsql_insert;
134
+
135
+ /**
136
+ * @param {string} table
137
+ * @param {bigint} id
138
+ * @returns {string}
139
+ */
140
+ function jsql_remove_by_id(table, id) {
141
+ let deferred2_0;
142
+ let deferred2_1;
143
+ try {
144
+ const ptr0 = passStringToWasm0(table, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
145
+ const len0 = WASM_VECTOR_LEN;
146
+ const ret = wasm.jsql_remove_by_id(ptr0, len0, id);
147
+ deferred2_0 = ret[0];
148
+ deferred2_1 = ret[1];
149
+ return getStringFromWasm0(ret[0], ret[1]);
150
+ } finally {
151
+ wasm.__wbindgen_free(deferred2_0, deferred2_1, 1);
152
+ }
153
+ }
154
+ exports.jsql_remove_by_id = jsql_remove_by_id;
155
+
156
+ /**
157
+ * @param {string} table
158
+ * @param {bigint} id
159
+ * @param {string} data_json
160
+ * @returns {string}
161
+ */
162
+ function jsql_update_by_id(table, id, data_json) {
163
+ let deferred3_0;
164
+ let deferred3_1;
165
+ try {
166
+ const ptr0 = passStringToWasm0(table, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
167
+ const len0 = WASM_VECTOR_LEN;
168
+ const ptr1 = passStringToWasm0(data_json, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
169
+ const len1 = WASM_VECTOR_LEN;
170
+ const ret = wasm.jsql_update_by_id(ptr0, len0, id, ptr1, len1);
171
+ deferred3_0 = ret[0];
172
+ deferred3_1 = ret[1];
173
+ return getStringFromWasm0(ret[0], ret[1]);
174
+ } finally {
175
+ wasm.__wbindgen_free(deferred3_0, deferred3_1, 1);
176
+ }
177
+ }
178
+ exports.jsql_update_by_id = jsql_update_by_id;
179
+ function __wbg_get_imports() {
180
+ const import0 = {
181
+ __proto__: null,
182
+ __wbg___wbindgen_string_get_b0ca35b86a603356: function(arg0, arg1) {
183
+ const obj = arg1;
184
+ const ret = typeof(obj) === 'string' ? obj : undefined;
185
+ var ptr1 = isLikeNone(ret) ? 0 : passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
186
+ var len1 = WASM_VECTOR_LEN;
187
+ getDataViewMemory0().setInt32(arg0 + 4 * 1, len1, true);
188
+ getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true);
189
+ },
190
+ __wbg___wbindgen_throw_344f42d3211c4765: function(arg0, arg1) {
191
+ throw new Error(getStringFromWasm0(arg0, arg1));
192
+ },
193
+ __wbg_new_0_3da9e97f24fc69be: function() {
194
+ const ret = new Date();
195
+ return ret;
196
+ },
197
+ __wbg_toISOString_706fbe321055ee58: function(arg0) {
198
+ const ret = arg0.toISOString();
199
+ return ret;
200
+ },
201
+ __wbindgen_init_externref_table: function() {
202
+ const table = wasm.__wbindgen_externrefs;
203
+ const offset = table.grow(4);
204
+ table.set(0, undefined);
205
+ table.set(offset + 0, undefined);
206
+ table.set(offset + 1, null);
207
+ table.set(offset + 2, true);
208
+ table.set(offset + 3, false);
209
+ },
210
+ };
211
+ return {
212
+ __proto__: null,
213
+ "./jsql_neo_wasm_bg.js": import0,
214
+ };
215
+ }
216
+
217
+ let cachedDataViewMemory0 = null;
218
+ function getDataViewMemory0() {
219
+ if (cachedDataViewMemory0 === null || cachedDataViewMemory0.buffer.detached === true || (cachedDataViewMemory0.buffer.detached === undefined && cachedDataViewMemory0.buffer !== wasm.memory.buffer)) {
220
+ cachedDataViewMemory0 = new DataView(wasm.memory.buffer);
221
+ }
222
+ return cachedDataViewMemory0;
223
+ }
224
+
225
+ function getStringFromWasm0(ptr, len) {
226
+ return decodeText(ptr >>> 0, len);
227
+ }
228
+
229
+ let cachedUint8ArrayMemory0 = null;
230
+ function getUint8ArrayMemory0() {
231
+ if (cachedUint8ArrayMemory0 === null || cachedUint8ArrayMemory0.byteLength === 0) {
232
+ cachedUint8ArrayMemory0 = new Uint8Array(wasm.memory.buffer);
233
+ }
234
+ return cachedUint8ArrayMemory0;
235
+ }
236
+
237
+ function isLikeNone(x) {
238
+ return x === undefined || x === null;
239
+ }
240
+
241
+ function passStringToWasm0(arg, malloc, realloc) {
242
+ if (realloc === undefined) {
243
+ const buf = cachedTextEncoder.encode(arg);
244
+ const ptr = malloc(buf.length, 1) >>> 0;
245
+ getUint8ArrayMemory0().subarray(ptr, ptr + buf.length).set(buf);
246
+ WASM_VECTOR_LEN = buf.length;
247
+ return ptr;
248
+ }
249
+
250
+ let len = arg.length;
251
+ let ptr = malloc(len, 1) >>> 0;
252
+
253
+ const mem = getUint8ArrayMemory0();
254
+
255
+ let offset = 0;
256
+
257
+ for (; offset < len; offset++) {
258
+ const code = arg.charCodeAt(offset);
259
+ if (code > 0x7F) break;
260
+ mem[ptr + offset] = code;
261
+ }
262
+ if (offset !== len) {
263
+ if (offset !== 0) {
264
+ arg = arg.slice(offset);
265
+ }
266
+ ptr = realloc(ptr, len, len = offset + arg.length * 3, 1) >>> 0;
267
+ const view = getUint8ArrayMemory0().subarray(ptr + offset, ptr + len);
268
+ const ret = cachedTextEncoder.encodeInto(arg, view);
269
+
270
+ offset += ret.written;
271
+ ptr = realloc(ptr, len, offset, 1) >>> 0;
272
+ }
273
+
274
+ WASM_VECTOR_LEN = offset;
275
+ return ptr;
276
+ }
277
+
278
+ let cachedTextDecoder = new TextDecoder('utf-8', { ignoreBOM: true, fatal: true });
279
+ cachedTextDecoder.decode();
280
+ function decodeText(ptr, len) {
281
+ return cachedTextDecoder.decode(getUint8ArrayMemory0().subarray(ptr, ptr + len));
282
+ }
283
+
284
+ const cachedTextEncoder = new TextEncoder();
285
+
286
+ if (!('encodeInto' in cachedTextEncoder)) {
287
+ cachedTextEncoder.encodeInto = function (arg, view) {
288
+ const buf = cachedTextEncoder.encode(arg);
289
+ view.set(buf);
290
+ return {
291
+ read: arg.length,
292
+ written: buf.length
293
+ };
294
+ };
295
+ }
296
+
297
+ let WASM_VECTOR_LEN = 0;
298
+
299
+ const wasmPath = `${__dirname}/jsql_neo_wasm_bg.wasm`;
300
+ const wasmBytes = require('fs').readFileSync(wasmPath);
301
+ const wasmModule = new WebAssembly.Module(wasmBytes);
302
+ let wasmInstance = new WebAssembly.Instance(wasmModule, __wbg_get_imports());
303
+ let wasm = wasmInstance.exports;
304
+ wasm.__wbindgen_start();
Binary file