arcane-os 0.28.1 → 0.28.3

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,28 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.28.3
4
+
5
+ - Add explicit `DBOPFS.removeEmptyTable(tableName)` for removing an existing
6
+ empty table directory through native non-recursive OPFS removal. It reports
7
+ `removed`, `absent`, or `not-empty` and preserves nonempty tables and file
8
+ targets without creating, scanning, or clearing them.
9
+ - Invalidate the logical/physical table alias and cached handles after removal
10
+ or confirmed absence. Existing recursive `deleteTable()` and
11
+ `clearAllStorage()` behavior remains unchanged. This release performs no
12
+ automatic cleanup or saved-data migration; npm paths and dependencies remain
13
+ unchanged.
14
+
15
+ ## 0.28.2
16
+
17
+ - Redirect unmatched Mail server routes with `303 See Other` to
18
+ `https://<current hostname>/404.html`, using the request's HTTP/1 Host or
19
+ HTTP/2 authority and omitting the API listener port. The redirect has an empty
20
+ body and uses the existing HTTP server response interface.
21
+ - Preserve `/v1/mail` requests with query strings, CORS and OPTIONS behavior,
22
+ method and API errors, subscription handling, and the complete Mail provider
23
+ and cancellation lifecycle. Client APIs, npm resource paths, dependencies,
24
+ and listener configuration are unchanged.
25
+
3
26
  ## 0.28.1
4
27
 
5
28
  - DBOPFS initialization opens the application's existing storage scope without
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.1` SDK contract. Applications pin one exact npm
22
+ This checkout defines the `0.28.3` 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",
@@ -712,6 +712,22 @@ browser and gateway preserve complete request and response content without
712
712
  body-size gates. With no event observer, the gateway does not construct event
713
713
  payloads or parse a second provider-request representation for observation.
714
714
 
715
+ Requests whose path does not match `/v1/mail` receive `303 See Other` with an
716
+ empty body and `Location: https://<current-hostname>/404.html`. The destination
717
+ uses the request's HTTP/2 authority or HTTP/1.1 Host hostname, omits the API port,
718
+ and replaces the requested path and query. For example:
719
+
720
+ | Unmatched request URL | Redirect destination |
721
+ | --- | --- |
722
+ | `https://mail.thewizardnexus.com:4433/` | `https://mail.thewizardnexus.com/404.html` |
723
+ | `https://mail.precrisis.ai:4433/missing?example=1` | `https://mail.precrisis.ai/404.html` |
724
+
725
+ The HTTPS website on the current hostname owns that page. The mail listener
726
+ only returns the redirect. Requests to `/v1/mail`, including its query-bearing
727
+ form, retain the existing CORS policy, OPTIONS preflight, method handling, and
728
+ structured API errors. For example, `GET /v1/mail` still receives `405`.
729
+ Subscription verification and its automatic same-IP exception remain unchanged.
730
+
715
731
  The gateway returns `202` only after Resend returns a nonempty string provider id.
716
732
  Transport loss, an explicit caller-selected timeout, an invalid success body,
717
733
  or an unreadable provider response returns an explicit uncertain result and never claims
@@ -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
@@ -4,6 +4,9 @@ The [same-IP subscription exception follow-up](#same-ip-subscription-exception-f
4
4
  records the later user-selected change to when the optional verifier runs. The
5
5
  earlier inventories retain their stated historical scope; current verification
6
6
  skips requests whose actual connection source and destination IPs are equal.
7
+ The [unmatched-route redirect follow-up](#unmatched-route-redirect-follow-up)
8
+ records the later change from a missing-route JSON error to the hostname's
9
+ HTTPS 404 page.
7
10
 
8
11
  | Decision | Behavior | Why it matters | Source status at this review |
9
12
  | --- | --- | --- | --- |
@@ -601,3 +604,28 @@ This follow-up records the selected behavior and source review. No local test,
601
604
  check, build, server launch, live mail send, or platform execution was performed
602
605
  by this documentation author. Release and runtime evidence remain with the
603
606
  corresponding operation's owner.
607
+
608
+ ## Unmatched-route redirect follow-up
609
+
610
+ The user selected the Stripe server's redirect behavior for unmatched mail
611
+ routes. The existing SDK `handleMailRequest` route decision owns this behavior:
612
+ an unmatched path returns `303 See Other` with an empty body and a Location of
613
+ `https://<current-hostname>/404.html`. The request authority supplies the hostname;
614
+ the destination omits the API port and replaces the original path and query.
615
+ The website on that hostname owns the page content.
616
+
617
+ | Method or action | Gate 1: Do we care? | Gate 2: Why is it worth the work? | Gate 3: Can we remove it without losing the required result? | Decision and concrete effect |
618
+ | --- | --- | --- | --- | --- |
619
+ | Unmatched-path JSON `404` response | No. The selected result is navigation to the hostname's 404 page. | Keeping it would preserve the superseded response instead of the requested navigation. | Yes, when replaced with the redirect at the existing route decision. | Replace only this response with `303` and an empty body. |
620
+ | Current-hostname HTTPS redirect without the API port | Yes. It sends visitors to the requested website page. | One request-authority parse supplies the hostname for every deployment without another setting or domain table. | No. Removing it loses the explicitly selected destination. | Keep the decision inline in the existing native request handler; add no server, dependency, proxy, or file-serving path. |
621
+ | Matched `/v1/mail` CORS, OPTIONS, method errors, subscription verification, same-IP exception, and delivery results | Yes. Existing applications depend on these mail contracts. | An unmatched navigation does not change how an actual API request is handled or reported. | No. Redirecting actual API failures would hide the caller's delivery outcome. | Preserve `/v1/mail` and its query-bearing form, including OPTIONS preflight, `405` for unsupported methods, and structured API errors. |
622
+
623
+ An unmatched request ends at its existing route branch without entering
624
+ subscription verification, report parsing, or provider delivery. There is one
625
+ redirect response per unmatched request, with no new asynchronous wait, DNS
626
+ lookup, configuration read, timer, or retained state. This is a source-level
627
+ operation description; no timing improvement or deployed result is claimed.
628
+
629
+ This follow-up is based on source review. This documentation author ran no local
630
+ tests, checks, builds, server launches, or production requests. Delivery and
631
+ selected-package verification remain with their respective owners.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "arcane-os",
3
- "version": "0.28.1",
3
+ "version": "0.28.3",
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",
@@ -85,7 +85,7 @@
85
85
  "test": "npm run test:unit && npm run test:functional && npm run test:integration && npm run test:regression",
86
86
  "test:release": "node ./bin/arcane-test.mjs test/npm-release.test.mjs",
87
87
  "test:unit": "node ./bin/arcane-test.mjs test/app-descriptor.test.mjs test/app-schema.test.mjs test/app-selection.test.mjs test/contracts.test.mjs test/doctor.test.mjs test/mail-credentials.test.mjs test/mail-outbox.test.mjs test/mail-public-api.test.mjs test/mail-send.test.mjs test/mail-transport.test.mjs test/targets.test.mjs test/workspace-operation-lock.test.mjs",
88
- "test:functional": "node ./bin/arcane-test.mjs test/browser-speech-providers.test.mjs test/browser-wasm-gpu-notice.test.mjs test/browser-wasm-download-resume.test.mjs test/cli.test.mjs test/dbopfs-document-library.test.mjs test/dev-server.test.mjs test/dev-pwa.test.mjs test/dom-event-instrumentation.test.mjs test/event-manager.test.mjs test/events.test.mjs test/import-map.test.mjs test/mail-cli.test.mjs test/mail-runtime.test.mjs test/mail-server.test.mjs test/modal.test.mjs test/packaging.test.mjs test/pwa-packaging.test.mjs test/pwa-client.test.mjs test/pwa-install.test.mjs test/pwa-worker.test.mjs test/persistent-ai-chat-session.test.mjs test/reference-completeness.test.mjs test/runtime-api-behavior.test.mjs test/runtime.test.mjs test/scaffold.test.mjs test/speech-playback.test.mjs test/site.test.mjs test/update-check.test.mjs",
88
+ "test:functional": "node ./bin/arcane-test.mjs test/browser-speech-providers.test.mjs test/browser-wasm-gpu-notice.test.mjs test/browser-wasm-download-resume.test.mjs test/cli.test.mjs test/dbopfs-document-library.test.mjs test/dbopfs.test.mjs test/dev-server.test.mjs test/dev-pwa.test.mjs test/dom-event-instrumentation.test.mjs test/event-manager.test.mjs test/events.test.mjs test/import-map.test.mjs test/mail-cli.test.mjs test/mail-runtime.test.mjs test/mail-server.test.mjs test/modal.test.mjs test/packaging.test.mjs test/pwa-packaging.test.mjs test/pwa-client.test.mjs test/pwa-install.test.mjs test/pwa-worker.test.mjs test/persistent-ai-chat-session.test.mjs test/reference-completeness.test.mjs test/runtime-api-behavior.test.mjs test/runtime.test.mjs test/scaffold.test.mjs test/speech-playback.test.mjs test/site.test.mjs test/update-check.test.mjs",
89
89
  "test:integration": "node ./bin/arcane-test.mjs test/installed-package-runtime.test.mjs test/root-app-layout.test.mjs test/integrated-shared.test.mjs test/integrated-workspace.test.mjs test/mail-browser.test.mjs test/native-plan.test.mjs test/native-provider-loader.test.mjs test/npm-release.test.mjs test/release-bundle.test.mjs test/release-capability-smoke.test.mjs test/shared-payload-batch.test.mjs test/tarball.test.mjs test/browser-wasm-cpu.test.mjs test/wllama-webgpu-runtime.test.mjs",
90
90
  "test:regression": "node ./bin/arcane-test.mjs test/channel-workflows.test.mjs test/html-import-registration.test.mjs test/logging-regression.test.mjs test/markdown-speech.test.mjs test/prepared-speech.test.mjs test/native-provider-generation.test.mjs test/speech-queue-regression.test.mjs test/testing.test.mjs test/test-sets.test.mjs",
91
91
  "check": "node tools/check-source.mjs && npm test",
@@ -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
  *
@@ -877,8 +877,21 @@ function createConfiguredMailHandler(configuration){
877
877
  });
878
878
 
879
879
  try{
880
- if(request.url!==RESEND_MAIL_PATH&&!request.url?.startsWith(`${RESEND_MAIL_PATH}?`)){
881
- throw new MailGatewayFault('mail_route_not_found',{statusCode:404});
880
+ if (request.url !== RESEND_MAIL_PATH && !request.url?.startsWith(`${RESEND_MAIL_PATH}?`)) {
881
+ const requestAuthority = request.authority || request.headers.host || '';
882
+ const hostname = new URL(`https://${requestAuthority}`).hostname;
883
+ response.writeHead(
884
+ 303,
885
+ {
886
+ 'content-type': 'text/plain; charset=utf-8',
887
+ 'location': `https://${hostname}/404.html`
888
+ }
889
+ );
890
+ response.end();
891
+ if (!request.readableEnded && !request.destroyed) {
892
+ request.resume();
893
+ }
894
+ return;
882
895
  }
883
896
  const requestOrigin=request.headers.origin;
884
897
  if(requestOrigin){