arcane-os 0.5.17 → 0.5.18

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 (183) hide show
  1. package/CHANGELOG.md +24 -0
  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 +127 -124
  7. package/browser-runtime/ai/browser-wasm.mjs +6 -3
  8. package/browser-runtime/ai/browser-wllama-runtime.mjs +26 -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/reference/ai/browser-speech.md +198 -5
  18. package/docs/reference/availability-and-normalization.md +40 -0
  19. package/docs/reference/inventory/package-api.json +1 -1
  20. package/docs/reference/inventory/runtime-components.json +1 -1
  21. package/docs/reference/inventory/runtime-modules.json +20 -24
  22. package/docs/reference/protocols.md +1 -1
  23. package/docs/reference/runtime-modules.md +125 -35
  24. package/docs/reference/sdk-api.md +23 -7
  25. package/examples/wasm-ai-demo/index.html +1 -0
  26. package/node_modules/event-pubsub/node_modules/strong-type/README.md +408 -0
  27. package/node_modules/event-pubsub/node_modules/strong-type/assets/strong-type-header.png +0 -0
  28. package/node_modules/event-pubsub/node_modules/strong-type/index.js +1151 -0
  29. package/node_modules/event-pubsub/node_modules/strong-type/licence +21 -0
  30. package/node_modules/event-pubsub/node_modules/strong-type/node.js +125 -0
  31. package/node_modules/event-pubsub/node_modules/strong-type/package.json +61 -0
  32. package/node_modules/strong-type/README.md +42 -24
  33. package/node_modules/strong-type/benchmark/core.js +324 -0
  34. package/node_modules/strong-type/index.js +207 -82
  35. package/node_modules/strong-type/package.json +14 -6
  36. package/package.json +3 -3
  37. package/runtime/arcane/components/app-bar.html +5 -2
  38. package/runtime/arcane/components/assistant-panel.html +5 -2
  39. package/runtime/arcane/components/chart.html +15 -12
  40. package/runtime/arcane/components/chat.html +117 -138
  41. package/runtime/arcane/components/dashboard-config.html +6 -3
  42. package/runtime/arcane/components/data-view.html +4 -1
  43. package/runtime/arcane/components/directory-picker.html +5 -2
  44. package/runtime/arcane/components/document-inspector.html +6 -3
  45. package/runtime/arcane/components/file-drop.html +5 -2
  46. package/runtime/arcane/components/file-inspector.html +9 -6
  47. package/runtime/arcane/components/file-manager.html +16 -13
  48. package/runtime/arcane/components/local-ai-status.html +5 -2
  49. package/runtime/arcane/components/markdown-document.html +14 -11
  50. package/runtime/arcane/components/markdown-editor.html +4 -1
  51. package/runtime/arcane/components/modal.html +4 -1
  52. package/runtime/arcane/components/output-panel.html +6 -3
  53. package/runtime/arcane/components/preferences-form.html +4 -1
  54. package/runtime/arcane/components/record-timeline.html +8 -5
  55. package/runtime/arcane/components/relationship-board.html +9 -6
  56. package/runtime/arcane/components/source-code-viewer.html +12 -9
  57. package/runtime/arcane/components/source-explanation.html +8 -5
  58. package/runtime/arcane/components/speech.html +24 -21
  59. package/runtime/arcane/components/summary-strip.html +4 -1
  60. package/runtime/arcane/components/task-progress.html +6 -3
  61. package/runtime/arcane/components/terminal-workspace.html +4 -1
  62. package/runtime/arcane/components/voice-transcription.html +11 -8
  63. package/runtime/arcane/components/web-navigator.html +5 -2
  64. package/runtime/arcane/entities/ApiModelRecord.js +5 -2
  65. package/runtime/arcane/entities/Calculation.js +5 -2
  66. package/runtime/arcane/entities/Chat.js +33 -33
  67. package/runtime/arcane/entities/CommunicationMessage.js +4 -1
  68. package/runtime/arcane/entities/CommunicationThread.js +4 -1
  69. package/runtime/arcane/entities/File.js +1 -1
  70. package/runtime/arcane/entities/Image.js +8 -5
  71. package/runtime/arcane/entities/IntentEnvelope.js +20 -17
  72. package/runtime/arcane/entities/Preference.js +9 -6
  73. package/runtime/arcane/entities/Theme.js +5 -2
  74. package/runtime/arcane/entities/User.js +11 -11
  75. package/runtime/arcane/entities/Weather.js +5 -2
  76. package/runtime/arcane/modules/AI.js +491 -165
  77. package/runtime/arcane/modules/AIPreferenceRuntime.js +5 -2
  78. package/runtime/arcane/modules/AIPreferenceTuple.js +9 -6
  79. package/runtime/arcane/modules/AIProviderRuntime.js +88 -85
  80. package/runtime/arcane/modules/AIResponseURLPolicy.js +148 -62
  81. package/runtime/arcane/modules/AIRuntimeState.js +29 -26
  82. package/runtime/arcane/modules/AnsiText.js +4 -1
  83. package/runtime/arcane/modules/ApiModelDatabase.js +22 -19
  84. package/runtime/arcane/modules/AppDataScope.js +8 -5
  85. package/runtime/arcane/modules/ArcaneCommunicationBridge.js +4 -1
  86. package/runtime/arcane/modules/ArcaneNavigationPolicy.js +8 -5
  87. package/runtime/arcane/modules/ArcaneNetworkPolicy.js +18 -15
  88. package/runtime/arcane/modules/AsyncBoundary.js +13 -10
  89. package/runtime/arcane/modules/BrowserTestSuite.js +19 -16
  90. package/runtime/arcane/modules/CalculatorEngine.js +5 -2
  91. package/runtime/arcane/modules/ChatRecords.js +18 -15
  92. package/runtime/arcane/modules/CommunicationAppController.js +9 -6
  93. package/runtime/arcane/modules/CommunicationHub.js +9 -6
  94. package/runtime/arcane/modules/CommunicationPreferences.js +4 -1
  95. package/runtime/arcane/modules/CommunicationProviderRegistry.js +4 -1
  96. package/runtime/arcane/modules/ComponentContracts.js +56 -53
  97. package/runtime/arcane/modules/ConfiguredAIChatSession.js +30 -27
  98. package/runtime/arcane/modules/ConversationActionItems.js +15 -12
  99. package/runtime/arcane/modules/ConversationClosingReport.js +11 -8
  100. package/runtime/arcane/modules/ConversationTimebox.js +28 -25
  101. package/runtime/arcane/modules/CoreLocalModelCatalog.js +17 -14
  102. package/runtime/arcane/modules/DBLS.js +7 -4
  103. package/runtime/arcane/modules/DBOPFS.js +7 -7
  104. package/runtime/arcane/modules/DBOPFSDocumentLibrary.js +34 -31
  105. package/runtime/arcane/modules/DevelopmentWorkspace.js +4 -1
  106. package/runtime/arcane/modules/DirectoryPicker.js +8 -5
  107. package/runtime/arcane/modules/DocumentLexicalSearch.js +11 -8
  108. package/runtime/arcane/modules/DocumentNavigation.js +7 -4
  109. package/runtime/arcane/modules/Errors.js +28 -25
  110. package/runtime/arcane/modules/HTMLImport.js +7 -4
  111. package/runtime/arcane/modules/IsolatedModelQuestionRunner.js +15 -12
  112. package/runtime/arcane/modules/LocalAIReadiness.js +21 -18
  113. package/runtime/arcane/modules/LocalAIReadinessController.js +6 -3
  114. package/runtime/arcane/modules/MD.js +1 -1
  115. package/runtime/arcane/modules/Mail.js +46 -43
  116. package/runtime/arcane/modules/MailOutbox.mjs +48 -45
  117. package/runtime/arcane/modules/MailTransport.mjs +29 -26
  118. package/runtime/arcane/modules/MemoryRecords.js +7 -4
  119. package/runtime/arcane/modules/MessageAdvisory.js +6 -3
  120. package/runtime/arcane/modules/ModelDefinition.js +7 -4
  121. package/runtime/arcane/modules/Ollama.js +6 -3
  122. package/runtime/arcane/modules/OllamaModelIdentifier.js +4 -1
  123. package/runtime/arcane/modules/OpenMeteoWeatherProvider.js +18 -15
  124. package/runtime/arcane/modules/PersistentAIChatSession.js +33 -30
  125. package/runtime/arcane/modules/PreferenceStore.js +20 -17
  126. package/runtime/arcane/modules/PreparedSpeech.js +485 -0
  127. package/runtime/arcane/modules/Questionnaire.js +6 -3
  128. package/runtime/arcane/modules/RecordLinkIndex.js +4 -1
  129. package/runtime/arcane/modules/RecordPassageIndex.js +9 -6
  130. package/runtime/arcane/modules/RecordReviewStore.js +12 -9
  131. package/runtime/arcane/modules/RiskSignalAnalyzer.js +4 -1
  132. package/runtime/arcane/modules/ScopedOPFSCache.js +6 -3
  133. package/runtime/arcane/modules/ScreenCapture.js +20 -17
  134. package/runtime/arcane/modules/SpeechPlayback.js +29 -26
  135. package/runtime/arcane/modules/StaticDocumentCatalog.js +33 -30
  136. package/runtime/arcane/modules/SystemAppearance.js +5 -2
  137. package/runtime/arcane/modules/SystemToolRegistry.js +6 -3
  138. package/runtime/arcane/modules/TerminalClient.js +14 -11
  139. package/runtime/arcane/modules/TerminalCommandRegistry.js +4 -1
  140. package/runtime/arcane/modules/ThemeBootstrap.js +9 -6
  141. package/runtime/arcane/modules/ThemeManager.js +5 -2
  142. package/runtime/arcane/modules/TimeGuard.js +1 -1
  143. package/runtime/arcane/modules/ToolCallRouter.js +11 -8
  144. package/runtime/arcane/modules/WaitForComponent.js +19 -16
  145. package/runtime/arcane/modules/YouTubeMedia.js +4 -1
  146. package/runtime/strong-type/index.js +1276 -352
  147. package/runtime/strong-type/package.json +69 -45
  148. package/src/app-descriptor.mjs +17 -14
  149. package/src/application-tests.mjs +4 -1
  150. package/src/cli/main.mjs +8 -5
  151. package/src/constants.mjs +4 -1
  152. package/src/dev-server.mjs +8 -5
  153. package/src/doctor.mjs +5 -2
  154. package/src/dom-event-instrumentation.mjs +24 -20
  155. package/src/errors.mjs +7 -3
  156. package/src/event-manager.mjs +111 -108
  157. package/src/event-queue.mjs +8 -5
  158. package/src/events.mjs +12 -9
  159. package/src/import-map.mjs +35 -27
  160. package/src/installed-sdk-runtime.mjs +4 -1
  161. package/src/integrated-provider-loader.mjs +9 -6
  162. package/src/mail-credentials.mjs +16 -13
  163. package/src/mail-server.mjs +53 -50
  164. package/src/mail.mjs +18 -15
  165. package/src/native-plan.mjs +12 -9
  166. package/src/native-provider-loader.mjs +7 -4
  167. package/src/packager/core.mjs +18 -15
  168. package/src/process.mjs +6 -3
  169. package/src/release-bundle.mjs +14 -11
  170. package/src/runtime.mjs +4 -1
  171. package/src/scaffold.mjs +9 -6
  172. package/src/sdk-browser-runtime.mjs +4 -1
  173. package/src/source-server.mjs +17 -14
  174. package/src/targets/index.mjs +5 -2
  175. package/src/templates/workspace-template.mjs +13 -6
  176. package/src/testing-loader.mjs +7 -4
  177. package/src/testing.mjs +10 -7
  178. package/src/toolchain.mjs +13 -10
  179. package/src/update-check.mjs +9 -6
  180. package/src/workspace-operation-lock.mjs +14 -11
  181. package/src/workspace-runtime.mjs +4 -1
  182. package/src/workspace.mjs +17 -14
  183. package/runtime/arcane/modules/AIResponseLength.js +0 -32
@@ -1,6 +1,9 @@
1
+ import Is from 'strong-type';
1
2
  import {createHash,randomUUID,timingSafeEqual} from 'node:crypto';
2
3
  import http from 'node:http';
3
4
 
5
+ const is = new Is(false);
6
+
4
7
  export const RESEND_MAIL_SERVER_PROTOCOL='arcane-resend-mail-gateway/1';
5
8
  export const RESEND_MAIL_PATH='/v1/mail';
6
9
 
@@ -41,22 +44,22 @@ function configurationError(message){
41
44
  }
42
45
 
43
46
  function completeErrorDetails(error){
44
- if(!error||typeof error!=='object'){
47
+ if(!error||!is.object(error)){
45
48
  return {message:String(error??''),name:'Error'};
46
49
  }
47
50
  return {
48
51
  ...error,
49
- ...(typeof error.code==='string'?{code:error.code}:{}),
50
- message:typeof error.message==='string'?error.message:String(error),
51
- name:typeof error.name==='string'?error.name:'Error',
52
- ...(typeof error.stack==='string'?{stack:error.stack}:{})
52
+ ...(is.string(error.code)?{code:error.code}:{}),
53
+ message:is.string(error.message)?error.message:String(error),
54
+ name:is.string(error.name)?error.name:'Error',
55
+ ...(is.string(error.stack)?{stack:error.stack}:{})
53
56
  };
54
57
  }
55
58
 
56
59
  function positiveInteger(value,fallback,{label,allowZero=false}={}){
57
60
  const resolved=value===undefined?fallback:value;
58
61
  const minimum=allowZero?0:1;
59
- if(!Number.isSafeInteger(resolved)||resolved<minimum){
62
+ if(!is.safeInteger(resolved)||resolved<minimum){
60
63
  throw configurationError(`${label} must be ${allowZero?'a nonnegative':'a positive'} integer.`);
61
64
  }
62
65
  return resolved;
@@ -64,7 +67,7 @@ function positiveInteger(value,fallback,{label,allowZero=false}={}){
64
67
 
65
68
  function optionalTimerDelay(value,label){
66
69
  if(value===undefined||value===null) return null;
67
- if(!Number.isSafeInteger(value)||value<1||value>MAX_NODE_TIMER_DELAY_MS){
70
+ if(!is.safeInteger(value)||value<1||value>MAX_NODE_TIMER_DELAY_MS){
68
71
  throw configurationError(
69
72
  `${label} must be an integer from 1 through ${MAX_NODE_TIMER_DELAY_MS} `
70
73
  +'milliseconds, the Node timer range.'
@@ -74,12 +77,12 @@ function optionalTimerDelay(value,label){
74
77
  }
75
78
 
76
79
  function normalizeRetryAfter(value){
77
- return Number.isSafeInteger(value)&&value>0?value:0;
80
+ return is.safeInteger(value)&&value>0?value:0;
78
81
  }
79
82
 
80
83
  function portNumber(value,fallback){
81
84
  const resolved=value===undefined?fallback:value;
82
- if(!Number.isSafeInteger(resolved)||resolved<0||resolved>65_535){
85
+ if(!is.safeInteger(resolved)||resolved<0||resolved>65_535){
83
86
  throw configurationError('port must be an integer between 0 and 65535.');
84
87
  }
85
88
  return resolved;
@@ -93,14 +96,14 @@ function validateSignal(signal){
93
96
  }
94
97
 
95
98
  function validateApiKey(value){
96
- if(typeof value!=='string'||value.length<1||!/^[\x21-\x7e]+$/u.test(value)){
99
+ if(!is.string(value)||value.length<1||!/^[\x21-\x7e]+$/u.test(value)){
97
100
  throw configurationError('Resend API key must be a nonempty printable ASCII string.');
98
101
  }
99
102
  return value;
100
103
  }
101
104
 
102
105
  function validateAppId(value){
103
- if(typeof value!=='string'||!APP_ID_PATTERN.test(value)){
106
+ if(!is.string(value)||!APP_ID_PATTERN.test(value)){
104
107
  throw configurationError('Mail application identity is invalid.');
105
108
  }
106
109
  return value;
@@ -112,7 +115,7 @@ function appKeyDigest(value){
112
115
 
113
116
  function normalizeCallerAuthentication(options){
114
117
  const allowUnauthenticatedCaller=options.allowUnauthenticatedCaller??false;
115
- if(typeof allowUnauthenticatedCaller!=='boolean'){
118
+ if(!is.boolean(allowUnauthenticatedCaller)){
116
119
  throw configurationError('allowUnauthenticatedCaller must be a boolean.');
117
120
  }
118
121
  if(allowUnauthenticatedCaller){
@@ -126,7 +129,7 @@ function normalizeCallerAuthentication(options){
126
129
  callerAuthentication:'origin-app-id-only'
127
130
  };
128
131
  }
129
- if(typeof options.appKey!=='string'||!/^[\x21-\x7e]+$/u.test(options.appKey)){
132
+ if(!is.string(options.appKey)||!/^[\x21-\x7e]+$/u.test(options.appKey)){
130
133
  throw configurationError(
131
134
  'appKey must be a nonempty printable ASCII string unless unauthenticated caller mode is explicitly enabled.'
132
135
  );
@@ -138,7 +141,7 @@ function normalizeCallerAuthentication(options){
138
141
  }
139
142
 
140
143
  function normalizedEmail(value,label){
141
- if(typeof value!=='string'){
144
+ if(!is.string(value)){
142
145
  throw configurationError(`${label} must be an email address.`);
143
146
  }
144
147
  const normalized=value.trim().toLowerCase();
@@ -149,7 +152,7 @@ function normalizedEmail(value,label){
149
152
  }
150
153
 
151
154
  function validateFrom(value){
152
- if(typeof value!=='string'||value!==value.trim()||value.length<3||value.length>320
155
+ if(!is.string(value)||value!==value.trim()||value.length<3||value.length>320
153
156
  ||/[\u0000-\u001f\u007f]/u.test(value)){
154
157
  throw configurationError('Mail sender is invalid.');
155
158
  }
@@ -165,7 +168,7 @@ function validateFrom(value){
165
168
  }
166
169
 
167
170
  function normalizeEmailList(value,label,{allowEmpty=false}={}){
168
- if(!Array.isArray(value)||(!allowEmpty&&value.length===0)){
171
+ if(!is.array(value)||(!allowEmpty&&value.length===0)){
169
172
  throw configurationError(`${label} must contain ${allowEmpty?'zero or more':'one or more'} addresses.`);
170
173
  }
171
174
  const result=[];
@@ -177,7 +180,7 @@ function normalizeEmailList(value,label,{allowEmpty=false}={}){
177
180
  }
178
181
 
179
182
  function normalizeOrigin(value){
180
- if(typeof value!=='string'||!value.trim()){
183
+ if(!is.string(value)||!value.trim()){
181
184
  throw configurationError('Every allowed mail origin must be a URL origin.');
182
185
  }
183
186
  let url;
@@ -194,7 +197,7 @@ function normalizeOrigin(value){
194
197
  }
195
198
 
196
199
  function normalizeOrigins(value){
197
- if(!Array.isArray(value)||value.length===0){
200
+ if(!is.array(value)||value.length===0){
198
201
  throw configurationError('allowedOrigins must contain one or more exact origins.');
199
202
  }
200
203
  const origins=[];
@@ -218,7 +221,7 @@ function validateLoopbackHost(value){
218
221
  }
219
222
 
220
223
  function normalizeConfiguration(options={}){
221
- if(!options||typeof options!=='object'||Array.isArray(options)){
224
+ if(!options||!is.object(options)||is.array(options)){
222
225
  throw configurationError('Mail server options must be an object.');
223
226
  }
224
227
  const callerAuthentication=normalizeCallerAuthentication(options);
@@ -239,13 +242,13 @@ function normalizeConfiguration(options={}){
239
242
  throw configurationError('Every error recipient must also be in recipientAllowlist.');
240
243
  }
241
244
  const fetchImpl=options.fetchImpl??globalThis.fetch;
242
- if(typeof fetchImpl!=='function'){
245
+ if(!is.function(fetchImpl)){
243
246
  throw configurationError('A fetch implementation is required for Resend delivery.');
244
247
  }
245
- if(options.onEvent!==undefined&&typeof options.onEvent!=='function'){
248
+ if(options.onEvent!==undefined&&!is.function(options.onEvent)){
246
249
  throw configurationError('onEvent must be a function when supplied.');
247
250
  }
248
- if(options.requestIdFactory!==undefined&&typeof options.requestIdFactory!=='function'){
251
+ if(options.requestIdFactory!==undefined&&!is.function(options.requestIdFactory)){
249
252
  throw configurationError('requestIdFactory must be a function when supplied.');
250
253
  }
251
254
  return {
@@ -277,7 +280,7 @@ function normalizeConfiguration(options={}){
277
280
  function createRequestId(factory){
278
281
  try{
279
282
  const candidate=factory();
280
- if(typeof candidate==='string'&&REQUEST_ID_PATTERN.test(candidate)){
283
+ if(is.string(candidate)&&REQUEST_ID_PATTERN.test(candidate)){
281
284
  return candidate;
282
285
  }
283
286
  }catch{
@@ -292,7 +295,7 @@ function isNumericLoopback(value){
292
295
 
293
296
  function headerValues(request,name){
294
297
  const distinct=request.headersDistinct?.[name];
295
- if(Array.isArray(distinct)){
298
+ if(is.array(distinct)){
296
299
  return distinct.map(function stringifyDistinctHeader(value){return String(value);});
297
300
  }
298
301
  const values=[];
@@ -308,7 +311,7 @@ function headerValues(request,name){
308
311
  if(fallback===undefined){
309
312
  return [];
310
313
  }
311
- return Array.isArray(fallback)?fallback.map(String):[String(fallback)];
314
+ return is.array(fallback)?fallback.map(String):[String(fallback)];
312
315
  }
313
316
 
314
317
  function singleHeader(request,name,{required=true}={}){
@@ -340,7 +343,7 @@ function validateLoopbackRequest(request){
340
343
  const hostLiteral=hostname.includes(':')?`[${hostname}]`:hostname;
341
344
  const expectedAuthority=localPort===80?hostLiteral:`${hostLiteral}:${String(localPort)}`;
342
345
  if(parsed.username||parsed.password||parsed.pathname!=='/'||parsed.search||parsed.hash
343
- ||!isNumericLoopback(hostname)||!Number.isSafeInteger(localPort)
346
+ ||!isNumericLoopback(hostname)||!is.safeInteger(localPort)
344
347
  ||statedPort!==localPort||rawHost!==expectedAuthority){
345
348
  throw new MailGatewayFault('mail_invalid_host',{statusCode:421});
346
349
  }
@@ -483,7 +486,7 @@ function createObserver(onEvent){
483
486
  }catch{
484
487
  return;
485
488
  }
486
- if(!result||typeof result.then!=='function'){
489
+ if(!result||!is.function(result.then)){
487
490
  return;
488
491
  }
489
492
  const task=Promise.resolve(result);
@@ -577,7 +580,7 @@ function readRequestBody(request,{timeoutMs,signal}){
577
580
  }
578
581
 
579
582
  function normalizedReportEmail(value){
580
- if(typeof value!=='string'){
583
+ if(!is.string(value)){
581
584
  throw new MailGatewayFault('mail_invalid_recipient',{statusCode:422});
582
585
  }
583
586
  const address=value.trim().toLowerCase();
@@ -588,7 +591,7 @@ function normalizedReportEmail(value){
588
591
  }
589
592
 
590
593
  function normalizeReportRecipients(report,configuration){
591
- if(!Array.isArray(report.to)){
594
+ if(!is.array(report.to)){
592
595
  throw new MailGatewayFault('mail_invalid_recipients',{statusCode:422});
593
596
  }
594
597
  const recipients=[];
@@ -609,23 +612,23 @@ function normalizeReportRecipients(report,configuration){
609
612
  }
610
613
 
611
614
  function normalizeReport(value,configuration){
612
- if(!value||typeof value!=='object'||Array.isArray(value)){
615
+ if(!value||!is.object(value)||is.array(value)){
613
616
  throw new MailGatewayFault('mail_invalid_report',{statusCode:422});
614
617
  }
615
618
  if(!Object.hasOwn(value,'subject')||!Object.hasOwn(value,'to')
616
619
  ||!Object.hasOwn(value,'type')){
617
620
  throw new MailGatewayFault('mail_invalid_report_shape',{statusCode:422});
618
621
  }
619
- if(typeof value.type!=='string'||!MAIL_TYPES.has(value.type)){
622
+ if(!is.string(value.type)||!MAIL_TYPES.has(value.type)){
620
623
  throw new MailGatewayFault('mail_invalid_type',{statusCode:422});
621
624
  }
622
- if(typeof value.subject!=='string'){
625
+ if(!is.string(value.subject)){
623
626
  throw new MailGatewayFault('mail_invalid_subject',{statusCode:422});
624
627
  }
625
628
  const hasText=Object.hasOwn(value,'text');
626
629
  const hasHtml=Object.hasOwn(value,'html');
627
- if(!hasText&&!hasHtml||(hasText&&typeof value.text!=='string')
628
- ||(hasHtml&&typeof value.html!=='string')){
630
+ if(!hasText&&!hasHtml||(hasText&&!is.string(value.text))
631
+ ||(hasHtml&&!is.string(value.html))){
629
632
  throw new MailGatewayFault('mail_content_required',{statusCode:422});
630
633
  }
631
634
  const recipients=normalizeReportRecipients(value,configuration);
@@ -658,7 +661,7 @@ function parseReport(serialized,configuration){
658
661
  }
659
662
 
660
663
  function parseRetryAfter(value,now=Date.now()){
661
- if(typeof value!=='string'||!value.trim()){
664
+ if(!is.string(value)||!value.trim()){
662
665
  return 0;
663
666
  }
664
667
  const trimmed=value.trim();
@@ -666,7 +669,7 @@ function parseRetryAfter(value,now=Date.now()){
666
669
  return normalizeRetryAfter(Math.ceil(Number(trimmed)*1000));
667
670
  }
668
671
  const timestamp=Date.parse(trimmed);
669
- return Number.isFinite(timestamp)
672
+ return is.finite(timestamp)
670
673
  ? normalizeRetryAfter(Math.max(0,timestamp-now))
671
674
  : 0;
672
675
  }
@@ -712,7 +715,7 @@ function awaitAbortable(value,signal){
712
715
  function ignoreCancellationFailure(){}
713
716
 
714
717
  function cancelProviderBody(body){
715
- if(!body||typeof body.cancel!=='function'){
718
+ if(!body||!is.function(body.cancel)){
716
719
  return;
717
720
  }
718
721
  try{
@@ -723,7 +726,7 @@ function cancelProviderBody(body){
723
726
  }
724
727
 
725
728
  function cancelProviderReader(reader){
726
- if(!reader||typeof reader.cancel!=='function'){
729
+ if(!reader||!is.function(reader.cancel)){
727
730
  return;
728
731
  }
729
732
  try{
@@ -737,7 +740,7 @@ async function readProviderBody(response,signal){
737
740
  if(response.body===null||response.body===undefined){
738
741
  return '';
739
742
  }
740
- if(typeof response.body.getReader!=='function'){
743
+ if(!is.function(response.body.getReader)){
741
744
  cancelProviderBody(response.body);
742
745
  throw new MailGatewayFault('resend_unreadable_response',{
743
746
  statusCode:502,
@@ -782,7 +785,7 @@ function parseProviderObject(text){
782
785
  }
783
786
  try{
784
787
  const value=JSON.parse(text);
785
- return value&&typeof value==='object'&&!Array.isArray(value)?value:null;
788
+ return value&&is.object(value)&&!is.array(value)?value:null;
786
789
  }catch{
787
790
  return null;
788
791
  }
@@ -790,7 +793,7 @@ function parseProviderObject(text){
790
793
 
791
794
  function providerCode(value,statusCode){
792
795
  const candidate=value?.name;
793
- if(typeof candidate==='string'&&PROVIDER_CODE_PATTERN.test(candidate)){
796
+ if(is.string(candidate)&&PROVIDER_CODE_PATTERN.test(candidate)){
794
797
  return candidate;
795
798
  }
796
799
  return `resend_http_${String(statusCode)}`;
@@ -883,7 +886,7 @@ async function performResendAttempt(configuration,delivery,idempotencyKey,signal
883
886
  ));
884
887
  }
885
888
  const statusCode=Number(response?.status);
886
- if(!Number.isSafeInteger(statusCode)||statusCode<100||statusCode>599){
889
+ if(!is.safeInteger(statusCode)||statusCode<100||statusCode>599){
887
890
  return completeAttempt(ambiguousResult(
888
891
  'resend_invalid_response',
889
892
  configuration.retryableDelayMs,
@@ -912,7 +915,7 @@ async function performResendAttempt(configuration,delivery,idempotencyKey,signal
912
915
  }
913
916
  const value=parseProviderObject(text);
914
917
  if(statusCode>=200&&statusCode<300){
915
- if(!value||typeof value.id!=='string'||!PROVIDER_ID_PATTERN.test(value.id)){
918
+ if(!value||!is.string(value.id)||!PROVIDER_ID_PATTERN.test(value.id)){
916
919
  return completeAttempt(ambiguousResult(
917
920
  'resend_invalid_success_response',
918
921
  configuration.retryableDelayMs,
@@ -945,26 +948,26 @@ async function performResendAttempt(configuration,delivery,idempotencyKey,signal
945
948
  report:delivery.report,
946
949
  durationMs:Math.max(0,Date.now()-startedAt),
947
950
  requestId,
948
- providerStatus:Number.isSafeInteger(Number(response?.status))?Number(response.status):0
951
+ providerStatus:is.safeInteger(Number(response?.status))?Number(response.status):0
949
952
  });
950
953
  }
951
954
  }
952
955
 
953
956
  function normalizeDirectSendOptions(options){
954
- if(!options||typeof options!=='object'||Array.isArray(options)){
957
+ if(!options||!is.object(options)||is.array(options)){
955
958
  throw configurationError('Mail send options must be an object.');
956
959
  }
957
- if(typeof options.reportKey!=='string'||!IDEMPOTENCY_KEY_PATTERN.test(options.reportKey)){
960
+ if(!is.string(options.reportKey)||!IDEMPOTENCY_KEY_PATTERN.test(options.reportKey)){
958
961
  throw configurationError('reportKey must contain safe identifier characters.');
959
962
  }
960
963
  const fetchImpl=options.fetchImpl??globalThis.fetch;
961
- if(typeof fetchImpl!=='function'){
964
+ if(!is.function(fetchImpl)){
962
965
  throw configurationError('A fetch implementation is required for Resend delivery.');
963
966
  }
964
- if(options.onEvent!==undefined&&typeof options.onEvent!=='function'){
967
+ if(options.onEvent!==undefined&&!is.function(options.onEvent)){
965
968
  throw configurationError('onEvent must be a function when supplied.');
966
969
  }
967
- if(options.requestIdFactory!==undefined&&typeof options.requestIdFactory!=='function'){
970
+ if(options.requestIdFactory!==undefined&&!is.function(options.requestIdFactory)){
968
971
  throw configurationError('requestIdFactory must be a function when supplied.');
969
972
  }
970
973
  return {
@@ -1343,7 +1346,7 @@ export async function startResendMailServer(options={}){
1343
1346
  throw error;
1344
1347
  }
1345
1348
  const address=server.address();
1346
- if(!address||typeof address==='string'||!isNumericLoopback(address.address)){
1349
+ if(!address||is.string(address)||!isNumericLoopback(address.address)){
1347
1350
  await Promise.allSettled([closeHttpServer(server),requestHandler.close()]);
1348
1351
  throw configurationError('Mail server did not bind to a numeric loopback address.');
1349
1352
  }
package/src/mail.mjs CHANGED
@@ -1,3 +1,4 @@
1
+ import Is from 'strong-type';
1
2
  import {ArcaneError,ERROR_CODES,throwIfAborted} from './errors.mjs';
2
3
  import {
3
4
  deleteMailCredential,
@@ -7,6 +8,8 @@ import {
7
8
  } from './mail-credentials.mjs';
8
9
  import {sendResendMail,startResendMailServer} from './mail-server.mjs';
9
10
 
11
+ const is = new Is(false);
12
+
10
13
  export const MAIL_COMMAND_ACTIONS=[
11
14
  'key-set',
12
15
  'key-status',
@@ -22,10 +25,10 @@ function usage(message){
22
25
  }
23
26
 
24
27
  function validateOptions(options){
25
- if(!options||typeof options!=='object'||Array.isArray(options)){
28
+ if(!options||!is.object(options)||is.array(options)){
26
29
  usage('Mail command options must be an object.');
27
30
  }
28
- if(typeof options.action!=='string'||!ACTION_SET.has(options.action)){
31
+ if(!is.string(options.action)||!ACTION_SET.has(options.action)){
29
32
  usage(`Mail action must be one of: ${MAIL_COMMAND_ACTIONS.join(', ')}.`);
30
33
  }
31
34
  return options;
@@ -33,26 +36,26 @@ function validateOptions(options){
33
36
 
34
37
  function dependency(options,name,fallback){
35
38
  const value=options[name]??fallback;
36
- if(typeof value!=='function'){
39
+ if(!is.function(value)){
37
40
  usage(`${name} must be a function when supplied.`);
38
41
  }
39
42
  return value;
40
43
  }
41
44
 
42
45
  function recipientList(value,label){
43
- if(value===undefined||value===null||value===''||(Array.isArray(value)&&value.length===0)){
46
+ if(value===undefined||value===null||value===''||(is.array(value)&&value.length===0)){
44
47
  return [];
45
48
  }
46
- const entries=Array.isArray(value)
49
+ const entries=is.array(value)
47
50
  ? value
48
- : typeof value==='string'
51
+ : is.string(value)
49
52
  ? value.split(',')
50
53
  : null;
51
54
  if(!entries||entries.length===0){
52
55
  usage(`${label} must contain at least one email address when supplied.`);
53
56
  }
54
57
  const result=entries.map(function normalizeMailRecipient(entry){
55
- if(typeof entry!=='string'||!entry.trim()){
58
+ if(!is.string(entry)||!entry.trim()){
56
59
  usage(`${label} contains an empty email address.`);
57
60
  }
58
61
  return entry.trim();
@@ -66,9 +69,9 @@ function recipientList(value,label){
66
69
  }
67
70
 
68
71
  function originList(value){
69
- const entries=Array.isArray(value)?value:[value];
72
+ const entries=is.array(value)?value:[value];
70
73
  if(entries.length===0||entries.some(function invalidOrigin(entry){
71
- return typeof entry!=='string'||!entry.trim();
74
+ return !is.string(entry)||!entry.trim();
72
75
  })){
73
76
  usage('Mail serve requires at least one exact allowed origin.');
74
77
  }
@@ -115,9 +118,9 @@ async function deleteCredential(options){
115
118
  }
116
119
 
117
120
  function withoutMailCredentials(value,seen=new WeakMap()){
118
- if(!value||typeof value!=='object')return value;
121
+ if(!value||!is.object(value))return value;
119
122
  if(seen.has(value))return seen.get(value);
120
- const copy=Array.isArray(value)?[]:{};
123
+ const copy=is.array(value)?[]:{};
121
124
  seen.set(value,copy);
122
125
  for(const [key,entry] of Object.entries(value)){
123
126
  if(key==='apiKey'||key==='appKey')continue;
@@ -127,7 +130,7 @@ function withoutMailCredentials(value,seen=new WeakMap()){
127
130
  }
128
131
 
129
132
  function completeSendFailure(result){
130
- if(!result||typeof result!=='object'||Array.isArray(result)){
133
+ if(!result||!is.object(result)||is.array(result)){
131
134
  return {provider:'resend',result:withoutMailCredentials(result)};
132
135
  }
133
136
  // Complete provider detail remains visible; only credential fields are omitted.
@@ -148,7 +151,7 @@ async function sendMail(options){
148
151
  `No Resend credential is configured for profile ${String(options.profile)}.`
149
152
  );
150
153
  }
151
- if(typeof apiKey!=='string'||!apiKey){
154
+ if(!is.string(apiKey)||!apiKey){
152
155
  throw new ArcaneError(
153
156
  ERROR_CODES.operationFailed,
154
157
  'The configured Resend credential could not be read.'
@@ -195,7 +198,7 @@ async function serveMail(options){
195
198
  `No Resend credential is configured for profile ${String(options.profile)}.`
196
199
  );
197
200
  }
198
- if(typeof apiKey!=='string'||!apiKey){
201
+ if(!is.string(apiKey)||!apiKey){
199
202
  throw new ArcaneError(
200
203
  ERROR_CODES.operationFailed,
201
204
  'The configured Resend credential could not be read.'
@@ -205,7 +208,7 @@ async function serveMail(options){
205
208
  try{
206
209
  throwIfAborted(options.signal);
207
210
  appKey=await readAppKey();
208
- if(typeof appKey!=='string'||!appKey){
211
+ if(!is.string(appKey)||!appKey){
209
212
  throw new ArcaneError(
210
213
  ERROR_CODES.operationFailed,
211
214
  'The local Mail gateway app key could not be read.'
@@ -1,9 +1,12 @@
1
+ import Is from 'strong-type';
1
2
  import {lstat,readFile,realpath} from 'node:fs/promises';
2
3
  import path from 'node:path';
3
4
  import {validateAppDescriptor} from './app-descriptor.mjs';
4
5
  import {normalizeRelativePath} from './packager/core.mjs';
5
6
  import {ArcaneError,ERROR_CODES,throwIfAborted} from './errors.mjs';
6
7
 
8
+ const is = new Is(false);
9
+
7
10
  export const NATIVE_BUILD_PLAN_PROTOCOL='arcane-native-build-plan/1';
8
11
  export const NATIVE_BUILDER_PROTOCOL='arcane-native-builder/1';
9
12
 
@@ -38,15 +41,15 @@ function fail(message,code=ERROR_CODES.policyDenied,details){
38
41
  }
39
42
 
40
43
  function isObject(value){
41
- return value!==null&&typeof value==='object'&&!Array.isArray(value);
44
+ return value!==null&&is.object(value)&&!is.array(value);
42
45
  }
43
46
 
44
47
  async function emit(onEvent,event){
45
- if(typeof onEvent==='function')await onEvent(event);
48
+ if(is.function(onEvent))await onEvent(event);
46
49
  }
47
50
 
48
51
  async function realDirectory(location,label){
49
- if(typeof location!=='string'||!location.trim())fail(`${label} is required.`,ERROR_CODES.usage);
52
+ if(!is.string(location)||!location.trim())fail(`${label} is required.`,ERROR_CODES.usage);
50
53
  const requested=path.resolve(location);
51
54
  let info;
52
55
  try{info=await lstat(requested);}
@@ -110,7 +113,7 @@ export function validateNativeBuilder(provider){
110
113
  fail(`Native builder protocol must be ${NATIVE_BUILDER_PROTOCOL}.`,ERROR_CODES.targetUnavailable);
111
114
  }
112
115
  for(const method of ['describe','doctor','prepare','build','verify','run']){
113
- if(typeof provider[method]!=='function'){
116
+ if(!is.function(provider[method])){
114
117
  fail(`Native builder must implement ${method}().`,ERROR_CODES.targetUnavailable);
115
118
  }
116
119
  }
@@ -119,7 +122,7 @@ export function validateNativeBuilder(provider){
119
122
 
120
123
  async function releaseSelection(releaseRoot,release,label){
121
124
  const root=await realDirectory(releaseRoot,`${label} root`);
122
- if(!isObject(release)||!Array.isArray(release.files)){
125
+ if(!isObject(release)||!is.array(release.files)){
123
126
  fail(`${label} must provide its structural file inventory.`,ERROR_CODES.usage);
124
127
  }
125
128
  const files=release.files.map((entry,index)=>normalizeRelativePath(
@@ -150,11 +153,11 @@ async function releaseSelection(releaseRoot,release,label){
150
153
 
151
154
  async function dependencySelections(value,{signal}={}){
152
155
  if(value===undefined)return [];
153
- if(!Array.isArray(value))fail('dependencyReleases must be an array.',ERROR_CODES.usage);
156
+ if(!is.array(value))fail('dependencyReleases must be an array.',ERROR_CODES.usage);
154
157
  const dependencies=[];
155
158
  for(const [index,item] of value.entries()){
156
159
  throwIfAborted(signal);
157
- if(!isObject(item)||typeof item.appId!=='string'){
160
+ if(!isObject(item)||!is.string(item.appId)){
158
161
  fail(`dependencyReleases[${index}] is malformed.`,ERROR_CODES.usage);
159
162
  }
160
163
  const selected=await releaseSelection(
@@ -191,10 +194,10 @@ export async function createNativeBuildPlan({
191
194
  ));
192
195
  const application=await releaseSelection(appReleaseRoot,release,'application release');
193
196
  const dependencies=await dependencySelections(dependencyReleases,{signal});
194
- if(!Array.isArray(protectedRoots)){
197
+ if(!is.array(protectedRoots)){
195
198
  fail('protectedRoots must be an array.',ERROR_CODES.usage);
196
199
  }
197
- if(typeof outputRoot!=='string'||!outputRoot.trim()){
200
+ if(!is.string(outputRoot)||!outputRoot.trim()){
198
201
  fail('outputRoot is required.',ERROR_CODES.usage);
199
202
  }
200
203
  const selectedOutput=path.resolve(outputRoot);
@@ -1,9 +1,12 @@
1
+ import Is from 'strong-type';
1
2
  import {lstat,realpath} from 'node:fs/promises';
2
3
  import path from 'node:path';
3
4
  import {pathToFileURL} from 'node:url';
4
5
  import {ArcaneError,ERROR_CODES,throwIfAborted} from './errors.mjs';
5
6
  import {validateNativeBuilder} from './native-plan.mjs';
6
7
 
8
+ const is = new Is(false);
9
+
7
10
  const PROVIDER_ROOT=['machine_bundles','arcane-os-machine-bundle','tools'];
8
11
 
9
12
  function providerPath(fileName){
@@ -55,10 +58,10 @@ async function resolveProviderLocation({
55
58
  supportedTargets:Object.keys(FIXED_NATIVE_PROVIDER_PATHS)
56
59
  });
57
60
  }
58
- if(typeof arcaneRoot!=='string'||!arcaneRoot.trim()){
61
+ if(!is.string(arcaneRoot)||!arcaneRoot.trim()){
59
62
  fail(`The ${target} native provider requires an Arcane OS checkout root.`);
60
63
  }
61
- if(typeof inspect!=='function'||typeof canonicalize!=='function'){
64
+ if(!is.function(inspect)||!is.function(canonicalize)){
62
65
  fail('The Arcane native provider loader dependencies are invalid.');
63
66
  }
64
67
  const requestedRoot=path.resolve(arcaneRoot);
@@ -107,7 +110,7 @@ async function resolveProviderLocation({
107
110
  }
108
111
 
109
112
  export async function loadArcaneNativeProvider(options={}){
110
- if(options===null||typeof options!=='object'||Array.isArray(options)){
113
+ if(options===null||!is.object(options)||is.array(options)){
111
114
  fail('Arcane native provider options must be an object.');
112
115
  }
113
116
  const {
@@ -120,7 +123,7 @@ export async function loadArcaneNativeProvider(options={}){
120
123
  onEvent
121
124
  }=options;
122
125
  throwIfAborted(signal);
123
- if(typeof importModule!=='function')fail('The Arcane native provider importer is invalid.');
126
+ if(!is.function(importModule))fail('The Arcane native provider importer is invalid.');
124
127
  await onEvent?.({
125
128
  type:'native.provider.load.started',
126
129
  target,