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.cjs
CHANGED
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
var fs = require('node:fs');
|
|
4
4
|
var fs$1 = require('node:fs/promises');
|
|
5
|
-
var fs$2 = require('fs');
|
|
6
5
|
var path = require('node:path');
|
|
6
|
+
var fs$2 = require('fs');
|
|
7
7
|
|
|
8
8
|
function _interopNamespaceDefault(e) {
|
|
9
9
|
var n = Object.create(null);
|
|
@@ -24,8 +24,8 @@ function _interopNamespaceDefault(e) {
|
|
|
24
24
|
|
|
25
25
|
var fs__namespace$2 = /*#__PURE__*/_interopNamespaceDefault(fs);
|
|
26
26
|
var fs__namespace = /*#__PURE__*/_interopNamespaceDefault(fs$1);
|
|
27
|
-
var fs__namespace$1 = /*#__PURE__*/_interopNamespaceDefault(fs$2);
|
|
28
27
|
var path__namespace = /*#__PURE__*/_interopNamespaceDefault(path);
|
|
28
|
+
var fs__namespace$1 = /*#__PURE__*/_interopNamespaceDefault(fs$2);
|
|
29
29
|
|
|
30
30
|
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;
|
|
31
31
|
|
|
@@ -513,31 +513,73 @@ class SchemaFlattener {
|
|
|
513
513
|
}
|
|
514
514
|
|
|
515
515
|
class JSONFS {
|
|
516
|
+
#writeLock = Promise.resolve();
|
|
516
517
|
async read(filename) {
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
const jsonText = await fs__namespace.readFile(filename, 'utf8');
|
|
520
|
-
try {
|
|
521
|
-
contents = JSON.parse(jsonText);
|
|
522
|
-
}
|
|
523
|
-
catch {
|
|
524
|
-
contents = {};
|
|
525
|
-
}
|
|
518
|
+
if (!fs.existsSync(filename)) {
|
|
519
|
+
return {};
|
|
526
520
|
}
|
|
527
|
-
|
|
528
|
-
|
|
521
|
+
const jsonText = await fs__namespace.readFile(filename, 'utf8');
|
|
522
|
+
if (jsonText.trim() === '') {
|
|
523
|
+
throw new Error(`JSON file is empty: "${filename}"`);
|
|
524
|
+
}
|
|
525
|
+
try {
|
|
526
|
+
return JSON.parse(jsonText);
|
|
527
|
+
}
|
|
528
|
+
catch (error) {
|
|
529
|
+
throw new Error(`Failed to parse JSON from "${filename}": ` +
|
|
530
|
+
`${error instanceof Error ? error.message : String(error)}. ` +
|
|
531
|
+
`Content preview: "${jsonText.substring(0, 100)}..."`);
|
|
529
532
|
}
|
|
530
|
-
return contents;
|
|
531
533
|
}
|
|
532
534
|
async write(filename, contents) {
|
|
535
|
+
// Serialize writes using a lock chain
|
|
536
|
+
const previousLock = this.#writeLock;
|
|
537
|
+
let releaseLock;
|
|
538
|
+
this.#writeLock = new Promise((resolve) => {
|
|
539
|
+
releaseLock = resolve;
|
|
540
|
+
});
|
|
541
|
+
try {
|
|
542
|
+
// Wait for previous write to complete
|
|
543
|
+
await previousLock;
|
|
544
|
+
// Perform atomic write
|
|
545
|
+
await this.#atomicWrite(filename, contents);
|
|
546
|
+
}
|
|
547
|
+
finally {
|
|
548
|
+
releaseLock();
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
async #atomicWrite(filename, contents) {
|
|
533
552
|
const jsonString = JSON.stringify(contents, null, 4);
|
|
534
|
-
|
|
553
|
+
const dir = path__namespace.dirname(filename);
|
|
554
|
+
// Generate unique temp file name
|
|
555
|
+
const tempFile = `${filename}.tmp.${Date.now()}.${Math.random().toString(36).substring(2)}`;
|
|
556
|
+
try {
|
|
557
|
+
// Ensure parent directory exists (create if needed)
|
|
558
|
+
await fs__namespace.mkdir(dir, { recursive: true });
|
|
559
|
+
// Write to temp file
|
|
560
|
+
await fs__namespace.writeFile(tempFile, jsonString, 'utf8');
|
|
561
|
+
// Atomic rename (this is atomic on POSIX systems)
|
|
562
|
+
await fs__namespace.rename(tempFile, filename);
|
|
563
|
+
}
|
|
564
|
+
catch (error) {
|
|
565
|
+
// Clean up temp file on failure
|
|
566
|
+
try {
|
|
567
|
+
if (fs.existsSync(tempFile)) {
|
|
568
|
+
await fs__namespace.rm(tempFile);
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
catch {
|
|
572
|
+
// Ignore cleanup errors
|
|
573
|
+
}
|
|
574
|
+
throw error;
|
|
575
|
+
}
|
|
535
576
|
}
|
|
536
577
|
async rm(filename) {
|
|
537
578
|
try {
|
|
538
579
|
await fs__namespace.rm(filename);
|
|
539
580
|
}
|
|
540
|
-
catch
|
|
581
|
+
catch {
|
|
582
|
+
// Ignore errors (file might not exist)
|
|
541
583
|
}
|
|
542
584
|
}
|
|
543
585
|
async exists(filename) {
|
|
@@ -565,36 +607,28 @@ class DefaultValueProvider {
|
|
|
565
607
|
constructor(navigate = null) {
|
|
566
608
|
this.#navigate = navigate;
|
|
567
609
|
}
|
|
568
|
-
|
|
569
|
-
*
|
|
570
|
-
*/
|
|
571
|
-
get(key, baseValue) {
|
|
610
|
+
get(key) {
|
|
572
611
|
if (this.#navigate == null) {
|
|
573
|
-
return
|
|
612
|
+
return undefined;
|
|
574
613
|
}
|
|
575
614
|
const result = this.#navigate.walk(key, { allowIntermediate: true });
|
|
576
615
|
if (result == null) {
|
|
577
616
|
return undefined;
|
|
578
617
|
}
|
|
579
618
|
else if (isJSONTypeData(result.value)) {
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
const defaultValue = result.value.default_value;
|
|
585
|
-
return ((defaultValue != null)
|
|
586
|
-
? defaultValue
|
|
587
|
-
: undefined);
|
|
588
|
-
}
|
|
619
|
+
const defaultValue = result.value.default_value;
|
|
620
|
+
return ((defaultValue != null)
|
|
621
|
+
? defaultValue
|
|
622
|
+
: undefined);
|
|
589
623
|
}
|
|
590
624
|
else {
|
|
591
625
|
// for (const [key, value] of Object.entries(result.value)) {
|
|
592
626
|
// console.log(key, value);
|
|
593
627
|
// }
|
|
594
|
-
return this.#getDefaultData(result.value
|
|
628
|
+
return this.#getDefaultData(result.value);
|
|
595
629
|
}
|
|
596
630
|
}
|
|
597
|
-
#getDefaultData(typeData
|
|
631
|
+
#getDefaultData(typeData) {
|
|
598
632
|
if (t$1 in typeData) {
|
|
599
633
|
if (typeData.value.default_value != null) {
|
|
600
634
|
return typeData.value.default_value;
|
|
@@ -602,17 +636,18 @@ class DefaultValueProvider {
|
|
|
602
636
|
return undefined;
|
|
603
637
|
}
|
|
604
638
|
else {
|
|
605
|
-
let
|
|
639
|
+
let hasKey = false;
|
|
640
|
+
const result = {};
|
|
606
641
|
for (const [key, value] of Object.entries(typeData)) {
|
|
607
642
|
const defaultValue = this.#getDefaultData(value);
|
|
608
643
|
if (defaultValue !== undefined) {
|
|
609
|
-
result
|
|
610
|
-
|
|
611
|
-
result[key] = defaultValue;
|
|
612
|
-
}
|
|
644
|
+
result[key] = defaultValue;
|
|
645
|
+
hasKey = true;
|
|
613
646
|
}
|
|
614
647
|
}
|
|
615
|
-
return
|
|
648
|
+
return (hasKey
|
|
649
|
+
? result
|
|
650
|
+
: undefined);
|
|
616
651
|
}
|
|
617
652
|
}
|
|
618
653
|
}
|
|
@@ -692,8 +727,8 @@ class JSONAccessor {
|
|
|
692
727
|
getOne(key) {
|
|
693
728
|
this.#ensureNotDropped();
|
|
694
729
|
let value = this.#getData(key);
|
|
695
|
-
if (value == null
|
|
696
|
-
value = this.#defaultValueProvider.get(key
|
|
730
|
+
if (value == null) {
|
|
731
|
+
value = this.#defaultValueProvider.get(key);
|
|
697
732
|
}
|
|
698
733
|
return value;
|
|
699
734
|
}
|
|
@@ -702,8 +737,8 @@ class JSONAccessor {
|
|
|
702
737
|
const result = {};
|
|
703
738
|
for (const key of keys) {
|
|
704
739
|
let value = this.#getData(key);
|
|
705
|
-
if (value == null
|
|
706
|
-
value = this.#defaultValueProvider.get(key
|
|
740
|
+
if (value == null) {
|
|
741
|
+
value = this.#defaultValueProvider.get(key);
|
|
707
742
|
}
|
|
708
743
|
const resolved = resolveNestedRef(result, key, true);
|
|
709
744
|
resolved.parent[resolved.key] = value;
|
|
@@ -889,7 +924,7 @@ class BinaryAccessor {
|
|
|
889
924
|
async writeBase64(data) {
|
|
890
925
|
this.#ensureNotDropped();
|
|
891
926
|
const buffer = Buffer.from(data, 'base64');
|
|
892
|
-
this.write(buffer);
|
|
927
|
+
await this.write(buffer);
|
|
893
928
|
}
|
|
894
929
|
async readBase64() {
|
|
895
930
|
this.#ensureNotDropped();
|
|
@@ -897,9 +932,11 @@ class BinaryAccessor {
|
|
|
897
932
|
return buffer.toString('base64');
|
|
898
933
|
}
|
|
899
934
|
async drop() {
|
|
900
|
-
this.#
|
|
935
|
+
if (this.#dropped)
|
|
936
|
+
return;
|
|
937
|
+
this.#dropped = true;
|
|
901
938
|
if (fs.existsSync(this.#filePath)) {
|
|
902
|
-
fs__namespace.rm(this.#filePath, { force: true });
|
|
939
|
+
await fs__namespace.rm(this.#filePath, { force: true });
|
|
903
940
|
}
|
|
904
941
|
}
|
|
905
942
|
async commit() {
|
|
@@ -1486,7 +1523,7 @@ class StorageAccessControl {
|
|
|
1486
1523
|
chainDependency();
|
|
1487
1524
|
return ac;
|
|
1488
1525
|
}
|
|
1489
|
-
async
|
|
1526
|
+
async destroy(identifier) {
|
|
1490
1527
|
const walked = this.accessTree.walk(identifier);
|
|
1491
1528
|
if (!walked) {
|
|
1492
1529
|
throw new NotRegisterError(`'${identifier}' is not registered.`);
|
|
@@ -1494,11 +1531,11 @@ class StorageAccessControl {
|
|
|
1494
1531
|
if (this.checkAccessIsDirectory(walked.value)) {
|
|
1495
1532
|
throw new DirectoryAccessError(`> '${identifier}' is directory.`);
|
|
1496
1533
|
}
|
|
1497
|
-
await this.#events.
|
|
1534
|
+
await this.#events.onDestroy(identifier);
|
|
1498
1535
|
}
|
|
1499
|
-
async
|
|
1536
|
+
async destroyDir(identifier) {
|
|
1500
1537
|
this.validateDirectoryPath(identifier);
|
|
1501
|
-
await this.#events.
|
|
1538
|
+
await this.#events.onDestroy(identifier);
|
|
1502
1539
|
}
|
|
1503
1540
|
getAccessType(identifier) {
|
|
1504
1541
|
const getAT = (access) => {
|
|
@@ -1522,6 +1559,16 @@ class StorageAccessControl {
|
|
|
1522
1559
|
}
|
|
1523
1560
|
}
|
|
1524
1561
|
}
|
|
1562
|
+
validateAccess(identifier, accessType) {
|
|
1563
|
+
const walked = this.accessTree.walk(identifier, { allowIntermediate: true });
|
|
1564
|
+
if (!walked) {
|
|
1565
|
+
throw new NotRegisterError(`'${identifier}' is not registered.`);
|
|
1566
|
+
}
|
|
1567
|
+
if (this.checkAccessIsDirectory(walked.value)) {
|
|
1568
|
+
throw new DirectoryAccessError(`'${identifier}' is directory.`);
|
|
1569
|
+
}
|
|
1570
|
+
return this.validateAndResolveAccess(walked.value, accessType, identifier);
|
|
1571
|
+
}
|
|
1525
1572
|
validateDirectoryPath(identifier) {
|
|
1526
1573
|
const walked = this.accessTree.walk(identifier, { allowIntermediate: true });
|
|
1527
1574
|
if (!walked || !this.checkAccessIsDirectory(walked.value)) {
|
|
@@ -1597,6 +1644,30 @@ class ACSubStorage {
|
|
|
1597
1644
|
async accessAsBinary(identifier) {
|
|
1598
1645
|
return await this.access(identifier, 'binary');
|
|
1599
1646
|
}
|
|
1647
|
+
async create(identifier, accessType) {
|
|
1648
|
+
return await this.#master.create(this.#prefix + ':' + identifier, accessType);
|
|
1649
|
+
}
|
|
1650
|
+
async createAsJSON(identifier) {
|
|
1651
|
+
return await this.create(identifier, 'json');
|
|
1652
|
+
}
|
|
1653
|
+
async createAsText(identifier) {
|
|
1654
|
+
return await this.create(identifier, 'text');
|
|
1655
|
+
}
|
|
1656
|
+
async createAsBinary(identifier) {
|
|
1657
|
+
return await this.create(identifier, 'binary');
|
|
1658
|
+
}
|
|
1659
|
+
async open(identifier, accessType) {
|
|
1660
|
+
return await this.#master.open(this.#prefix + ':' + identifier, accessType);
|
|
1661
|
+
}
|
|
1662
|
+
async openAsJSON(identifier) {
|
|
1663
|
+
return await this.open(identifier, 'json');
|
|
1664
|
+
}
|
|
1665
|
+
async openAsText(identifier) {
|
|
1666
|
+
return await this.open(identifier, 'text');
|
|
1667
|
+
}
|
|
1668
|
+
async openAsBinary(identifier) {
|
|
1669
|
+
return await this.open(identifier, 'binary');
|
|
1670
|
+
}
|
|
1600
1671
|
async copy(oldIdentifier, newIdentifier) {
|
|
1601
1672
|
await this.#master.copy(this.#prefix + ':' + oldIdentifier, this.#prefix + ':' + newIdentifier);
|
|
1602
1673
|
}
|
|
@@ -1612,6 +1683,15 @@ class ACSubStorage {
|
|
|
1612
1683
|
async dropAll() {
|
|
1613
1684
|
await this.#master.dropDir(this.#prefix);
|
|
1614
1685
|
}
|
|
1686
|
+
async release(identifier) {
|
|
1687
|
+
await this.#master.release(this.#prefix + ':' + identifier);
|
|
1688
|
+
}
|
|
1689
|
+
async releaseDir(identifier) {
|
|
1690
|
+
await this.#master.releaseDir(this.#prefix + ':' + identifier);
|
|
1691
|
+
}
|
|
1692
|
+
async releaseAll() {
|
|
1693
|
+
await this.#master.releaseDir(this.#prefix);
|
|
1694
|
+
}
|
|
1615
1695
|
async commit(identifier = '') {
|
|
1616
1696
|
await this.#master.commit(this.#prefix + ':' + identifier);
|
|
1617
1697
|
}
|
|
@@ -1652,63 +1732,87 @@ class ACStorage {
|
|
|
1652
1732
|
const cacheData = JSON.stringify(this.accessCache, null, 4);
|
|
1653
1733
|
fs__namespace$2.writeFileSync(this.cachePath, cacheData, 'utf8');
|
|
1654
1734
|
}
|
|
1655
|
-
|
|
1656
|
-
const
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
if (
|
|
1661
|
-
|
|
1735
|
+
async getOrCreateAccessorFromAccess(identifier, sa, mode) {
|
|
1736
|
+
const targetPath = path__namespace.join(this.basePath, identifier.replaceAll(':', '/'));
|
|
1737
|
+
this.eventListeners.access?.(identifier, sa);
|
|
1738
|
+
let item = this.accessors.get(identifier);
|
|
1739
|
+
if (item != undefined && !item.isDropped()) {
|
|
1740
|
+
if (mode === 'create') {
|
|
1741
|
+
throw new StorageError(`File '${identifier}' already exists in memory`);
|
|
1662
1742
|
}
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
const ac = await event.init(targetPath, ...sa.args);
|
|
1683
|
-
acm = CustomAccessorManager.from(ac, {
|
|
1684
|
-
customId: sa.id,
|
|
1685
|
-
event,
|
|
1686
|
-
actualPath: targetPath,
|
|
1687
|
-
customArgs: sa.args,
|
|
1688
|
-
});
|
|
1689
|
-
break;
|
|
1690
|
-
default:
|
|
1691
|
-
// 기본 타입 이외에는 custom 타입으로 wrap되기 때문에 이 경우가 발생하지 않음
|
|
1743
|
+
return item;
|
|
1744
|
+
}
|
|
1745
|
+
let acm;
|
|
1746
|
+
switch (sa.accessType) {
|
|
1747
|
+
case 'directory':
|
|
1748
|
+
acm = DirectoryAccessorManager.fromFS(targetPath, sa.tree);
|
|
1749
|
+
break;
|
|
1750
|
+
case 'json':
|
|
1751
|
+
acm = JSONAccessorManager.fromFS(targetPath, sa.structure);
|
|
1752
|
+
break;
|
|
1753
|
+
case 'binary':
|
|
1754
|
+
acm = BinaryAccessorManager.fromFS(targetPath);
|
|
1755
|
+
break;
|
|
1756
|
+
case 'text':
|
|
1757
|
+
acm = TextAccessorManager.fromFS(targetPath);
|
|
1758
|
+
break;
|
|
1759
|
+
case 'custom':
|
|
1760
|
+
const event = this.customAccessEvents[sa.id];
|
|
1761
|
+
if (!event) {
|
|
1692
1762
|
throw new StorageError('Invalid access type');
|
|
1763
|
+
}
|
|
1764
|
+
const ac = await event.init(targetPath, ...sa.args);
|
|
1765
|
+
acm = CustomAccessorManager.from(ac, {
|
|
1766
|
+
customId: sa.id,
|
|
1767
|
+
event,
|
|
1768
|
+
actualPath: targetPath,
|
|
1769
|
+
customArgs: sa.args,
|
|
1770
|
+
});
|
|
1771
|
+
break;
|
|
1772
|
+
default:
|
|
1773
|
+
throw new StorageError('Invalid access type');
|
|
1774
|
+
}
|
|
1775
|
+
const exists = await acm.exists();
|
|
1776
|
+
if (mode === 'create') {
|
|
1777
|
+
if (exists) {
|
|
1778
|
+
throw new StorageError(`File '${identifier}' already exists on disk`);
|
|
1693
1779
|
}
|
|
1694
|
-
|
|
1780
|
+
await acm.create();
|
|
1781
|
+
}
|
|
1782
|
+
else if (mode === 'open') {
|
|
1783
|
+
if (!exists) {
|
|
1784
|
+
throw new StorageError(`File '${identifier}' does not exist`);
|
|
1785
|
+
}
|
|
1786
|
+
await acm.load();
|
|
1787
|
+
}
|
|
1788
|
+
else {
|
|
1789
|
+
if (!exists)
|
|
1695
1790
|
await acm.create();
|
|
1696
1791
|
else
|
|
1697
1792
|
await acm.load();
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1793
|
+
}
|
|
1794
|
+
this.accessCache[identifier] = sa.accessType !== 'custom' ? sa.accessType : sa.id;
|
|
1795
|
+
this.accessors.set(identifier, acm);
|
|
1796
|
+
return acm;
|
|
1797
|
+
}
|
|
1798
|
+
async getOrCreateAccessor(identifier, accessType, mode) {
|
|
1799
|
+
const sa = this.accessControl.validateAccess(identifier, accessType);
|
|
1800
|
+
return await this.getOrCreateAccessorFromAccess(identifier, sa, mode);
|
|
1801
|
+
}
|
|
1802
|
+
initAccessControl() {
|
|
1803
|
+
const onAccess = async (identifier, sa) => {
|
|
1804
|
+
return await this.getOrCreateAccessorFromAccess(identifier, sa, 'access');
|
|
1701
1805
|
};
|
|
1702
|
-
const
|
|
1806
|
+
const onDestroy = async (identifier) => {
|
|
1703
1807
|
const accessor = this.accessors.get(identifier);
|
|
1704
1808
|
if (!accessor)
|
|
1705
1809
|
return;
|
|
1706
1810
|
for (const child of accessor.dependent) {
|
|
1707
|
-
await
|
|
1811
|
+
await onDestroy(child);
|
|
1708
1812
|
}
|
|
1709
1813
|
if (identifier === '')
|
|
1710
1814
|
return;
|
|
1711
|
-
this.eventListeners.
|
|
1815
|
+
this.eventListeners.destroy?.(identifier);
|
|
1712
1816
|
if (!accessor.isDropped())
|
|
1713
1817
|
await accessor.drop();
|
|
1714
1818
|
delete this.accessCache[identifier];
|
|
@@ -1722,7 +1826,7 @@ class ACStorage {
|
|
|
1722
1826
|
};
|
|
1723
1827
|
return new StorageAccessControl({
|
|
1724
1828
|
onAccess,
|
|
1725
|
-
|
|
1829
|
+
onDestroy,
|
|
1726
1830
|
onChainDependency,
|
|
1727
1831
|
});
|
|
1728
1832
|
}
|
|
@@ -1731,8 +1835,8 @@ class ACStorage {
|
|
|
1731
1835
|
case 'access':
|
|
1732
1836
|
this.eventListeners.access = listener;
|
|
1733
1837
|
break;
|
|
1734
|
-
case '
|
|
1735
|
-
this.eventListeners.
|
|
1838
|
+
case 'destroy':
|
|
1839
|
+
this.eventListeners.destroy = listener;
|
|
1736
1840
|
break;
|
|
1737
1841
|
}
|
|
1738
1842
|
}
|
|
@@ -1761,6 +1865,32 @@ class ACStorage {
|
|
|
1761
1865
|
async accessAsBinary(identifier) {
|
|
1762
1866
|
return await this.access(identifier, 'binary');
|
|
1763
1867
|
}
|
|
1868
|
+
async create(identifier, accessType) {
|
|
1869
|
+
const acm = await this.getOrCreateAccessor(identifier, accessType, 'create');
|
|
1870
|
+
return acm.accessor;
|
|
1871
|
+
}
|
|
1872
|
+
async createAsJSON(identifier) {
|
|
1873
|
+
return await this.create(identifier, 'json');
|
|
1874
|
+
}
|
|
1875
|
+
async createAsText(identifier) {
|
|
1876
|
+
return await this.create(identifier, 'text');
|
|
1877
|
+
}
|
|
1878
|
+
async createAsBinary(identifier) {
|
|
1879
|
+
return await this.create(identifier, 'binary');
|
|
1880
|
+
}
|
|
1881
|
+
async open(identifier, accessType) {
|
|
1882
|
+
const acm = await this.getOrCreateAccessor(identifier, accessType, 'open');
|
|
1883
|
+
return acm.accessor;
|
|
1884
|
+
}
|
|
1885
|
+
async openAsJSON(identifier) {
|
|
1886
|
+
return await this.open(identifier, 'json');
|
|
1887
|
+
}
|
|
1888
|
+
async openAsText(identifier) {
|
|
1889
|
+
return await this.open(identifier, 'text');
|
|
1890
|
+
}
|
|
1891
|
+
async openAsBinary(identifier) {
|
|
1892
|
+
return await this.open(identifier, 'binary');
|
|
1893
|
+
}
|
|
1764
1894
|
async copy(oldIdentifier, newIdentifier) {
|
|
1765
1895
|
const accessType = this.validateAndGetAccessTypePair(oldIdentifier, newIdentifier);
|
|
1766
1896
|
await this.commit(oldIdentifier);
|
|
@@ -1808,13 +1938,40 @@ class ACStorage {
|
|
|
1808
1938
|
if (identifier === '') {
|
|
1809
1939
|
throw new StorageError('Cannot drop the root directory. use dropAll() instead.');
|
|
1810
1940
|
}
|
|
1811
|
-
await this.accessControl.
|
|
1941
|
+
await this.accessControl.destroyDir(identifier);
|
|
1812
1942
|
}
|
|
1813
1943
|
async drop(identifier) {
|
|
1814
|
-
await this.accessControl.
|
|
1944
|
+
await this.accessControl.destroy(identifier);
|
|
1815
1945
|
}
|
|
1816
1946
|
async dropAll() {
|
|
1817
|
-
await this.accessControl.
|
|
1947
|
+
await this.accessControl.destroyDir('');
|
|
1948
|
+
}
|
|
1949
|
+
async release(identifier) {
|
|
1950
|
+
await this.commit(identifier);
|
|
1951
|
+
await this.#unloadFromMemory(identifier);
|
|
1952
|
+
}
|
|
1953
|
+
async releaseDir(identifier) {
|
|
1954
|
+
if (identifier === '') {
|
|
1955
|
+
throw new StorageError('Cannot release the root directory. use releaseAll() instead.');
|
|
1956
|
+
}
|
|
1957
|
+
await this.commit(identifier);
|
|
1958
|
+
await this.#unloadFromMemory(identifier);
|
|
1959
|
+
}
|
|
1960
|
+
async releaseAll() {
|
|
1961
|
+
await this.commitAll();
|
|
1962
|
+
await this.#unloadFromMemory('');
|
|
1963
|
+
}
|
|
1964
|
+
async #unloadFromMemory(identifier) {
|
|
1965
|
+
const accessor = this.accessors.get(identifier);
|
|
1966
|
+
if (!accessor)
|
|
1967
|
+
return;
|
|
1968
|
+
for (const child of accessor.dependent) {
|
|
1969
|
+
await this.#unloadFromMemory(child);
|
|
1970
|
+
}
|
|
1971
|
+
if (identifier !== '') {
|
|
1972
|
+
delete this.accessCache[identifier];
|
|
1973
|
+
this.accessors.delete(identifier);
|
|
1974
|
+
}
|
|
1818
1975
|
}
|
|
1819
1976
|
async commit(identifier = '') {
|
|
1820
1977
|
await this.#commitRecursive(identifier);
|
|
@@ -1840,62 +1997,82 @@ class MemACStorage extends ACStorage {
|
|
|
1840
1997
|
constructor() {
|
|
1841
1998
|
super('', { noCache: true });
|
|
1842
1999
|
}
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
if (
|
|
1848
|
-
|
|
2000
|
+
async getOrCreateAccessorFromAccess(identifier, sa, mode) {
|
|
2001
|
+
this.eventListeners.access?.(identifier, sa);
|
|
2002
|
+
let item = this.accessors.get(identifier);
|
|
2003
|
+
if (item != undefined && !item.isDropped()) {
|
|
2004
|
+
if (mode === 'create') {
|
|
2005
|
+
throw new StorageError(`File '${identifier}' already exists in memory`);
|
|
1849
2006
|
}
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
|
|
2007
|
+
return item;
|
|
2008
|
+
}
|
|
2009
|
+
let acm;
|
|
2010
|
+
switch (sa.accessType) {
|
|
2011
|
+
case 'directory':
|
|
2012
|
+
acm = DirectoryAccessorManager.fromMemory(sa.tree);
|
|
2013
|
+
break;
|
|
2014
|
+
case 'json':
|
|
2015
|
+
acm = JSONAccessorManager.fromMemory(sa.structure);
|
|
2016
|
+
break;
|
|
2017
|
+
case 'binary':
|
|
2018
|
+
acm = BinaryAccessorManager.fromMemory();
|
|
2019
|
+
break;
|
|
2020
|
+
case 'text':
|
|
2021
|
+
acm = TextAccessorManager.fromMemory();
|
|
2022
|
+
break;
|
|
2023
|
+
case 'custom':
|
|
2024
|
+
const event = this.customAccessEvents[sa.id];
|
|
2025
|
+
if (!event) {
|
|
2026
|
+
throw new StorageError('Invalid access type');
|
|
2027
|
+
}
|
|
2028
|
+
const ac = await event.init(null, ...sa.args);
|
|
2029
|
+
acm = CustomAccessorManager.from(ac, {
|
|
2030
|
+
customId: sa.id,
|
|
2031
|
+
event,
|
|
2032
|
+
actualPath: null,
|
|
2033
|
+
customArgs: sa.args,
|
|
2034
|
+
});
|
|
2035
|
+
break;
|
|
2036
|
+
default:
|
|
2037
|
+
throw new StorageError('Logic Error : Invalid access type');
|
|
2038
|
+
}
|
|
2039
|
+
const exists = await acm.exists();
|
|
2040
|
+
if (mode === 'create') {
|
|
2041
|
+
if (exists) {
|
|
2042
|
+
throw new StorageError(`File '${identifier}' already exists in memory`);
|
|
1880
2043
|
}
|
|
1881
|
-
|
|
1882
|
-
|
|
2044
|
+
await acm.create();
|
|
2045
|
+
}
|
|
2046
|
+
else if (mode === 'open') {
|
|
2047
|
+
if (!exists) {
|
|
2048
|
+
throw new StorageError(`File '${identifier}' does not exist`);
|
|
2049
|
+
}
|
|
2050
|
+
await acm.load();
|
|
2051
|
+
}
|
|
2052
|
+
else {
|
|
2053
|
+
if (!exists)
|
|
2054
|
+
await acm.create();
|
|
1883
2055
|
else
|
|
1884
|
-
acm.load();
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
|
|
2056
|
+
await acm.load();
|
|
2057
|
+
}
|
|
2058
|
+
this.accessCache[identifier] = sa.accessType !== 'custom' ? sa.accessType : sa.id;
|
|
2059
|
+
this.accessors.set(identifier, acm);
|
|
2060
|
+
return acm;
|
|
2061
|
+
}
|
|
2062
|
+
initAccessControl() {
|
|
2063
|
+
const onAccess = async (identifier, sa) => {
|
|
2064
|
+
return await this.getOrCreateAccessorFromAccess(identifier, sa, 'access');
|
|
1888
2065
|
};
|
|
1889
|
-
const
|
|
2066
|
+
const onDestroy = async (identifier) => {
|
|
1890
2067
|
const accessor = this.accessors.get(identifier);
|
|
1891
2068
|
if (!accessor)
|
|
1892
2069
|
return;
|
|
1893
2070
|
for (const child of accessor.dependent) {
|
|
1894
|
-
await
|
|
2071
|
+
await onDestroy(child);
|
|
1895
2072
|
}
|
|
1896
2073
|
if (identifier === '')
|
|
1897
2074
|
return;
|
|
1898
|
-
this.eventListeners.
|
|
2075
|
+
this.eventListeners.destroy?.(identifier);
|
|
1899
2076
|
if (!accessor.isDropped())
|
|
1900
2077
|
accessor.drop();
|
|
1901
2078
|
delete this.accessCache[identifier];
|
|
@@ -1909,10 +2086,37 @@ class MemACStorage extends ACStorage {
|
|
|
1909
2086
|
};
|
|
1910
2087
|
return new StorageAccessControl({
|
|
1911
2088
|
onAccess,
|
|
1912
|
-
|
|
2089
|
+
onDestroy,
|
|
1913
2090
|
onChainDependency,
|
|
1914
2091
|
});
|
|
1915
2092
|
}
|
|
2093
|
+
async release(identifier) {
|
|
2094
|
+
await this.commit(identifier);
|
|
2095
|
+
await this.#unloadFromMemory(identifier);
|
|
2096
|
+
}
|
|
2097
|
+
async releaseDir(identifier) {
|
|
2098
|
+
if (identifier === '') {
|
|
2099
|
+
throw new StorageError('Cannot release the root directory. use releaseAll() instead.');
|
|
2100
|
+
}
|
|
2101
|
+
await this.commit(identifier);
|
|
2102
|
+
await this.#unloadFromMemory(identifier);
|
|
2103
|
+
}
|
|
2104
|
+
async releaseAll() {
|
|
2105
|
+
await this.commitAll();
|
|
2106
|
+
await this.#unloadFromMemory('');
|
|
2107
|
+
}
|
|
2108
|
+
async #unloadFromMemory(identifier) {
|
|
2109
|
+
const accessor = this.accessors.get(identifier);
|
|
2110
|
+
if (!accessor)
|
|
2111
|
+
return;
|
|
2112
|
+
for (const child of accessor.dependent) {
|
|
2113
|
+
await this.#unloadFromMemory(child);
|
|
2114
|
+
}
|
|
2115
|
+
if (identifier !== '') {
|
|
2116
|
+
delete this.accessCache[identifier];
|
|
2117
|
+
this.accessors.delete(identifier);
|
|
2118
|
+
}
|
|
2119
|
+
}
|
|
1916
2120
|
}
|
|
1917
2121
|
|
|
1918
2122
|
class StorageAccess {
|