@cap-js-community/common 0.2.8 → 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.
- package/CHANGELOG.md +8 -0
- package/README.md +3 -0
- package/package.json +2 -2
- package/src/replication-cache/ReplicationCache.js +36 -35
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,14 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
|
|
6
6
|
and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## Version 0.3.0 - 2025-11-03
|
|
9
|
+
|
|
10
|
+
### Fixed
|
|
11
|
+
|
|
12
|
+
- Refactor replication cache
|
|
13
|
+
- Change `@cap-js/sqlite` to dev dependency
|
|
14
|
+
- Replication cache requires `@cap-js/sqlite` as project dependency (no dev dependency)
|
|
15
|
+
|
|
8
16
|
## Version 0.2.8 - 2025-10-13
|
|
9
17
|
|
|
10
18
|
### Fixed
|
package/README.md
CHANGED
|
@@ -30,6 +30,9 @@ Local replicated SQLite database can be queried with same query as the original
|
|
|
30
30
|
|
|
31
31
|
### Usage
|
|
32
32
|
|
|
33
|
+
> Replication cache uses SQLite as local database for productive usage.
|
|
34
|
+
> Ensure `@cap-js/sqlite` is installed as dependency (not as dev dependency) in your project.
|
|
35
|
+
|
|
33
36
|
```cds
|
|
34
37
|
@cds.replicate
|
|
35
38
|
entity Books {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cap-js-community/common",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "CAP Node.js Community Common",
|
|
5
5
|
"homepage": "https://cap.cloud.sap/",
|
|
6
6
|
"engines": {
|
|
@@ -45,7 +45,6 @@
|
|
|
45
45
|
"audit": "npm audit --only=prod"
|
|
46
46
|
},
|
|
47
47
|
"dependencies": {
|
|
48
|
-
"@cap-js/sqlite": "^2.0.3",
|
|
49
48
|
"commander": "^14.0.1",
|
|
50
49
|
"redis": "^4.7.1",
|
|
51
50
|
"verror": "^1.10.1"
|
|
@@ -53,6 +52,7 @@
|
|
|
53
52
|
"devDependencies": {
|
|
54
53
|
"@cap-js-community/common": "./",
|
|
55
54
|
"@cap-js/cds-test": "^0.4.0",
|
|
55
|
+
"@cap-js/sqlite": "^2.0.3",
|
|
56
56
|
"@sap/cds": "^9.4.3",
|
|
57
57
|
"@sap/cds-common-content": "^3.0.1",
|
|
58
58
|
"@sap/cds-dk": "^9.4.1",
|
|
@@ -5,8 +5,6 @@ const path = require("path");
|
|
|
5
5
|
const fs = require("fs").promises;
|
|
6
6
|
|
|
7
7
|
const cds = require("@sap/cds");
|
|
8
|
-
const SQLiteService = require("@cap-js/sqlite");
|
|
9
|
-
|
|
10
8
|
require("../common/promise");
|
|
11
9
|
|
|
12
10
|
const Component = "replicationCache";
|
|
@@ -51,7 +49,7 @@ class ReplicationCache {
|
|
|
51
49
|
this.group = this.options.group;
|
|
52
50
|
this.log = cds.log(Component);
|
|
53
51
|
this.template = null;
|
|
54
|
-
this.
|
|
52
|
+
this.entries = new Map();
|
|
55
53
|
this.initStats();
|
|
56
54
|
this.attach();
|
|
57
55
|
}
|
|
@@ -64,6 +62,8 @@ class ReplicationCache {
|
|
|
64
62
|
if (service.name === this.name) {
|
|
65
63
|
const refs = ReplicationCache.replicationRefs(this.model, service, this.options.deploy);
|
|
66
64
|
if (refs.length > 0) {
|
|
65
|
+
// @cap-js/sqlite dependency is required for replication cache
|
|
66
|
+
this.SQLiteService = require("@cap-js/sqlite");
|
|
67
67
|
this.setup(service, refs);
|
|
68
68
|
this.log.info("using replication cache", {
|
|
69
69
|
service: service.name,
|
|
@@ -74,7 +74,7 @@ class ReplicationCache {
|
|
|
74
74
|
this.options?.credentials?.database !== Constants.InMemory
|
|
75
75
|
) {
|
|
76
76
|
this.log.info("Preparing replication cache template database");
|
|
77
|
-
this.template = createDB(Tenant.Template, this.model, this.options)
|
|
77
|
+
this.template = createDB(this.SQLiteService, Tenant.Template, this.model, this.options)
|
|
78
78
|
.then(() => {
|
|
79
79
|
this.log.info("Prepared replication cache template database");
|
|
80
80
|
})
|
|
@@ -220,7 +220,7 @@ class ReplicationCache {
|
|
|
220
220
|
this.stats.used++;
|
|
221
221
|
this.stats.ratio = Math.round(this.stats.used / this.stats.hits);
|
|
222
222
|
this.log.debug("Replication cache was used");
|
|
223
|
-
const db = this.
|
|
223
|
+
const db = this.entries.get(tenant).db;
|
|
224
224
|
if (this.options.measure) {
|
|
225
225
|
return this.measure(
|
|
226
226
|
async () => {
|
|
@@ -316,15 +316,15 @@ class ReplicationCache {
|
|
|
316
316
|
if (refs.length === 0) {
|
|
317
317
|
return;
|
|
318
318
|
}
|
|
319
|
-
let tenantCache = cached(this.
|
|
320
|
-
return new ReplicationCacheTenant(tenant, model, this.options).prepare();
|
|
319
|
+
let tenantCache = cached(this.entries, tenant, async () => {
|
|
320
|
+
return new ReplicationCacheTenant(this, tenant, model, this.options).prepare();
|
|
321
321
|
});
|
|
322
322
|
return (async () => {
|
|
323
323
|
try {
|
|
324
324
|
const prepared = Promise.resolve(tenantCache).then(async (tenantCache) => {
|
|
325
325
|
const prepares = [];
|
|
326
326
|
for (const ref of refs) {
|
|
327
|
-
const entry = cached(tenantCache.
|
|
327
|
+
const entry = cached(tenantCache.entries, ref, () => {
|
|
328
328
|
return new ReplicationCacheEntry(this, tenantCache, ref);
|
|
329
329
|
});
|
|
330
330
|
entry.touched = Date.now();
|
|
@@ -346,7 +346,7 @@ class ReplicationCache {
|
|
|
346
346
|
return Status.NotReady;
|
|
347
347
|
}
|
|
348
348
|
for (const ref of refs) {
|
|
349
|
-
const entry = tenantCache.
|
|
349
|
+
const entry = tenantCache.entries.get(ref);
|
|
350
350
|
if (!entry || entry.status !== Status.Ready) {
|
|
351
351
|
return Status.NotReady;
|
|
352
352
|
}
|
|
@@ -361,13 +361,13 @@ class ReplicationCache {
|
|
|
361
361
|
}
|
|
362
362
|
|
|
363
363
|
async prepared(tenant, ref) {
|
|
364
|
-
const tenants = tenant ? [tenant] : this.
|
|
364
|
+
const tenants = tenant ? [tenant] : this.entries.keys();
|
|
365
365
|
for (const id of tenants) {
|
|
366
|
-
const tenant = await this.
|
|
366
|
+
const tenant = await this.entries.get(id);
|
|
367
367
|
if (tenant) {
|
|
368
|
-
const refs = ref ? [ref] : tenant.
|
|
368
|
+
const refs = ref ? [ref] : tenant.entries.keys();
|
|
369
369
|
for (const ref of refs) {
|
|
370
|
-
const entry = tenant.
|
|
370
|
+
const entry = tenant.entries.get(ref);
|
|
371
371
|
if (entry) {
|
|
372
372
|
await entry.prepared;
|
|
373
373
|
}
|
|
@@ -377,13 +377,13 @@ class ReplicationCache {
|
|
|
377
377
|
}
|
|
378
378
|
|
|
379
379
|
async clear(tenant, ref) {
|
|
380
|
-
const tenants = tenant ? [tenant] : this.
|
|
380
|
+
const tenants = tenant ? [tenant] : this.entries.keys();
|
|
381
381
|
for (const id of tenants) {
|
|
382
|
-
const tenant = await this.
|
|
382
|
+
const tenant = await this.entries.get(id);
|
|
383
383
|
if (tenant) {
|
|
384
|
-
const refs = ref ? [ref] : tenant.
|
|
384
|
+
const refs = ref ? [ref] : tenant.entries.keys();
|
|
385
385
|
for (const ref of refs) {
|
|
386
|
-
const entry = tenant.
|
|
386
|
+
const entry = tenant.entries.get(ref);
|
|
387
387
|
if (entry) {
|
|
388
388
|
await entry.clear();
|
|
389
389
|
this.log.debug("Replication cache cleared", {
|
|
@@ -404,10 +404,10 @@ class ReplicationCache {
|
|
|
404
404
|
}
|
|
405
405
|
|
|
406
406
|
async prune(tenant) {
|
|
407
|
-
const maxSize = this.options.size / this.
|
|
408
|
-
const tenants = tenant ? [tenant] : this.
|
|
407
|
+
const maxSize = this.options.size / this.entries.size;
|
|
408
|
+
const tenants = tenant ? [tenant] : this.entries.keys();
|
|
409
409
|
for (const id of tenants) {
|
|
410
|
-
const tenant = await this.
|
|
410
|
+
const tenant = await this.entries.get(id);
|
|
411
411
|
const size = await this.size(tenant.id);
|
|
412
412
|
let diff = size - maxSize;
|
|
413
413
|
if (diff > 0) {
|
|
@@ -415,12 +415,12 @@ class ReplicationCache {
|
|
|
415
415
|
tenant,
|
|
416
416
|
diff,
|
|
417
417
|
});
|
|
418
|
-
const refs = [...tenant.
|
|
419
|
-
refs.sort((ref1, ref2) => tenant.
|
|
418
|
+
const refs = [...tenant.entries.keys()];
|
|
419
|
+
refs.sort((ref1, ref2) => tenant.entries.get(ref1).touched - tenant.entries.get(ref2).touched);
|
|
420
420
|
const pruneRefs = [];
|
|
421
421
|
for (const ref of refs) {
|
|
422
422
|
pruneRefs.push(ref);
|
|
423
|
-
const entry = tenant.
|
|
423
|
+
const entry = tenant.entries.get(ref);
|
|
424
424
|
if (entry) {
|
|
425
425
|
diff -= entry.size;
|
|
426
426
|
if (diff <= 0) {
|
|
@@ -429,7 +429,7 @@ class ReplicationCache {
|
|
|
429
429
|
}
|
|
430
430
|
}
|
|
431
431
|
for (const ref of pruneRefs) {
|
|
432
|
-
const entry = tenant.
|
|
432
|
+
const entry = tenant.entries.get(ref);
|
|
433
433
|
this.log.debug("Replication cache prunes ref for tenant", {
|
|
434
434
|
tenant,
|
|
435
435
|
ref,
|
|
@@ -444,13 +444,13 @@ class ReplicationCache {
|
|
|
444
444
|
|
|
445
445
|
async size(tenant, ref) {
|
|
446
446
|
let size = 0;
|
|
447
|
-
const tenants = tenant ? [tenant] : this.
|
|
447
|
+
const tenants = tenant ? [tenant] : this.entries.keys();
|
|
448
448
|
for (const id of tenants) {
|
|
449
|
-
const tenant = await this.
|
|
449
|
+
const tenant = await this.entries.get(id);
|
|
450
450
|
if (tenant) {
|
|
451
|
-
const refs = ref ? [ref] : tenant.
|
|
451
|
+
const refs = ref ? [ref] : tenant.entries.keys();
|
|
452
452
|
for (const ref of refs) {
|
|
453
|
-
const entry = tenant.
|
|
453
|
+
const entry = tenant.entries.get(ref);
|
|
454
454
|
if (entry) {
|
|
455
455
|
size += entry.size;
|
|
456
456
|
}
|
|
@@ -461,7 +461,7 @@ class ReplicationCache {
|
|
|
461
461
|
}
|
|
462
462
|
|
|
463
463
|
async tenantSize(id) {
|
|
464
|
-
const tenant = await this.
|
|
464
|
+
const tenant = await this.entries.get(id);
|
|
465
465
|
if (tenant) {
|
|
466
466
|
return await tenant.db.tx(async (tx) => {
|
|
467
467
|
const result = await tx.run(
|
|
@@ -578,16 +578,17 @@ class ReplicationCache {
|
|
|
578
578
|
}
|
|
579
579
|
|
|
580
580
|
class ReplicationCacheTenant {
|
|
581
|
-
constructor(tenant, model, options) {
|
|
581
|
+
constructor(cache, tenant, model, options) {
|
|
582
|
+
this.cache = cache;
|
|
582
583
|
this.id = tenant;
|
|
583
584
|
this.model = model;
|
|
584
585
|
this.options = options;
|
|
585
586
|
this.csn = model.definitions;
|
|
586
|
-
this.
|
|
587
|
+
this.entries = new Map();
|
|
587
588
|
}
|
|
588
589
|
|
|
589
590
|
async prepare() {
|
|
590
|
-
this.db = await createDB(this.id, this.model, this.options);
|
|
591
|
+
this.db = await createDB(this.cache.SQLiteService, this.id, this.model, this.options);
|
|
591
592
|
return this;
|
|
592
593
|
}
|
|
593
594
|
}
|
|
@@ -697,7 +698,7 @@ class ReplicationCacheEntry {
|
|
|
697
698
|
async load(thread) {
|
|
698
699
|
this.timestamp = Date.now();
|
|
699
700
|
await this.clear();
|
|
700
|
-
if (thread && cds.context && this.service instanceof SQLiteService) {
|
|
701
|
+
if (thread && cds.context && this.service instanceof this.cache.SQLiteService) {
|
|
701
702
|
const srcTx = this.service.tx(cds.context);
|
|
702
703
|
await this.db.tx({ tenant: this.tenant.id }, async (destTx) => {
|
|
703
704
|
await this.loadRecords(srcTx, destTx);
|
|
@@ -803,7 +804,7 @@ class ReplicationCacheEntry {
|
|
|
803
804
|
|
|
804
805
|
module.exports = ReplicationCache;
|
|
805
806
|
|
|
806
|
-
async function createDB(tenant, model, options) {
|
|
807
|
+
async function createDB(DBService, tenant, model, options) {
|
|
807
808
|
const filePath = await dbPath(tenant, options);
|
|
808
809
|
cds.log(Component).debug("Preparing replication cache database", {
|
|
809
810
|
tenant,
|
|
@@ -813,7 +814,7 @@ async function createDB(tenant, model, options) {
|
|
|
813
814
|
const templateDatabase = await dbPath(Tenant.Template, options);
|
|
814
815
|
await fs.copyFile(templateDatabase, filePath);
|
|
815
816
|
}
|
|
816
|
-
const db = new
|
|
817
|
+
const db = new DBService(tenant ?? Tenant.Default, model, {
|
|
817
818
|
kind: "sqlite",
|
|
818
819
|
impl: "@cap-js/sqlite",
|
|
819
820
|
multiTenant: false,
|