arcane-os 0.1.0-dev.5
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 +154 -0
- package/COMMERCIAL-LICENSE.md +13 -0
- package/LICENSE +661 -0
- package/NOTICE +70 -0
- package/README.md +448 -0
- package/bin/arcane-test.mjs +741 -0
- package/bin/arcane.mjs +5 -0
- package/docs/architecture.md +234 -0
- package/docs/compatibility.md +36 -0
- package/docs/event-manager.md +166 -0
- package/docs/platform-targets.md +108 -0
- package/docs/publishing.md +203 -0
- package/docs/reference/README.md +117 -0
- package/docs/reference/arcane-ollama.md +288 -0
- package/docs/reference/availability-and-normalization.md +123 -0
- package/docs/reference/behavioral-testing.md +86 -0
- package/docs/reference/cli.md +569 -0
- package/docs/reference/core/README.md +62 -0
- package/docs/reference/core/arcane-ai-contracts.md +873 -0
- package/docs/reference/core/arcane-api.md +601 -0
- package/docs/reference/core/arcane-entities.md +65 -0
- package/docs/reference/core/arcane-events.md +134 -0
- package/docs/reference/core/ollama-module.md +181 -0
- package/docs/reference/core/reference/arcane-api/ai-and-ollama.md +1909 -0
- package/docs/reference/core/reference/arcane-api/applications-terminal-capabilities.md +1057 -0
- package/docs/reference/core/reference/arcane-api/core-and-events.md +320 -0
- package/docs/reference/core/reference/arcane-api/filesystem-storage-preferences-appearance.md +610 -0
- package/docs/reference/core/reference/arcane-api/namespaces.md +1157 -0
- package/docs/reference/core/reference/arcane-api/platform-installation-users-system.md +1423 -0
- package/docs/reference/core/reference/arcane-api/session-provisioning-diagnostics-development.md +315 -0
- package/docs/reference/event-manager.md +957 -0
- package/docs/reference/inventory/package-api.json +2632 -0
- package/docs/reference/inventory/runtime-components.json +934 -0
- package/docs/reference/inventory/runtime-entities.json +26 -0
- package/docs/reference/inventory/runtime-modules.json +1249 -0
- package/docs/reference/protocols.md +242 -0
- package/docs/reference/runtime-components.md +1098 -0
- package/docs/reference/runtime-entities.md +303 -0
- package/docs/reference/runtime-modules.md +2010 -0
- package/docs/reference/sdk-api.md +4901 -0
- package/docs/roadmap.md +79 -0
- package/docs/work-amplification.md +124 -0
- package/node_modules/event-pubsub/CHANGELOG.md +55 -0
- package/node_modules/event-pubsub/MIGRATION.md +70 -0
- package/node_modules/event-pubsub/README.md +363 -0
- package/node_modules/event-pubsub/SECURITY.md +37 -0
- package/node_modules/event-pubsub/index.js +141 -0
- package/node_modules/event-pubsub/licence +21 -0
- package/node_modules/event-pubsub/package.json +59 -0
- package/node_modules/strong-type/README.md +408 -0
- package/node_modules/strong-type/assets/strong-type-header.png +0 -0
- package/node_modules/strong-type/index.js +1151 -0
- package/node_modules/strong-type/licence +21 -0
- package/node_modules/strong-type/node.js +125 -0
- package/node_modules/strong-type/package.json +61 -0
- package/package.json +95 -0
- package/runtime/ARCANE_RUNTIME_RELEASE.json +791 -0
- package/runtime/arcane/components/app-bar.html +468 -0
- package/runtime/arcane/components/assistant-panel.html +715 -0
- package/runtime/arcane/components/calculator.html +8 -0
- package/runtime/arcane/components/chart.html +655 -0
- package/runtime/arcane/components/chat.html +1225 -0
- package/runtime/arcane/components/conversation-view.html +13 -0
- package/runtime/arcane/components/dashboard-config.html +341 -0
- package/runtime/arcane/components/data-maintenance.html +112 -0
- package/runtime/arcane/components/data-view.html +92 -0
- package/runtime/arcane/components/directory-picker.html +197 -0
- package/runtime/arcane/components/document-inspector.html +252 -0
- package/runtime/arcane/components/file-drop.html +264 -0
- package/runtime/arcane/components/file-inspector.html +293 -0
- package/runtime/arcane/components/file-manager.html +1715 -0
- package/runtime/arcane/components/header.html +142 -0
- package/runtime/arcane/components/integration-settings.html +14 -0
- package/runtime/arcane/components/local-ai-status.html +360 -0
- package/runtime/arcane/components/markdown-document.html +1048 -0
- package/runtime/arcane/components/markdown-editor.html +360 -0
- package/runtime/arcane/components/media-embed.html +8 -0
- package/runtime/arcane/components/modal.html +402 -0
- package/runtime/arcane/components/output-panel.html +259 -0
- package/runtime/arcane/components/preferences-form.html +135 -0
- package/runtime/arcane/components/record-timeline.html +105 -0
- package/runtime/arcane/components/relationship-board.html +116 -0
- package/runtime/arcane/components/screen-capture.html +8 -0
- package/runtime/arcane/components/source-code-viewer.html +441 -0
- package/runtime/arcane/components/source-explanation.html +124 -0
- package/runtime/arcane/components/speech.html +365 -0
- package/runtime/arcane/components/summary-strip.html +177 -0
- package/runtime/arcane/components/table.html +77 -0
- package/runtime/arcane/components/task-progress.html +282 -0
- package/runtime/arcane/components/terminal-workspace.html +65 -0
- package/runtime/arcane/components/theme-editor.html +41 -0
- package/runtime/arcane/components/theme-switcher.html +46 -0
- package/runtime/arcane/components/unified-inbox.html +20 -0
- package/runtime/arcane/components/voice-transcription.html +476 -0
- package/runtime/arcane/components/weather-widget.html +8 -0
- package/runtime/arcane/components/web-navigator.html +239 -0
- package/runtime/arcane/css/communications.css +1 -0
- package/runtime/arcane/css/dashboard-config.css +45 -0
- package/runtime/arcane/css/document-site.css +981 -0
- package/runtime/arcane/css/layout.css +438 -0
- package/runtime/arcane/css/primitives.css +321 -0
- package/runtime/arcane/css/theme.css +112 -0
- package/runtime/arcane/css/utility-workspace.css +1 -0
- package/runtime/arcane/entities/ApiModelRecord.js +20 -0
- package/runtime/arcane/entities/Calculation.js +13 -0
- package/runtime/arcane/entities/Chat.js +581 -0
- package/runtime/arcane/entities/CommunicationMessage.js +29 -0
- package/runtime/arcane/entities/CommunicationThread.js +21 -0
- package/runtime/arcane/entities/Document.js +10 -0
- package/runtime/arcane/entities/File.js +143 -0
- package/runtime/arcane/entities/Image.js +135 -0
- package/runtime/arcane/entities/IntentEnvelope.js +834 -0
- package/runtime/arcane/entities/Preference.js +83 -0
- package/runtime/arcane/entities/TWiNPolicyDecision.js +1092 -0
- package/runtime/arcane/entities/TerminalSession.js +46 -0
- package/runtime/arcane/entities/Theme.js +107 -0
- package/runtime/arcane/entities/User.js +1046 -0
- package/runtime/arcane/entities/Weather.js +23 -0
- package/runtime/arcane/img/arcane-os-everywhere.png +0 -0
- package/runtime/arcane/img/arrow-left.png +0 -0
- package/runtime/arcane/img/arrow-right.png +0 -0
- package/runtime/arcane/img/doc.svg +5 -0
- package/runtime/arcane/img/folder.svg +4 -0
- package/runtime/arcane/img/image.svg +5 -0
- package/runtime/arcane/img/refresh.png +0 -0
- package/runtime/arcane/img/send.svg +9 -0
- package/runtime/arcane/img/trash.svg +5 -0
- package/runtime/arcane/img/upload.svg +5 -0
- package/runtime/arcane/modules/AI.js +2048 -0
- package/runtime/arcane/modules/AIPreferenceRuntime.js +32 -0
- package/runtime/arcane/modules/AIPreferenceTuple.js +92 -0
- package/runtime/arcane/modules/AIResponseLength.js +42 -0
- package/runtime/arcane/modules/AIResponseURLPolicy.js +626 -0
- package/runtime/arcane/modules/AnsiText.js +53 -0
- package/runtime/arcane/modules/ApiModelDatabase.js +25 -0
- package/runtime/arcane/modules/AppDataScope.js +245 -0
- package/runtime/arcane/modules/AppearancePreferences.js +28 -0
- package/runtime/arcane/modules/ArcaneCommunicationBridge.js +22 -0
- package/runtime/arcane/modules/ArcaneNavigationPolicy.js +135 -0
- package/runtime/arcane/modules/ArcaneNetworkPolicy.js +255 -0
- package/runtime/arcane/modules/AsyncBoundary.js +161 -0
- package/runtime/arcane/modules/BrowserTestSuite.js +326 -0
- package/runtime/arcane/modules/CalculatorEngine.js +20 -0
- package/runtime/arcane/modules/CaseEvidenceIndexer.js +134 -0
- package/runtime/arcane/modules/ChartLibrary.js +35 -0
- package/runtime/arcane/modules/ChatRecords.js +13 -0
- package/runtime/arcane/modules/CommunicationAppController.js +43 -0
- package/runtime/arcane/modules/CommunicationHub.js +16 -0
- package/runtime/arcane/modules/CommunicationPreferences.js +13 -0
- package/runtime/arcane/modules/CommunicationProviderRegistry.js +16 -0
- package/runtime/arcane/modules/ComponentContracts.js +586 -0
- package/runtime/arcane/modules/ConfiguredAIChatSession.js +288 -0
- package/runtime/arcane/modules/ConversationActionItems.js +488 -0
- package/runtime/arcane/modules/ConversationClosingReport.js +274 -0
- package/runtime/arcane/modules/ConversationTimebox.js +527 -0
- package/runtime/arcane/modules/CoreLocalModelCatalog.js +255 -0
- package/runtime/arcane/modules/DBLS.js +171 -0
- package/runtime/arcane/modules/DBOPFS.js +1154 -0
- package/runtime/arcane/modules/DBOPFSWorker.js +116 -0
- package/runtime/arcane/modules/DataMaintenance.js +91 -0
- package/runtime/arcane/modules/DevelopmentWorkspace.js +74 -0
- package/runtime/arcane/modules/DirectoryPicker.js +109 -0
- package/runtime/arcane/modules/DocumentNavigation.js +223 -0
- package/runtime/arcane/modules/Errors.js +1025 -0
- package/runtime/arcane/modules/GifEncoder.js +29 -0
- package/runtime/arcane/modules/HTMLImport.js +114 -0
- package/runtime/arcane/modules/InMemoryCommunicationProvider.js +14 -0
- package/runtime/arcane/modules/IsolatedModelQuestionRunner.js +275 -0
- package/runtime/arcane/modules/LocalAIReadiness.js +870 -0
- package/runtime/arcane/modules/LocalAIReadinessController.js +156 -0
- package/runtime/arcane/modules/MD.js +111 -0
- package/runtime/arcane/modules/Mail.js +352 -0
- package/runtime/arcane/modules/MailTransport.mjs +180 -0
- package/runtime/arcane/modules/Marked.min.js +71 -0
- package/runtime/arcane/modules/MemoryRecords.js +44 -0
- package/runtime/arcane/modules/MessageAdvisory.js +38 -0
- package/runtime/arcane/modules/ModelDefinition.js +189 -0
- package/runtime/arcane/modules/Ollama.js +74 -0
- package/runtime/arcane/modules/OllamaModelIdentifier.js +21 -0
- package/runtime/arcane/modules/OllamaSettings.js +24 -0
- package/runtime/arcane/modules/OpenMeteoWeatherProvider.js +14 -0
- package/runtime/arcane/modules/PreferenceStore.js +109 -0
- package/runtime/arcane/modules/QRCode.min.js +1 -0
- package/runtime/arcane/modules/Questionnaire.js +61 -0
- package/runtime/arcane/modules/RecordLinkIndex.js +24 -0
- package/runtime/arcane/modules/RecordPassageIndex.js +222 -0
- package/runtime/arcane/modules/RecordReviewStore.js +94 -0
- package/runtime/arcane/modules/RevocableProjectionLedger.js +1623 -0
- package/runtime/arcane/modules/RiskSignalAnalyzer.js +37 -0
- package/runtime/arcane/modules/ScamRiskPolicy.js +62 -0
- package/runtime/arcane/modules/ScopedOPFSCache.js +183 -0
- package/runtime/arcane/modules/ScreenCapture.js +20 -0
- package/runtime/arcane/modules/SpeechPlayback.js +581 -0
- package/runtime/arcane/modules/StaticDocumentCatalog.js +1248 -0
- package/runtime/arcane/modules/SystemAppearance.js +20 -0
- package/runtime/arcane/modules/SystemPlatformPresentation.js +51 -0
- package/runtime/arcane/modules/SystemToolRegistry.js +28 -0
- package/runtime/arcane/modules/TerminalClient.js +52 -0
- package/runtime/arcane/modules/TerminalCommandRegistry.js +50 -0
- package/runtime/arcane/modules/ThemeBootstrap.js +26 -0
- package/runtime/arcane/modules/ThemeManager.js +131 -0
- package/runtime/arcane/modules/TimeGuard.js +149 -0
- package/runtime/arcane/modules/ToolCallRouter.js +83 -0
- package/runtime/arcane/modules/WaitForComponent.js +102 -0
- package/runtime/arcane/modules/YouTubeMedia.js +16 -0
- package/runtime/arcane/modules/uPlot.LICENSE.txt +21 -0
- package/runtime/arcane/modules/uPlot.iife.min.js +2 -0
- package/runtime/arcane/modules/uPlot.min.css +1 -0
- package/runtime/arcane/security/arcane-network-policy.json +6 -0
- package/runtime/strong-type/index.js +352 -0
- package/runtime/strong-type/licence +21 -0
- package/runtime/strong-type/package.json +45 -0
- package/schemas/arcane-app-bundle.schema.json +125 -0
- package/schemas/arcane-app.schema.json +329 -0
- package/schemas/arcane-lock.schema.json +86 -0
- package/schemas/arcane-package.schema.json +224 -0
- package/schemas/cli-event.schema.json +122 -0
- package/schemas/event-stack.schema.json +152 -0
- package/schemas/native-build-plan.schema.json +176 -0
- package/schemas/target-adapter.schema.json +117 -0
- package/src/app-descriptor.mjs +500 -0
- package/src/cli/main.mjs +561 -0
- package/src/constants.mjs +31 -0
- package/src/dev-server.mjs +718 -0
- package/src/doctor.mjs +315 -0
- package/src/dom-event-instrumentation.mjs +594 -0
- package/src/errors.mjs +75 -0
- package/src/event-manager.mjs +1342 -0
- package/src/event-queue.mjs +138 -0
- package/src/events.mjs +219 -0
- package/src/index.mjs +177 -0
- package/src/integrated-provider-loader.mjs +432 -0
- package/src/native-plan.mjs +698 -0
- package/src/native-provider-loader.mjs +1126 -0
- package/src/packager/core.mjs +2691 -0
- package/src/process.mjs +353 -0
- package/src/release-bundle.mjs +2523 -0
- package/src/repository.mjs +90 -0
- package/src/runtime.mjs +452 -0
- package/src/scaffold.mjs +380 -0
- package/src/targets/index.mjs +436 -0
- package/src/templates/assets/app-icon.png +0 -0
- package/src/templates/workspace-template.mjs +388 -0
- package/src/testing-loader.mjs +9 -0
- package/src/testing.mjs +427 -0
- package/src/toolchain.mjs +1335 -0
- package/src/update-check.mjs +307 -0
- package/src/workspace.mjs +449 -0
|
@@ -0,0 +1,1342 @@
|
|
|
1
|
+
import EventPubSub from 'event-pubsub';
|
|
2
|
+
import {createDOMInstrumentation} from './dom-event-instrumentation.mjs';
|
|
3
|
+
|
|
4
|
+
export {
|
|
5
|
+
createDOMInstrumentation,
|
|
6
|
+
DEFAULT_DOM_EVENT_TYPES,
|
|
7
|
+
describeDOMTarget,
|
|
8
|
+
DOM_INTERACTION_EVENT,
|
|
9
|
+
DOM_MUTATION_EVENT,
|
|
10
|
+
DOM_OBSERVATION_STARTED_EVENT,
|
|
11
|
+
DOM_OBSERVATION_STOPPED_EVENT,
|
|
12
|
+
domSelector
|
|
13
|
+
} from './dom-event-instrumentation.mjs';
|
|
14
|
+
|
|
15
|
+
export const ARCANE_EVENT_STACK_PROTOCOL='arcane-event-stack/1';
|
|
16
|
+
export const TIME_TRAVEL_SEEK_EVENT='arcane.time-travel.seek';
|
|
17
|
+
export const PLAYBACK_STARTED_EVENT='arcane.time-travel.playback.started';
|
|
18
|
+
export const PLAYBACK_RECORD_EVENT='arcane.time-travel.playback.record';
|
|
19
|
+
export const PLAYBACK_COMPLETED_EVENT='arcane.time-travel.playback.completed';
|
|
20
|
+
export const PLAYBACK_CANCELLED_EVENT='arcane.time-travel.playback.cancelled';
|
|
21
|
+
export const PLAYBACK_FAILED_EVENT='arcane.time-travel.playback.failed';
|
|
22
|
+
export const TIME_TRAVEL_OVERFLOW_EVENT='arcane.time-travel.overflow';
|
|
23
|
+
|
|
24
|
+
const SENSITIVE_KEY_PATTERN=/(?:authorization|cookie|credential|pass(?:word|phrase)?|private.?key|secret|session.?token|token|api.?key)/iu;
|
|
25
|
+
const PRIVATE_CONTENT_KEY_PATTERN=/^(?:data|detail|key)$/iu;
|
|
26
|
+
const INTERNAL_STACK_PATTERN=/(?:EventManager\.|#dispatch|event-manager\.mjs)/u;
|
|
27
|
+
const URL_PATTERN=/\b(?:blob|data|file|ftp|ftps|http|https|ws|wss):[^\s<>'"\])}]+/giu;
|
|
28
|
+
const DEFAULT_MAX_EVENTS=10_000;
|
|
29
|
+
const DEFAULT_MAX_SNAPSHOT_DEPTH=50;
|
|
30
|
+
const DEFAULT_MAX_SNAPSHOT_ENTRIES=1_000;
|
|
31
|
+
const DEFAULT_MAX_SNAPSHOT_STRING_LENGTH=10_000;
|
|
32
|
+
const MIN_SNAPSHOT_STRING_LENGTH=64;
|
|
33
|
+
const DOCUMENT_KEYS=Object.freeze(['protocol','sessionId','createdAt','events']);
|
|
34
|
+
const RECORD_KEYS=Object.freeze([
|
|
35
|
+
'protocol','sessionId','id','sequence','timestamp','monotonicMs','type','source',
|
|
36
|
+
'category','correlationId','causationId','parentSequence','depth','stack','payload',
|
|
37
|
+
'metadata','status','completedAt','durationMs','error'
|
|
38
|
+
]);
|
|
39
|
+
const EVENT_STATUSES=new Set(['dispatching','completed','failed']);
|
|
40
|
+
const DATE_TO_ISO=Date.prototype.toISOString;
|
|
41
|
+
const REGEXP_SOURCE_GETTER=Object.getOwnPropertyDescriptor(RegExp.prototype,'source')?.get;
|
|
42
|
+
const REGEXP_FLAG_GETTERS=Object.freeze([
|
|
43
|
+
['d',Object.getOwnPropertyDescriptor(RegExp.prototype,'hasIndices')?.get],
|
|
44
|
+
['g',Object.getOwnPropertyDescriptor(RegExp.prototype,'global')?.get],
|
|
45
|
+
['i',Object.getOwnPropertyDescriptor(RegExp.prototype,'ignoreCase')?.get],
|
|
46
|
+
['m',Object.getOwnPropertyDescriptor(RegExp.prototype,'multiline')?.get],
|
|
47
|
+
['s',Object.getOwnPropertyDescriptor(RegExp.prototype,'dotAll')?.get],
|
|
48
|
+
['u',Object.getOwnPropertyDescriptor(RegExp.prototype,'unicode')?.get],
|
|
49
|
+
['v',Object.getOwnPropertyDescriptor(RegExp.prototype,'unicodeSets')?.get],
|
|
50
|
+
['y',Object.getOwnPropertyDescriptor(RegExp.prototype,'sticky')?.get]
|
|
51
|
+
]);
|
|
52
|
+
const MAP_ENTRIES=Map.prototype.entries;
|
|
53
|
+
const MAP_SIZE_GETTER=Object.getOwnPropertyDescriptor(Map.prototype,'size')?.get;
|
|
54
|
+
const SET_VALUES=Set.prototype.values;
|
|
55
|
+
const SET_SIZE_GETTER=Object.getOwnPropertyDescriptor(Set.prototype,'size')?.get;
|
|
56
|
+
const TYPED_ARRAY_PROTOTYPE=Object.getPrototypeOf(Uint8Array.prototype);
|
|
57
|
+
const TYPED_ARRAY_LENGTH_GETTER=
|
|
58
|
+
Object.getOwnPropertyDescriptor(TYPED_ARRAY_PROTOTYPE,'length')?.get;
|
|
59
|
+
const TYPED_ARRAY_VALUES=TYPED_ARRAY_PROTOTYPE.values;
|
|
60
|
+
const TYPED_ARRAY_TAG_GETTER=
|
|
61
|
+
Object.getOwnPropertyDescriptor(TYPED_ARRAY_PROTOTYPE,Symbol.toStringTag)?.get;
|
|
62
|
+
const DATA_VIEW_BUFFER_GETTER=Object.getOwnPropertyDescriptor(DataView.prototype,'buffer')?.get;
|
|
63
|
+
const DATA_VIEW_BYTE_LENGTH_GETTER=
|
|
64
|
+
Object.getOwnPropertyDescriptor(DataView.prototype,'byteLength')?.get;
|
|
65
|
+
const DATA_VIEW_BYTE_OFFSET_GETTER=
|
|
66
|
+
Object.getOwnPropertyDescriptor(DataView.prototype,'byteOffset')?.get;
|
|
67
|
+
|
|
68
|
+
function dataObject(entries=[]){
|
|
69
|
+
const result=Object.create(null);
|
|
70
|
+
for(const [key,value] of entries){
|
|
71
|
+
Object.defineProperty(result,key,{
|
|
72
|
+
value,
|
|
73
|
+
enumerable:true,
|
|
74
|
+
configurable:true,
|
|
75
|
+
writable:true
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
return result;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function snapshotObject(entries=[]){
|
|
82
|
+
const result={};
|
|
83
|
+
for(const [key,value] of entries){
|
|
84
|
+
Object.defineProperty(result,key,{
|
|
85
|
+
value,
|
|
86
|
+
enumerable:true,
|
|
87
|
+
configurable:true,
|
|
88
|
+
writable:true
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
return result;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function taggedSnapshot(type,entries=[]){
|
|
95
|
+
return snapshotObject([['$type',type],...entries]);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function dataProperty(value,key,{inherited=false}={}){
|
|
99
|
+
let current=value;
|
|
100
|
+
while(current!==null&&(typeof current==='object'||typeof current==='function')){
|
|
101
|
+
const descriptor=Object.getOwnPropertyDescriptor(current,key);
|
|
102
|
+
if(descriptor){
|
|
103
|
+
return 'value' in descriptor
|
|
104
|
+
?{found:true,readable:true,value:descriptor.value}
|
|
105
|
+
:{found:true,readable:false,value:undefined};
|
|
106
|
+
}
|
|
107
|
+
if(!inherited)break;
|
|
108
|
+
current=Object.getPrototypeOf(current);
|
|
109
|
+
}
|
|
110
|
+
return {found:false,readable:false,value:undefined};
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function safeDataString(value,key,{fallback='',inherited=false}={}){
|
|
114
|
+
try{
|
|
115
|
+
const property=dataProperty(value,key,{inherited});
|
|
116
|
+
if(property.found&&!property.readable)return '[UNREADABLE]';
|
|
117
|
+
if(property.readable&&typeof property.value==='string')return property.value;
|
|
118
|
+
}catch{}
|
|
119
|
+
return fallback;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function regexpFlags(value){
|
|
123
|
+
let result='';
|
|
124
|
+
for(const [flag,getter] of REGEXP_FLAG_GETTERS){
|
|
125
|
+
if(typeof getter==='function'&&getter.call(value))result+=flag;
|
|
126
|
+
}
|
|
127
|
+
return result;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function boundedString(value,maxLength,{redactSensitive=true}={}){
|
|
131
|
+
let result=String(value);
|
|
132
|
+
if(redactSensitive)result=result.replace(URL_PATTERN,'[REDACTED URL]');
|
|
133
|
+
return result.length<=maxLength?result:`${result.slice(0,maxLength)}…`;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function safeErrorText(error,maxLength,{redactSensitive=true}={}){
|
|
137
|
+
let text='Snapshot capture failed.';
|
|
138
|
+
try{
|
|
139
|
+
if(error!==null&&(typeof error==='object'||typeof error==='function')){
|
|
140
|
+
const message=safeDataString(error,'message');
|
|
141
|
+
if(message)text=message;
|
|
142
|
+
}else if(error!==undefined){
|
|
143
|
+
text=String(error);
|
|
144
|
+
}
|
|
145
|
+
}catch{}
|
|
146
|
+
try{return boundedString(text,maxLength,{redactSensitive});}
|
|
147
|
+
catch{return 'Snapshot capture failed.';}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function sessionIdentifier(){
|
|
151
|
+
if(typeof globalThis.crypto?.randomUUID==='function')return globalThis.crypto.randomUUID();
|
|
152
|
+
return `${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}`;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function clockDate(clock){
|
|
156
|
+
const value=clock();
|
|
157
|
+
const result=value instanceof Date?new Date(value.getTime()):new Date(value);
|
|
158
|
+
if(Number.isNaN(result.getTime()))throw new TypeError('The event clock returned an invalid timestamp.');
|
|
159
|
+
return result;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function monotonicValue(now){
|
|
163
|
+
const value=Number(now());
|
|
164
|
+
if(!Number.isFinite(value)||value<0){
|
|
165
|
+
throw new TypeError('The monotonic event clock returned an invalid value.');
|
|
166
|
+
}
|
|
167
|
+
return value;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function defaultMonotonicClock(){
|
|
171
|
+
return typeof globalThis.performance?.now==='function'
|
|
172
|
+
?globalThis.performance.now()
|
|
173
|
+
:Date.now();
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function sourceStack(){
|
|
177
|
+
const stack=new Error('Arcane event source').stack;
|
|
178
|
+
if(typeof stack!=='string')return null;
|
|
179
|
+
const lines=stack.split(/\r?\n/u);
|
|
180
|
+
const filtered=[lines[0],...lines.slice(1).filter(line=>!INTERNAL_STACK_PATTERN.test(line))];
|
|
181
|
+
return filtered.join('\n');
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
function snapshot(value,{
|
|
185
|
+
redactSensitive=true,
|
|
186
|
+
captureStacks=false,
|
|
187
|
+
maxDepth=DEFAULT_MAX_SNAPSHOT_DEPTH,
|
|
188
|
+
maxEntries=DEFAULT_MAX_SNAPSHOT_ENTRIES,
|
|
189
|
+
maxStringLength=DEFAULT_MAX_SNAPSHOT_STRING_LENGTH,
|
|
190
|
+
key='',
|
|
191
|
+
path='$',
|
|
192
|
+
depth=0,
|
|
193
|
+
seen=new Map()
|
|
194
|
+
}={}){
|
|
195
|
+
if(redactSensitive&&key
|
|
196
|
+
&&(SENSITIVE_KEY_PATTERN.test(key)||PRIVATE_CONTENT_KEY_PATTERN.test(key))){
|
|
197
|
+
return '[REDACTED]';
|
|
198
|
+
}
|
|
199
|
+
if(value===null||value===undefined||typeof value==='boolean')return value;
|
|
200
|
+
if(typeof value==='string')return boundedString(value,maxStringLength,{redactSensitive});
|
|
201
|
+
if(typeof value==='number')return Number.isFinite(value)
|
|
202
|
+
?value
|
|
203
|
+
:taggedSnapshot('number',[['value',String(value)]]);
|
|
204
|
+
if(typeof value==='bigint')return taggedSnapshot('bigint',[[
|
|
205
|
+
'value',boundedString(value.toString(),maxStringLength,{redactSensitive})
|
|
206
|
+
]]);
|
|
207
|
+
if(typeof value==='symbol')return taggedSnapshot('symbol',[[
|
|
208
|
+
'value',value.description===undefined
|
|
209
|
+
?null
|
|
210
|
+
:boundedString(value.description,maxStringLength,{redactSensitive})
|
|
211
|
+
]]);
|
|
212
|
+
if(typeof value==='function'){
|
|
213
|
+
const name=safeDataString(value,'name');
|
|
214
|
+
return taggedSnapshot('function',[[
|
|
215
|
+
'name',name?boundedString(name,maxStringLength,{redactSensitive}):null
|
|
216
|
+
]]);
|
|
217
|
+
}
|
|
218
|
+
if(depth>=maxDepth)return taggedSnapshot('depth-limit',[[
|
|
219
|
+
'path',boundedString(path,maxStringLength,{redactSensitive})
|
|
220
|
+
]]);
|
|
221
|
+
if(seen.has(value))return snapshotObject([[
|
|
222
|
+
'$ref',boundedString(seen.get(value),maxStringLength,{redactSensitive})
|
|
223
|
+
]]);
|
|
224
|
+
seen.set(value,path);
|
|
225
|
+
|
|
226
|
+
const next=(item,itemKey,itemPath)=>{
|
|
227
|
+
try{
|
|
228
|
+
return snapshot(item,{
|
|
229
|
+
redactSensitive,captureStacks,maxDepth,maxEntries,maxStringLength,
|
|
230
|
+
key:itemKey,
|
|
231
|
+
path:boundedString(itemPath,maxStringLength,{redactSensitive}),
|
|
232
|
+
depth:depth+1,
|
|
233
|
+
seen
|
|
234
|
+
});
|
|
235
|
+
}catch(error){
|
|
236
|
+
return snapshotFailure(error,{
|
|
237
|
+
redactSensitive,maxStringLength,
|
|
238
|
+
path:itemPath
|
|
239
|
+
});
|
|
240
|
+
}
|
|
241
|
+
};
|
|
242
|
+
if(value instanceof Date)return taggedSnapshot('date',[['value',DATE_TO_ISO.call(value)]]);
|
|
243
|
+
if(value instanceof RegExp)return taggedSnapshot('regexp',[
|
|
244
|
+
['source',boundedString(
|
|
245
|
+
REGEXP_SOURCE_GETTER.call(value),maxStringLength,{redactSensitive}
|
|
246
|
+
)],
|
|
247
|
+
['flags',regexpFlags(value)]
|
|
248
|
+
]);
|
|
249
|
+
if(value instanceof Error){
|
|
250
|
+
const name=safeDataString(value,'name',{fallback:'Error',inherited:true});
|
|
251
|
+
const message=safeDataString(value,'message');
|
|
252
|
+
const stack=safeDataString(value,'stack');
|
|
253
|
+
const entries=[
|
|
254
|
+
['name',boundedString(name||'Error',maxStringLength,{redactSensitive})],
|
|
255
|
+
['message',boundedString(message,maxStringLength,{redactSensitive})],
|
|
256
|
+
['stack',captureStacks&&stack
|
|
257
|
+
?boundedString(stack,maxStringLength,{redactSensitive})
|
|
258
|
+
:null]
|
|
259
|
+
];
|
|
260
|
+
let causeDescriptor;
|
|
261
|
+
try{causeDescriptor=Object.getOwnPropertyDescriptor(value,'cause');}catch{}
|
|
262
|
+
if(causeDescriptor){
|
|
263
|
+
entries.push(['cause','value' in causeDescriptor
|
|
264
|
+
?next(causeDescriptor.value,'cause',`${path}.cause`)
|
|
265
|
+
:taggedSnapshot('unreadable',[['error','Accessor properties are not evaluated.']])]);
|
|
266
|
+
}
|
|
267
|
+
return taggedSnapshot('error',entries);
|
|
268
|
+
}
|
|
269
|
+
if(Array.isArray(value)){
|
|
270
|
+
const result=[];
|
|
271
|
+
const limit=Math.min(value.length,maxEntries);
|
|
272
|
+
for(let index=0;index<limit;index+=1){
|
|
273
|
+
const descriptor=Object.getOwnPropertyDescriptor(value,String(index));
|
|
274
|
+
const item=descriptor&&'value' in descriptor
|
|
275
|
+
?descriptor.value
|
|
276
|
+
:taggedSnapshot('unreadable',[['error','Accessor properties are not evaluated.']]);
|
|
277
|
+
result.push(next(item,String(index),`${path}[${index}]`));
|
|
278
|
+
}
|
|
279
|
+
if(value.length>maxEntries){
|
|
280
|
+
result.push(taggedSnapshot('entries-truncated',[
|
|
281
|
+
['omitted',value.length-maxEntries]
|
|
282
|
+
]));
|
|
283
|
+
}
|
|
284
|
+
return result;
|
|
285
|
+
}
|
|
286
|
+
if(value instanceof Map){
|
|
287
|
+
const entries=[];
|
|
288
|
+
let index=0;
|
|
289
|
+
for(const [mapKey,item] of MAP_ENTRIES.call(value)){
|
|
290
|
+
if(index>=maxEntries)break;
|
|
291
|
+
entries.push([
|
|
292
|
+
next(mapKey,'mapKey',`${path}.entries[${index}].key`),
|
|
293
|
+
next(item,'value',`${path}.entries[${index}].value`)
|
|
294
|
+
]);
|
|
295
|
+
index+=1;
|
|
296
|
+
}
|
|
297
|
+
const size=MAP_SIZE_GETTER.call(value);
|
|
298
|
+
return taggedSnapshot('map',[
|
|
299
|
+
['entries',entries],
|
|
300
|
+
...(size>index?[['omitted',size-index]]:[])
|
|
301
|
+
]);
|
|
302
|
+
}
|
|
303
|
+
if(value instanceof Set){
|
|
304
|
+
const values=[];
|
|
305
|
+
let index=0;
|
|
306
|
+
for(const item of SET_VALUES.call(value)){
|
|
307
|
+
if(index>=maxEntries)break;
|
|
308
|
+
values.push(next(item,String(index),`${path}.values[${index}]`));
|
|
309
|
+
index+=1;
|
|
310
|
+
}
|
|
311
|
+
const size=SET_SIZE_GETTER.call(value);
|
|
312
|
+
return taggedSnapshot('set',[
|
|
313
|
+
['values',values],
|
|
314
|
+
...(size>index?[['omitted',size-index]]:[])
|
|
315
|
+
]);
|
|
316
|
+
}
|
|
317
|
+
if(ArrayBuffer.isView(value)){
|
|
318
|
+
const dataView=value instanceof DataView;
|
|
319
|
+
const length=dataView
|
|
320
|
+
?DATA_VIEW_BYTE_LENGTH_GETTER.call(value)
|
|
321
|
+
:TYPED_ARRAY_LENGTH_GETTER.call(value);
|
|
322
|
+
let values;
|
|
323
|
+
let type='DataView';
|
|
324
|
+
if(dataView){
|
|
325
|
+
const buffer=DATA_VIEW_BUFFER_GETTER.call(value);
|
|
326
|
+
const byteOffset=DATA_VIEW_BYTE_OFFSET_GETTER.call(value);
|
|
327
|
+
values=Array.from(new Uint8Array(buffer,byteOffset,Math.min(length,maxEntries)));
|
|
328
|
+
}else{
|
|
329
|
+
type=TYPED_ARRAY_TAG_GETTER.call(value)??'TypedArray';
|
|
330
|
+
values=[];
|
|
331
|
+
const iterator=TYPED_ARRAY_VALUES.call(value);
|
|
332
|
+
while(values.length<Math.min(length,maxEntries)){
|
|
333
|
+
const step=iterator.next();
|
|
334
|
+
if(step.done)break;
|
|
335
|
+
values.push(step.value);
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
return taggedSnapshot(type,[
|
|
339
|
+
['values',values],
|
|
340
|
+
...(length>maxEntries?[['omitted',length-maxEntries]]:[])
|
|
341
|
+
]);
|
|
342
|
+
}
|
|
343
|
+
if(value instanceof ArrayBuffer){
|
|
344
|
+
const bytes=new Uint8Array(value);
|
|
345
|
+
return taggedSnapshot('ArrayBuffer',[
|
|
346
|
+
['values',Array.from(bytes.subarray(0,maxEntries))],
|
|
347
|
+
...(bytes.length>maxEntries?[['omitted',bytes.length-maxEntries]]:[])
|
|
348
|
+
]);
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
const result=snapshotObject();
|
|
352
|
+
const properties=Reflect.ownKeys(value).filter(property=>{
|
|
353
|
+
if(typeof property!=='string')return false;
|
|
354
|
+
const descriptor=Object.getOwnPropertyDescriptor(value,property);
|
|
355
|
+
return descriptor?.enumerable===true;
|
|
356
|
+
});
|
|
357
|
+
for(let index=0;index<Math.min(properties.length,maxEntries);index+=1){
|
|
358
|
+
const property=properties[index];
|
|
359
|
+
const descriptor=Object.getOwnPropertyDescriptor(value,property);
|
|
360
|
+
const item=descriptor&&'value' in descriptor
|
|
361
|
+
?descriptor.value
|
|
362
|
+
:taggedSnapshot('unreadable',[['error','Accessor properties are not evaluated.']]);
|
|
363
|
+
let outputKey=boundedString(property,maxStringLength,{redactSensitive:false});
|
|
364
|
+
if(Object.hasOwn(result,outputKey))outputKey=`$arcaneCollision:${String(index)}`;
|
|
365
|
+
Object.defineProperty(result,outputKey,{
|
|
366
|
+
value:next(item,property,`${path}.${property}`),
|
|
367
|
+
enumerable:true,
|
|
368
|
+
configurable:true,
|
|
369
|
+
writable:true
|
|
370
|
+
});
|
|
371
|
+
}
|
|
372
|
+
if(properties.length>maxEntries){
|
|
373
|
+
Object.defineProperty(result,'$arcaneTruncated',{
|
|
374
|
+
value:snapshotObject([['omitted',properties.length-maxEntries]]),
|
|
375
|
+
enumerable:true,
|
|
376
|
+
configurable:true,
|
|
377
|
+
writable:true
|
|
378
|
+
});
|
|
379
|
+
}
|
|
380
|
+
return result;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
function snapshotFailure(error,{
|
|
384
|
+
redactSensitive=true,
|
|
385
|
+
maxStringLength=DEFAULT_MAX_SNAPSHOT_STRING_LENGTH,
|
|
386
|
+
path='$'
|
|
387
|
+
}={}){
|
|
388
|
+
const name=safeDataString(error,'name',{fallback:'Error',inherited:true})||'Error';
|
|
389
|
+
return taggedSnapshot('snapshot-failed',[
|
|
390
|
+
['name',boundedString(name,maxStringLength,{redactSensitive})],
|
|
391
|
+
['message',safeErrorText(error,maxStringLength,{redactSensitive})],
|
|
392
|
+
['path',boundedString(path,maxStringLength,{redactSensitive})]
|
|
393
|
+
]);
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
function deepFreeze(value,seen=new WeakSet()){
|
|
397
|
+
if(!value||typeof value!=='object'||seen.has(value))return value;
|
|
398
|
+
seen.add(value);
|
|
399
|
+
for(const property of Reflect.ownKeys(value)){
|
|
400
|
+
const descriptor=Object.getOwnPropertyDescriptor(value,property);
|
|
401
|
+
if(descriptor&&'value' in descriptor)deepFreeze(descriptor.value,seen);
|
|
402
|
+
}
|
|
403
|
+
return Object.freeze(value);
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
function frozenSnapshot(value,options){
|
|
407
|
+
try{return deepFreeze(snapshot(value,options));}
|
|
408
|
+
catch(error){return deepFreeze(snapshotFailure(error,options));}
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
function frozenErrorSnapshot(value,options){
|
|
412
|
+
const captured=frozenSnapshot(value,options);
|
|
413
|
+
if(captured&&typeof captured==='object'&&!Array.isArray(captured))return captured;
|
|
414
|
+
return deepFreeze(taggedSnapshot('thrown',[['value',captured]]));
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
function normalizeMetadata(metadata){
|
|
418
|
+
if(metadata===undefined)return {};
|
|
419
|
+
if(metadata===null||typeof metadata!=='object'||Array.isArray(metadata)){
|
|
420
|
+
throw new TypeError('Event instrumentation metadata must be an object.');
|
|
421
|
+
}
|
|
422
|
+
return metadata;
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
function abortError(reason){
|
|
426
|
+
if(reason instanceof Error)return reason;
|
|
427
|
+
const error=new Error(reason===undefined?'Playback was aborted.':String(reason));
|
|
428
|
+
error.name='AbortError';
|
|
429
|
+
return error;
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
function wait(milliseconds,signal){
|
|
433
|
+
if(milliseconds<=0)return Promise.resolve();
|
|
434
|
+
return new Promise((resolve,reject)=>{
|
|
435
|
+
if(signal?.aborted){
|
|
436
|
+
reject(abortError(signal.reason));
|
|
437
|
+
return;
|
|
438
|
+
}
|
|
439
|
+
const timer=setTimeout(done,milliseconds);
|
|
440
|
+
function done(){
|
|
441
|
+
signal?.removeEventListener?.('abort',cancel);
|
|
442
|
+
resolve();
|
|
443
|
+
}
|
|
444
|
+
function cancel(){
|
|
445
|
+
clearTimeout(timer);
|
|
446
|
+
signal?.removeEventListener?.('abort',cancel);
|
|
447
|
+
reject(abortError(signal.reason));
|
|
448
|
+
}
|
|
449
|
+
signal?.addEventListener?.('abort',cancel,{once:true});
|
|
450
|
+
});
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
function invalidStack(label){
|
|
454
|
+
throw new TypeError(`${label} is invalid.`);
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
function exactDataObject(value,expectedKeys,label){
|
|
458
|
+
try{
|
|
459
|
+
if(!value||typeof value!=='object'||Array.isArray(value))invalidStack(label);
|
|
460
|
+
const prototype=Object.getPrototypeOf(value);
|
|
461
|
+
if(prototype!==Object.prototype&&prototype!==null)invalidStack(label);
|
|
462
|
+
const keys=Reflect.ownKeys(value);
|
|
463
|
+
if(keys.length!==expectedKeys.length||keys.some(key=>typeof key!=='string')){
|
|
464
|
+
invalidStack(label);
|
|
465
|
+
}
|
|
466
|
+
const expected=new Set(expectedKeys);
|
|
467
|
+
if(keys.some(key=>!expected.has(key)))invalidStack(label);
|
|
468
|
+
const result=dataObject();
|
|
469
|
+
for(const key of expectedKeys){
|
|
470
|
+
const descriptor=Object.getOwnPropertyDescriptor(value,key);
|
|
471
|
+
if(!descriptor||descriptor.enumerable!==true||!('value' in descriptor)){
|
|
472
|
+
invalidStack(label);
|
|
473
|
+
}
|
|
474
|
+
Object.defineProperty(result,key,{
|
|
475
|
+
value:descriptor.value,
|
|
476
|
+
enumerable:true,
|
|
477
|
+
configurable:true,
|
|
478
|
+
writable:true
|
|
479
|
+
});
|
|
480
|
+
}
|
|
481
|
+
return result;
|
|
482
|
+
}catch(error){
|
|
483
|
+
if(error instanceof TypeError&&error.message===`${label} is invalid.`)throw error;
|
|
484
|
+
throw new TypeError(`${label} is invalid.`,{cause:error});
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
function denseArrayValues(value,label,{maxLength}={}){
|
|
489
|
+
try{
|
|
490
|
+
if(!Array.isArray(value)||!Number.isSafeInteger(value.length)||value.length<0){
|
|
491
|
+
invalidStack(label);
|
|
492
|
+
}
|
|
493
|
+
if(maxLength!==undefined&&value.length>maxLength)invalidStack(label);
|
|
494
|
+
const keys=Reflect.ownKeys(value);
|
|
495
|
+
if(keys.some(key=>typeof key!=='string'))invalidStack(label);
|
|
496
|
+
const expected=new Set(['length']);
|
|
497
|
+
for(let index=0;index<value.length;index+=1)expected.add(String(index));
|
|
498
|
+
if(keys.length!==expected.size||keys.some(key=>!expected.has(key)))invalidStack(label);
|
|
499
|
+
const result=[];
|
|
500
|
+
for(let index=0;index<value.length;index+=1){
|
|
501
|
+
const descriptor=Object.getOwnPropertyDescriptor(value,String(index));
|
|
502
|
+
if(!descriptor||descriptor.enumerable!==true||!('value' in descriptor)){
|
|
503
|
+
invalidStack(label);
|
|
504
|
+
}
|
|
505
|
+
result.push(descriptor.value);
|
|
506
|
+
}
|
|
507
|
+
return result;
|
|
508
|
+
}catch(error){
|
|
509
|
+
if(error instanceof TypeError&&error.message===`${label} is invalid.`)throw error;
|
|
510
|
+
throw new TypeError(`${label} is invalid.`,{cause:error});
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
function cloneImportedValue(value,{
|
|
515
|
+
maxDepth,
|
|
516
|
+
maxEntries,
|
|
517
|
+
maxStringLength,
|
|
518
|
+
path='$',
|
|
519
|
+
depth=0,
|
|
520
|
+
seen=new WeakSet()
|
|
521
|
+
}){
|
|
522
|
+
if(value===null||typeof value==='boolean')return value;
|
|
523
|
+
if(typeof value==='number'){
|
|
524
|
+
if(!Number.isFinite(value))invalidStack(`Event stack value at ${path}`);
|
|
525
|
+
return value;
|
|
526
|
+
}
|
|
527
|
+
if(typeof value==='string'){
|
|
528
|
+
if(value.length>maxStringLength+1
|
|
529
|
+
||(value.length===maxStringLength+1&&!value.endsWith('…'))){
|
|
530
|
+
invalidStack(`Event stack value at ${path}`);
|
|
531
|
+
}
|
|
532
|
+
return value;
|
|
533
|
+
}
|
|
534
|
+
if(!value||typeof value!=='object')invalidStack(`Event stack value at ${path}`);
|
|
535
|
+
if(depth>maxDepth||seen.has(value))invalidStack(`Event stack value at ${path}`);
|
|
536
|
+
seen.add(value);
|
|
537
|
+
|
|
538
|
+
if(Array.isArray(value)){
|
|
539
|
+
const items=denseArrayValues(value,`Event stack array at ${path}`,{
|
|
540
|
+
maxLength:maxEntries+1
|
|
541
|
+
});
|
|
542
|
+
return items.map((item,index)=>cloneImportedValue(item,{
|
|
543
|
+
maxDepth,maxEntries,maxStringLength,
|
|
544
|
+
path:`${path}[${String(index)}]`,
|
|
545
|
+
depth:depth+1,
|
|
546
|
+
seen
|
|
547
|
+
}));
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
let prototype;
|
|
551
|
+
let keys;
|
|
552
|
+
try{
|
|
553
|
+
prototype=Object.getPrototypeOf(value);
|
|
554
|
+
keys=Reflect.ownKeys(value);
|
|
555
|
+
}catch(error){
|
|
556
|
+
throw new TypeError(`Event stack value at ${path} is invalid.`,{cause:error});
|
|
557
|
+
}
|
|
558
|
+
if((prototype!==Object.prototype&&prototype!==null)
|
|
559
|
+
||keys.some(key=>typeof key!=='string')
|
|
560
|
+
||keys.length>maxEntries+1){
|
|
561
|
+
invalidStack(`Event stack value at ${path}`);
|
|
562
|
+
}
|
|
563
|
+
const result=dataObject();
|
|
564
|
+
for(const key of keys){
|
|
565
|
+
if(key.length>maxStringLength+1
|
|
566
|
+
||(key.length===maxStringLength+1&&!key.endsWith('…'))){
|
|
567
|
+
invalidStack(`Event stack value at ${path}`);
|
|
568
|
+
}
|
|
569
|
+
let descriptor;
|
|
570
|
+
try{descriptor=Object.getOwnPropertyDescriptor(value,key);}catch(error){
|
|
571
|
+
throw new TypeError(`Event stack value at ${path} is invalid.`,{cause:error});
|
|
572
|
+
}
|
|
573
|
+
if(!descriptor||descriptor.enumerable!==true||!('value' in descriptor)){
|
|
574
|
+
invalidStack(`Event stack value at ${path}`);
|
|
575
|
+
}
|
|
576
|
+
Object.defineProperty(result,key,{
|
|
577
|
+
value:cloneImportedValue(descriptor.value,{
|
|
578
|
+
maxDepth,maxEntries,maxStringLength,
|
|
579
|
+
path:`${path}.${key}`,
|
|
580
|
+
depth:depth+1,
|
|
581
|
+
seen
|
|
582
|
+
}),
|
|
583
|
+
enumerable:true,
|
|
584
|
+
configurable:true,
|
|
585
|
+
writable:true
|
|
586
|
+
});
|
|
587
|
+
}
|
|
588
|
+
return result;
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
function canonicalTimestamp(value,label){
|
|
592
|
+
if(typeof value!=='string')invalidStack(label);
|
|
593
|
+
const milliseconds=Date.parse(value);
|
|
594
|
+
if(!Number.isFinite(milliseconds)||new Date(milliseconds).toISOString()!==value){
|
|
595
|
+
invalidStack(label);
|
|
596
|
+
}
|
|
597
|
+
return milliseconds;
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
function boundedRecordString(value,label,maxStringLength,{nullable=false,empty=true}={}){
|
|
601
|
+
if(nullable&&value===null)return null;
|
|
602
|
+
if(typeof value!=='string'||(!empty&&!value)
|
|
603
|
+
||value.length>maxStringLength+1
|
|
604
|
+
||(value.length===maxStringLength+1&&!value.endsWith('…'))){
|
|
605
|
+
invalidStack(label);
|
|
606
|
+
}
|
|
607
|
+
return value;
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
function validateRecord(record,index,{
|
|
611
|
+
sessionId,
|
|
612
|
+
maxDepth,
|
|
613
|
+
maxEntries,
|
|
614
|
+
maxStringLength
|
|
615
|
+
}){
|
|
616
|
+
const label=`Event stack record ${String(index)}`;
|
|
617
|
+
const fields=exactDataObject(record,RECORD_KEYS,label);
|
|
618
|
+
if(fields.protocol!==ARCANE_EVENT_STACK_PROTOCOL||fields.sessionId!==sessionId){
|
|
619
|
+
invalidStack(label);
|
|
620
|
+
}
|
|
621
|
+
if(!Number.isSafeInteger(fields.sequence)||fields.sequence<1
|
|
622
|
+
||fields.id!==`${sessionId}:${String(fields.sequence)}`){
|
|
623
|
+
invalidStack(label);
|
|
624
|
+
}
|
|
625
|
+
const timestampMs=canonicalTimestamp(fields.timestamp,`${label} timestamp`);
|
|
626
|
+
if(typeof fields.monotonicMs!=='number'||!Number.isFinite(fields.monotonicMs)
|
|
627
|
+
||fields.monotonicMs<0){
|
|
628
|
+
invalidStack(`${label} monotonic timing`);
|
|
629
|
+
}
|
|
630
|
+
boundedRecordString(fields.type,`${label} type`,maxStringLength);
|
|
631
|
+
boundedRecordString(fields.source,`${label} source`,maxStringLength,{empty:false});
|
|
632
|
+
boundedRecordString(fields.category,`${label} category`,maxStringLength,{
|
|
633
|
+
nullable:true,empty:false
|
|
634
|
+
});
|
|
635
|
+
boundedRecordString(fields.correlationId,`${label} correlation`,maxStringLength,{
|
|
636
|
+
nullable:true,empty:false
|
|
637
|
+
});
|
|
638
|
+
boundedRecordString(fields.causationId,`${label} causation`,maxStringLength,{
|
|
639
|
+
nullable:true,empty:false
|
|
640
|
+
});
|
|
641
|
+
if(fields.parentSequence!==null
|
|
642
|
+
&&(!Number.isSafeInteger(fields.parentSequence)||fields.parentSequence<1
|
|
643
|
+
||fields.parentSequence>=fields.sequence)){
|
|
644
|
+
invalidStack(`${label} parent`);
|
|
645
|
+
}
|
|
646
|
+
if(!Number.isSafeInteger(fields.depth)||fields.depth<0
|
|
647
|
+
||(fields.parentSequence===null)!==(fields.depth===0)){
|
|
648
|
+
invalidStack(`${label} depth`);
|
|
649
|
+
}
|
|
650
|
+
boundedRecordString(fields.stack,`${label} stack`,maxStringLength,{nullable:true});
|
|
651
|
+
if(!EVENT_STATUSES.has(fields.status))invalidStack(`${label} status`);
|
|
652
|
+
|
|
653
|
+
const payloadValues=denseArrayValues(fields.payload,`${label} payload`,{
|
|
654
|
+
maxLength:maxEntries+1
|
|
655
|
+
});
|
|
656
|
+
const payload=payloadValues.map((item,payloadIndex)=>cloneImportedValue(item,{
|
|
657
|
+
maxDepth,maxEntries,maxStringLength,
|
|
658
|
+
path:`$.events[${String(index)}].payload[${String(payloadIndex)}]`,
|
|
659
|
+
depth:1,
|
|
660
|
+
seen:new WeakSet()
|
|
661
|
+
}));
|
|
662
|
+
const metadata=cloneImportedValue(fields.metadata,{
|
|
663
|
+
maxDepth,maxEntries,maxStringLength,
|
|
664
|
+
path:`$.events[${String(index)}].metadata`,
|
|
665
|
+
depth:0,
|
|
666
|
+
seen:new WeakSet()
|
|
667
|
+
});
|
|
668
|
+
if(!metadata||typeof metadata!=='object'||Array.isArray(metadata)){
|
|
669
|
+
invalidStack(`${label} metadata`);
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
let completedAt=null;
|
|
673
|
+
let durationMs=null;
|
|
674
|
+
let error=null;
|
|
675
|
+
if(fields.status==='dispatching'){
|
|
676
|
+
if(fields.completedAt!==null||fields.durationMs!==null||fields.error!==null){
|
|
677
|
+
invalidStack(`${label} dispatch timing`);
|
|
678
|
+
}
|
|
679
|
+
}else{
|
|
680
|
+
const completedAtMs=canonicalTimestamp(fields.completedAt,`${label} completion timestamp`);
|
|
681
|
+
if(completedAtMs<timestampMs
|
|
682
|
+
||typeof fields.durationMs!=='number'||!Number.isFinite(fields.durationMs)
|
|
683
|
+
||fields.durationMs<0){
|
|
684
|
+
invalidStack(`${label} completion timing`);
|
|
685
|
+
}
|
|
686
|
+
completedAt=fields.completedAt;
|
|
687
|
+
durationMs=fields.durationMs;
|
|
688
|
+
if(fields.status==='completed'){
|
|
689
|
+
if(fields.error!==null)invalidStack(`${label} error`);
|
|
690
|
+
}else{
|
|
691
|
+
error=cloneImportedValue(fields.error,{
|
|
692
|
+
maxDepth,maxEntries,maxStringLength,
|
|
693
|
+
path:`$.events[${String(index)}].error`,
|
|
694
|
+
depth:0,
|
|
695
|
+
seen:new WeakSet()
|
|
696
|
+
});
|
|
697
|
+
if(!error||typeof error!=='object'||Array.isArray(error)){
|
|
698
|
+
invalidStack(`${label} error`);
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
return dataObject([
|
|
704
|
+
['protocol',ARCANE_EVENT_STACK_PROTOCOL],
|
|
705
|
+
['sessionId',sessionId],
|
|
706
|
+
['id',fields.id],
|
|
707
|
+
['sequence',fields.sequence],
|
|
708
|
+
['timestamp',fields.timestamp],
|
|
709
|
+
['monotonicMs',fields.monotonicMs],
|
|
710
|
+
['type',fields.type],
|
|
711
|
+
['source',fields.source],
|
|
712
|
+
['category',fields.category],
|
|
713
|
+
['correlationId',fields.correlationId],
|
|
714
|
+
['causationId',fields.causationId],
|
|
715
|
+
['parentSequence',fields.parentSequence],
|
|
716
|
+
['depth',fields.depth],
|
|
717
|
+
['stack',fields.stack],
|
|
718
|
+
['payload',payload],
|
|
719
|
+
['metadata',metadata],
|
|
720
|
+
['status',fields.status],
|
|
721
|
+
['completedAt',completedAt],
|
|
722
|
+
['durationMs',durationMs],
|
|
723
|
+
['error',error]
|
|
724
|
+
]);
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
export function parseEventStack(source,{
|
|
728
|
+
maxEvents=DEFAULT_MAX_EVENTS,
|
|
729
|
+
maxSnapshotDepth=DEFAULT_MAX_SNAPSHOT_DEPTH,
|
|
730
|
+
maxSnapshotEntries=DEFAULT_MAX_SNAPSHOT_ENTRIES,
|
|
731
|
+
maxSnapshotStringLength=DEFAULT_MAX_SNAPSHOT_STRING_LENGTH
|
|
732
|
+
}={}){
|
|
733
|
+
if(!Number.isSafeInteger(maxEvents)||maxEvents<1
|
|
734
|
+
||!Number.isSafeInteger(maxSnapshotDepth)||maxSnapshotDepth<1
|
|
735
|
+
||!Number.isSafeInteger(maxSnapshotEntries)||maxSnapshotEntries<1
|
|
736
|
+
||!Number.isSafeInteger(maxSnapshotStringLength)
|
|
737
|
+
||maxSnapshotStringLength<MIN_SNAPSHOT_STRING_LENGTH){
|
|
738
|
+
throw new RangeError(
|
|
739
|
+
'Event stack import limits must be positive safe integers and the string limit must be at least 64.'
|
|
740
|
+
);
|
|
741
|
+
}
|
|
742
|
+
let document=source;
|
|
743
|
+
if(typeof source==='string'){
|
|
744
|
+
try{document=JSON.parse(source);}catch(error){
|
|
745
|
+
throw new TypeError('The event stack is not valid JSON.',{cause:error});
|
|
746
|
+
}
|
|
747
|
+
}
|
|
748
|
+
const fields=exactDataObject(document,DOCUMENT_KEYS,'The event stack document');
|
|
749
|
+
if(fields.protocol!==ARCANE_EVENT_STACK_PROTOCOL
|
|
750
|
+
||typeof fields.sessionId!=='string'||!fields.sessionId
|
|
751
|
+
||fields.sessionId.length>256){
|
|
752
|
+
invalidStack('The event stack document');
|
|
753
|
+
}
|
|
754
|
+
canonicalTimestamp(fields.createdAt,'The event stack creation timestamp');
|
|
755
|
+
const sourceEvents=denseArrayValues(fields.events,'The event stack events',{
|
|
756
|
+
maxLength:maxEvents+1
|
|
757
|
+
});
|
|
758
|
+
let previous=0;
|
|
759
|
+
const bySequence=new Map();
|
|
760
|
+
const events=sourceEvents.map((record,index)=>{
|
|
761
|
+
const validated=validateRecord(record,index,{
|
|
762
|
+
sessionId:fields.sessionId,
|
|
763
|
+
maxDepth:maxSnapshotDepth,
|
|
764
|
+
maxEntries:maxSnapshotEntries,
|
|
765
|
+
maxStringLength:maxSnapshotStringLength
|
|
766
|
+
});
|
|
767
|
+
if(validated.sequence<=previous){
|
|
768
|
+
throw new TypeError('Event stack sequences must be strictly increasing.');
|
|
769
|
+
}
|
|
770
|
+
if(validated.parentSequence!==null){
|
|
771
|
+
const parent=bySequence.get(validated.parentSequence);
|
|
772
|
+
if(!parent||validated.depth!==parent.depth+1||validated.causationId===null){
|
|
773
|
+
invalidStack(`Event stack record ${String(index)} parent`);
|
|
774
|
+
}
|
|
775
|
+
}
|
|
776
|
+
previous=validated.sequence;
|
|
777
|
+
bySequence.set(validated.sequence,validated);
|
|
778
|
+
return validated;
|
|
779
|
+
});
|
|
780
|
+
const overflowIndexes=[];
|
|
781
|
+
for(let index=0;index<events.length;index+=1){
|
|
782
|
+
if(events[index].type===TIME_TRAVEL_OVERFLOW_EVENT)overflowIndexes.push(index);
|
|
783
|
+
}
|
|
784
|
+
if(overflowIndexes.length>1
|
|
785
|
+
||(overflowIndexes.length===1&&overflowIndexes[0]!==events.length-1)
|
|
786
|
+
||(events.length>maxEvents
|
|
787
|
+
&&(events.length!==maxEvents+1||overflowIndexes[0]!==events.length-1))){
|
|
788
|
+
invalidStack('The event stack overflow history');
|
|
789
|
+
}
|
|
790
|
+
if(overflowIndexes.length===1){
|
|
791
|
+
const overflow=events.at(-1);
|
|
792
|
+
const payload=denseArrayValues(
|
|
793
|
+
overflow.payload,'The event stack overflow payload',{maxLength:1}
|
|
794
|
+
);
|
|
795
|
+
if(payload.length!==1)invalidStack('The event stack overflow payload');
|
|
796
|
+
const counts=exactDataObject(
|
|
797
|
+
payload[0],['maxEvents','retainedEvents'],'The event stack overflow payload counts'
|
|
798
|
+
);
|
|
799
|
+
const metadata=exactDataObject(
|
|
800
|
+
overflow.metadata,['maxEvents'],'The event stack overflow metadata'
|
|
801
|
+
);
|
|
802
|
+
const retainedEvents=events.length-1;
|
|
803
|
+
if(overflow.source!=='event-manager'||overflow.category!=='overflow'
|
|
804
|
+
||overflow.correlationId!==null||overflow.causationId!==null
|
|
805
|
+
||overflow.parentSequence!==null||overflow.depth!==0||overflow.stack!==null
|
|
806
|
+
||overflow.status!=='completed'||overflow.completedAt!==overflow.timestamp
|
|
807
|
+
||overflow.durationMs!==0||overflow.error!==null
|
|
808
|
+
||!Number.isSafeInteger(counts.maxEvents)||counts.maxEvents<1
|
|
809
|
+
||!Number.isSafeInteger(counts.retainedEvents)||counts.retainedEvents<1
|
|
810
|
+
||counts.maxEvents!==counts.retainedEvents
|
|
811
|
+
||metadata.maxEvents!==counts.maxEvents
|
|
812
|
+
||retainedEvents!==counts.retainedEvents
|
|
813
|
+
||overflow.sequence!==retainedEvents+1
|
|
814
|
+
||counts.maxEvents>maxEvents){
|
|
815
|
+
invalidStack('The event stack overflow record');
|
|
816
|
+
}
|
|
817
|
+
}
|
|
818
|
+
return deepFreeze(dataObject([
|
|
819
|
+
['protocol',ARCANE_EVENT_STACK_PROTOCOL],
|
|
820
|
+
['sessionId',fields.sessionId],
|
|
821
|
+
['createdAt',fields.createdAt],
|
|
822
|
+
['events',events]
|
|
823
|
+
]));
|
|
824
|
+
}
|
|
825
|
+
|
|
826
|
+
export class EventManager{
|
|
827
|
+
#activeDispatch=[];
|
|
828
|
+
#bus=new EventPubSub();
|
|
829
|
+
#captureStacks;
|
|
830
|
+
#clock;
|
|
831
|
+
#cursor=0;
|
|
832
|
+
#domInstrumentation=null;
|
|
833
|
+
#history=[];
|
|
834
|
+
#maxEvents;
|
|
835
|
+
#maxSnapshotDepth;
|
|
836
|
+
#maxSnapshotEntries;
|
|
837
|
+
#maxSnapshotStringLength;
|
|
838
|
+
#now;
|
|
839
|
+
#overflowed=false;
|
|
840
|
+
#redactSensitive;
|
|
841
|
+
#replaying=false;
|
|
842
|
+
#sequence=0;
|
|
843
|
+
#sessionId;
|
|
844
|
+
#timeTravelEnabled=false;
|
|
845
|
+
|
|
846
|
+
constructor({
|
|
847
|
+
timeTravel=false,
|
|
848
|
+
dom=null,
|
|
849
|
+
captureStacks=false,
|
|
850
|
+
redactSensitive=true,
|
|
851
|
+
maxEvents=DEFAULT_MAX_EVENTS,
|
|
852
|
+
maxSnapshotDepth=DEFAULT_MAX_SNAPSHOT_DEPTH,
|
|
853
|
+
maxSnapshotEntries=DEFAULT_MAX_SNAPSHOT_ENTRIES,
|
|
854
|
+
maxSnapshotStringLength=DEFAULT_MAX_SNAPSHOT_STRING_LENGTH,
|
|
855
|
+
clock=()=>new Date(),
|
|
856
|
+
now=defaultMonotonicClock,
|
|
857
|
+
sessionId=sessionIdentifier()
|
|
858
|
+
}={}){
|
|
859
|
+
if(typeof timeTravel!=='boolean'||typeof captureStacks!=='boolean'
|
|
860
|
+
||typeof redactSensitive!=='boolean'){
|
|
861
|
+
throw new TypeError('EventManager flags must be boolean values.');
|
|
862
|
+
}
|
|
863
|
+
if(typeof clock!=='function'||typeof now!=='function'){
|
|
864
|
+
throw new TypeError('EventManager clocks must be functions.');
|
|
865
|
+
}
|
|
866
|
+
if(!Number.isSafeInteger(maxEvents)||maxEvents<1
|
|
867
|
+
||!Number.isSafeInteger(maxSnapshotDepth)||maxSnapshotDepth<1
|
|
868
|
+
||!Number.isSafeInteger(maxSnapshotEntries)||maxSnapshotEntries<1
|
|
869
|
+
||!Number.isSafeInteger(maxSnapshotStringLength)
|
|
870
|
+
||maxSnapshotStringLength<MIN_SNAPSHOT_STRING_LENGTH){
|
|
871
|
+
throw new RangeError(
|
|
872
|
+
'EventManager retention limits must be positive safe integers and the string limit must be at least 64.'
|
|
873
|
+
);
|
|
874
|
+
}
|
|
875
|
+
if(typeof sessionId!=='string'||!sessionId||sessionId.length>256){
|
|
876
|
+
throw new TypeError('EventManager sessionId must be a non-empty string.');
|
|
877
|
+
}
|
|
878
|
+
this.#captureStacks=captureStacks;
|
|
879
|
+
this.#redactSensitive=redactSensitive;
|
|
880
|
+
this.#maxEvents=maxEvents;
|
|
881
|
+
this.#maxSnapshotDepth=maxSnapshotDepth;
|
|
882
|
+
this.#maxSnapshotEntries=maxSnapshotEntries;
|
|
883
|
+
this.#maxSnapshotStringLength=maxSnapshotStringLength;
|
|
884
|
+
this.#clock=clock;
|
|
885
|
+
this.#now=now;
|
|
886
|
+
this.#sessionId=sessionId;
|
|
887
|
+
this.#timeTravelEnabled=timeTravel;
|
|
888
|
+
if(dom)this.attachDOM(dom?.root??dom,dom?.root?dom:{});
|
|
889
|
+
}
|
|
890
|
+
|
|
891
|
+
get list(){return this.#bus.list;}
|
|
892
|
+
get sessionId(){return this.#sessionId;}
|
|
893
|
+
get timeTravelEnabled(){return this.#timeTravelEnabled;}
|
|
894
|
+
get replaying(){return this.#replaying;}
|
|
895
|
+
get cursor(){return this.#cursor;}
|
|
896
|
+
get eventCount(){return this.#history.length;}
|
|
897
|
+
get maxEvents(){return this.#maxEvents;}
|
|
898
|
+
get overflowed(){return this.#overflowed;}
|
|
899
|
+
get history(){return Object.freeze([...this.#history]);}
|
|
900
|
+
get domInstrumentation(){return this.#domInstrumentation;}
|
|
901
|
+
|
|
902
|
+
on(type,handler,once=false){
|
|
903
|
+
this.#bus.on(type,handler,once);
|
|
904
|
+
return this;
|
|
905
|
+
}
|
|
906
|
+
|
|
907
|
+
once(type,handler){
|
|
908
|
+
this.#bus.once(type,handler);
|
|
909
|
+
return this;
|
|
910
|
+
}
|
|
911
|
+
|
|
912
|
+
off(type='*',handler='*'){
|
|
913
|
+
this.#bus.off(type,handler);
|
|
914
|
+
return this;
|
|
915
|
+
}
|
|
916
|
+
|
|
917
|
+
reset(){
|
|
918
|
+
this.#bus.reset();
|
|
919
|
+
return this;
|
|
920
|
+
}
|
|
921
|
+
|
|
922
|
+
emit(type,...payload){
|
|
923
|
+
return this.#dispatch(type,payload,{});
|
|
924
|
+
}
|
|
925
|
+
|
|
926
|
+
instrument(type,payload,metadata={}){
|
|
927
|
+
return this.#dispatch(type,[payload],normalizeMetadata(metadata));
|
|
928
|
+
}
|
|
929
|
+
|
|
930
|
+
forward(event,metadata={}){
|
|
931
|
+
if(!event||typeof event!=='object'||Array.isArray(event)||typeof event.type!=='string'){
|
|
932
|
+
throw new TypeError('Forwarded events must be objects with a string type.');
|
|
933
|
+
}
|
|
934
|
+
return this.instrument(event.type,event,metadata);
|
|
935
|
+
}
|
|
936
|
+
|
|
937
|
+
#dispatch(type,payload,metadata){
|
|
938
|
+
if(typeof type!=='string'){
|
|
939
|
+
this.#bus.emit(type,...payload);
|
|
940
|
+
return this;
|
|
941
|
+
}
|
|
942
|
+
let recording=this.#timeTravelEnabled&&!this.#replaying;
|
|
943
|
+
if(recording&&this.#history.length>=this.#maxEvents){
|
|
944
|
+
this.#recordOverflow();
|
|
945
|
+
recording=false;
|
|
946
|
+
}
|
|
947
|
+
let draft=null;
|
|
948
|
+
let startedMonotonic=null;
|
|
949
|
+
if(recording){
|
|
950
|
+
try{
|
|
951
|
+
const timestamp=clockDate(this.#clock).toISOString();
|
|
952
|
+
startedMonotonic=monotonicValue(this.#now);
|
|
953
|
+
const snapshotOptions={
|
|
954
|
+
redactSensitive:this.#redactSensitive,
|
|
955
|
+
captureStacks:this.#captureStacks,
|
|
956
|
+
maxDepth:this.#maxSnapshotDepth,
|
|
957
|
+
maxEntries:this.#maxSnapshotEntries,
|
|
958
|
+
maxStringLength:this.#maxSnapshotStringLength
|
|
959
|
+
};
|
|
960
|
+
const payloadSnapshot=frozenSnapshot(payload,snapshotOptions);
|
|
961
|
+
const metadataSnapshot=frozenSnapshot(metadata,snapshotOptions);
|
|
962
|
+
const safeMetadata=metadataSnapshot&&typeof metadataSnapshot==='object'
|
|
963
|
+
&&!Array.isArray(metadataSnapshot)
|
|
964
|
+
?metadataSnapshot
|
|
965
|
+
:snapshotObject();
|
|
966
|
+
const sequence=this.#sequence+1;
|
|
967
|
+
const parentSequence=this.#activeDispatch.at(-1)??null;
|
|
968
|
+
let stack=null;
|
|
969
|
+
if(this.#captureStacks){
|
|
970
|
+
try{
|
|
971
|
+
stack=boundedString(sourceStack()??'',this.#maxSnapshotStringLength,{
|
|
972
|
+
redactSensitive:this.#redactSensitive
|
|
973
|
+
});
|
|
974
|
+
}catch{
|
|
975
|
+
stack='[STACK CAPTURE FAILED]';
|
|
976
|
+
}
|
|
977
|
+
}
|
|
978
|
+
draft={
|
|
979
|
+
protocol:ARCANE_EVENT_STACK_PROTOCOL,
|
|
980
|
+
sessionId:this.#sessionId,
|
|
981
|
+
id:`${this.#sessionId}:${sequence}`,
|
|
982
|
+
sequence,
|
|
983
|
+
timestamp,
|
|
984
|
+
monotonicMs:startedMonotonic,
|
|
985
|
+
type:boundedString(type,this.#maxSnapshotStringLength,{
|
|
986
|
+
redactSensitive:this.#redactSensitive
|
|
987
|
+
}),
|
|
988
|
+
source:typeof safeMetadata.source==='string'&&safeMetadata.source
|
|
989
|
+
?boundedString(safeMetadata.source,this.#maxSnapshotStringLength,{
|
|
990
|
+
redactSensitive:this.#redactSensitive
|
|
991
|
+
})
|
|
992
|
+
:'application',
|
|
993
|
+
category:typeof safeMetadata.category==='string'&&safeMetadata.category
|
|
994
|
+
?boundedString(safeMetadata.category,this.#maxSnapshotStringLength,{
|
|
995
|
+
redactSensitive:this.#redactSensitive
|
|
996
|
+
})
|
|
997
|
+
:null,
|
|
998
|
+
correlationId:typeof safeMetadata.correlationId==='string'
|
|
999
|
+
&&safeMetadata.correlationId
|
|
1000
|
+
?boundedString(safeMetadata.correlationId,this.#maxSnapshotStringLength,{
|
|
1001
|
+
redactSensitive:this.#redactSensitive
|
|
1002
|
+
}):null,
|
|
1003
|
+
causationId:typeof safeMetadata.causationId==='string'
|
|
1004
|
+
&&safeMetadata.causationId
|
|
1005
|
+
?boundedString(safeMetadata.causationId,this.#maxSnapshotStringLength,{
|
|
1006
|
+
redactSensitive:this.#redactSensitive
|
|
1007
|
+
}):(parentSequence===null?null:`${this.#sessionId}:${parentSequence}`),
|
|
1008
|
+
parentSequence,
|
|
1009
|
+
depth:this.#activeDispatch.length,
|
|
1010
|
+
stack,
|
|
1011
|
+
payload:Array.isArray(payloadSnapshot)
|
|
1012
|
+
?payloadSnapshot
|
|
1013
|
+
:deepFreeze([payloadSnapshot]),
|
|
1014
|
+
metadata:safeMetadata,
|
|
1015
|
+
status:'dispatching',
|
|
1016
|
+
completedAt:null,
|
|
1017
|
+
durationMs:null,
|
|
1018
|
+
error:null
|
|
1019
|
+
};
|
|
1020
|
+
this.#history.push(deepFreeze({...draft}));
|
|
1021
|
+
this.#sequence=sequence;
|
|
1022
|
+
this.#cursor=sequence;
|
|
1023
|
+
this.#activeDispatch.push(sequence);
|
|
1024
|
+
}catch{
|
|
1025
|
+
draft=null;
|
|
1026
|
+
startedMonotonic=null;
|
|
1027
|
+
}
|
|
1028
|
+
}
|
|
1029
|
+
|
|
1030
|
+
let listenerFailed=false;
|
|
1031
|
+
let listenerError;
|
|
1032
|
+
try{
|
|
1033
|
+
this.#bus.emit(type,...payload);
|
|
1034
|
+
}catch(error){
|
|
1035
|
+
listenerFailed=true;
|
|
1036
|
+
listenerError=error;
|
|
1037
|
+
}finally{
|
|
1038
|
+
if(draft){
|
|
1039
|
+
this.#finalize(
|
|
1040
|
+
draft,
|
|
1041
|
+
startedMonotonic,
|
|
1042
|
+
listenerFailed?'failed':'completed',
|
|
1043
|
+
listenerFailed?listenerError:null
|
|
1044
|
+
);
|
|
1045
|
+
this.#activeDispatch.pop();
|
|
1046
|
+
}
|
|
1047
|
+
}
|
|
1048
|
+
if(listenerFailed)throw listenerError;
|
|
1049
|
+
return this;
|
|
1050
|
+
}
|
|
1051
|
+
|
|
1052
|
+
#recordOverflow(){
|
|
1053
|
+
this.#overflowed=true;
|
|
1054
|
+
this.#timeTravelEnabled=false;
|
|
1055
|
+
try{this.#domInstrumentation?.stop({emitLifecycle:false});}catch{}
|
|
1056
|
+
let timestamp;
|
|
1057
|
+
let monotonicMs;
|
|
1058
|
+
try{timestamp=clockDate(this.#clock).toISOString();}
|
|
1059
|
+
catch{timestamp=new Date().toISOString();}
|
|
1060
|
+
try{monotonicMs=monotonicValue(this.#now);}
|
|
1061
|
+
catch{monotonicMs=Math.max(0,Number(this.#history.at(-1)?.monotonicMs??0));}
|
|
1062
|
+
const sequence=++this.#sequence;
|
|
1063
|
+
const payload=deepFreeze([snapshotObject([
|
|
1064
|
+
['maxEvents',this.#maxEvents],
|
|
1065
|
+
['retainedEvents',this.#history.length]
|
|
1066
|
+
])]);
|
|
1067
|
+
const record=deepFreeze({
|
|
1068
|
+
protocol:ARCANE_EVENT_STACK_PROTOCOL,
|
|
1069
|
+
sessionId:this.#sessionId,
|
|
1070
|
+
id:`${this.#sessionId}:${sequence}`,
|
|
1071
|
+
sequence,
|
|
1072
|
+
timestamp,
|
|
1073
|
+
monotonicMs,
|
|
1074
|
+
type:TIME_TRAVEL_OVERFLOW_EVENT,
|
|
1075
|
+
source:'event-manager',
|
|
1076
|
+
category:'overflow',
|
|
1077
|
+
correlationId:null,
|
|
1078
|
+
causationId:null,
|
|
1079
|
+
parentSequence:null,
|
|
1080
|
+
depth:0,
|
|
1081
|
+
stack:null,
|
|
1082
|
+
payload,
|
|
1083
|
+
metadata:deepFreeze(snapshotObject([['maxEvents',this.#maxEvents]])),
|
|
1084
|
+
status:'completed',
|
|
1085
|
+
completedAt:timestamp,
|
|
1086
|
+
durationMs:0,
|
|
1087
|
+
error:null
|
|
1088
|
+
});
|
|
1089
|
+
this.#history.push(record);
|
|
1090
|
+
this.#cursor=sequence;
|
|
1091
|
+
}
|
|
1092
|
+
|
|
1093
|
+
#finalize(draft,startedMonotonic,status,error){
|
|
1094
|
+
try{
|
|
1095
|
+
const index=this.#history.findIndex(record=>record.sequence===draft.sequence);
|
|
1096
|
+
if(index<0)return;
|
|
1097
|
+
let completedAt=draft.timestamp;
|
|
1098
|
+
let durationMs=0;
|
|
1099
|
+
try{
|
|
1100
|
+
const completion=clockDate(this.#clock);
|
|
1101
|
+
if(completion.getTime()>=Date.parse(draft.timestamp)){
|
|
1102
|
+
completedAt=completion.toISOString();
|
|
1103
|
+
}
|
|
1104
|
+
}catch{}
|
|
1105
|
+
try{
|
|
1106
|
+
durationMs=Math.max(0,monotonicValue(this.#now)-startedMonotonic);
|
|
1107
|
+
}catch{}
|
|
1108
|
+
this.#history[index]=deepFreeze({
|
|
1109
|
+
...draft,
|
|
1110
|
+
status,
|
|
1111
|
+
completedAt,
|
|
1112
|
+
durationMs,
|
|
1113
|
+
error:error===null?null:frozenErrorSnapshot(error,{
|
|
1114
|
+
redactSensitive:this.#redactSensitive,
|
|
1115
|
+
captureStacks:this.#captureStacks,
|
|
1116
|
+
maxDepth:this.#maxSnapshotDepth,
|
|
1117
|
+
maxEntries:this.#maxSnapshotEntries,
|
|
1118
|
+
maxStringLength:this.#maxSnapshotStringLength
|
|
1119
|
+
})
|
|
1120
|
+
});
|
|
1121
|
+
}catch{}
|
|
1122
|
+
}
|
|
1123
|
+
|
|
1124
|
+
enableTimeTravel({dom}={}){
|
|
1125
|
+
if(this.#overflowed){
|
|
1126
|
+
throw new Error('Clear the overflowed event history before enabling time travel again.');
|
|
1127
|
+
}
|
|
1128
|
+
const wasEnabled=this.#timeTravelEnabled;
|
|
1129
|
+
this.#timeTravelEnabled=true;
|
|
1130
|
+
try{
|
|
1131
|
+
if(dom)this.attachDOM(dom?.root??dom,dom?.root?dom:{});
|
|
1132
|
+
else this.#domInstrumentation?.start();
|
|
1133
|
+
}catch(error){
|
|
1134
|
+
this.#timeTravelEnabled=wasEnabled;
|
|
1135
|
+
throw error;
|
|
1136
|
+
}
|
|
1137
|
+
return this;
|
|
1138
|
+
}
|
|
1139
|
+
|
|
1140
|
+
disableTimeTravel(){
|
|
1141
|
+
try{return this.#domInstrumentation?.stop(),this;}
|
|
1142
|
+
finally{this.#timeTravelEnabled=false;}
|
|
1143
|
+
}
|
|
1144
|
+
|
|
1145
|
+
attachDOM(root=globalThis.document,options={}){
|
|
1146
|
+
const previous=this.#domInstrumentation;
|
|
1147
|
+
const replacement=createDOMInstrumentation({
|
|
1148
|
+
...options,
|
|
1149
|
+
root,
|
|
1150
|
+
eventManager:this
|
|
1151
|
+
});
|
|
1152
|
+
if(previous&&previous!==replacement){
|
|
1153
|
+
previous.stop();
|
|
1154
|
+
if(this.#domInstrumentation===previous)this.#domInstrumentation=null;
|
|
1155
|
+
}
|
|
1156
|
+
try{
|
|
1157
|
+
if(this.#timeTravelEnabled)replacement.start();
|
|
1158
|
+
if(!this.#timeTravelEnabled||this.#overflowed){
|
|
1159
|
+
replacement.stop({emitLifecycle:false});
|
|
1160
|
+
}
|
|
1161
|
+
}catch(error){
|
|
1162
|
+
try{replacement.stop({emitLifecycle:false});}catch{}
|
|
1163
|
+
if(replacement.active||replacement.cleanupPending){
|
|
1164
|
+
this.#domInstrumentation=replacement;
|
|
1165
|
+
try{
|
|
1166
|
+
if(error&&(typeof error==='object'||typeof error==='function')){
|
|
1167
|
+
Object.defineProperty(error,'domInstrumentation',{
|
|
1168
|
+
value:replacement,
|
|
1169
|
+
enumerable:false,
|
|
1170
|
+
configurable:true
|
|
1171
|
+
});
|
|
1172
|
+
}
|
|
1173
|
+
}catch{}
|
|
1174
|
+
}
|
|
1175
|
+
throw error;
|
|
1176
|
+
}
|
|
1177
|
+
this.#domInstrumentation=replacement;
|
|
1178
|
+
return replacement;
|
|
1179
|
+
}
|
|
1180
|
+
|
|
1181
|
+
detachDOM(){
|
|
1182
|
+
const current=this.#domInstrumentation;
|
|
1183
|
+
try{current?.stop();}
|
|
1184
|
+
finally{
|
|
1185
|
+
if(this.#domInstrumentation===current
|
|
1186
|
+
&&!current?.active&&!current?.cleanupPending){
|
|
1187
|
+
this.#domInstrumentation=null;
|
|
1188
|
+
}
|
|
1189
|
+
}
|
|
1190
|
+
return this;
|
|
1191
|
+
}
|
|
1192
|
+
|
|
1193
|
+
clearHistory({newSession=true}={}){
|
|
1194
|
+
if(typeof newSession!=='boolean')throw new TypeError('newSession must be boolean.');
|
|
1195
|
+
if(this.#activeDispatch.length||this.#replaying){
|
|
1196
|
+
throw new Error('Event history cannot be cleared during dispatch or playback.');
|
|
1197
|
+
}
|
|
1198
|
+
this.#history=[];
|
|
1199
|
+
this.#sequence=0;
|
|
1200
|
+
this.#cursor=0;
|
|
1201
|
+
this.#activeDispatch=[];
|
|
1202
|
+
this.#overflowed=false;
|
|
1203
|
+
if(newSession)this.#sessionId=sessionIdentifier();
|
|
1204
|
+
return this;
|
|
1205
|
+
}
|
|
1206
|
+
|
|
1207
|
+
getEventStack({fromSequence=1,toSequence=Number.MAX_SAFE_INTEGER,type=null}={}){
|
|
1208
|
+
if(!Number.isSafeInteger(fromSequence)||fromSequence<1
|
|
1209
|
+
||!Number.isSafeInteger(toSequence)||toSequence<fromSequence
|
|
1210
|
+
||(type!==null&&typeof type!=='string')){
|
|
1211
|
+
throw new TypeError('The event stack range is invalid.');
|
|
1212
|
+
}
|
|
1213
|
+
return Object.freeze(this.#history.filter(record=>
|
|
1214
|
+
record.sequence>=fromSequence&&record.sequence<=toSequence
|
|
1215
|
+
&&(type===null||record.type===type)
|
|
1216
|
+
));
|
|
1217
|
+
}
|
|
1218
|
+
|
|
1219
|
+
exportStack({space=2}={}){
|
|
1220
|
+
if(!Number.isSafeInteger(space)||space<0||space>10){
|
|
1221
|
+
throw new RangeError('Event stack JSON indentation must be from 0 through 10.');
|
|
1222
|
+
}
|
|
1223
|
+
return JSON.stringify({
|
|
1224
|
+
protocol:ARCANE_EVENT_STACK_PROTOCOL,
|
|
1225
|
+
sessionId:this.#sessionId,
|
|
1226
|
+
createdAt:clockDate(this.#clock).toISOString(),
|
|
1227
|
+
events:this.#history
|
|
1228
|
+
},null,space);
|
|
1229
|
+
}
|
|
1230
|
+
|
|
1231
|
+
seek(sequence){
|
|
1232
|
+
if(!Number.isSafeInteger(sequence)||sequence<0||sequence>this.#sequence){
|
|
1233
|
+
throw new RangeError('The time-travel sequence is outside this event stack.');
|
|
1234
|
+
}
|
|
1235
|
+
this.#cursor=sequence;
|
|
1236
|
+
const record=sequence===0?null:this.#history.find(item=>item.sequence===sequence)??null;
|
|
1237
|
+
this.#bus.emit(TIME_TRAVEL_SEEK_EVENT,{sessionId:this.#sessionId,sequence,record});
|
|
1238
|
+
return record;
|
|
1239
|
+
}
|
|
1240
|
+
|
|
1241
|
+
async playback({
|
|
1242
|
+
stack=null,
|
|
1243
|
+
fromSequence=1,
|
|
1244
|
+
toSequence=Number.MAX_SAFE_INTEGER,
|
|
1245
|
+
speed=0,
|
|
1246
|
+
mode='review',
|
|
1247
|
+
signal,
|
|
1248
|
+
onRecord
|
|
1249
|
+
}={}){
|
|
1250
|
+
if(!Number.isSafeInteger(fromSequence)||fromSequence<1
|
|
1251
|
+
||!Number.isSafeInteger(toSequence)||toSequence<fromSequence){
|
|
1252
|
+
throw new TypeError('The playback range is invalid.');
|
|
1253
|
+
}
|
|
1254
|
+
if(typeof speed!=='number'||!Number.isFinite(speed)||speed<0){
|
|
1255
|
+
throw new RangeError('Playback speed must be zero or a positive finite number.');
|
|
1256
|
+
}
|
|
1257
|
+
if(!['review','events','none'].includes(mode)){
|
|
1258
|
+
throw new TypeError('Playback mode must be review, events, or none.');
|
|
1259
|
+
}
|
|
1260
|
+
if(onRecord!==undefined&&typeof onRecord!=='function'){
|
|
1261
|
+
throw new TypeError('onRecord must be a function.');
|
|
1262
|
+
}
|
|
1263
|
+
if(this.#replaying)throw new Error('Event playback is already active.');
|
|
1264
|
+
const document=stack===null
|
|
1265
|
+
?{protocol:ARCANE_EVENT_STACK_PROTOCOL,sessionId:this.#sessionId,events:this.#history}
|
|
1266
|
+
:parseEventStack(stack,{
|
|
1267
|
+
maxEvents:this.#maxEvents,
|
|
1268
|
+
maxSnapshotDepth:this.#maxSnapshotDepth,
|
|
1269
|
+
maxSnapshotEntries:this.#maxSnapshotEntries,
|
|
1270
|
+
maxSnapshotStringLength:this.#maxSnapshotStringLength
|
|
1271
|
+
});
|
|
1272
|
+
const records=document.events.filter(record=>
|
|
1273
|
+
record.sequence>=fromSequence&&record.sequence<=toSequence
|
|
1274
|
+
);
|
|
1275
|
+
this.#replaying=true;
|
|
1276
|
+
let delivered=0;
|
|
1277
|
+
let previousTimestamp=null;
|
|
1278
|
+
let previousMonotonic=null;
|
|
1279
|
+
try{
|
|
1280
|
+
if(signal?.aborted)throw abortError(signal.reason);
|
|
1281
|
+
this.#bus.emit(PLAYBACK_STARTED_EVENT,{
|
|
1282
|
+
sessionId:document.sessionId,
|
|
1283
|
+
count:records.length,
|
|
1284
|
+
fromSequence,
|
|
1285
|
+
toSequence,
|
|
1286
|
+
speed,
|
|
1287
|
+
mode
|
|
1288
|
+
});
|
|
1289
|
+
for(const record of records){
|
|
1290
|
+
if(signal?.aborted)throw abortError(signal.reason);
|
|
1291
|
+
if(speed>0&&(previousTimestamp!==null||previousMonotonic!==null)){
|
|
1292
|
+
const monotonic=Number(record.monotonicMs);
|
|
1293
|
+
const difference=Number.isFinite(monotonic)&&previousMonotonic!==null
|
|
1294
|
+
?Math.max(0,monotonic-previousMonotonic)
|
|
1295
|
+
:Math.max(0,Date.parse(record.timestamp)-previousTimestamp);
|
|
1296
|
+
await wait(difference/speed,signal);
|
|
1297
|
+
}
|
|
1298
|
+
previousTimestamp=Date.parse(record.timestamp);
|
|
1299
|
+
previousMonotonic=Number.isFinite(Number(record.monotonicMs))
|
|
1300
|
+
?Number(record.monotonicMs)
|
|
1301
|
+
:null;
|
|
1302
|
+
if(mode==='review')this.#bus.emit(PLAYBACK_RECORD_EVENT,record);
|
|
1303
|
+
else if(mode==='events')this.#bus.emit(record.type,...record.payload);
|
|
1304
|
+
await onRecord?.(record);
|
|
1305
|
+
this.#cursor=record.sequence;
|
|
1306
|
+
delivered+=1;
|
|
1307
|
+
}
|
|
1308
|
+
const result=Object.freeze({
|
|
1309
|
+
sessionId:document.sessionId,
|
|
1310
|
+
delivered,
|
|
1311
|
+
cursor:this.#cursor,
|
|
1312
|
+
completed:true
|
|
1313
|
+
});
|
|
1314
|
+
this.#bus.emit(PLAYBACK_COMPLETED_EVENT,result);
|
|
1315
|
+
return result;
|
|
1316
|
+
}catch(error){
|
|
1317
|
+
const cancelled=signal?.aborted||error?.name==='AbortError';
|
|
1318
|
+
this.#bus.emit(cancelled?PLAYBACK_CANCELLED_EVENT:PLAYBACK_FAILED_EVENT,Object.freeze({
|
|
1319
|
+
sessionId:document.sessionId,
|
|
1320
|
+
delivered,
|
|
1321
|
+
cursor:this.#cursor,
|
|
1322
|
+
completed:false,
|
|
1323
|
+
error:frozenSnapshot(error,{
|
|
1324
|
+
redactSensitive:this.#redactSensitive,
|
|
1325
|
+
captureStacks:this.#captureStacks,
|
|
1326
|
+
maxDepth:this.#maxSnapshotDepth,
|
|
1327
|
+
maxEntries:this.#maxSnapshotEntries,
|
|
1328
|
+
maxStringLength:this.#maxSnapshotStringLength
|
|
1329
|
+
})
|
|
1330
|
+
}));
|
|
1331
|
+
throw error;
|
|
1332
|
+
}finally{
|
|
1333
|
+
this.#replaying=false;
|
|
1334
|
+
}
|
|
1335
|
+
}
|
|
1336
|
+
}
|
|
1337
|
+
|
|
1338
|
+
export function createEventManager(options){
|
|
1339
|
+
return new EventManager(options);
|
|
1340
|
+
}
|
|
1341
|
+
|
|
1342
|
+
export const arcaneEvents=new EventManager();
|