allez-orm 1.0.4 → 1.0.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.
Files changed (2) hide show
  1. package/allez-orm.mjs +222 -206
  2. package/package.json +1 -1
package/allez-orm.mjs CHANGED
@@ -20,259 +20,275 @@
20
20
  * @property {number=} autoSaveMs
21
21
  * @property {(file:string)=>string=} wasmLocateFile
22
22
  * @property {Schema[]=} schemas
23
- * @property {Record<string,{default:Schema}>=} schemaModules // for bundlers (import.meta.glob)
23
+ * @property {Record<string,{default:Schema}>=} schemaModules
24
24
  */
25
25
 
26
26
  const DEFAULT_DB_NAME = "allez.db";
27
27
  const DEFAULT_AUTOSAVE_MS = 1500;
28
28
 
29
+ /** Resolve/init sql.js WASM in a way that never triggers node core deps. */
30
+ async function loadSqlJs(opts = {}) {
31
+ // 1) If user included <script src="https://sql.js.org/dist/sql-wasm.js">, use it.
32
+ if (typeof window !== "undefined" && window.initSqlJs) {
33
+ return await window.initSqlJs({
34
+ locateFile: opts.wasmLocateFile ?? (f => `https://sql.js.org/dist/${f}`)
35
+ });
36
+ }
37
+
38
+ // 2) Try a CDN ESM import that bundlers won't touch (no fs/path/crypto resolution).
39
+ // Dynamic URL import requires CORS; the official CDN allows it.
40
+ try {
41
+ // @ts-ignore
42
+ const mod = await import(/* webpackIgnore: true */ "https://sql.js.org/dist/sql-wasm.js");
43
+ const initSqlJs = mod.default || mod;
44
+ return await initSqlJs({
45
+ locateFile: opts.wasmLocateFile ?? (f => `https://sql.js.org/dist/${f}`)
46
+ });
47
+ } catch (_) {
48
+ // continue to step 3
49
+ }
50
+
51
+ // 3) Last resort: local dist entry. This ONLY works if the consumer configured
52
+ // resolve.alias OR fallbacks to disable node core modules in their bundler.
53
+ const mod = await import("sql.js/dist/sql-wasm.js"); // never import "sql.js"
54
+ const initSqlJs = mod.default || mod;
55
+ return await initSqlJs({
56
+ locateFile: opts.wasmLocateFile ?? (f => `https://sql.js.org/dist/${f}`)
57
+ });
58
+ }
59
+
29
60
  export class AllezORM {
30
- /** @param {any} SQL @param {any} db @param {InitOptions} opts */
31
- constructor(SQL, db, opts) {
32
- this.SQL = SQL;
33
- this.db = db;
34
- this.dbName = opts.dbName ?? DEFAULT_DB_NAME;
35
- this.autoSaveMs = opts.autoSaveMs ?? DEFAULT_AUTOSAVE_MS;
36
- this.saveTimer = null;
37
- }
61
+ /** @param {any} SQL @param {any} db @param {InitOptions} opts */
62
+ constructor(SQL, db, opts) {
63
+ this.SQL = SQL;
64
+ this.db = db;
65
+ this.dbName = opts.dbName ?? DEFAULT_DB_NAME;
66
+ this.autoSaveMs = opts.autoSaveMs ?? DEFAULT_AUTOSAVE_MS;
67
+ this.saveTimer = null;
68
+ }
38
69
 
39
- // run arbitrary SQL (DDL/DML). Returns true on success.
40
- // Usage: await orm.exec("ALTER TABLE posts ADD COLUMN ...");
41
- async exec(sql, params = []) {
42
- if (params && params.length) {
43
- const stmt = this.db.prepare(sql);
44
- try {
45
- stmt.bind(params);
46
- while (stmt.step()) { } // iterate to completion
47
- } finally {
48
- stmt.free();
49
- }
50
- } else {
51
- this.db.exec(sql);
52
- }
53
- // persist to IndexedDB if your class exposes it
54
- if (typeof this.saveNow === "function") await this.saveNow();
55
- return true;
70
+ // run arbitrary SQL (DDL/DML). Returns true on success.
71
+ async exec(sql, params = []) {
72
+ if (params && params.length) {
73
+ const stmt = this.db.prepare(sql);
74
+ try {
75
+ stmt.bind(params);
76
+ while (stmt.step()) { /* drain */ }
77
+ } finally {
78
+ stmt.free();
79
+ }
80
+ } else {
81
+ this.db.exec(sql);
56
82
  }
83
+ if (typeof this.saveNow === "function") await this.saveNow();
84
+ return true;
85
+ }
57
86
 
58
- // nice alias
59
- run(sql, params) { return this.exec(sql, params); }
60
-
61
-
62
- /** @param {InitOptions=} opts */
63
- static async init(opts = {}) {
64
- // Resolve sql.js from global <script> or dynamic import
65
- let SQL;
66
- if (typeof window !== "undefined" && window.initSqlJs) {
67
- SQL = await window.initSqlJs({
68
- locateFile: opts.wasmLocateFile ?? (f => `https://sql.js.org/dist/${f}`)
69
- });
70
- } else {
71
- const { default: initSqlJs } = await import("sql.js");
72
- SQL = await initSqlJs({
73
- locateFile: opts.wasmLocateFile ?? (f => `https://sql.js.org/dist/${f}`)
74
- });
75
- }
87
+ run(sql, params) { return this.exec(sql, params); }
76
88
 
77
- // Restore DB from IndexedDB, or create fresh
78
- const saved = await idbGet(opts.dbName ?? DEFAULT_DB_NAME);
79
- const db = saved ? new SQL.Database(saved) : new SQL.Database();
80
- // after: const db = saved ? new SQL.Database(saved) : new SQL.Database();
81
- const orm = new AllezORM(SQL, db, opts);
82
- await orm.execute("PRAGMA foreign_keys = ON;"); // <-- add this line
83
- await orm.#ensureMeta();
89
+ /** @param {InitOptions=} opts */
90
+ static async init(opts = {}) {
91
+ // Always use the WASM/browser build loader above.
92
+ const SQL = await loadSqlJs(opts);
84
93
 
85
- const schemas = collectSchemas(opts);
86
- await orm.registerSchemas(schemas);
87
- db.exec("PRAGMA foreign_keys = ON;");
94
+ // Restore DB from IndexedDB, or create fresh
95
+ const saved = await idbGet(opts.dbName ?? DEFAULT_DB_NAME);
96
+ const db = saved ? new SQL.Database(saved) : new SQL.Database();
88
97
 
98
+ const orm = new AllezORM(SQL, db, opts);
99
+ await orm.execute("PRAGMA foreign_keys = ON;");
100
+ await orm.#ensureMeta();
89
101
 
90
- return orm;
91
- }
102
+ const schemas = collectSchemas(opts);
103
+ await orm.registerSchemas(schemas);
104
+ db.exec("PRAGMA foreign_keys = ON;");
92
105
 
93
- // ---------------- core SQL helpers ----------------
106
+ return orm;
107
+ }
94
108
 
95
- async execute(sql, params = []) {
96
- const stmt = this.db.prepare(sql);
97
- try {
98
- stmt.bind(params);
99
- while (stmt.step()) { } // drain
100
- } finally {
101
- stmt.free();
102
- }
103
- this.#scheduleSave();
104
- }
109
+ // ---------------- core SQL helpers ----------------
105
110
 
106
- async query(sql, params = []) {
107
- const stmt = this.db.prepare(sql);
108
- const out = [];
109
- try {
110
- stmt.bind(params);
111
- while (stmt.step()) out.push(stmt.getAsObject());
112
- } finally {
113
- stmt.free();
114
- }
115
- return out;
111
+ async execute(sql, params = []) {
112
+ const stmt = this.db.prepare(sql);
113
+ try {
114
+ stmt.bind(params);
115
+ while (stmt.step()) { /* drain */ }
116
+ } finally {
117
+ stmt.free();
116
118
  }
119
+ this.#scheduleSave();
120
+ }
117
121
 
118
- async get(sql, params = []) {
119
- const rows = await this.query(sql, params);
120
- return rows[0];
122
+ async query(sql, params = []) {
123
+ const stmt = this.db.prepare(sql);
124
+ const out = [];
125
+ try {
126
+ stmt.bind(params);
127
+ while (stmt.step()) out.push(stmt.getAsObject());
128
+ } finally {
129
+ stmt.free();
121
130
  }
131
+ return out;
132
+ }
133
+
134
+ async get(sql, params = []) {
135
+ const rows = await this.query(sql, params);
136
+ return rows[0];
137
+ }
122
138
 
123
- // ---------------- table helper ----------------
124
-
125
- table(table) {
126
- const self = this;
127
- return {
128
- async insert(obj) {
129
- const cols = Object.keys(obj);
130
- const qs = cols.map(() => "?").join(",");
131
- await self.execute(
132
- `INSERT INTO ${table} (${cols.join(",")}) VALUES (${qs})`,
133
- cols.map(c => obj[c])
134
- );
135
- },
136
- async upsert(obj) {
137
- const cols = Object.keys(obj);
138
- const qs = cols.map(() => "?").join(",");
139
- const updates = cols.map(c => `${c}=excluded.${c}`).join(",");
140
- await self.execute(
141
- `INSERT INTO ${table} (${cols.join(",")}) VALUES (${qs})
139
+ // ---------------- table helper ----------------
140
+
141
+ table(table) {
142
+ const self = this;
143
+ return {
144
+ async insert(obj) {
145
+ const cols = Object.keys(obj);
146
+ const qs = cols.map(() => "?").join(",");
147
+ await self.execute(
148
+ `INSERT INTO ${table} (${cols.join(",")}) VALUES (${qs})`,
149
+ cols.map(c => obj[c])
150
+ );
151
+ },
152
+ async upsert(obj) {
153
+ const cols = Object.keys(obj);
154
+ const qs = cols.map(() => "?").join(",");
155
+ const updates = cols.map(c => `${c}=excluded.${c}`).join(",");
156
+ await self.execute(
157
+ `INSERT INTO ${table} (${cols.join(",")}) VALUES (${qs})
142
158
  ON CONFLICT(id) DO UPDATE SET ${updates}`,
143
- cols.map(c => obj[c])
144
- );
145
- },
146
- async update(id, patch) {
147
- const cols = Object.keys(patch);
148
- if (!cols.length) return;
149
- const assigns = cols.map(c => `${c}=?`).join(",");
150
- await self.execute(
151
- `UPDATE ${table} SET ${assigns} WHERE id=?`,
152
- [...cols.map(c => patch[c]), id]
153
- );
154
- },
155
- async deleteSoft(id, ts = new Date().toISOString()) {
156
- await self.execute(`UPDATE ${table} SET deleted_at=? WHERE id=?`, [ts, id]);
157
- },
158
- async remove(id) {
159
- await self.execute(`DELETE FROM ${table} WHERE id=?`, [id]);
160
- },
161
- async findById(id) {
162
- return await self.get(`SELECT * FROM ${table} WHERE id=?`, [id]);
163
- },
164
- async searchLike(q, columns, limit = 50) {
165
- if (!columns?.length) return [];
166
- const where = columns.map(c => `${table}.${c} LIKE ?`).join(" OR ");
167
- const params = columns.map(() => `%${q}%`);
168
- return await self.query(
169
- `SELECT * FROM ${table} WHERE (${where}) LIMIT ?`,
170
- [...params, limit]
171
- );
172
- }
173
- };
174
- }
159
+ cols.map(c => obj[c])
160
+ );
161
+ },
162
+ async update(id, patch) {
163
+ const cols = Object.keys(patch);
164
+ if (!cols.length) return;
165
+ const assigns = cols.map(c => `${c}=?`).join(",");
166
+ await self.execute(
167
+ `UPDATE ${table} SET ${assigns} WHERE id=?`,
168
+ [...cols.map(c => patch[c]), id]
169
+ );
170
+ },
171
+ async deleteSoft(id, ts = new Date().toISOString()) {
172
+ await self.execute(`UPDATE ${table} SET deleted_at=? WHERE id=?`, [ts, id]);
173
+ },
174
+ async remove(id) {
175
+ await self.execute(`DELETE FROM ${table} WHERE id=?`, [id]);
176
+ },
177
+ async findById(id) {
178
+ return await self.get(`SELECT * FROM ${table} WHERE id=?`, [id]);
179
+ },
180
+ async searchLike(q, columns, limit = 50) {
181
+ if (!columns?.length) return [];
182
+ const where = columns.map(c => `${table}.${c} LIKE ?`).join(" OR ");
183
+ const params = columns.map(() => `%${q}%`);
184
+ return await self.query(
185
+ `SELECT * FROM ${table} WHERE (${where}) LIMIT ?`,
186
+ [...params, limit]
187
+ );
188
+ }
189
+ };
190
+ }
175
191
 
176
- // ---------------- schema registration ----------------
177
-
178
- /** @param {Schema[]} schemas */
179
- async registerSchemas(schemas) {
180
- const meta = await this.#currentVersions();
181
- for (const s of schemas) {
182
- const exists = await this.get(
183
- `SELECT name FROM sqlite_master WHERE type='table' AND name=?`,
184
- [s.table]
185
- );
186
- if (!exists) {
187
- await this.execute(s.createSQL);
188
- if (Array.isArray(s.extraSQL)) {
189
- for (const x of s.extraSQL) await this.execute(x);
190
- }
191
- await this.execute(
192
- `INSERT OR REPLACE INTO allez_meta(table_name,version) VALUES(?,?)`,
193
- [s.table, s.version ?? 1]
194
- );
195
- } else {
196
- const cur = meta.get(s.table) ?? 1;
197
- const next = s.version ?? cur;
198
- if (s.onUpgrade && next > cur) {
199
- await s.onUpgrade(this.db, cur, next);
200
- await this.execute(
201
- `UPDATE allez_meta SET version=? WHERE table_name=?`,
202
- [next, s.table]
203
- );
204
- }
205
- }
192
+ // ---------------- schema registration ----------------
193
+
194
+ /** @param {Schema[]} schemas */
195
+ async registerSchemas(schemas) {
196
+ const meta = await this.#currentVersions();
197
+ for (const s of schemas) {
198
+ const exists = await this.get(
199
+ `SELECT name FROM sqlite_master WHERE type='table' AND name=?`,
200
+ [s.table]
201
+ );
202
+ if (!exists) {
203
+ await this.execute(s.createSQL);
204
+ if (Array.isArray(s.extraSQL)) {
205
+ for (const x of s.extraSQL) await this.execute(x);
206
+ }
207
+ await this.execute(
208
+ `INSERT OR REPLACE INTO allez_meta(table_name,version) VALUES(?,?)`,
209
+ [s.table, s.version ?? 1]
210
+ );
211
+ } else {
212
+ const cur = meta.get(s.table) ?? 1;
213
+ const next = s.version ?? cur;
214
+ if (s.onUpgrade && next > cur) {
215
+ await s.onUpgrade(this.db, cur, next);
216
+ await this.execute(
217
+ `UPDATE allez_meta SET version=? WHERE table_name=?`,
218
+ [next, s.table]
219
+ );
206
220
  }
207
- this.#scheduleSave();
221
+ }
208
222
  }
223
+ this.#scheduleSave();
224
+ }
209
225
 
210
- async saveNow() {
211
- const data = this.db.export(); // Uint8Array
212
- await idbSet(this.dbName, data);
213
- }
226
+ async saveNow() {
227
+ const data = this.db.export(); // Uint8Array
228
+ await idbSet(this.dbName, data);
229
+ }
214
230
 
215
- // ---------------- internals ----------------
231
+ // ---------------- internals ----------------
216
232
 
217
- async #ensureMeta() {
218
- await this.execute(`
233
+ async #ensureMeta() {
234
+ await this.execute(`
219
235
  CREATE TABLE IF NOT EXISTS allez_meta (
220
236
  table_name TEXT PRIMARY KEY,
221
237
  version INTEGER NOT NULL
222
238
  );
223
239
  `);
224
- }
240
+ }
225
241
 
226
- async #currentVersions() {
227
- const rows = await this.query(
228
- `SELECT table_name, version FROM allez_meta`
229
- );
230
- return new Map(rows.map(r => [r.table_name, r.version]));
231
- }
242
+ async #currentVersions() {
243
+ const rows = await this.query(
244
+ `SELECT table_name, version FROM allez_meta`
245
+ );
246
+ return new Map(rows.map(r => [r.table_name, r.version]));
247
+ }
232
248
 
233
- #scheduleSave() {
234
- clearTimeout(this.saveTimer);
235
- this.saveTimer = setTimeout(() => { void this.saveNow(); }, this.autoSaveMs);
236
- }
249
+ #scheduleSave() {
250
+ clearTimeout(this.saveTimer);
251
+ this.saveTimer = setTimeout(() => { void this.saveNow(); }, this.autoSaveMs);
252
+ }
237
253
  }
238
254
 
239
255
  // ---------------- helpers: schema collection + IndexedDB ----------------
240
256
 
241
257
  /** @param {InitOptions} opts */
242
258
  function collectSchemas(opts) {
243
- const fromModules = opts.schemaModules
244
- ? Object.values(opts.schemaModules).map(m => m.default)
245
- : [];
246
- const fromArray = opts.schemas ?? [];
247
- return [...fromModules, ...fromArray];
259
+ const fromModules = opts.schemaModules
260
+ ? Object.values(opts.schemaModules).map(m => m.default)
261
+ : [];
262
+ const fromArray = opts.schemas ?? [];
263
+ return [...fromModules, ...fromArray];
248
264
  }
249
265
 
250
266
  function openIdb() {
251
- return new Promise((resolve, reject) => {
252
- const req = indexedDB.open("allez-orm-store", 1);
253
- req.onupgradeneeded = () => req.result.createObjectStore("dbs");
254
- req.onerror = () => reject(req.error);
255
- req.onsuccess = () => resolve(req.result);
256
- });
267
+ return new Promise((resolve, reject) => {
268
+ const req = indexedDB.open("allez-orm-store", 1);
269
+ req.onupgradeneeded = () => req.result.createObjectStore("dbs");
270
+ req.onerror = () => reject(req.error);
271
+ req.onsuccess = () => resolve(req.result);
272
+ });
257
273
  }
258
274
 
259
275
  async function idbGet(key) {
260
- const db = await openIdb();
261
- return new Promise((resolve, reject) => {
262
- const tx = db.transaction("dbs", "readonly");
263
- const store = tx.objectStore("dbs");
264
- const get = store.get(key);
265
- get.onsuccess = () => resolve(get.result || null);
266
- get.onerror = () => reject(get.error);
267
- });
276
+ const db = await openIdb();
277
+ return new Promise((resolve, reject) => {
278
+ const tx = db.transaction("dbs", "readonly");
279
+ const store = tx.objectStore("dbs");
280
+ const get = store.get(key);
281
+ get.onsuccess = () => resolve(get.result || null);
282
+ get.onerror = () => reject(get.error);
283
+ });
268
284
  }
269
285
 
270
286
  async function idbSet(key, value) {
271
- const db = await openIdb();
272
- return new Promise((resolve, reject) => {
273
- const tx = db.transaction("dbs", "readwrite");
274
- tx.objectStore("dbs").put(value, key);
275
- tx.oncomplete = () => resolve();
276
- tx.onerror = () => reject(tx.error);
277
- });
287
+ const db = await openIdb();
288
+ return new Promise((resolve, reject) => {
289
+ const tx = db.transaction("dbs", "readwrite");
290
+ tx.objectStore("dbs").put(value, key);
291
+ tx.oncomplete = () => resolve();
292
+ tx.onerror = () => reject(tx.error);
293
+ });
278
294
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "allez-orm",
3
- "version": "1.0.4",
3
+ "version": "1.0.6",
4
4
  "description": "AllezORM: lightweight browser SQLite ORM (sql.js) + schema generator CLI",
5
5
  "type": "module",
6
6
  "main": "./allez-orm.mjs",