arcane-os 0.28.2 → 0.28.4

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 CHANGED
@@ -1,5 +1,29 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.28.4
4
+
5
+ - Preserve installed component-relative browser-runtime and strong-type import
6
+ aliases in direct npm maps, including npm-alias workspaces. These aliases
7
+ resolve to the same installed modules as the direct paths, restoring browser
8
+ device-settings loading without copies or a second module instance.
9
+ - Serve explicitly included historical authored resources at their retained
10
+ paths in root-app development. Unselected navigation aliases and generated
11
+ PWA endpoints retain their existing behavior. Applications refresh managed
12
+ maps through the public SDK command after updating the dependency; no
13
+ authored SDK edits, saved-data migration, or dependency changes are required.
14
+
15
+ ## 0.28.3
16
+
17
+ - Add explicit `DBOPFS.removeEmptyTable(tableName)` for removing an existing
18
+ empty table directory through native non-recursive OPFS removal. It reports
19
+ `removed`, `absent`, or `not-empty` and preserves nonempty tables and file
20
+ targets without creating, scanning, or clearing them.
21
+ - Invalidate the logical/physical table alias and cached handles after removal
22
+ or confirmed absence. Existing recursive `deleteTable()` and
23
+ `clearAllStorage()` behavior remains unchanged. This release performs no
24
+ automatic cleanup or saved-data migration; npm paths and dependencies remain
25
+ unchanged.
26
+
3
27
  ## 0.28.2
4
28
 
5
29
  - Redirect unmatched Mail server routes with `303 See Other` to
package/README.md CHANGED
@@ -19,7 +19,7 @@ version-locked SDK runtime, while an integrated Arcane checkout uses its live
19
19
  `arcane/` runtime. Both profiles preserve the same app URLs, theme, packaging,
20
20
  event, cancellation, and browser run contracts.
21
21
 
22
- This checkout defines the `0.28.2` SDK contract. Applications pin one exact npm
22
+ This checkout defines the `0.28.4` SDK contract. Applications pin one exact npm
23
23
  version and lockfile; registry state is deliberately not baked into application
24
24
  artifacts.
25
25
 
@@ -550,11 +550,11 @@
550
550
  "DBOPFS_REASONS",
551
551
  "default"
552
552
  ],
553
- "summary": "Provides app-scoped OPFS tables, worker I/O, backup/restore, compression, and CRUD/batch APIs.",
553
+ "summary": "Provides app-scoped OPFS tables, explicit non-recursive empty-table removal, worker I/O, backup/restore, compression, and CRUD/batch APIs.",
554
554
  "availability": "Browser / native WebView",
555
555
  "protocol": "OPFS, DBOPFSWorker, Compression Streams, and per-realm globalThis.arcaneEvents authority",
556
- "normalization": "App scope and recognized file parsing are normalized; nonblank unreadable JSONL rows remain complete raw strings, and DOM/storage errors are preserved.",
557
- "surface": "default `DBOPFS`; installs `window.dbopfs`, emits `dbopfs-ready`; table/file/backup APIs."
556
+ "normalization": "App scope and recognized file parsing are normalized; nonblank unreadable JSONL rows remain complete raw strings, empty-table removal reports removed, absent, or not-empty after a create:false directory lookup and without scanning or recursively clearing a directory, same-named files are preserved, aliases and cached handles are invalidated only when the directory is gone, and unexpected DOM/storage errors are preserved.",
557
+ "surface": "default `DBOPFS`; installs `window.dbopfs`, emits `dbopfs-ready`; table/file/backup APIs including removeEmptyTable(tableName)."
558
558
  },
559
559
  {
560
560
  "file": "runtime/arcane/modules/DBOPFSDocumentLibrary.js",
@@ -1783,7 +1783,7 @@ Provides app-scoped OPFS tables, worker I/O, backup/restore, compression, and CR
1783
1783
 
1784
1784
  ### Public surface
1785
1785
 
1786
- default `DBOPFS`; installs `window.dbopfs`, emits `dbopfs-ready`; table/file/backup APIs.
1786
+ default `DBOPFS`; installs `window.dbopfs`, emits `dbopfs-ready`; table/file/backup APIs. `removeEmptyTable(tableName)` resolves one explicitly selected existing directory without creating or scanning it, then removes it only when OPFS confirms that it is empty. It never creates, clears, or recursively removes the target. It resolves a mutable record with `status` set to `removed`, `absent`, or `not-empty`, the matching `removed` boolean, and the logical `tableName` plus physical `directoryName`; a same-named file and unexpected platform errors reject without removal.
1787
1787
 
1788
1788
  Exact exports: `DBOPFS_EVENT_TYPES`, `DBOPFS_REASONS`, `default`.
1789
1789
 
@@ -1793,7 +1793,10 @@ Exact exports: `DBOPFS_EVENT_TYPES`, `DBOPFS_REASONS`, `default`.
1793
1793
  are normalized. Each readable JSONL row becomes its parsed value; a nonblank
1794
1794
  unreadable row remains in its original string form so the owning application
1795
1795
  can display, diagnose, or recover it without silent data loss. DOM and storage
1796
- errors remain observable. Transport: OPFS, DBOPFSWorker, Compression Streams.
1796
+ errors remain observable. The logical `memories` table continues to map to the
1797
+ physical `memory` directory, and successful or already-absent empty-table
1798
+ removal invalidates both alias and cached-handle state. Transport: OPFS,
1799
+ DBOPFSWorker, Compression Streams.
1797
1800
  [Deep protocol details](protocols.md).
1798
1801
 
1799
1802
  ### Example
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "arcane-os",
3
- "version": "0.28.2",
3
+ "version": "0.28.4",
4
4
  "description": "Arcane OS JavaScript SDK, project-local CLI, browser runtime, and repository-portable application packager.",
5
5
  "type": "module",
6
6
  "main": "./src/index.mjs",
@@ -286,6 +286,25 @@ class DBOPFS {
286
286
  return `${tableName}:${fileName}`
287
287
  }
288
288
 
289
+ /**
290
+ * Invalidates every known cache key for one logical/physical table pair.
291
+ * @private
292
+ * @param {string} tableName
293
+ * @param {string} directoryName
294
+ * @param {string} registeredTableName
295
+ */
296
+ #forgetTable(tableName,directoryName,registeredTableName){
297
+ for(const name of new Set([
298
+ tableName,
299
+ directoryName,
300
+ registeredTableName
301
+ ])){
302
+ delete this.#tables[name]
303
+ delete this.#tableHandles[name]
304
+ delete this.#tableHandlePromises[name]
305
+ }
306
+ }
307
+
289
308
  /** @type {boolean} */
290
309
  ready=false;
291
310
 
@@ -825,6 +844,66 @@ class DBOPFS {
825
844
  return true
826
845
  }
827
846
 
847
+ /**
848
+ * Removes one existing table only when its physical directory is empty.
849
+ * The target is resolved as an existing directory without creating or
850
+ * scanning it. Native non-recursive OPFS removal owns the emptiness
851
+ * decision, so this method never clears or recursively removes a table.
852
+ *
853
+ * @param {string} tableName
854
+ * @returns {Promise<{
855
+ * status:'removed'|'absent'|'not-empty',
856
+ * removed:boolean,
857
+ * tableName:string,
858
+ * directoryName:string
859
+ * }>}
860
+ */
861
+ async removeEmptyTable(tableName){
862
+ if(!this.ready){
863
+ await this.readyPromise;
864
+ }
865
+
866
+ const directoryName=directoryNameForTable(tableName);
867
+ const registeredTableName=tableNameForDirectory(directoryName);
868
+
869
+ try{
870
+ await this.#db.getDirectoryHandle(directoryName,{create:false})
871
+ await this.#db.removeEntry(directoryName)
872
+ }catch(error){
873
+ if(error.name==='InvalidModificationError'){
874
+ return {
875
+ status:'not-empty',
876
+ removed:false,
877
+ tableName:registeredTableName,
878
+ directoryName
879
+ }
880
+ }
881
+
882
+ if(error.name!=='NotFoundError'){
883
+ arcaneLogging.error(error)
884
+ throw error
885
+ }
886
+
887
+ this.#forgetTable(tableName,directoryName,registeredTableName)
888
+
889
+ return {
890
+ status:'absent',
891
+ removed:false,
892
+ tableName:registeredTableName,
893
+ directoryName
894
+ }
895
+ }
896
+
897
+ this.#forgetTable(tableName,directoryName,registeredTableName)
898
+
899
+ return {
900
+ status:'removed',
901
+ removed:true,
902
+ tableName:registeredTableName,
903
+ directoryName
904
+ }
905
+ }
906
+
828
907
  /**
829
908
  * Clears only the current application's OPFS database.
830
909
  *
@@ -1192,7 +1192,11 @@ async function startOwnedDevServer({
1192
1192
  const selectedRoutes = mode === 'source' && (appRequest || segments.length === 0 || generatedPwaPath)
1193
1193
  ? await refreshSourceRoutes() : currentSourceRoutes;
1194
1194
  const pwaEnabled = mode === 'source' ? selectedRoutes.app?.pwa?.enabled === true : routeSet.pwa;
1195
- if (legacyAppRequest && !(pwaEnabled && legacyPwaRequest)) {
1195
+ const authoredLegacyResource = legacyAppRequest
1196
+ && sourcePathAllowed(segments, selectedRoutes.app);
1197
+ if (legacyAppRequest
1198
+ && !(pwaEnabled && legacyPwaRequest)
1199
+ && !authoredLegacyResource) {
1196
1200
  const legacyPath = target.pathname.slice(`/apps/${routeSet.appId}`.length);
1197
1201
  const location = !legacyPath || legacyPath === '/' || legacyPath === '/index.html'
1198
1202
  ? selectedRoutes.startPath : legacyPath;
@@ -1470,12 +1470,19 @@ async function managedImportMapBuild(resolvedWorkspace,resolvedApp,signal,pwaEna
1470
1470
  return value.startsWith('./')
1471
1471
  ?`./${installedRuntimeTarget(value.slice(2),installed,{browser:true})}`:value;
1472
1472
  }
1473
+ function installedPhysicalCompatibilityUrl(value){
1474
+ if(!value.startsWith('./arcane/sdk/')
1475
+ &&!value.startsWith('./arcane/dependencies/strong-type/'))return null;
1476
+ return `./${installed.routes[0].destination}/${value.slice('./arcane/'.length)}`;
1477
+ }
1473
1478
  for(const [specifier,target] of Object.entries(built.imports)){
1474
1479
  if(rootApplication&&specifier.startsWith('arcane/'))continue;
1475
1480
  const selected=installedBrowserUrl(target);
1476
1481
  if(!rootApplication)imports[specifier]=selected;
1477
1482
  // Relative module imports and bare names must resolve to the same instance.
1478
1483
  imports[installedBrowserUrl(specifier)]=selected;
1484
+ const physicalCompatibilityUrl=installedPhysicalCompatibilityUrl(specifier);
1485
+ if(physicalCompatibilityUrl)imports[physicalCompatibilityUrl]=selected;
1479
1486
  }
1480
1487
  built.imports=imports;
1481
1488
  }