ac-storage 0.14.1 → 0.15.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/AMBIGUOUS_TESTS_REPORT.md +57 -0
- package/CLAUDE.md +415 -0
- package/COMMENTS_IMPROVEMENT_REPORT.md +259 -0
- package/COMPLETE_TEST_CLEANUP_SUMMARY.md +217 -0
- package/FINAL_TEST_CLEANUP_REPORT.md +116 -0
- package/INCONSISTENT_TESTS_REPORT.md +165 -0
- package/README.md +172 -32
- package/REPORT.md +178 -0
- package/REPORT_2.md +31 -0
- package/TEST_CLEANUP_REPORT.md +81 -0
- package/TEST_COMMENTS_REVIEW.md +283 -0
- package/TEST_REFACTORING_REPORT.md +209 -0
- package/TODO.md +167 -0
- package/_TESTPATH/ac-drop/.acstorage +8 -0
- package/_TESTPATH/access-separation-test/.acstorage +5 -0
- package/_TESTPATH/data-corruption-investigation/data/config.json +4 -0
- package/_TESTPATH/idempotent-test/.acstorage +4 -0
- package/_TESTPATH/idempotent-test/test.json +4 -0
- package/_TESTPATH/invalid-operation-order-test/.acstorage +3 -0
- package/_TESTPATH/release-test/.acstorage +6 -0
- package/_TESTPATH/release-test/dir/file4.json +5 -0
- package/_TESTPATH/release-test/dir/file5.txt +1 -0
- package/_TESTPATH/release-test/file1.json +5 -0
- package/_TESTPATH/release-test/file2.txt +1 -0
- package/_TESTPATH/single-acstorage-corruption/config.json +1263 -0
- package/dist/bundle.cjs +348 -144
- package/dist/bundle.cjs.map +1 -1
- package/dist/bundle.mjs +347 -143
- package/dist/bundle.mjs.map +1 -1
- package/dist/index.d.ts +38 -7
- package/package.json +45 -45
package/dist/bundle.mjs
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import * as fs$2 from 'node:fs';
|
|
2
2
|
import { existsSync } from 'node:fs';
|
|
3
3
|
import * as fs from 'node:fs/promises';
|
|
4
|
-
import * as fs$1 from 'fs';
|
|
5
4
|
import * as path from 'node:path';
|
|
5
|
+
import * as fs$1 from 'fs';
|
|
6
6
|
|
|
7
7
|
let e$1 = class e extends Error{constructor(e){super(e),this.name="TreeNavigateError";}};const t$1="--tree-leaf";var i$1;let r$1 = class r{#e={};#t=".";#i=false;#r=true;constructor(){}static from(e,t={}){const r=new i$1;return r.#e=e,r.#t=t.delimiter??".",r.#i=t.allowWildcard??false,r.#r=t.allowRecursiveWildcard??true,r}subtree(t){const r=t.split(this.#t),a=this.#a(r,this.#e);if(!a)throw new e$1(`Path '${t}' does not exist`);if(i$1.#l(a.value)||i$1.#s(a.value))throw new e$1(`Path '${t}' is not a subtree`);const l=a.path.reduce((e,t)=>e[t],this.#e),s=new i$1;return s.#e=l,s.#t=this.#t,s.#i=this.#i,s}get(e,t){return this.walk(e,t)?.value??null}walk(e,t={}){if(""===e)return t.allowIntermediate?{value:this.#e,path:[]}:null;const r=e.split(this.#t),a=this.#a(r,this.#e);if(!a)return null;const l=a.value;return i$1.#l(l)?a:i$1.#s(l)?{value:l.value,path:a.path}:t.allowIntermediate?a:null}trace(e){const t=e.split(this.#t),r={treePath:[]},a=this.#a(t,this.#e,r);let l,s;if(a){const e=a.value;return i$1.#l(e)?(l=true,s=e):i$1.#s(e)?(l=true,s=e.value):(l=false,s=e),{find:true,isLeaf:l,value:s,nodePath:a?.path??[],tracePath:r.treePath,untracePath:t}}return {find:false,isLeaf:false,value:void 0,nodePath:[],tracePath:r.treePath,untracePath:t}}#a(e,t,r={treePath:[]}){const{treePath:a}=r,l=e.shift();if(null==l)return {value:t,path:[]};if("object"!=typeof t||i$1.#s(t))return e.unshift(l),null;if(a.push(l),l in t){const i=this.#a(e,t[l],r);if(i)return {value:i.value,path:[l,...i.path]}}else if(this.#i){if("*"in t){const i=this.#a(e,t["*"],r);if(i)return {value:i.value,path:["*",...i.path]}}if(this.#r&&"**/*"in t)return {value:t["**/*"],path:["**/*"]}}return null}static#l(e){return null==e||"object"!=typeof e}static#s(e){return 1==e[t$1]}};i$1=r$1;
|
|
8
8
|
|
|
@@ -490,31 +490,73 @@ class SchemaFlattener {
|
|
|
490
490
|
}
|
|
491
491
|
|
|
492
492
|
class JSONFS {
|
|
493
|
+
#writeLock = Promise.resolve();
|
|
493
494
|
async read(filename) {
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
const jsonText = await fs.readFile(filename, 'utf8');
|
|
497
|
-
try {
|
|
498
|
-
contents = JSON.parse(jsonText);
|
|
499
|
-
}
|
|
500
|
-
catch {
|
|
501
|
-
contents = {};
|
|
502
|
-
}
|
|
495
|
+
if (!existsSync(filename)) {
|
|
496
|
+
return {};
|
|
503
497
|
}
|
|
504
|
-
|
|
505
|
-
|
|
498
|
+
const jsonText = await fs.readFile(filename, 'utf8');
|
|
499
|
+
if (jsonText.trim() === '') {
|
|
500
|
+
throw new Error(`JSON file is empty: "${filename}"`);
|
|
501
|
+
}
|
|
502
|
+
try {
|
|
503
|
+
return JSON.parse(jsonText);
|
|
504
|
+
}
|
|
505
|
+
catch (error) {
|
|
506
|
+
throw new Error(`Failed to parse JSON from "${filename}": ` +
|
|
507
|
+
`${error instanceof Error ? error.message : String(error)}. ` +
|
|
508
|
+
`Content preview: "${jsonText.substring(0, 100)}..."`);
|
|
506
509
|
}
|
|
507
|
-
return contents;
|
|
508
510
|
}
|
|
509
511
|
async write(filename, contents) {
|
|
512
|
+
// Serialize writes using a lock chain
|
|
513
|
+
const previousLock = this.#writeLock;
|
|
514
|
+
let releaseLock;
|
|
515
|
+
this.#writeLock = new Promise((resolve) => {
|
|
516
|
+
releaseLock = resolve;
|
|
517
|
+
});
|
|
518
|
+
try {
|
|
519
|
+
// Wait for previous write to complete
|
|
520
|
+
await previousLock;
|
|
521
|
+
// Perform atomic write
|
|
522
|
+
await this.#atomicWrite(filename, contents);
|
|
523
|
+
}
|
|
524
|
+
finally {
|
|
525
|
+
releaseLock();
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
async #atomicWrite(filename, contents) {
|
|
510
529
|
const jsonString = JSON.stringify(contents, null, 4);
|
|
511
|
-
|
|
530
|
+
const dir = path.dirname(filename);
|
|
531
|
+
// Generate unique temp file name
|
|
532
|
+
const tempFile = `${filename}.tmp.${Date.now()}.${Math.random().toString(36).substring(2)}`;
|
|
533
|
+
try {
|
|
534
|
+
// Ensure parent directory exists (create if needed)
|
|
535
|
+
await fs.mkdir(dir, { recursive: true });
|
|
536
|
+
// Write to temp file
|
|
537
|
+
await fs.writeFile(tempFile, jsonString, 'utf8');
|
|
538
|
+
// Atomic rename (this is atomic on POSIX systems)
|
|
539
|
+
await fs.rename(tempFile, filename);
|
|
540
|
+
}
|
|
541
|
+
catch (error) {
|
|
542
|
+
// Clean up temp file on failure
|
|
543
|
+
try {
|
|
544
|
+
if (existsSync(tempFile)) {
|
|
545
|
+
await fs.rm(tempFile);
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
catch {
|
|
549
|
+
// Ignore cleanup errors
|
|
550
|
+
}
|
|
551
|
+
throw error;
|
|
552
|
+
}
|
|
512
553
|
}
|
|
513
554
|
async rm(filename) {
|
|
514
555
|
try {
|
|
515
556
|
await fs.rm(filename);
|
|
516
557
|
}
|
|
517
|
-
catch
|
|
558
|
+
catch {
|
|
559
|
+
// Ignore errors (file might not exist)
|
|
518
560
|
}
|
|
519
561
|
}
|
|
520
562
|
async exists(filename) {
|
|
@@ -542,36 +584,28 @@ class DefaultValueProvider {
|
|
|
542
584
|
constructor(navigate = null) {
|
|
543
585
|
this.#navigate = navigate;
|
|
544
586
|
}
|
|
545
|
-
|
|
546
|
-
*
|
|
547
|
-
*/
|
|
548
|
-
get(key, baseValue) {
|
|
587
|
+
get(key) {
|
|
549
588
|
if (this.#navigate == null) {
|
|
550
|
-
return
|
|
589
|
+
return undefined;
|
|
551
590
|
}
|
|
552
591
|
const result = this.#navigate.walk(key, { allowIntermediate: true });
|
|
553
592
|
if (result == null) {
|
|
554
593
|
return undefined;
|
|
555
594
|
}
|
|
556
595
|
else if (isJSONTypeData(result.value)) {
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
const defaultValue = result.value.default_value;
|
|
562
|
-
return ((defaultValue != null)
|
|
563
|
-
? defaultValue
|
|
564
|
-
: undefined);
|
|
565
|
-
}
|
|
596
|
+
const defaultValue = result.value.default_value;
|
|
597
|
+
return ((defaultValue != null)
|
|
598
|
+
? defaultValue
|
|
599
|
+
: undefined);
|
|
566
600
|
}
|
|
567
601
|
else {
|
|
568
602
|
// for (const [key, value] of Object.entries(result.value)) {
|
|
569
603
|
// console.log(key, value);
|
|
570
604
|
// }
|
|
571
|
-
return this.#getDefaultData(result.value
|
|
605
|
+
return this.#getDefaultData(result.value);
|
|
572
606
|
}
|
|
573
607
|
}
|
|
574
|
-
#getDefaultData(typeData
|
|
608
|
+
#getDefaultData(typeData) {
|
|
575
609
|
if (t$1 in typeData) {
|
|
576
610
|
if (typeData.value.default_value != null) {
|
|
577
611
|
return typeData.value.default_value;
|
|
@@ -579,17 +613,18 @@ class DefaultValueProvider {
|
|
|
579
613
|
return undefined;
|
|
580
614
|
}
|
|
581
615
|
else {
|
|
582
|
-
let
|
|
616
|
+
let hasKey = false;
|
|
617
|
+
const result = {};
|
|
583
618
|
for (const [key, value] of Object.entries(typeData)) {
|
|
584
619
|
const defaultValue = this.#getDefaultData(value);
|
|
585
620
|
if (defaultValue !== undefined) {
|
|
586
|
-
result
|
|
587
|
-
|
|
588
|
-
result[key] = defaultValue;
|
|
589
|
-
}
|
|
621
|
+
result[key] = defaultValue;
|
|
622
|
+
hasKey = true;
|
|
590
623
|
}
|
|
591
624
|
}
|
|
592
|
-
return
|
|
625
|
+
return (hasKey
|
|
626
|
+
? result
|
|
627
|
+
: undefined);
|
|
593
628
|
}
|
|
594
629
|
}
|
|
595
630
|
}
|
|
@@ -669,8 +704,8 @@ class JSONAccessor {
|
|
|
669
704
|
getOne(key) {
|
|
670
705
|
this.#ensureNotDropped();
|
|
671
706
|
let value = this.#getData(key);
|
|
672
|
-
if (value == null
|
|
673
|
-
value = this.#defaultValueProvider.get(key
|
|
707
|
+
if (value == null) {
|
|
708
|
+
value = this.#defaultValueProvider.get(key);
|
|
674
709
|
}
|
|
675
710
|
return value;
|
|
676
711
|
}
|
|
@@ -679,8 +714,8 @@ class JSONAccessor {
|
|
|
679
714
|
const result = {};
|
|
680
715
|
for (const key of keys) {
|
|
681
716
|
let value = this.#getData(key);
|
|
682
|
-
if (value == null
|
|
683
|
-
value = this.#defaultValueProvider.get(key
|
|
717
|
+
if (value == null) {
|
|
718
|
+
value = this.#defaultValueProvider.get(key);
|
|
684
719
|
}
|
|
685
720
|
const resolved = resolveNestedRef(result, key, true);
|
|
686
721
|
resolved.parent[resolved.key] = value;
|
|
@@ -866,7 +901,7 @@ class BinaryAccessor {
|
|
|
866
901
|
async writeBase64(data) {
|
|
867
902
|
this.#ensureNotDropped();
|
|
868
903
|
const buffer = Buffer.from(data, 'base64');
|
|
869
|
-
this.write(buffer);
|
|
904
|
+
await this.write(buffer);
|
|
870
905
|
}
|
|
871
906
|
async readBase64() {
|
|
872
907
|
this.#ensureNotDropped();
|
|
@@ -874,9 +909,11 @@ class BinaryAccessor {
|
|
|
874
909
|
return buffer.toString('base64');
|
|
875
910
|
}
|
|
876
911
|
async drop() {
|
|
877
|
-
this.#
|
|
912
|
+
if (this.#dropped)
|
|
913
|
+
return;
|
|
914
|
+
this.#dropped = true;
|
|
878
915
|
if (existsSync(this.#filePath)) {
|
|
879
|
-
fs.rm(this.#filePath, { force: true });
|
|
916
|
+
await fs.rm(this.#filePath, { force: true });
|
|
880
917
|
}
|
|
881
918
|
}
|
|
882
919
|
async commit() {
|
|
@@ -1463,7 +1500,7 @@ class StorageAccessControl {
|
|
|
1463
1500
|
chainDependency();
|
|
1464
1501
|
return ac;
|
|
1465
1502
|
}
|
|
1466
|
-
async
|
|
1503
|
+
async destroy(identifier) {
|
|
1467
1504
|
const walked = this.accessTree.walk(identifier);
|
|
1468
1505
|
if (!walked) {
|
|
1469
1506
|
throw new NotRegisterError(`'${identifier}' is not registered.`);
|
|
@@ -1471,11 +1508,11 @@ class StorageAccessControl {
|
|
|
1471
1508
|
if (this.checkAccessIsDirectory(walked.value)) {
|
|
1472
1509
|
throw new DirectoryAccessError(`> '${identifier}' is directory.`);
|
|
1473
1510
|
}
|
|
1474
|
-
await this.#events.
|
|
1511
|
+
await this.#events.onDestroy(identifier);
|
|
1475
1512
|
}
|
|
1476
|
-
async
|
|
1513
|
+
async destroyDir(identifier) {
|
|
1477
1514
|
this.validateDirectoryPath(identifier);
|
|
1478
|
-
await this.#events.
|
|
1515
|
+
await this.#events.onDestroy(identifier);
|
|
1479
1516
|
}
|
|
1480
1517
|
getAccessType(identifier) {
|
|
1481
1518
|
const getAT = (access) => {
|
|
@@ -1499,6 +1536,16 @@ class StorageAccessControl {
|
|
|
1499
1536
|
}
|
|
1500
1537
|
}
|
|
1501
1538
|
}
|
|
1539
|
+
validateAccess(identifier, accessType) {
|
|
1540
|
+
const walked = this.accessTree.walk(identifier, { allowIntermediate: true });
|
|
1541
|
+
if (!walked) {
|
|
1542
|
+
throw new NotRegisterError(`'${identifier}' is not registered.`);
|
|
1543
|
+
}
|
|
1544
|
+
if (this.checkAccessIsDirectory(walked.value)) {
|
|
1545
|
+
throw new DirectoryAccessError(`'${identifier}' is directory.`);
|
|
1546
|
+
}
|
|
1547
|
+
return this.validateAndResolveAccess(walked.value, accessType, identifier);
|
|
1548
|
+
}
|
|
1502
1549
|
validateDirectoryPath(identifier) {
|
|
1503
1550
|
const walked = this.accessTree.walk(identifier, { allowIntermediate: true });
|
|
1504
1551
|
if (!walked || !this.checkAccessIsDirectory(walked.value)) {
|
|
@@ -1574,6 +1621,30 @@ class ACSubStorage {
|
|
|
1574
1621
|
async accessAsBinary(identifier) {
|
|
1575
1622
|
return await this.access(identifier, 'binary');
|
|
1576
1623
|
}
|
|
1624
|
+
async create(identifier, accessType) {
|
|
1625
|
+
return await this.#master.create(this.#prefix + ':' + identifier, accessType);
|
|
1626
|
+
}
|
|
1627
|
+
async createAsJSON(identifier) {
|
|
1628
|
+
return await this.create(identifier, 'json');
|
|
1629
|
+
}
|
|
1630
|
+
async createAsText(identifier) {
|
|
1631
|
+
return await this.create(identifier, 'text');
|
|
1632
|
+
}
|
|
1633
|
+
async createAsBinary(identifier) {
|
|
1634
|
+
return await this.create(identifier, 'binary');
|
|
1635
|
+
}
|
|
1636
|
+
async open(identifier, accessType) {
|
|
1637
|
+
return await this.#master.open(this.#prefix + ':' + identifier, accessType);
|
|
1638
|
+
}
|
|
1639
|
+
async openAsJSON(identifier) {
|
|
1640
|
+
return await this.open(identifier, 'json');
|
|
1641
|
+
}
|
|
1642
|
+
async openAsText(identifier) {
|
|
1643
|
+
return await this.open(identifier, 'text');
|
|
1644
|
+
}
|
|
1645
|
+
async openAsBinary(identifier) {
|
|
1646
|
+
return await this.open(identifier, 'binary');
|
|
1647
|
+
}
|
|
1577
1648
|
async copy(oldIdentifier, newIdentifier) {
|
|
1578
1649
|
await this.#master.copy(this.#prefix + ':' + oldIdentifier, this.#prefix + ':' + newIdentifier);
|
|
1579
1650
|
}
|
|
@@ -1589,6 +1660,15 @@ class ACSubStorage {
|
|
|
1589
1660
|
async dropAll() {
|
|
1590
1661
|
await this.#master.dropDir(this.#prefix);
|
|
1591
1662
|
}
|
|
1663
|
+
async release(identifier) {
|
|
1664
|
+
await this.#master.release(this.#prefix + ':' + identifier);
|
|
1665
|
+
}
|
|
1666
|
+
async releaseDir(identifier) {
|
|
1667
|
+
await this.#master.releaseDir(this.#prefix + ':' + identifier);
|
|
1668
|
+
}
|
|
1669
|
+
async releaseAll() {
|
|
1670
|
+
await this.#master.releaseDir(this.#prefix);
|
|
1671
|
+
}
|
|
1592
1672
|
async commit(identifier = '') {
|
|
1593
1673
|
await this.#master.commit(this.#prefix + ':' + identifier);
|
|
1594
1674
|
}
|
|
@@ -1629,63 +1709,87 @@ class ACStorage {
|
|
|
1629
1709
|
const cacheData = JSON.stringify(this.accessCache, null, 4);
|
|
1630
1710
|
fs$2.writeFileSync(this.cachePath, cacheData, 'utf8');
|
|
1631
1711
|
}
|
|
1632
|
-
|
|
1633
|
-
const
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
if (
|
|
1638
|
-
|
|
1712
|
+
async getOrCreateAccessorFromAccess(identifier, sa, mode) {
|
|
1713
|
+
const targetPath = path.join(this.basePath, identifier.replaceAll(':', '/'));
|
|
1714
|
+
this.eventListeners.access?.(identifier, sa);
|
|
1715
|
+
let item = this.accessors.get(identifier);
|
|
1716
|
+
if (item != undefined && !item.isDropped()) {
|
|
1717
|
+
if (mode === 'create') {
|
|
1718
|
+
throw new StorageError(`File '${identifier}' already exists in memory`);
|
|
1639
1719
|
}
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
const ac = await event.init(targetPath, ...sa.args);
|
|
1660
|
-
acm = CustomAccessorManager.from(ac, {
|
|
1661
|
-
customId: sa.id,
|
|
1662
|
-
event,
|
|
1663
|
-
actualPath: targetPath,
|
|
1664
|
-
customArgs: sa.args,
|
|
1665
|
-
});
|
|
1666
|
-
break;
|
|
1667
|
-
default:
|
|
1668
|
-
// 기본 타입 이외에는 custom 타입으로 wrap되기 때문에 이 경우가 발생하지 않음
|
|
1720
|
+
return item;
|
|
1721
|
+
}
|
|
1722
|
+
let acm;
|
|
1723
|
+
switch (sa.accessType) {
|
|
1724
|
+
case 'directory':
|
|
1725
|
+
acm = DirectoryAccessorManager.fromFS(targetPath, sa.tree);
|
|
1726
|
+
break;
|
|
1727
|
+
case 'json':
|
|
1728
|
+
acm = JSONAccessorManager.fromFS(targetPath, sa.structure);
|
|
1729
|
+
break;
|
|
1730
|
+
case 'binary':
|
|
1731
|
+
acm = BinaryAccessorManager.fromFS(targetPath);
|
|
1732
|
+
break;
|
|
1733
|
+
case 'text':
|
|
1734
|
+
acm = TextAccessorManager.fromFS(targetPath);
|
|
1735
|
+
break;
|
|
1736
|
+
case 'custom':
|
|
1737
|
+
const event = this.customAccessEvents[sa.id];
|
|
1738
|
+
if (!event) {
|
|
1669
1739
|
throw new StorageError('Invalid access type');
|
|
1740
|
+
}
|
|
1741
|
+
const ac = await event.init(targetPath, ...sa.args);
|
|
1742
|
+
acm = CustomAccessorManager.from(ac, {
|
|
1743
|
+
customId: sa.id,
|
|
1744
|
+
event,
|
|
1745
|
+
actualPath: targetPath,
|
|
1746
|
+
customArgs: sa.args,
|
|
1747
|
+
});
|
|
1748
|
+
break;
|
|
1749
|
+
default:
|
|
1750
|
+
throw new StorageError('Invalid access type');
|
|
1751
|
+
}
|
|
1752
|
+
const exists = await acm.exists();
|
|
1753
|
+
if (mode === 'create') {
|
|
1754
|
+
if (exists) {
|
|
1755
|
+
throw new StorageError(`File '${identifier}' already exists on disk`);
|
|
1756
|
+
}
|
|
1757
|
+
await acm.create();
|
|
1758
|
+
}
|
|
1759
|
+
else if (mode === 'open') {
|
|
1760
|
+
if (!exists) {
|
|
1761
|
+
throw new StorageError(`File '${identifier}' does not exist`);
|
|
1670
1762
|
}
|
|
1671
|
-
|
|
1763
|
+
await acm.load();
|
|
1764
|
+
}
|
|
1765
|
+
else {
|
|
1766
|
+
if (!exists)
|
|
1672
1767
|
await acm.create();
|
|
1673
1768
|
else
|
|
1674
1769
|
await acm.load();
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1770
|
+
}
|
|
1771
|
+
this.accessCache[identifier] = sa.accessType !== 'custom' ? sa.accessType : sa.id;
|
|
1772
|
+
this.accessors.set(identifier, acm);
|
|
1773
|
+
return acm;
|
|
1774
|
+
}
|
|
1775
|
+
async getOrCreateAccessor(identifier, accessType, mode) {
|
|
1776
|
+
const sa = this.accessControl.validateAccess(identifier, accessType);
|
|
1777
|
+
return await this.getOrCreateAccessorFromAccess(identifier, sa, mode);
|
|
1778
|
+
}
|
|
1779
|
+
initAccessControl() {
|
|
1780
|
+
const onAccess = async (identifier, sa) => {
|
|
1781
|
+
return await this.getOrCreateAccessorFromAccess(identifier, sa, 'access');
|
|
1678
1782
|
};
|
|
1679
|
-
const
|
|
1783
|
+
const onDestroy = async (identifier) => {
|
|
1680
1784
|
const accessor = this.accessors.get(identifier);
|
|
1681
1785
|
if (!accessor)
|
|
1682
1786
|
return;
|
|
1683
1787
|
for (const child of accessor.dependent) {
|
|
1684
|
-
await
|
|
1788
|
+
await onDestroy(child);
|
|
1685
1789
|
}
|
|
1686
1790
|
if (identifier === '')
|
|
1687
1791
|
return;
|
|
1688
|
-
this.eventListeners.
|
|
1792
|
+
this.eventListeners.destroy?.(identifier);
|
|
1689
1793
|
if (!accessor.isDropped())
|
|
1690
1794
|
await accessor.drop();
|
|
1691
1795
|
delete this.accessCache[identifier];
|
|
@@ -1699,7 +1803,7 @@ class ACStorage {
|
|
|
1699
1803
|
};
|
|
1700
1804
|
return new StorageAccessControl({
|
|
1701
1805
|
onAccess,
|
|
1702
|
-
|
|
1806
|
+
onDestroy,
|
|
1703
1807
|
onChainDependency,
|
|
1704
1808
|
});
|
|
1705
1809
|
}
|
|
@@ -1708,8 +1812,8 @@ class ACStorage {
|
|
|
1708
1812
|
case 'access':
|
|
1709
1813
|
this.eventListeners.access = listener;
|
|
1710
1814
|
break;
|
|
1711
|
-
case '
|
|
1712
|
-
this.eventListeners.
|
|
1815
|
+
case 'destroy':
|
|
1816
|
+
this.eventListeners.destroy = listener;
|
|
1713
1817
|
break;
|
|
1714
1818
|
}
|
|
1715
1819
|
}
|
|
@@ -1738,6 +1842,32 @@ class ACStorage {
|
|
|
1738
1842
|
async accessAsBinary(identifier) {
|
|
1739
1843
|
return await this.access(identifier, 'binary');
|
|
1740
1844
|
}
|
|
1845
|
+
async create(identifier, accessType) {
|
|
1846
|
+
const acm = await this.getOrCreateAccessor(identifier, accessType, 'create');
|
|
1847
|
+
return acm.accessor;
|
|
1848
|
+
}
|
|
1849
|
+
async createAsJSON(identifier) {
|
|
1850
|
+
return await this.create(identifier, 'json');
|
|
1851
|
+
}
|
|
1852
|
+
async createAsText(identifier) {
|
|
1853
|
+
return await this.create(identifier, 'text');
|
|
1854
|
+
}
|
|
1855
|
+
async createAsBinary(identifier) {
|
|
1856
|
+
return await this.create(identifier, 'binary');
|
|
1857
|
+
}
|
|
1858
|
+
async open(identifier, accessType) {
|
|
1859
|
+
const acm = await this.getOrCreateAccessor(identifier, accessType, 'open');
|
|
1860
|
+
return acm.accessor;
|
|
1861
|
+
}
|
|
1862
|
+
async openAsJSON(identifier) {
|
|
1863
|
+
return await this.open(identifier, 'json');
|
|
1864
|
+
}
|
|
1865
|
+
async openAsText(identifier) {
|
|
1866
|
+
return await this.open(identifier, 'text');
|
|
1867
|
+
}
|
|
1868
|
+
async openAsBinary(identifier) {
|
|
1869
|
+
return await this.open(identifier, 'binary');
|
|
1870
|
+
}
|
|
1741
1871
|
async copy(oldIdentifier, newIdentifier) {
|
|
1742
1872
|
const accessType = this.validateAndGetAccessTypePair(oldIdentifier, newIdentifier);
|
|
1743
1873
|
await this.commit(oldIdentifier);
|
|
@@ -1785,13 +1915,40 @@ class ACStorage {
|
|
|
1785
1915
|
if (identifier === '') {
|
|
1786
1916
|
throw new StorageError('Cannot drop the root directory. use dropAll() instead.');
|
|
1787
1917
|
}
|
|
1788
|
-
await this.accessControl.
|
|
1918
|
+
await this.accessControl.destroyDir(identifier);
|
|
1789
1919
|
}
|
|
1790
1920
|
async drop(identifier) {
|
|
1791
|
-
await this.accessControl.
|
|
1921
|
+
await this.accessControl.destroy(identifier);
|
|
1792
1922
|
}
|
|
1793
1923
|
async dropAll() {
|
|
1794
|
-
await this.accessControl.
|
|
1924
|
+
await this.accessControl.destroyDir('');
|
|
1925
|
+
}
|
|
1926
|
+
async release(identifier) {
|
|
1927
|
+
await this.commit(identifier);
|
|
1928
|
+
await this.#unloadFromMemory(identifier);
|
|
1929
|
+
}
|
|
1930
|
+
async releaseDir(identifier) {
|
|
1931
|
+
if (identifier === '') {
|
|
1932
|
+
throw new StorageError('Cannot release the root directory. use releaseAll() instead.');
|
|
1933
|
+
}
|
|
1934
|
+
await this.commit(identifier);
|
|
1935
|
+
await this.#unloadFromMemory(identifier);
|
|
1936
|
+
}
|
|
1937
|
+
async releaseAll() {
|
|
1938
|
+
await this.commitAll();
|
|
1939
|
+
await this.#unloadFromMemory('');
|
|
1940
|
+
}
|
|
1941
|
+
async #unloadFromMemory(identifier) {
|
|
1942
|
+
const accessor = this.accessors.get(identifier);
|
|
1943
|
+
if (!accessor)
|
|
1944
|
+
return;
|
|
1945
|
+
for (const child of accessor.dependent) {
|
|
1946
|
+
await this.#unloadFromMemory(child);
|
|
1947
|
+
}
|
|
1948
|
+
if (identifier !== '') {
|
|
1949
|
+
delete this.accessCache[identifier];
|
|
1950
|
+
this.accessors.delete(identifier);
|
|
1951
|
+
}
|
|
1795
1952
|
}
|
|
1796
1953
|
async commit(identifier = '') {
|
|
1797
1954
|
await this.#commitRecursive(identifier);
|
|
@@ -1817,62 +1974,82 @@ class MemACStorage extends ACStorage {
|
|
|
1817
1974
|
constructor() {
|
|
1818
1975
|
super('', { noCache: true });
|
|
1819
1976
|
}
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
if (
|
|
1825
|
-
|
|
1977
|
+
async getOrCreateAccessorFromAccess(identifier, sa, mode) {
|
|
1978
|
+
this.eventListeners.access?.(identifier, sa);
|
|
1979
|
+
let item = this.accessors.get(identifier);
|
|
1980
|
+
if (item != undefined && !item.isDropped()) {
|
|
1981
|
+
if (mode === 'create') {
|
|
1982
|
+
throw new StorageError(`File '${identifier}' already exists in memory`);
|
|
1826
1983
|
}
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
1984
|
+
return item;
|
|
1985
|
+
}
|
|
1986
|
+
let acm;
|
|
1987
|
+
switch (sa.accessType) {
|
|
1988
|
+
case 'directory':
|
|
1989
|
+
acm = DirectoryAccessorManager.fromMemory(sa.tree);
|
|
1990
|
+
break;
|
|
1991
|
+
case 'json':
|
|
1992
|
+
acm = JSONAccessorManager.fromMemory(sa.structure);
|
|
1993
|
+
break;
|
|
1994
|
+
case 'binary':
|
|
1995
|
+
acm = BinaryAccessorManager.fromMemory();
|
|
1996
|
+
break;
|
|
1997
|
+
case 'text':
|
|
1998
|
+
acm = TextAccessorManager.fromMemory();
|
|
1999
|
+
break;
|
|
2000
|
+
case 'custom':
|
|
2001
|
+
const event = this.customAccessEvents[sa.id];
|
|
2002
|
+
if (!event) {
|
|
2003
|
+
throw new StorageError('Invalid access type');
|
|
2004
|
+
}
|
|
2005
|
+
const ac = await event.init(null, ...sa.args);
|
|
2006
|
+
acm = CustomAccessorManager.from(ac, {
|
|
2007
|
+
customId: sa.id,
|
|
2008
|
+
event,
|
|
2009
|
+
actualPath: null,
|
|
2010
|
+
customArgs: sa.args,
|
|
2011
|
+
});
|
|
2012
|
+
break;
|
|
2013
|
+
default:
|
|
2014
|
+
throw new StorageError('Logic Error : Invalid access type');
|
|
2015
|
+
}
|
|
2016
|
+
const exists = await acm.exists();
|
|
2017
|
+
if (mode === 'create') {
|
|
2018
|
+
if (exists) {
|
|
2019
|
+
throw new StorageError(`File '${identifier}' already exists in memory`);
|
|
1857
2020
|
}
|
|
1858
|
-
|
|
1859
|
-
|
|
2021
|
+
await acm.create();
|
|
2022
|
+
}
|
|
2023
|
+
else if (mode === 'open') {
|
|
2024
|
+
if (!exists) {
|
|
2025
|
+
throw new StorageError(`File '${identifier}' does not exist`);
|
|
2026
|
+
}
|
|
2027
|
+
await acm.load();
|
|
2028
|
+
}
|
|
2029
|
+
else {
|
|
2030
|
+
if (!exists)
|
|
2031
|
+
await acm.create();
|
|
1860
2032
|
else
|
|
1861
|
-
acm.load();
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
2033
|
+
await acm.load();
|
|
2034
|
+
}
|
|
2035
|
+
this.accessCache[identifier] = sa.accessType !== 'custom' ? sa.accessType : sa.id;
|
|
2036
|
+
this.accessors.set(identifier, acm);
|
|
2037
|
+
return acm;
|
|
2038
|
+
}
|
|
2039
|
+
initAccessControl() {
|
|
2040
|
+
const onAccess = async (identifier, sa) => {
|
|
2041
|
+
return await this.getOrCreateAccessorFromAccess(identifier, sa, 'access');
|
|
1865
2042
|
};
|
|
1866
|
-
const
|
|
2043
|
+
const onDestroy = async (identifier) => {
|
|
1867
2044
|
const accessor = this.accessors.get(identifier);
|
|
1868
2045
|
if (!accessor)
|
|
1869
2046
|
return;
|
|
1870
2047
|
for (const child of accessor.dependent) {
|
|
1871
|
-
await
|
|
2048
|
+
await onDestroy(child);
|
|
1872
2049
|
}
|
|
1873
2050
|
if (identifier === '')
|
|
1874
2051
|
return;
|
|
1875
|
-
this.eventListeners.
|
|
2052
|
+
this.eventListeners.destroy?.(identifier);
|
|
1876
2053
|
if (!accessor.isDropped())
|
|
1877
2054
|
accessor.drop();
|
|
1878
2055
|
delete this.accessCache[identifier];
|
|
@@ -1886,10 +2063,37 @@ class MemACStorage extends ACStorage {
|
|
|
1886
2063
|
};
|
|
1887
2064
|
return new StorageAccessControl({
|
|
1888
2065
|
onAccess,
|
|
1889
|
-
|
|
2066
|
+
onDestroy,
|
|
1890
2067
|
onChainDependency,
|
|
1891
2068
|
});
|
|
1892
2069
|
}
|
|
2070
|
+
async release(identifier) {
|
|
2071
|
+
await this.commit(identifier);
|
|
2072
|
+
await this.#unloadFromMemory(identifier);
|
|
2073
|
+
}
|
|
2074
|
+
async releaseDir(identifier) {
|
|
2075
|
+
if (identifier === '') {
|
|
2076
|
+
throw new StorageError('Cannot release the root directory. use releaseAll() instead.');
|
|
2077
|
+
}
|
|
2078
|
+
await this.commit(identifier);
|
|
2079
|
+
await this.#unloadFromMemory(identifier);
|
|
2080
|
+
}
|
|
2081
|
+
async releaseAll() {
|
|
2082
|
+
await this.commitAll();
|
|
2083
|
+
await this.#unloadFromMemory('');
|
|
2084
|
+
}
|
|
2085
|
+
async #unloadFromMemory(identifier) {
|
|
2086
|
+
const accessor = this.accessors.get(identifier);
|
|
2087
|
+
if (!accessor)
|
|
2088
|
+
return;
|
|
2089
|
+
for (const child of accessor.dependent) {
|
|
2090
|
+
await this.#unloadFromMemory(child);
|
|
2091
|
+
}
|
|
2092
|
+
if (identifier !== '') {
|
|
2093
|
+
delete this.accessCache[identifier];
|
|
2094
|
+
this.accessors.delete(identifier);
|
|
2095
|
+
}
|
|
2096
|
+
}
|
|
1893
2097
|
}
|
|
1894
2098
|
|
|
1895
2099
|
class StorageAccess {
|