arcane-os 0.5.17 → 0.5.19

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.
Files changed (186) hide show
  1. package/CHANGELOG.md +34 -1
  2. package/README.md +9 -9
  3. package/bin/arcane-test.mjs +16 -13
  4. package/browser-runtime/ai/browser-speech-artifacts.mjs +41 -38
  5. package/browser-runtime/ai/browser-speech-providers.mjs +46 -43
  6. package/browser-runtime/ai/browser-wasm-llm-provider.mjs +158 -135
  7. package/browser-runtime/ai/browser-wasm.mjs +6 -3
  8. package/browser-runtime/ai/browser-wllama-runtime.mjs +104 -23
  9. package/browser-runtime/ai/model-controller.mjs +94 -91
  10. package/browser-runtime/ai/speech-worker-client.mjs +12 -9
  11. package/browser-runtime/ai/speech-worker-runtime.mjs +56 -53
  12. package/browser-runtime/dependencies/strong-type/index.js +207 -82
  13. package/browser-runtime/dependencies/strong-type/package.json +15 -6
  14. package/browser-runtime/dom-event-instrumentation.mjs +24 -20
  15. package/browser-runtime/event-manager.mjs +111 -108
  16. package/browser-runtime/speech-text.mjs +5 -1
  17. package/docs/architecture.md +1 -1
  18. package/docs/publishing.md +10 -4
  19. package/docs/reference/ai/browser-speech.md +198 -5
  20. package/docs/reference/ai/browser-wasm.md +29 -6
  21. package/docs/reference/availability-and-normalization.md +40 -0
  22. package/docs/reference/inventory/package-api.json +1 -1
  23. package/docs/reference/inventory/runtime-components.json +1 -1
  24. package/docs/reference/inventory/runtime-modules.json +20 -24
  25. package/docs/reference/protocols.md +1 -1
  26. package/docs/reference/runtime-modules.md +125 -35
  27. package/docs/reference/sdk-api.md +23 -7
  28. package/examples/wasm-ai-demo/index.html +1 -0
  29. package/node_modules/event-pubsub/node_modules/strong-type/README.md +408 -0
  30. package/node_modules/event-pubsub/node_modules/strong-type/assets/strong-type-header.png +0 -0
  31. package/node_modules/event-pubsub/node_modules/strong-type/index.js +1151 -0
  32. package/node_modules/event-pubsub/node_modules/strong-type/licence +21 -0
  33. package/node_modules/event-pubsub/node_modules/strong-type/node.js +125 -0
  34. package/node_modules/event-pubsub/node_modules/strong-type/package.json +61 -0
  35. package/node_modules/strong-type/README.md +42 -24
  36. package/node_modules/strong-type/benchmark/core.js +324 -0
  37. package/node_modules/strong-type/index.js +207 -82
  38. package/node_modules/strong-type/package.json +14 -6
  39. package/package.json +3 -3
  40. package/runtime/arcane/components/app-bar.html +5 -2
  41. package/runtime/arcane/components/assistant-panel.html +5 -2
  42. package/runtime/arcane/components/chart.html +15 -12
  43. package/runtime/arcane/components/chat.html +170 -148
  44. package/runtime/arcane/components/dashboard-config.html +6 -3
  45. package/runtime/arcane/components/data-view.html +4 -1
  46. package/runtime/arcane/components/directory-picker.html +5 -2
  47. package/runtime/arcane/components/document-inspector.html +6 -3
  48. package/runtime/arcane/components/file-drop.html +5 -2
  49. package/runtime/arcane/components/file-inspector.html +9 -6
  50. package/runtime/arcane/components/file-manager.html +16 -13
  51. package/runtime/arcane/components/local-ai-status.html +5 -2
  52. package/runtime/arcane/components/markdown-document.html +14 -11
  53. package/runtime/arcane/components/markdown-editor.html +4 -1
  54. package/runtime/arcane/components/modal.html +4 -1
  55. package/runtime/arcane/components/output-panel.html +6 -3
  56. package/runtime/arcane/components/preferences-form.html +4 -1
  57. package/runtime/arcane/components/record-timeline.html +8 -5
  58. package/runtime/arcane/components/relationship-board.html +9 -6
  59. package/runtime/arcane/components/source-code-viewer.html +12 -9
  60. package/runtime/arcane/components/source-explanation.html +8 -5
  61. package/runtime/arcane/components/speech.html +24 -21
  62. package/runtime/arcane/components/summary-strip.html +4 -1
  63. package/runtime/arcane/components/task-progress.html +6 -3
  64. package/runtime/arcane/components/terminal-workspace.html +4 -1
  65. package/runtime/arcane/components/voice-transcription.html +11 -8
  66. package/runtime/arcane/components/web-navigator.html +5 -2
  67. package/runtime/arcane/entities/ApiModelRecord.js +5 -2
  68. package/runtime/arcane/entities/Calculation.js +5 -2
  69. package/runtime/arcane/entities/Chat.js +33 -33
  70. package/runtime/arcane/entities/CommunicationMessage.js +4 -1
  71. package/runtime/arcane/entities/CommunicationThread.js +4 -1
  72. package/runtime/arcane/entities/File.js +1 -1
  73. package/runtime/arcane/entities/Image.js +8 -5
  74. package/runtime/arcane/entities/IntentEnvelope.js +20 -17
  75. package/runtime/arcane/entities/Preference.js +9 -6
  76. package/runtime/arcane/entities/Theme.js +5 -2
  77. package/runtime/arcane/entities/User.js +11 -11
  78. package/runtime/arcane/entities/Weather.js +5 -2
  79. package/runtime/arcane/modules/AI.js +491 -165
  80. package/runtime/arcane/modules/AIPreferenceRuntime.js +5 -2
  81. package/runtime/arcane/modules/AIPreferenceTuple.js +9 -6
  82. package/runtime/arcane/modules/AIProviderRuntime.js +88 -85
  83. package/runtime/arcane/modules/AIResponseURLPolicy.js +148 -62
  84. package/runtime/arcane/modules/AIRuntimeState.js +29 -26
  85. package/runtime/arcane/modules/AnsiText.js +4 -1
  86. package/runtime/arcane/modules/ApiModelDatabase.js +22 -19
  87. package/runtime/arcane/modules/AppDataScope.js +8 -5
  88. package/runtime/arcane/modules/ArcaneCommunicationBridge.js +4 -1
  89. package/runtime/arcane/modules/ArcaneNavigationPolicy.js +8 -5
  90. package/runtime/arcane/modules/ArcaneNetworkPolicy.js +18 -15
  91. package/runtime/arcane/modules/AsyncBoundary.js +13 -10
  92. package/runtime/arcane/modules/BrowserTestSuite.js +19 -16
  93. package/runtime/arcane/modules/CalculatorEngine.js +5 -2
  94. package/runtime/arcane/modules/ChatRecords.js +18 -15
  95. package/runtime/arcane/modules/CommunicationAppController.js +9 -6
  96. package/runtime/arcane/modules/CommunicationHub.js +9 -6
  97. package/runtime/arcane/modules/CommunicationPreferences.js +4 -1
  98. package/runtime/arcane/modules/CommunicationProviderRegistry.js +4 -1
  99. package/runtime/arcane/modules/ComponentContracts.js +56 -53
  100. package/runtime/arcane/modules/ConfiguredAIChatSession.js +30 -27
  101. package/runtime/arcane/modules/ConversationActionItems.js +15 -12
  102. package/runtime/arcane/modules/ConversationClosingReport.js +11 -8
  103. package/runtime/arcane/modules/ConversationTimebox.js +28 -25
  104. package/runtime/arcane/modules/CoreLocalModelCatalog.js +17 -14
  105. package/runtime/arcane/modules/DBLS.js +7 -4
  106. package/runtime/arcane/modules/DBOPFS.js +7 -7
  107. package/runtime/arcane/modules/DBOPFSDocumentLibrary.js +34 -31
  108. package/runtime/arcane/modules/DevelopmentWorkspace.js +4 -1
  109. package/runtime/arcane/modules/DirectoryPicker.js +8 -5
  110. package/runtime/arcane/modules/DocumentLexicalSearch.js +11 -8
  111. package/runtime/arcane/modules/DocumentNavigation.js +7 -4
  112. package/runtime/arcane/modules/Errors.js +28 -25
  113. package/runtime/arcane/modules/HTMLImport.js +7 -4
  114. package/runtime/arcane/modules/IsolatedModelQuestionRunner.js +15 -12
  115. package/runtime/arcane/modules/LocalAIReadiness.js +21 -18
  116. package/runtime/arcane/modules/LocalAIReadinessController.js +6 -3
  117. package/runtime/arcane/modules/MD.js +1 -1
  118. package/runtime/arcane/modules/Mail.js +46 -43
  119. package/runtime/arcane/modules/MailOutbox.mjs +48 -45
  120. package/runtime/arcane/modules/MailTransport.mjs +29 -26
  121. package/runtime/arcane/modules/MemoryRecords.js +7 -4
  122. package/runtime/arcane/modules/MessageAdvisory.js +6 -3
  123. package/runtime/arcane/modules/ModelDefinition.js +7 -4
  124. package/runtime/arcane/modules/Ollama.js +6 -3
  125. package/runtime/arcane/modules/OllamaModelIdentifier.js +4 -1
  126. package/runtime/arcane/modules/OpenMeteoWeatherProvider.js +18 -15
  127. package/runtime/arcane/modules/PersistentAIChatSession.js +33 -30
  128. package/runtime/arcane/modules/PreferenceStore.js +20 -17
  129. package/runtime/arcane/modules/PreparedSpeech.js +485 -0
  130. package/runtime/arcane/modules/Questionnaire.js +6 -3
  131. package/runtime/arcane/modules/RecordLinkIndex.js +4 -1
  132. package/runtime/arcane/modules/RecordPassageIndex.js +9 -6
  133. package/runtime/arcane/modules/RecordReviewStore.js +12 -9
  134. package/runtime/arcane/modules/RiskSignalAnalyzer.js +4 -1
  135. package/runtime/arcane/modules/ScopedOPFSCache.js +6 -3
  136. package/runtime/arcane/modules/ScreenCapture.js +20 -17
  137. package/runtime/arcane/modules/SpeechPlayback.js +29 -26
  138. package/runtime/arcane/modules/StaticDocumentCatalog.js +33 -30
  139. package/runtime/arcane/modules/SystemAppearance.js +5 -2
  140. package/runtime/arcane/modules/SystemToolRegistry.js +6 -3
  141. package/runtime/arcane/modules/TerminalClient.js +14 -11
  142. package/runtime/arcane/modules/TerminalCommandRegistry.js +4 -1
  143. package/runtime/arcane/modules/ThemeBootstrap.js +9 -6
  144. package/runtime/arcane/modules/ThemeManager.js +5 -2
  145. package/runtime/arcane/modules/TimeGuard.js +1 -1
  146. package/runtime/arcane/modules/ToolCallRouter.js +11 -8
  147. package/runtime/arcane/modules/WaitForComponent.js +19 -16
  148. package/runtime/arcane/modules/YouTubeMedia.js +4 -1
  149. package/runtime/strong-type/index.js +1276 -352
  150. package/runtime/strong-type/package.json +69 -45
  151. package/src/app-descriptor.mjs +17 -14
  152. package/src/application-tests.mjs +4 -1
  153. package/src/cli/main.mjs +8 -5
  154. package/src/constants.mjs +4 -1
  155. package/src/dev-server.mjs +8 -5
  156. package/src/doctor.mjs +5 -2
  157. package/src/dom-event-instrumentation.mjs +24 -20
  158. package/src/errors.mjs +7 -3
  159. package/src/event-manager.mjs +111 -108
  160. package/src/event-queue.mjs +8 -5
  161. package/src/events.mjs +12 -9
  162. package/src/import-map.mjs +35 -27
  163. package/src/installed-sdk-runtime.mjs +4 -1
  164. package/src/integrated-provider-loader.mjs +9 -6
  165. package/src/mail-credentials.mjs +16 -13
  166. package/src/mail-server.mjs +53 -50
  167. package/src/mail.mjs +18 -15
  168. package/src/native-plan.mjs +12 -9
  169. package/src/native-provider-loader.mjs +7 -4
  170. package/src/packager/core.mjs +18 -15
  171. package/src/process.mjs +6 -3
  172. package/src/release-bundle.mjs +14 -11
  173. package/src/runtime.mjs +4 -1
  174. package/src/scaffold.mjs +9 -6
  175. package/src/sdk-browser-runtime.mjs +4 -1
  176. package/src/source-server.mjs +17 -14
  177. package/src/targets/index.mjs +5 -2
  178. package/src/templates/workspace-template.mjs +13 -6
  179. package/src/testing-loader.mjs +7 -4
  180. package/src/testing.mjs +10 -7
  181. package/src/toolchain.mjs +13 -10
  182. package/src/update-check.mjs +9 -6
  183. package/src/workspace-operation-lock.mjs +14 -11
  184. package/src/workspace-runtime.mjs +4 -1
  185. package/src/workspace.mjs +17 -14
  186. package/runtime/arcane/modules/AIResponseLength.js +0 -32
@@ -1,3 +1,6 @@
1
+ import Is from 'strong-type';
2
+ const is=new Is(false);
3
+
1
4
  import {AI_PREFERENCE_SLOT_KEYS} from './AIPreferenceTuple.js';
2
5
  import {
3
6
  getCoreLocalModelCatalog,
@@ -18,7 +21,7 @@ const LOCAL_SPEECH_PROVIDERS=new Set([
18
21
  ]);
19
22
 
20
23
  function token(value,{uppercase=false}={}){
21
- const normalized=typeof value==='string'
24
+ const normalized=is.string(value)
22
25
  ?value.trim()
23
26
  :'';
24
27
 
@@ -37,7 +40,7 @@ function completeResult(value){
37
40
  }
38
41
 
39
42
  function positiveSafeInteger(value){
40
- return Number.isSafeInteger(value)&&value>0
43
+ return is.safeInteger(value)&&value>0
41
44
  ?value
42
45
  :null;
43
46
  }
@@ -45,7 +48,7 @@ function positiveSafeInteger(value){
45
48
  function preferenceSlot(preferences,index){
46
49
  const value=preferences[index];
47
50
 
48
- if(typeof value!=='string'||!value.trim()){
51
+ if(!is.string(value)||!value.trim()){
49
52
  throw new TypeError(
50
53
  'The AI preference for '
51
54
  +AI_PREFERENCE_SLOT_KEYS[index]
@@ -62,7 +65,7 @@ function preferenceSlot(preferences,index){
62
65
  * tuple or choose a replacement provider.
63
66
  */
64
67
  export function deriveLocalAIRequirements(preferences){
65
- if(!Array.isArray(preferences)||preferences.length!==AI_PREFERENCE_SLOT_KEYS.length){
68
+ if(!is.array(preferences)||preferences.length!==AI_PREFERENCE_SLOT_KEYS.length){
66
69
  throw new TypeError(
67
70
  'AI preferences must contain exactly '
68
71
  +AI_PREFERENCE_SLOT_KEYS.length
@@ -138,7 +141,7 @@ function healthText(status,names){
138
141
  * aggregate ready proof.
139
142
  */
140
143
  export function evaluateLocalSpeechHealth(status){
141
- if(!status||typeof status!=='object'||Array.isArray(status)){
144
+ if(!status||!is.object(status)||is.array(status)){
142
145
  return completeResult({
143
146
  reachable:false,
144
147
  ready:false,
@@ -232,7 +235,7 @@ function normalizedTimeout(value){
232
235
  return DEFAULT_TIMEOUT_MS;
233
236
  }
234
237
 
235
- if(!Number.isSafeInteger(value)||value<1){
238
+ if(!is.safeInteger(value)||value<1){
236
239
  throw new RangeError('Local AI readiness timeout must be a positive integer.');
237
240
  }
238
241
 
@@ -240,13 +243,13 @@ function normalizedTimeout(value){
240
243
  }
241
244
 
242
245
  async function fetchJSON(fetchImpl,url,timeoutMs){
243
- if(typeof fetchImpl!=='function'){
246
+ if(!is.function(fetchImpl)){
244
247
  const error=new Error('Browser fetch is unavailable.');
245
248
  error.code='BROWSER_FETCH_UNAVAILABLE';
246
249
  throw error;
247
250
  }
248
251
 
249
- const controller=typeof AbortController==='function'
252
+ const controller=is.function(globalThis.AbortController)
250
253
  ?new AbortController()
251
254
  :null;
252
255
  let timer;
@@ -275,7 +278,7 @@ async function fetchJSON(fetchImpl,url,timeoutMs){
275
278
  throw error;
276
279
  }
277
280
 
278
- if(typeof response.text!=='function'){
281
+ if(!is.function(response.text)){
279
282
  const error=new Error('The local readiness endpoint returned an invalid response.');
280
283
  error.code='LOCAL_AI_HEALTH_INVALID_RESPONSE';
281
284
  throw error;
@@ -283,7 +286,7 @@ async function fetchJSON(fetchImpl,url,timeoutMs){
283
286
 
284
287
  const text=await response.text();
285
288
 
286
- if(typeof text!=='string'||!text){
289
+ if(!is.string(text)||!text){
287
290
  const error=new Error('The local readiness endpoint returned an invalid response.');
288
291
  error.code='LOCAL_AI_HEALTH_INVALID_RESPONSE';
289
292
  throw error;
@@ -299,7 +302,7 @@ async function fetchJSON(fetchImpl,url,timeoutMs){
299
302
  throw error;
300
303
  }
301
304
 
302
- if(!parsed||typeof parsed!=='object'||Array.isArray(parsed)){
305
+ if(!parsed||!is.object(parsed)||is.array(parsed)){
303
306
  const error=new Error('The local readiness endpoint returned an invalid record.');
304
307
  error.code='LOCAL_AI_HEALTH_INVALID_RESPONSE';
305
308
  throw error;
@@ -316,7 +319,7 @@ async function fetchJSON(fetchImpl,url,timeoutMs){
316
319
  }
317
320
 
318
321
  async function probeNativeOllama(arcane,selectedModel){
319
- if(typeof arcane?.localAI?.status!=='function'){
322
+ if(!is.function(arcane?.localAI?.status)){
320
323
  return completeResult({
321
324
  ready:false,
322
325
  model:null,
@@ -377,7 +380,7 @@ async function probeNativeOllama(arcane,selectedModel){
377
380
  }
378
381
 
379
382
  async function probeUserManagedLoopbackOllama(arcane,selectedModel){
380
- if(typeof arcane?.localAI?.status!=='function'){
383
+ if(!is.function(arcane?.localAI?.status)){
381
384
  return completeResult({
382
385
  ready:false,
383
386
  model:null,
@@ -457,7 +460,7 @@ function unavailableBrowserOllama(){
457
460
  }
458
461
 
459
462
  async function probeNativeSpeech(arcane){
460
- if(typeof arcane?.speech?.status!=='function'){
463
+ if(!is.function(arcane?.speech?.status)){
461
464
  return completeResult({
462
465
  ...evaluateLocalSpeechHealth(null),
463
466
  errorCode:'ARCANE_SPEECH_UNAVAILABLE'
@@ -506,7 +509,7 @@ async function resolveMode(runtime,arcane){
506
509
  return runtime;
507
510
  }
508
511
 
509
- if(runtime!==undefined&&(!runtime||typeof runtime!=='object')){
512
+ if(runtime!==undefined&&(!runtime||!is.object(runtime))){
510
513
  throw new TypeError(
511
514
  'Local AI runtime must be "native", "browser", '
512
515
  +'"user-managed-loopback", or a runtime snapshot.'
@@ -515,7 +518,7 @@ async function resolveMode(runtime,arcane){
515
518
 
516
519
  let snapshot=runtime;
517
520
 
518
- if(snapshot===undefined&&typeof arcane?.runtime?.current==='function'){
521
+ if(snapshot===undefined&&is.function(arcane?.runtime?.current)){
519
522
  try{
520
523
  snapshot=await arcane.runtime.current();
521
524
  }catch{
@@ -744,14 +747,14 @@ function guidance(mode,slots){
744
747
 
745
748
  function nativeRecovery(arcane,recover){
746
749
  if(recover!==undefined){
747
- if(typeof recover!=='function'){
750
+ if(!is.function(recover)){
748
751
  throw new TypeError('Local AI recovery must be a function.');
749
752
  }
750
753
 
751
754
  return recover;
752
755
  }
753
756
 
754
- return typeof arcane?.localAI?.recover==='function'
757
+ return is.function(arcane?.localAI?.recover)
755
758
  ?arcane.localAI.recover.bind(arcane.localAI)
756
759
  :null;
757
760
  }
@@ -1,3 +1,6 @@
1
+ import Is from 'strong-type';
2
+ const is=new Is(false);
3
+
1
4
  import { arcaneLogging } from 'arcane-os/logging';
2
5
  import {
3
6
  checkLocalAIReadiness,
@@ -36,7 +39,7 @@ function availabilityFromReport(report={}){
36
39
  }
37
40
 
38
41
  function selectedPreferences(source){
39
- return typeof source==='function'?source():source;
42
+ return is.function(source)?source():source;
40
43
  }
41
44
 
42
45
  function pendingReport(requirements){
@@ -172,7 +175,7 @@ export function createLocalAIReadinessController({
172
175
  if(destroyed){
173
176
  return;
174
177
  }
175
- if(typeof chat.setAIAvailability==='function'){
178
+ if(is.function(chat.setAIAvailability)){
176
179
  if(chatReadyListenerPending){
177
180
  chat.removeEventListener('chat-ready',publishAvailability);
178
181
  }
@@ -239,7 +242,7 @@ export function createLocalAIReadinessController({
239
242
  }
240
243
  status?.present?.(latestReport);
241
244
  applyAvailability(latestReport);
242
- if(typeof onChange==='function'){
245
+ if(is.function(onChange)){
243
246
  onChange(latestReport);
244
247
  }
245
248
  const availability=availabilityFromReport(latestReport);
@@ -1,6 +1,6 @@
1
1
  import { arcaneLogging } from 'arcane-os/logging';
2
2
  import { marked } from './Marked.min.js';
3
- import Is from '../../node_modules/strong-type/index.js';
3
+ import Is from 'strong-type';
4
4
 
5
5
  marked.use(
6
6
  {
@@ -1,3 +1,6 @@
1
+ import Is from 'strong-type';
2
+ const is=new Is(false);
3
+
1
4
  import {createArcaneEventSource} from 'arcane-os/event-manager';
2
5
  import MailOutbox from './MailOutbox.mjs';
3
6
  import {
@@ -44,10 +47,10 @@ function codedError(message,code,ErrorType=Error){
44
47
 
45
48
  function validateMailSignal(signal){
46
49
  if(signal===null||signal===undefined) return null;
47
- if(typeof signal!=='object'
48
- ||typeof signal.aborted!=='boolean'
49
- ||typeof signal.addEventListener!=='function'
50
- ||typeof signal.removeEventListener!=='function'){
50
+ if(!is.object(signal)
51
+ ||!is.boolean(signal.aborted)
52
+ ||!is.function(signal.addEventListener)
53
+ ||!is.function(signal.removeEventListener)){
51
54
  throw new TypeError('Mail signal must be an AbortSignal');
52
55
  }
53
56
  return signal;
@@ -122,14 +125,14 @@ function waitForMailOperation(operation,signal){
122
125
  }
123
126
 
124
127
  function exactNativeMailResult(value){
125
- if(!value||typeof value!=='object'||Array.isArray(value)
126
- ||typeof value.requestId!=='string'
128
+ if(!value||!is.object(value)||is.array(value)
129
+ ||!is.string(value.requestId)
127
130
  ||!NATIVE_MAIL_REQUEST_ID_PATTERN.test(value.requestId)
128
131
  ||!Object.hasOwn(NATIVE_MAIL_RESPONSE_STATUS_CODES,value.status)
129
132
  ||value.statusCode!==NATIVE_MAIL_RESPONSE_STATUS_CODES[value.status]
130
- ||typeof value.sent!=='boolean'
131
- ||typeof value.partial!=='boolean'
132
- ||typeof value.uncertain!=='boolean'
133
+ ||!is.boolean(value.sent)
134
+ ||!is.boolean(value.partial)
135
+ ||!is.boolean(value.uncertain)
133
136
  ||value.sent!==(value.status==='accepted')
134
137
  ||value.partial!==(value.status==='partially_accepted')
135
138
  ||value.uncertain!==(value.status==='delivery_uncertain')){
@@ -150,7 +153,7 @@ function normalizedNativeMailError(error){
150
153
  const retryable=uncertain||NATIVE_MAIL_RETRYABLE_ERROR_CODES.has(error?.code);
151
154
  if(!retryable) return error;
152
155
  const normalized=new Error(
153
- typeof error?.message==='string'&&error.message
156
+ is.string(error?.message)&&error.message
154
157
  ?error.message
155
158
  :uncertain
156
159
  ?'Native Arcane mail delivery has an uncertain outcome.'
@@ -162,7 +165,7 @@ function normalizedNativeMailError(error){
162
165
  normalized.retryable=true;
163
166
  normalized.uncertain=uncertain;
164
167
  const statusCode=Number(error?.statusCode??error?.status);
165
- if(Number.isSafeInteger(statusCode)&&statusCode>=100&&statusCode<=599){
168
+ if(is.safeInteger(statusCode)&&statusCode>=100&&statusCode<=599){
166
169
  normalized.statusCode=statusCode;
167
170
  }
168
171
  return normalized;
@@ -178,7 +181,7 @@ async function loadRequiredMailStorage(){
178
181
  );
179
182
  }
180
183
  const storage=globalThis.dbopfs;
181
- if(!storage||typeof storage!=='object'){
184
+ if(!storage||!is.object(storage)){
182
185
  throw codedError(
183
186
  'Mail outbox storage is unavailable.',
184
187
  'MAIL_OUTBOX_STORAGE_UNAVAILABLE'
@@ -245,17 +248,17 @@ export function resolveMailConfig(
245
248
  config=globalThis.arcane?.config?.mail||{},
246
249
  {document=globalThis.document,location=globalThis.location}={}
247
250
  ){
248
- const supplied=config&&typeof config==='object'&&!Array.isArray(config)?config:{};
249
- const appName=typeof supplied.appName==='string'&&supplied.appName.trim()
251
+ const supplied=config&&is.object(config)&&!is.array(config)?config:{};
252
+ const appName=is.string(supplied.appName)&&supplied.appName.trim()
250
253
  ? supplied.appName.trim()
251
254
  : declaredApplicationId(document);
252
255
  return completeResult({
253
256
  appName:ARCANE_APP_ID_PATTERN.test(appName) ? appName:'',
254
- appKey:typeof supplied.appKey==='string' ? supplied.appKey:'',
255
- endpoint:typeof supplied.endpoint==='string'&&supplied.endpoint.trim()
257
+ appKey:is.string(supplied.appKey) ? supplied.appKey:'',
258
+ endpoint:is.string(supplied.endpoint)&&supplied.endpoint.trim()
256
259
  ? supplied.endpoint.trim()
257
260
  : defaultMailEndpoint(location,supplied.baseDomain||declaredMailBaseDomain(document)),
258
- requestTimeout:Number.isFinite(supplied.requestTimeout)
261
+ requestTimeout:is.finite(supplied.requestTimeout)
259
262
  ? supplied.requestTimeout
260
263
  : null,
261
264
  });
@@ -271,7 +274,7 @@ function escapeHtml(value){
271
274
  }
272
275
 
273
276
  function clonePayload(value){
274
- if(typeof globalThis.structuredClone==='function'){
277
+ if(is.function(globalThis.structuredClone)){
275
278
  return globalThis.structuredClone(value);
276
279
  }
277
280
  return JSON.parse(JSON.stringify(value));
@@ -294,7 +297,7 @@ function serializePayload(value){
294
297
  let reportNonceSequence=0;
295
298
 
296
299
  function randomReportNonce(cryptoProvider){
297
- if(typeof cryptoProvider?.randomUUID==='function'){
300
+ if(is.function(cryptoProvider?.randomUUID)){
298
301
  try{
299
302
  const value=cryptoProvider.randomUUID();
300
303
  if(/^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(value)){
@@ -311,12 +314,12 @@ function createReportKey(timestamp,cryptoProvider){
311
314
  }
312
315
 
313
316
  function normalizeRecipients(values){
314
- if(!Array.isArray(values)){
317
+ if(!is.array(values)){
315
318
  throw new TypeError('Mail recipients must be an array');
316
319
  }
317
320
  const recipients=[];
318
321
  for(const value of values){
319
- if(typeof value!=='string'){
322
+ if(!is.string(value)){
320
323
  throw new TypeError('Every mail recipient must be an email address');
321
324
  }
322
325
  const address=value.trim().toLowerCase();
@@ -331,17 +334,17 @@ function normalizeRecipients(values){
331
334
  function profileValue(user,key){
332
335
  try{
333
336
  const value=user?.[key];
334
- return typeof value==='string'?value:'';
337
+ return is.string(value)?value:'';
335
338
  }catch{
336
339
  return '';
337
340
  }
338
341
  }
339
342
 
340
343
  function normalizedDeliveryResult(value){
341
- if(!value||typeof value!=='object'||Array.isArray(value)) return value;
342
- let status=typeof value.status==='string'?value.status:'';
344
+ if(!value||!is.object(value)||is.array(value)) return value;
345
+ let status=is.string(value.status)?value.status:'';
343
346
  if(!status&&value.sent===true) status='accepted';
344
- let classification=typeof value.classification==='string'?value.classification:'';
347
+ let classification=is.string(value.classification)?value.classification:'';
345
348
  if(!classification){
346
349
  classification={
347
350
  accepted:'accepted',
@@ -352,9 +355,9 @@ function normalizedDeliveryResult(value){
352
355
  }[status]||'';
353
356
  }
354
357
  const retryAfterMilliseconds=Number(value.retryAfterMs);
355
- const retryAfterSeconds=Number.isSafeInteger(value.retryAfterSeconds)
358
+ const retryAfterSeconds=is.safeInteger(value.retryAfterSeconds)
356
359
  ? value.retryAfterSeconds
357
- : Number.isFinite(retryAfterMilliseconds)&&retryAfterMilliseconds>0
360
+ : is.finite(retryAfterMilliseconds)&&retryAfterMilliseconds>0
358
361
  ? Math.ceil(retryAfterMilliseconds/1000)
359
362
  : null;
360
363
  return {
@@ -390,7 +393,7 @@ function safeDrainDetail(summary){
390
393
  }
391
394
 
392
395
  function normalizeMailOptions(options){
393
- if(!options||typeof options!=='object'||Array.isArray(options)){
396
+ if(!options||!is.object(options)||is.array(options)){
394
397
  throw new TypeError('Mail options must be an object');
395
398
  }
396
399
  const deliver=options.deliver;
@@ -399,29 +402,29 @@ function normalizeMailOptions(options){
399
402
  return globalThis.navigator?.onLine!==false;
400
403
  };
401
404
  const onlineTarget=options.onlineTarget===undefined
402
- ?typeof globalThis.addEventListener==='function'?globalThis:null
405
+ ?is.function(globalThis.addEventListener)?globalThis:null
403
406
  :options.onlineTarget;
404
407
  const includeContext=options.includeContext??false;
405
408
  const cryptoProvider=Object.hasOwn(options,'crypto')
406
409
  ?options.crypto
407
410
  :globalThis.crypto;
408
- if(deliver!==undefined&&typeof deliver!=='function'){
411
+ if(deliver!==undefined&&!is.function(deliver)){
409
412
  throw new TypeError('Mail deliver must be a function');
410
413
  }
411
- if(typeof clock!=='function'||typeof isOnline!=='function'){
414
+ if(!is.function(clock)||!is.function(isOnline)){
412
415
  throw new TypeError('Mail clock and isOnline hooks must be functions');
413
416
  }
414
417
  if(onlineTarget!==null&&(
415
- typeof onlineTarget?.addEventListener!=='function'
416
- ||typeof onlineTarget?.removeEventListener!=='function'
418
+ !is.function(onlineTarget?.addEventListener)
419
+ ||!is.function(onlineTarget?.removeEventListener)
417
420
  )){
418
421
  throw new TypeError('Mail onlineTarget must be an EventTarget');
419
422
  }
420
- if(typeof includeContext!=='boolean'){
423
+ if(!is.boolean(includeContext)){
421
424
  throw new TypeError('Mail includeContext must be boolean');
422
425
  }
423
426
  if(cryptoProvider!==null&&cryptoProvider!==undefined
424
- &&typeof cryptoProvider!=='object'){
427
+ &&!is.object(cryptoProvider)){
425
428
  throw new TypeError('Mail crypto must be a Web Crypto provider');
426
429
  }
427
430
  return {
@@ -533,7 +536,7 @@ class Mail {
533
536
 
534
537
  #assertConfigured(){
535
538
  const hasTransport=this.#deliver!==undefined
536
- ||typeof globalThis.Arcane?.mail?.send==='function'
539
+ ||is.function(globalThis.Arcane?.mail?.send)
537
540
  ||Boolean(this.endpoint);
538
541
  if(!this.appName||!hasTransport){
539
542
  throw codedError('Mail transport is not configured.','MAIL_NOT_CONFIGURED');
@@ -550,8 +553,8 @@ class Mail {
550
553
  #time(){
551
554
  const value=this.#clock();
552
555
  const milliseconds=value instanceof Date?value.getTime():Number(value);
553
- if(!Number.isSafeInteger(milliseconds)||milliseconds<0
554
- ||Number.isNaN(new Date(milliseconds).getTime())){
556
+ if(!is.safeInteger(milliseconds)||milliseconds<0
557
+ ||is.NaN(new Date(milliseconds).getTime())){
555
558
  throw codedError('Mail clock returned an invalid time.','MAIL_CLOCK_INVALID');
556
559
  }
557
560
  return milliseconds;
@@ -811,20 +814,20 @@ class Mail {
811
814
 
812
815
  async send(to=[], subject='', payload={}, messageStyle='', messageType='') {
813
816
  this.#assertActive();
814
- if(typeof subject!=='string'){
817
+ if(!is.string(subject)){
815
818
  throw new TypeError('Mail subject must be a string');
816
819
  }
817
820
  const normalizedSubject=subject;
818
- if(!payload||typeof payload!=='object'||Array.isArray(payload)){
821
+ if(!payload||!is.object(payload)||is.array(payload)){
819
822
  throw new TypeError('Mail payload must be an object');
820
823
  }
821
- if(typeof messageStyle!=='string'){
824
+ if(!is.string(messageStyle)){
822
825
  throw new TypeError('Mail message style must be a string');
823
826
  }
824
- if(!Array.isArray(to)){
827
+ if(!is.array(to)){
825
828
  throw new TypeError('Mail recipients must be an array');
826
829
  }
827
- if(typeof messageType!=='string'||!MAIL_TYPES.has(messageType)){
830
+ if(!is.string(messageType)||!MAIL_TYPES.has(messageType)){
828
831
  throw new TypeError('Mail type must be error, report, or crisis_detected');
829
832
  }
830
833