ac-storage 0.14.0 → 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/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
- let contents;
495
- if (existsSync(filename)) {
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
- else {
505
- contents = {};
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
- await fs.writeFile(filename, jsonString, 'utf8');
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 (error) {
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 baseValue;
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
- if (baseValue != null) {
558
- return baseValue;
559
- }
560
- else {
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, baseValue);
605
+ return this.#getDefaultData(result.value);
572
606
  }
573
607
  }
574
- #getDefaultData(typeData, baseValue) {
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 result = baseValue;
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
- if (!(key in result)) {
588
- result[key] = defaultValue;
589
- }
621
+ result[key] = defaultValue;
622
+ hasKey = true;
590
623
  }
591
624
  }
592
- return result;
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 || typeof value === 'object') {
673
- value = this.#defaultValueProvider.get(key, value);
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 || typeof value === 'object') {
683
- value = this.#defaultValueProvider.get(key, value);
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.#ensureNotDropped();
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 release(identifier) {
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.onRelease(identifier);
1511
+ await this.#events.onDestroy(identifier);
1475
1512
  }
1476
- async releaseDir(identifier) {
1513
+ async destroyDir(identifier) {
1477
1514
  this.validateDirectoryPath(identifier);
1478
- await this.#events.onRelease(identifier);
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
  }
@@ -1581,13 +1652,22 @@ class ACSubStorage {
1581
1652
  await this.#master.move(this.#prefix + ':' + oldIdentifier, this.#prefix + ':' + newIdentifier);
1582
1653
  }
1583
1654
  async dropDir(identifier) {
1584
- await this.dropDir(this.#prefix + ':' + identifier);
1655
+ await this.#master.dropDir(this.#prefix + ':' + identifier);
1585
1656
  }
1586
1657
  async drop(identifier) {
1587
- await this.drop(this.#prefix + ':' + identifier);
1658
+ await this.#master.drop(this.#prefix + ':' + identifier);
1588
1659
  }
1589
1660
  async dropAll() {
1590
- await this.dropDir(this.#prefix);
1661
+ await this.#master.dropDir(this.#prefix);
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);
1591
1671
  }
1592
1672
  async commit(identifier = '') {
1593
1673
  await this.#master.commit(this.#prefix + ':' + identifier);
@@ -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
- initAccessControl() {
1633
- const onAccess = async (identifier, sa) => {
1634
- const targetPath = path.join(this.basePath, identifier.replaceAll(':', '/'));
1635
- this.eventListeners.access?.(identifier, sa);
1636
- let item = this.accessors.get(identifier);
1637
- if (item != undefined && !item.isDropped()) {
1638
- return item;
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
- let acm;
1641
- switch (sa.accessType) {
1642
- case 'directory':
1643
- acm = DirectoryAccessorManager.fromFS(targetPath, sa.tree);
1644
- break;
1645
- case 'json':
1646
- acm = JSONAccessorManager.fromFS(targetPath, sa.structure);
1647
- break;
1648
- case 'binary':
1649
- acm = BinaryAccessorManager.fromFS(targetPath);
1650
- break;
1651
- case 'text':
1652
- acm = TextAccessorManager.fromFS(targetPath);
1653
- break;
1654
- case 'custom':
1655
- const event = this.customAccessEvents[sa.id];
1656
- if (!event) {
1657
- throw new StorageError('Invalid access type');
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
- if (!await acm.exists())
1763
+ await acm.load();
1764
+ }
1765
+ else {
1766
+ if (!exists)
1672
1767
  await acm.create();
1673
1768
  else
1674
1769
  await acm.load();
1675
- this.accessCache[identifier] = sa.accessType !== 'custom' ? sa.accessType : sa.id;
1676
- this.accessors.set(identifier, acm);
1677
- return acm;
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 onRelease = async (identifier) => {
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 onRelease(child);
1788
+ await onDestroy(child);
1685
1789
  }
1686
1790
  if (identifier === '')
1687
1791
  return;
1688
- this.eventListeners.release?.(identifier);
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
- onRelease,
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 'release':
1712
- this.eventListeners.release = listener;
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.releaseDir(identifier);
1918
+ await this.accessControl.destroyDir(identifier);
1789
1919
  }
1790
1920
  async drop(identifier) {
1791
- await this.accessControl.release(identifier);
1921
+ await this.accessControl.destroy(identifier);
1792
1922
  }
1793
1923
  async dropAll() {
1794
- await this.accessControl.releaseDir('');
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
- initAccessControl() {
1821
- const onAccess = async (identifier, sa) => {
1822
- this.eventListeners.access?.(identifier, sa);
1823
- let item = this.accessors.get(identifier);
1824
- if (item != undefined && !item.isDropped()) {
1825
- return item;
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
- let acm;
1828
- switch (sa.accessType) {
1829
- case 'directory':
1830
- acm = DirectoryAccessorManager.fromMemory(sa.tree);
1831
- break;
1832
- case 'json':
1833
- acm = JSONAccessorManager.fromMemory(sa.structure);
1834
- break;
1835
- case 'binary':
1836
- acm = BinaryAccessorManager.fromMemory();
1837
- break;
1838
- case 'text':
1839
- acm = TextAccessorManager.fromMemory();
1840
- break;
1841
- case 'custom':
1842
- const event = this.customAccessEvents[sa.id];
1843
- if (!event) {
1844
- throw new StorageError('Invalid access type');
1845
- }
1846
- const ac = await event.init(null, ...sa.args);
1847
- acm = CustomAccessorManager.from(ac, {
1848
- customId: sa.id,
1849
- event,
1850
- actualPath: null,
1851
- customArgs: sa.args,
1852
- });
1853
- break;
1854
- default:
1855
- // 기본 타입 이외에는 custom 타입으로 wrap되기 때문에 이 경우가 발생하지 않음
1856
- throw new StorageError('Logic Error : Invalid access type');
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
- if (!acm.exists())
1859
- acm.create();
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
- this.accessCache[identifier] = sa.accessType !== 'custom' ? sa.accessType : sa.id;
1863
- this.accessors.set(identifier, acm);
1864
- return acm;
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 onRelease = async (identifier) => {
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 onRelease(child);
2048
+ await onDestroy(child);
1872
2049
  }
1873
2050
  if (identifier === '')
1874
2051
  return;
1875
- this.eventListeners.release?.(identifier);
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
- onRelease,
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 {