@mostajs/backup 0.2.0 → 0.3.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.
@@ -0,0 +1,395 @@
1
+ "use strict";
2
+ /**
3
+ * @mostajs/backup — Sauvegarder, vérifier, restaurer. Pour de vrai.
4
+ * @author Dr Hamid MADANI <drmdh@msn.com>
5
+ * Licence : AGPL-3.0-or-later
6
+ * Niveau : N1 · Compose : archive-box · orm-copy-data · crypto-box · blob-store
7
+ *
8
+ * ─── UNE COPIE N'EST PAS UNE SAUVEGARDE ──────────────────────────────────────
9
+ * `@mostajs/orm-copy-data` TRANSFÈRE (A → B) : pas d'artefact, pas de temps, pas de
10
+ * vérification. Copier vers une seconde base ne donne pas une sauvegarde mais une RÉPLIQUE :
11
+ * le jour où quelqu'un supprime 300 tickets par erreur, la suppression se propage fidèlement.
12
+ * On a recopié le désastre.
13
+ *
14
+ * Une SAUVEGARDE est un artefact FIGÉ, DATÉ, VÉRIFIABLE et RESTAURABLE. Ce qui la définit,
15
+ * ce n'est pas la copie — c'est le TEMPS, l'INTÉGRITÉ et la RESTAURABILITÉ.
16
+ *
17
+ * ─── CE QUE CE MODULE N'INVENTE PAS ──────────────────────────────────────────
18
+ * données → @mostajs/orm-copy-data (13 dialectes, entre bases et formats)
19
+ * artefact → @mostajs/archive-box (zip + manifeste + empreintes + signature)
20
+ * chiffrement → @mostajs/crypto-box (flux AEAD, enveloppe X25519)
21
+ * dépôt → @mostajs/blob-store (disque, S3, tout service S3-compatible)
22
+ *
23
+ * Il n'ajoute que ce qu'aucun d'eux n'a : le PÉRIMÈTRE (au-delà des entités ORM : la config,
24
+ * la licence, les fichiers), la RÉTENTION, le TEST DE RESTAURATION, et la restauration
25
+ * ATOMIQUE avec retour arrière.
26
+ *
27
+ * ─── UN BACKUP JAMAIS RESTAURÉ N'EST PAS UN BACKUP ───────────────────────────
28
+ * C'est la panne la plus banale du métier : on sauvegarde deux ans, et le jour J l'archive est
29
+ * inutilisable. D'où `testRestore()` — qui restaure pour de vrai dans une base jetable et
30
+ * RECOMPTE les entités. Sans lui, on vend une promesse qu'on n'a pas vérifiée.
31
+ */
32
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
33
+ if (k2 === undefined) k2 = k;
34
+ var desc = Object.getOwnPropertyDescriptor(m, k);
35
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
36
+ desc = { enumerable: true, get: function() { return m[k]; } };
37
+ }
38
+ Object.defineProperty(o, k2, desc);
39
+ }) : (function(o, m, k, k2) {
40
+ if (k2 === undefined) k2 = k;
41
+ o[k2] = m[k];
42
+ }));
43
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
44
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
45
+ }) : function(o, v) {
46
+ o["default"] = v;
47
+ });
48
+ var __importStar = (this && this.__importStar) || (function () {
49
+ var ownKeys = function(o) {
50
+ ownKeys = Object.getOwnPropertyNames || function (o) {
51
+ var ar = [];
52
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
53
+ return ar;
54
+ };
55
+ return ownKeys(o);
56
+ };
57
+ return function (mod) {
58
+ if (mod && mod.__esModule) return mod;
59
+ var result = {};
60
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
61
+ __setModuleDefault(result, mod);
62
+ return result;
63
+ };
64
+ })();
65
+ Object.defineProperty(exports, "__esModule", { value: true });
66
+ exports.readManifest = exports.SECRETS_EXCLUS = exports.moduleInfo = void 0;
67
+ exports.createBackup = createBackup;
68
+ exports.storeBackup = storeBackup;
69
+ exports.listBackups = listBackups;
70
+ exports.verifyBackup = verifyBackup;
71
+ exports.restoreBackup = restoreBackup;
72
+ exports.testRestore = testRestore;
73
+ exports.pruneBackups = pruneBackups;
74
+ exports.scheduleBackups = scheduleBackups;
75
+ const archive_box_1 = require("@mostajs/archive-box");
76
+ Object.defineProperty(exports, "readManifest", { enumerable: true, get: function () { return archive_box_1.readManifest; } });
77
+ const orm_copy_data_1 = require("@mostajs/orm-copy-data");
78
+ const crypto_box_1 = require("@mostajs/crypto-box");
79
+ const scheduler_1 = require("@mostajs/scheduler");
80
+ exports.moduleInfo = { name: '@mostajs/backup', version: '0.3.0', level: 'N1' };
81
+ /** Ce que le module NE met JAMAIS dans une archive. */
82
+ exports.SECRETS_EXCLUS = ['JWT_SECRET', 'SGBD_URI', 'DB_PASSWORD', 'token', 'password'];
83
+ /**
84
+ * Garde-fou : un schéma sans `collection` fait écrire TOUTES les entités dans une seule table —
85
+ * en SILENCE. L'ORM l'accepte sans broncher : les tickets et les guichets se mélangent, les
86
+ * colonnes fusionnent, et personne ne s'en aperçoit avant la restauration… chez le client.
87
+ *
88
+ * Une sauvegarde silencieusement fausse est pire que pas de sauvegarde du tout. On refuse.
89
+ */
90
+ function exigerCollections(schemas) {
91
+ const sans = schemas.filter((s) => !s.collection).map((s) => s.name || '(sans nom)');
92
+ if (sans.length) {
93
+ throw new Error(`[backup] schéma(s) sans « collection » : ${sans.join(', ')}. ` +
94
+ 'L\'ORM en dérive le nom de table — sans lui, TOUTES les entités seraient écrites dans la même table, en silence.');
95
+ }
96
+ }
97
+ const horodatage = (d) => d.toISOString().replace(/[:.]/g, '-');
98
+ /**
99
+ * Crée une sauvegarde COMPLÈTE : entités + configuration + fichiers.
100
+ *
101
+ * Le périmètre dépasse structurellement `orm-copy-data`, qui ne voit QUE les entités de l'ORM.
102
+ * Une restauration qui rend les tickets mais pas la configuration ni la licence ne remet pas
103
+ * le cabinet en marche lundi matin.
104
+ */
105
+ async function createBackup(opts) {
106
+ exigerCollections(opts.source.schemas);
107
+ const createdAt = new Date();
108
+ // 1. Dump des entités : db → JSON en mémoire (via orm-copy-data, on ne réinvente rien).
109
+ const dump = await dumpEntities(opts.source);
110
+ const files = [];
111
+ const counts = {};
112
+ for (const [entity, rows] of Object.entries(dump)) {
113
+ counts[entity] = rows.length;
114
+ files.push({ path: `entities/${entity}.json`, content: JSON.stringify(rows) });
115
+ }
116
+ // 2. Le hors-base : sans lui, l'archive n'est pas restaurable en pratique.
117
+ if (opts.extras?.config !== undefined) {
118
+ files.push({ path: 'config.json', content: JSON.stringify(opts.extras.config, null, 2) });
119
+ }
120
+ for (const f of opts.extras?.files ?? []) {
121
+ files.push({ path: `files/${f.path}`, content: f.content });
122
+ }
123
+ // 3. L'artefact : zip + manifeste + empreintes (+ signature).
124
+ const built = await (0, archive_box_1.buildArchive)({
125
+ name: `sauvegarde-${opts.siteId}-${horodatage(createdAt)}`,
126
+ kind: 'backup',
127
+ createdBy: opts.createdBy,
128
+ sourceApp: '@mostajs/backup',
129
+ files,
130
+ extra: {
131
+ siteId: opts.siteId,
132
+ dialect: opts.source.dialect,
133
+ counts, // ← la référence du test de restauration
134
+ entities: Object.keys(counts),
135
+ total: Object.values(counts).reduce((a, b) => a + b, 0),
136
+ },
137
+ signingKey: opts.signingKey,
138
+ signingPublicKey: opts.signingPublicKey,
139
+ });
140
+ // 4. Scellement facultatif : le prestataire conserve sans pouvoir lire.
141
+ const sealed = !!opts.recipients?.length;
142
+ const buffer = sealed ? (0, crypto_box_1.seal)(opts.recipients, built.buffer) : built.buffer;
143
+ return {
144
+ buffer,
145
+ plainBuffer: built.buffer, // jamais déposé — sert au test de restauration
146
+ manifest: built.manifest,
147
+ counts,
148
+ sealed,
149
+ siteId: opts.siteId,
150
+ createdAt: createdAt.toISOString(),
151
+ path: `backups/${opts.siteId}/${horodatage(createdAt)}.archive`,
152
+ size: buffer.length,
153
+ sha256: (0, crypto_box_1.sha256)(buffer),
154
+ };
155
+ }
156
+ /** Dump des entités via `orm-copy-data` (source db → writer json en mémoire). */
157
+ async function dumpEntities(source) {
158
+ const { writeFileSync, mkdtempSync, readFileSync, rmSync } = await Promise.resolve().then(() => __importStar(require('node:fs')));
159
+ const { tmpdir } = await Promise.resolve().then(() => __importStar(require('node:os')));
160
+ const path = await Promise.resolve().then(() => __importStar(require('node:path')));
161
+ const dir = mkdtempSync(path.join(tmpdir(), 'mostajs-backup-'));
162
+ const out = path.join(dir, 'dump.json');
163
+ try {
164
+ await (0, orm_copy_data_1.copyData)({
165
+ source: { type: 'db', dialect: source.dialect, uri: source.uri },
166
+ destinations: [{ type: 'json', file: out }],
167
+ schemas: source.schemas,
168
+ options: { batchSize: 500 },
169
+ });
170
+ const raw = JSON.parse(readFileSync(out, 'utf8'));
171
+ return raw.entities ?? {};
172
+ }
173
+ finally {
174
+ try {
175
+ rmSync(dir, { recursive: true, force: true });
176
+ }
177
+ catch { /* nettoyage best-effort */ }
178
+ void writeFileSync;
179
+ }
180
+ }
181
+ // ─── Dépôt (blob-store : disque, S3, MinIO…) ────────────────────────────────
182
+ async function storeBackup(driver, bucket, backup) {
183
+ const ref = { bucket, path: backup.path };
184
+ await driver.put(ref, backup.buffer, {
185
+ mimeType: 'application/octet-stream',
186
+ metadata: {
187
+ siteid: backup.siteId,
188
+ createdat: backup.createdAt,
189
+ sealed: String(backup.sealed),
190
+ sha256: backup.sha256,
191
+ },
192
+ });
193
+ return ref;
194
+ }
195
+ async function listBackups(driver, bucket, siteId) {
196
+ const res = await driver.list({ bucket, prefix: `backups/${siteId}/`, limit: 1000 });
197
+ return res.items
198
+ .map((i) => ({
199
+ path: i.path,
200
+ siteId,
201
+ createdAt: i.modifiedAt.toISOString(),
202
+ size: i.size,
203
+ sha256: '',
204
+ sealed: false,
205
+ }))
206
+ .sort((a, b) => (a.path < b.path ? 1 : -1)); // plus récentes d'abord
207
+ }
208
+ /**
209
+ * Vérifie une archive SANS la restaurer. `privateKey` est requise si elle est scellée.
210
+ *
211
+ * `expectedPublicKey` n'est pas un détail : une archive parfaitement valide, SIGNÉE PAR UN
212
+ * TIERS, reste dangereuse. Sans cette clé, on restaure une base fabriquée par quelqu'un d'autre.
213
+ */
214
+ async function verifyBackup(buffer, opts = {}) {
215
+ const zip = unseal(buffer, opts.privateKey);
216
+ const v = await (0, archive_box_1.verifyArchive)(zip, { expectedPublicKey: opts.expectedPublicKey });
217
+ const counts = v.manifest?.archiveBox?.extra?.counts ?? {};
218
+ return { ...v, counts };
219
+ }
220
+ /** Descelle si nécessaire. Une archive scellée sans clé n'est pas lisible — c'est le but. */
221
+ function unseal(buffer, privateKey) {
222
+ const estScellee = buffer.subarray(0, 5).toString() === 'MJSE1';
223
+ if (!estScellee)
224
+ return buffer;
225
+ if (!privateKey) {
226
+ throw new Error('[backup] archive SCELLÉE : clé privée requise (le prestataire, lui, ne peut pas l’ouvrir)');
227
+ }
228
+ return (0, crypto_box_1.open)(privateKey, buffer);
229
+ }
230
+ /**
231
+ * Restaure une archive. **Simulation par défaut** : une restauration est destructrice, elle ne
232
+ * part jamais sans un choix explicite.
233
+ *
234
+ * La restauration se termine par un RECOMPTAGE : les entités relues dans la base doivent
235
+ * correspondre aux compteurs du manifeste. Sans cela, on déclare « restauré » sans savoir.
236
+ */
237
+ async function restoreBackup(buffer, opts) {
238
+ exigerCollections(opts.schemas);
239
+ const dryRun = opts.dryRun !== false;
240
+ const zip = unseal(buffer, opts.privateKey);
241
+ // Strict : intégrité ET signature vérifiées AVANT toute écriture.
242
+ const archive = await (0, archive_box_1.openArchive)(zip, { expectedPublicKey: opts.expectedPublicKey, strict: true });
243
+ const attendus = archive.manifest.archiveBox.extra?.counts ?? {};
244
+ const config = archive.files.has('config.json')
245
+ ? JSON.parse(archive.files.get('config.json').toString('utf8'))
246
+ : undefined;
247
+ if (dryRun) {
248
+ return { dryRun: true, attendus, obtenus: {}, verifie: true, config, ecarts: [] };
249
+ }
250
+ // Reconstitution du dump JSON attendu par orm-copy-data.
251
+ const entities = {};
252
+ for (const [path, content] of archive.files) {
253
+ const m = /^entities\/(.+)\.json$/.exec(path);
254
+ if (m)
255
+ entities[m[1]] = JSON.parse(content.toString('utf8'));
256
+ }
257
+ const { writeFileSync, mkdtempSync, rmSync } = await Promise.resolve().then(() => __importStar(require('node:fs')));
258
+ const { tmpdir } = await Promise.resolve().then(() => __importStar(require('node:os')));
259
+ const nodePath = await Promise.resolve().then(() => __importStar(require('node:path')));
260
+ const dir = mkdtempSync(nodePath.join(tmpdir(), 'mostajs-restore-'));
261
+ const src = nodePath.join(dir, 'restore.json');
262
+ try {
263
+ writeFileSync(src, JSON.stringify({ entities }));
264
+ await (0, orm_copy_data_1.copyData)({
265
+ source: { type: 'json', file: src },
266
+ destinations: [{ type: 'db', dialect: opts.dialect, uri: opts.uri, createTables: true }],
267
+ schemas: opts.schemas,
268
+ options: { batchSize: 500 },
269
+ });
270
+ }
271
+ finally {
272
+ try {
273
+ rmSync(dir, { recursive: true, force: true });
274
+ }
275
+ catch { /* best-effort */ }
276
+ }
277
+ // RECOMPTAGE — le contrôle qui transforme « restauré » en « restauré ET vérifié ».
278
+ const obtenus = await dumpEntities({ dialect: opts.dialect, uri: opts.uri, schemas: opts.schemas })
279
+ .then((d) => Object.fromEntries(Object.entries(d).map(([k, v]) => [k, v.length])));
280
+ const ecarts = [];
281
+ for (const [entity, n] of Object.entries(attendus)) {
282
+ const got = obtenus[entity] ?? 0;
283
+ if (got !== n)
284
+ ecarts.push(`${entity} : attendu ${n}, obtenu ${got}`);
285
+ }
286
+ return { dryRun: false, attendus, obtenus, verifie: ecarts.length === 0, config, ecarts };
287
+ }
288
+ /**
289
+ * TEST DE RESTAURATION — restaure dans une base JETABLE et recompte.
290
+ *
291
+ * Un backup jamais restauré n'est pas un backup : c'est la panne la plus banale du métier. On
292
+ * sauvegarde deux ans, et le jour J l'archive est inutilisable. Ce test doit tourner
293
+ * AUTOMATIQUEMENT — sans lui, on vend une promesse qu'on n'a jamais vérifiée.
294
+ */
295
+ async function testRestore(buffer, opts) {
296
+ const { mkdtempSync, rmSync } = await Promise.resolve().then(() => __importStar(require('node:fs')));
297
+ const { tmpdir } = await Promise.resolve().then(() => __importStar(require('node:os')));
298
+ const path = await Promise.resolve().then(() => __importStar(require('node:path')));
299
+ const dir = mkdtempSync(path.join(tmpdir(), 'mostajs-testrestore-'));
300
+ const db = path.join(dir, 'jetable.sqlite');
301
+ try {
302
+ return await restoreBackup(buffer, {
303
+ dialect: 'sqljs',
304
+ uri: db,
305
+ schemas: opts.schemas,
306
+ privateKey: opts.privateKey,
307
+ expectedPublicKey: opts.expectedPublicKey,
308
+ dryRun: false,
309
+ });
310
+ }
311
+ finally {
312
+ try {
313
+ rmSync(dir, { recursive: true, force: true });
314
+ }
315
+ catch { /* best-effort */ }
316
+ }
317
+ }
318
+ /**
319
+ * Purge les sauvegardes excédentaires. Retourne CE QUI A ÉTÉ SUPPRIMÉ — jamais en silence :
320
+ * une purge muette est le meilleur moyen de découvrir trop tard qu'on a effacé la seule copie.
321
+ */
322
+ async function pruneBackups(driver, bucket, siteId, policy) {
323
+ const all = await listBackups(driver, bucket, siteId);
324
+ if (!policy.keepLast || all.length <= policy.keepLast) {
325
+ return { supprimees: [], conservees: all.map((b) => b.path) };
326
+ }
327
+ const conservees = all.slice(0, policy.keepLast);
328
+ const aSupprimer = all.slice(policy.keepLast);
329
+ for (const b of aSupprimer)
330
+ await driver.delete({ bucket, path: b.path });
331
+ return { supprimees: aSupprimer.map((b) => b.path), conservees: conservees.map((b) => b.path) };
332
+ }
333
+ /**
334
+ * Planifie les sauvegardes. Compose `@mostajs/scheduler` — on ne réinvente ni la cadence, ni
335
+ * l'idempotence, ni l'adoption de la période au démarrage.
336
+ *
337
+ * ─── DEUX PIÈGES MORTELS, TRAITÉS ICI ────────────────────────────────────────
338
+ *
339
+ * 1. **La rétention qui purge après un ÉCHEC.** C'est la façon classique de tout perdre : la
340
+ * sauvegarde de ce soir échoue (base verrouillée, disque plein), la rotation s'exécute quand
341
+ * même, et elle supprime la plus ancienne des N archives valides. Répétez l'opération N nuits,
342
+ * et il ne reste RIEN — alors que chaque nuit « la sauvegarde a tourné ».
343
+ * → La purge n'a lieu **QUE** si la nouvelle sauvegarde est créée ET vérifiée.
344
+ *
345
+ * 2. **L'échec silencieux.** Un planificateur qui n'a personne pour écouter ses erreurs a l'air
346
+ * de fonctionner. On ne s'aperçoit de rien jusqu'au jour où l'on cherche une archive qui
347
+ * n'existe pas.
348
+ * → `onBackup` est notifié à CHAQUE cycle, succès **comme** échec. C'est le point de
349
+ * supervision : branchez-y une alerte, sinon vous ne saurez jamais.
350
+ */
351
+ function scheduleBackups(opts) {
352
+ const verifyRestore = opts.verifyRestore !== false;
353
+ return (0, scheduler_1.createScheduler)({
354
+ getSchedule: opts.getSchedule,
355
+ intervalMs: opts.intervalMs,
356
+ now: opts.now,
357
+ onTrigger: async () => {
358
+ let backup;
359
+ try {
360
+ backup = await createBackup(opts);
361
+ await storeBackup(opts.driver, opts.bucket, backup);
362
+ // Le test de restauration fait partie de la sauvegarde, pas d'une corvée séparée :
363
+ // une archive non testée n'est pas une archive vérifiée.
364
+ let restoreTest;
365
+ if (verifyRestore) {
366
+ // On teste l'archive AVANT scellement. Une archive scellée pour le client ne peut pas
367
+ // être ouverte par la console — c'est le point du modèle. Tester sur `buffer` exigerait
368
+ // donc la clé PRIVÉE du client, exactement ce qu'on refuse de détenir.
369
+ restoreTest = await testRestore(backup.plainBuffer, {
370
+ schemas: opts.source.schemas,
371
+ expectedPublicKey: opts.signingPublicKey,
372
+ });
373
+ if (!restoreTest.verifie) {
374
+ // On NE PURGE PAS : les anciennes archives sont peut-être tout ce qui reste de bon.
375
+ throw new Error(`[backup] sauvegarde créée mais NON RESTAURABLE — rétention suspendue. ${restoreTest.ecarts.join(' · ')}`);
376
+ }
377
+ }
378
+ // Purge — uniquement maintenant, la nouvelle sauvegarde étant créée ET vérifiée.
379
+ let pruned;
380
+ if (opts.retention) {
381
+ const r = await pruneBackups(opts.driver, opts.bucket, opts.siteId, opts.retention);
382
+ pruned = r.supprimees;
383
+ }
384
+ opts.onBackup?.({ ok: true, backup, restoreTest, pruned });
385
+ }
386
+ catch (error) {
387
+ // La rétention n'a PAS tourné : c'est le point. Un échec ne doit jamais faire disparaître
388
+ // une archive valide.
389
+ opts.onBackup?.({ ok: false, backup, error });
390
+ throw error; // remonte aussi à `onError` du planificateur
391
+ }
392
+ },
393
+ onError: (e) => { void e; /* déjà remonté par onBackup */ },
394
+ });
395
+ }
@@ -0,0 +1 @@
1
+ {"type":"commonjs"}
package/dist/index.d.ts CHANGED
@@ -34,7 +34,7 @@ import type { StorageDriver, ObjectRef } from '@mostajs/blob-store';
34
34
  import type { Schedule, Scheduler } from '@mostajs/scheduler';
35
35
  export declare const moduleInfo: {
36
36
  readonly name: "@mostajs/backup";
37
- readonly version: "0.1.0";
37
+ readonly version: "0.3.0";
38
38
  readonly level: "N1";
39
39
  };
40
40
  /** Ce que le module NE met JAMAIS dans une archive. */
package/dist/index.js CHANGED
@@ -32,7 +32,7 @@ import { buildArchive, openArchive, verifyArchive, readManifest } from '@mostajs
32
32
  import { copyData } from '@mostajs/orm-copy-data';
33
33
  import { seal, open as openSealed, sha256 } from '@mostajs/crypto-box';
34
34
  import { createScheduler } from '@mostajs/scheduler';
35
- export const moduleInfo = { name: '@mostajs/backup', version: '0.1.0', level: 'N1' };
35
+ export const moduleInfo = { name: '@mostajs/backup', version: '0.3.0', level: 'N1' };
36
36
  /** Ce que le module NE met JAMAIS dans une archive. */
37
37
  export const SECRETS_EXCLUS = ['JWT_SECRET', 'SGBD_URI', 'DB_PASSWORD', 'token', 'password'];
38
38
  /**
package/package.json CHANGED
@@ -1,14 +1,17 @@
1
1
  {
2
2
  "name": "@mostajs/backup",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "description": "Sauvegarder, vérifier, restaurer — pour de vrai. Artefact daté, signé, chiffrable par enveloppe. Sauvegardes PLANIFIÉES avec test de restauration automatique et rétention qui ne purge JAMAIS après un échec. Compose archive-box, orm-copy-data, crypto-box, blob-store et scheduler.",
5
+ "type": "module",
6
+ "main": "./dist/cjs/index.js",
7
+ "types": "./dist/index.d.ts",
5
8
  "license": "AGPL-3.0-or-later",
6
9
  "author": "Dr Hamid MADANI <drmdh@msn.com>",
7
- "type": "module",
8
10
  "exports": {
9
11
  ".": {
10
12
  "types": "./dist/index.d.ts",
11
13
  "import": "./dist/index.js",
14
+ "require": "./dist/cjs/index.js",
12
15
  "default": "./dist/index.js"
13
16
  }
14
17
  },
@@ -19,7 +22,7 @@
19
22
  "LICENSE"
20
23
  ],
21
24
  "scripts": {
22
- "build": "tsc",
25
+ "build": "tsc && tsc -p tsconfig.cjs.json && node -e \"require('fs').writeFileSync('dist/cjs/package.json', JSON.stringify({type:'commonjs'}))\"",
23
26
  "test": "bash test-scripts/run-tests.sh",
24
27
  "prepublishOnly": "npm run build"
25
28
  },
@@ -33,10 +36,10 @@
33
36
  "mostajs"
34
37
  ],
35
38
  "dependencies": {
36
- "@mostajs/archive-box": "^0.1.0",
37
- "@mostajs/blob-store": "^0.3.0",
38
- "@mostajs/crypto-box": "^0.2.0",
39
- "@mostajs/orm-copy-data": "^0.4.0",
39
+ "@mostajs/archive-box": "^0.2.0",
40
+ "@mostajs/blob-store": "^0.4.0",
41
+ "@mostajs/crypto-box": "^0.2.1",
42
+ "@mostajs/orm-copy-data": "^0.5.0",
40
43
  "@mostajs/scheduler": "^0.2.0"
41
44
  },
42
45
  "peerDependencies": {
@@ -54,4 +57,4 @@
54
57
  "sql.js": "^1.14.1",
55
58
  "typescript": "^5.6.0"
56
59
  }
57
- }
60
+ }