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,1046 @@
|
|
|
1
|
+
import Is from '../../node_modules/strong-type/index.js';
|
|
2
|
+
import DBLS from '../modules/DBLS.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* DBOPFS Module
|
|
6
|
+
*
|
|
7
|
+
* This import initializes the DBOPFS singleton.
|
|
8
|
+
*
|
|
9
|
+
* The module attaches itself to the global scope:
|
|
10
|
+
*
|
|
11
|
+
* window.dbopfs
|
|
12
|
+
*
|
|
13
|
+
* Engineers can access persistence anywhere via:
|
|
14
|
+
*
|
|
15
|
+
* dbopfs.get(...)
|
|
16
|
+
* dbopfs.set(...)
|
|
17
|
+
*
|
|
18
|
+
* The import must remain even if unused because it registers the singleton.
|
|
19
|
+
*/
|
|
20
|
+
import DBOPFS from '../modules/DBOPFS.js';
|
|
21
|
+
|
|
22
|
+
function createDefaultDashboard(){
|
|
23
|
+
return { charts:{} };
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function normalizeDashboard(dashboard={}){
|
|
27
|
+
const source=dashboard&&typeof dashboard==='object'&&!Array.isArray(dashboard)
|
|
28
|
+
&&dashboard.charts&&typeof dashboard.charts==='object'&&!Array.isArray(dashboard.charts)
|
|
29
|
+
? dashboard.charts
|
|
30
|
+
: {};
|
|
31
|
+
const charts={};
|
|
32
|
+
|
|
33
|
+
for(const [key,value] of Object.entries(source)){
|
|
34
|
+
if(typeof value==='boolean'){
|
|
35
|
+
charts[key]=value;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return { charts };
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const is = new Is(false);
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Email validation pattern
|
|
46
|
+
*/
|
|
47
|
+
const EMAIL_REGEX =
|
|
48
|
+
/^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Canonical schema for a UserEntity record.
|
|
53
|
+
*
|
|
54
|
+
* @typedef {Object} UserEntityData
|
|
55
|
+
* @property {string|number} username
|
|
56
|
+
* @property {string} email
|
|
57
|
+
* @property {string|number} phone
|
|
58
|
+
* @property {string} license_key
|
|
59
|
+
* @property {string} subscription_key
|
|
60
|
+
* @property {number} current_time
|
|
61
|
+
* @property {number} last_successful_time
|
|
62
|
+
* @property {string|number} contact_1
|
|
63
|
+
* @property {string|number} contact_2
|
|
64
|
+
* @property {string|number} contact_3
|
|
65
|
+
* @property {string|number} contact_4
|
|
66
|
+
* @property {string|number} contact_5
|
|
67
|
+
* @property {string|number} contact_6
|
|
68
|
+
* @property {string} AI_personality
|
|
69
|
+
* @property {string} religion
|
|
70
|
+
* @property {string} AI_voice
|
|
71
|
+
* @property {number} AI_speed
|
|
72
|
+
* @property {string} ai_verbosity
|
|
73
|
+
* @property {boolean} initialSpeechMuted
|
|
74
|
+
* @property {boolean} conversationClosingReportEnabled
|
|
75
|
+
* @property {boolean} conversationActionItemsEnabled
|
|
76
|
+
* @property {number} firstBootUp
|
|
77
|
+
* @property {boolean} questionnaireShown
|
|
78
|
+
* @property {string|number} skin
|
|
79
|
+
* @property {boolean} preferrsLocal
|
|
80
|
+
* @property {boolean} developer
|
|
81
|
+
* @property {array} preferredModels
|
|
82
|
+
* @property {{charts:Object<string,boolean>}} dashboard
|
|
83
|
+
*/
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
class UserEntity {
|
|
87
|
+
|
|
88
|
+
/** @type {string} */
|
|
89
|
+
#tableName = 'users';
|
|
90
|
+
|
|
91
|
+
/** @type {string} */
|
|
92
|
+
fileName = 'users.json';
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Controls automatic persistence
|
|
96
|
+
*
|
|
97
|
+
* @type {boolean}
|
|
98
|
+
*/
|
|
99
|
+
persist = true;
|
|
100
|
+
|
|
101
|
+
ready = false;
|
|
102
|
+
|
|
103
|
+
#loadPromise = null;
|
|
104
|
+
|
|
105
|
+
#explicitUpdateQueue = Promise.resolve();
|
|
106
|
+
|
|
107
|
+
#schema = Object.freeze(
|
|
108
|
+
[
|
|
109
|
+
'username',
|
|
110
|
+
'email',
|
|
111
|
+
'phone',
|
|
112
|
+
'language',
|
|
113
|
+
'license_key',
|
|
114
|
+
'subscription_key',
|
|
115
|
+
'current_time',
|
|
116
|
+
'last_successful_time',
|
|
117
|
+
|
|
118
|
+
'contact_1',
|
|
119
|
+
'contact_2',
|
|
120
|
+
'contact_3',
|
|
121
|
+
'contact_4',
|
|
122
|
+
'contact_5',
|
|
123
|
+
'contact_6',
|
|
124
|
+
|
|
125
|
+
'AI_personality',
|
|
126
|
+
'religion',
|
|
127
|
+
'AI_voice',
|
|
128
|
+
'AI_speed',
|
|
129
|
+
'ai_verbosity',
|
|
130
|
+
'initialSpeechMuted',
|
|
131
|
+
'conversationClosingReportEnabled',
|
|
132
|
+
'conversationActionItemsEnabled',
|
|
133
|
+
'firstBootUp',
|
|
134
|
+
'questionnaireShown',
|
|
135
|
+
'skin',
|
|
136
|
+
'developer',
|
|
137
|
+
'prefersLocal',
|
|
138
|
+
'preferredModels',
|
|
139
|
+
'dashboard'
|
|
140
|
+
]
|
|
141
|
+
);
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
#username = '';
|
|
146
|
+
#email = '';
|
|
147
|
+
#phone = '';
|
|
148
|
+
#license_key = '';
|
|
149
|
+
#subscription_key = '';
|
|
150
|
+
#current_time = 0;
|
|
151
|
+
#last_successful_time = 0;
|
|
152
|
+
#language = '';
|
|
153
|
+
|
|
154
|
+
#contact_1 = '';
|
|
155
|
+
#contact_2 = '';
|
|
156
|
+
#contact_3 = '';
|
|
157
|
+
#contact_4 = '';
|
|
158
|
+
#contact_5 = '';
|
|
159
|
+
#contact_6 = '';
|
|
160
|
+
|
|
161
|
+
#AI_personality = '';
|
|
162
|
+
#religion = '';
|
|
163
|
+
#AI_voice = '';
|
|
164
|
+
#AI_speed = 1.0;
|
|
165
|
+
#ai_verbosity = 'medium';
|
|
166
|
+
#initialSpeechMuted = true;
|
|
167
|
+
#conversationClosingReportEnabled = true;
|
|
168
|
+
#conversationActionItemsEnabled = true;
|
|
169
|
+
#firstBootUp = 0;
|
|
170
|
+
#questionnaireShown = false;
|
|
171
|
+
#skin = 'default';
|
|
172
|
+
#developer = false;
|
|
173
|
+
#prefersLocal = false;
|
|
174
|
+
#preferredModels = [];
|
|
175
|
+
#dashboard = createDefaultDashboard();
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Create a new UserEntity
|
|
180
|
+
*
|
|
181
|
+
* @param {string} fileName
|
|
182
|
+
*/
|
|
183
|
+
constructor(
|
|
184
|
+
fileName = ''
|
|
185
|
+
){
|
|
186
|
+
if(window.user){
|
|
187
|
+
return window.user;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
if(!is.string(fileName) || !fileName){
|
|
191
|
+
fileName=this.fileName;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
this.fileName = fileName;
|
|
195
|
+
|
|
196
|
+
window.addEventListener(
|
|
197
|
+
'dbopfs-ready',
|
|
198
|
+
this.load.bind(this)
|
|
199
|
+
);
|
|
200
|
+
|
|
201
|
+
if(window.dbopfs?.ready){
|
|
202
|
+
this.load();
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
return this;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
/** @returns {string|number} */
|
|
210
|
+
get username(){
|
|
211
|
+
return this.#username;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/** @param {string|number} v */
|
|
215
|
+
set username(v){
|
|
216
|
+
if(!is.union(v,'string','number')){
|
|
217
|
+
throw new Error('username must be string or number');
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
this.#username = v;
|
|
221
|
+
|
|
222
|
+
this.#persist();
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
/** @returns {string} */
|
|
228
|
+
get email(){
|
|
229
|
+
return this.#email;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/** @param {string} v */
|
|
233
|
+
set email(v){
|
|
234
|
+
if(!is.string(v)){
|
|
235
|
+
throw new Error('email must be string');
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
if(v.length>0 && !EMAIL_REGEX.test(v)){
|
|
239
|
+
//throw new Error('email must be a valid email address');
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
this.#email = v;
|
|
243
|
+
|
|
244
|
+
this.#persist();
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
/** @returns {string|number} */
|
|
250
|
+
get phone(){
|
|
251
|
+
return this.#phone;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/** @param {string|number} v */
|
|
255
|
+
set phone(v){
|
|
256
|
+
if(!is.union(v,'string','number')){
|
|
257
|
+
throw new Error('phone must be string or number');
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
this.#phone = v;
|
|
261
|
+
|
|
262
|
+
this.#persist();
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
|
|
267
|
+
/** @returns {string} */
|
|
268
|
+
get license_key(){
|
|
269
|
+
return this.#license_key;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
/** @param {string} v */
|
|
273
|
+
set license_key(v){
|
|
274
|
+
if(!is.string(v)){
|
|
275
|
+
throw new Error('license_key must be string');
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
this.#license_key = v;
|
|
279
|
+
|
|
280
|
+
this.#persist();
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
/** @returns {string} */
|
|
284
|
+
get subscription_key(){
|
|
285
|
+
return this.#subscription_key;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
/** @param {string} v */
|
|
289
|
+
set subscription_key(v){
|
|
290
|
+
if(!is.string(v)){
|
|
291
|
+
throw new Error('subscription_key must be string');
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
this.#subscription_key = v;
|
|
295
|
+
|
|
296
|
+
this.#persist();
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
/** @returns {number} */
|
|
300
|
+
get current_time(){
|
|
301
|
+
return this.#current_time;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
/** @param {number} v */
|
|
305
|
+
set current_time(v){
|
|
306
|
+
if(!is.number(v)){
|
|
307
|
+
throw new Error('current_time must be number');
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
this.#current_time = v;
|
|
311
|
+
|
|
312
|
+
this.#persist();
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
/** @returns {number} */
|
|
316
|
+
get last_successful_time(){
|
|
317
|
+
return this.#last_successful_time;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
/** @param {number} v */
|
|
321
|
+
set last_successful_time(v){
|
|
322
|
+
if(!is.number(v)){
|
|
323
|
+
throw new Error('last_successful_time must be number');
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
this.#last_successful_time = v;
|
|
327
|
+
|
|
328
|
+
this.#persist();
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
/** @returns {string} */
|
|
332
|
+
get language(){
|
|
333
|
+
return this.#language;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
/** @param {string} v */
|
|
337
|
+
set language(v){
|
|
338
|
+
if(!is.string(v)){
|
|
339
|
+
throw new Error('language must be string');
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
this.#language = v;
|
|
343
|
+
|
|
344
|
+
this.#persist();
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
|
|
348
|
+
|
|
349
|
+
/** @returns {string|number} */
|
|
350
|
+
get contact_1(){
|
|
351
|
+
return this.#contact_1;
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
/** @param {string|number} v */
|
|
355
|
+
set contact_1(v){
|
|
356
|
+
if(!is.union(v,'string','number')){
|
|
357
|
+
throw new Error('contact_1 must be string or number');
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
this.#contact_1 = v;
|
|
361
|
+
|
|
362
|
+
this.#persist();
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
|
|
366
|
+
|
|
367
|
+
/** @returns {string|number} */
|
|
368
|
+
get contact_2(){
|
|
369
|
+
return this.#contact_2;
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
/** @param {string|number} v */
|
|
373
|
+
set contact_2(v){
|
|
374
|
+
if(!is.union(v,'string','number')){
|
|
375
|
+
throw new Error('contact_2 must be string or number');
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
this.#contact_2 = v;
|
|
379
|
+
|
|
380
|
+
this.#persist();
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
|
|
384
|
+
|
|
385
|
+
/** @returns {string|number} */
|
|
386
|
+
get contact_3(){
|
|
387
|
+
return this.#contact_3;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
/** @param {string|number} v */
|
|
391
|
+
set contact_3(v){
|
|
392
|
+
if(!is.union(v,'string','number')){
|
|
393
|
+
throw new Error('contact_3 must be string or number');
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
this.#contact_3 = v;
|
|
397
|
+
|
|
398
|
+
this.#persist();
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
|
|
402
|
+
|
|
403
|
+
/** @returns {string|number} */
|
|
404
|
+
get contact_4(){
|
|
405
|
+
return this.#contact_4;
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
/** @param {string|number} v */
|
|
409
|
+
set contact_4(v){
|
|
410
|
+
if(!is.union(v,'string','number')){
|
|
411
|
+
throw new Error('contact_4 must be string or number');
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
this.#contact_4 = v;
|
|
415
|
+
|
|
416
|
+
this.#persist();
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
|
|
420
|
+
|
|
421
|
+
/** @returns {string|number} */
|
|
422
|
+
get contact_5(){
|
|
423
|
+
return this.#contact_5;
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
/** @param {string|number} v */
|
|
427
|
+
set contact_5(v){
|
|
428
|
+
if(!is.union(v,'string','number')){
|
|
429
|
+
throw new Error('contact_5 must be string or number');
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
this.#contact_5 = v;
|
|
433
|
+
|
|
434
|
+
this.#persist();
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
|
|
438
|
+
|
|
439
|
+
/** @returns {string|number} */
|
|
440
|
+
get contact_6(){
|
|
441
|
+
return this.#contact_6;
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
/** @param {string|number} v */
|
|
445
|
+
set contact_6(v){
|
|
446
|
+
if(!is.union(v,'string','number')){
|
|
447
|
+
throw new Error('contact_6 must be string or number');
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
this.#contact_6 = v;
|
|
451
|
+
|
|
452
|
+
this.#persist();
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
|
|
456
|
+
|
|
457
|
+
/** @returns {string} */
|
|
458
|
+
get AI_personality(){
|
|
459
|
+
return this.#AI_personality;
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
/** @param {string} v */
|
|
463
|
+
set AI_personality(v){
|
|
464
|
+
if(!is.string(v)){
|
|
465
|
+
throw new Error('AI_personality must be string');
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
if(v.length > 1000){
|
|
469
|
+
throw new Error('AI_personality must be less than 1000 characters');
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
this.#AI_personality = v;
|
|
473
|
+
|
|
474
|
+
this.#persist();
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
|
|
478
|
+
|
|
479
|
+
/** @returns {string} */
|
|
480
|
+
get religion(){
|
|
481
|
+
return this.#religion;
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
/** @param {string} v */
|
|
485
|
+
set religion(v){
|
|
486
|
+
if(!is.string(v)){
|
|
487
|
+
throw new Error('religion must be string');
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
this.#religion = v;
|
|
491
|
+
|
|
492
|
+
this.#persist();
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
|
|
496
|
+
|
|
497
|
+
/** @returns {string} */
|
|
498
|
+
get AI_voice(){
|
|
499
|
+
return this.#AI_voice;
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
/** @param {string} v */
|
|
503
|
+
set AI_voice(v){
|
|
504
|
+
if(!is.string(v)){
|
|
505
|
+
throw new Error('AI_voice must be string');
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
this.#AI_voice = v;
|
|
509
|
+
|
|
510
|
+
this.#persist();
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
|
|
514
|
+
|
|
515
|
+
/** @returns {number} */
|
|
516
|
+
get AI_speed(){
|
|
517
|
+
return this.#AI_speed;
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
/** @param {number} v */
|
|
521
|
+
set AI_speed(v){
|
|
522
|
+
if(!is.number(v)){
|
|
523
|
+
throw new Error('AI_speed must be number');
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
const MIN_SPEED=0.5;
|
|
527
|
+
const MAX_SPEED=2.0;
|
|
528
|
+
|
|
529
|
+
if(v<MIN_SPEED||v>MAX_SPEED){
|
|
530
|
+
throw new Error(`AI_speed must be between ${MIN_SPEED} and ${MAX_SPEED}`);
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
this.#AI_speed=v;
|
|
534
|
+
|
|
535
|
+
this.#persist();
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
|
|
539
|
+
|
|
540
|
+
/** @returns {string} */
|
|
541
|
+
get ai_verbosity(){
|
|
542
|
+
return this.#ai_verbosity;
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
/** @param {string} v */
|
|
546
|
+
set ai_verbosity(v){
|
|
547
|
+
if(!is.string(v)||!v.trim()){
|
|
548
|
+
throw new Error('ai_verbosity must be a non-empty string');
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
this.#ai_verbosity=v.trim();
|
|
552
|
+
|
|
553
|
+
this.#persist();
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
|
|
557
|
+
|
|
558
|
+
/** @returns {boolean} */
|
|
559
|
+
get initialSpeechMuted(){
|
|
560
|
+
return this.#initialSpeechMuted;
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
/** @param {boolean} v */
|
|
564
|
+
set initialSpeechMuted(v){
|
|
565
|
+
if(!is.boolean(v)){
|
|
566
|
+
throw new Error('initialSpeechMuted must be boolean');
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
this.#initialSpeechMuted = v;
|
|
570
|
+
|
|
571
|
+
this.#persist();
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
|
|
575
|
+
|
|
576
|
+
/** @returns {boolean} */
|
|
577
|
+
get conversationClosingReportEnabled(){
|
|
578
|
+
return this.#conversationClosingReportEnabled;
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
/** @param {boolean} v */
|
|
582
|
+
set conversationClosingReportEnabled(v){
|
|
583
|
+
if(!is.boolean(v)){
|
|
584
|
+
throw new Error('conversationClosingReportEnabled must be boolean');
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
this.#conversationClosingReportEnabled = v;
|
|
588
|
+
|
|
589
|
+
this.#persist();
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
|
|
593
|
+
|
|
594
|
+
/** @returns {boolean} */
|
|
595
|
+
get conversationActionItemsEnabled(){
|
|
596
|
+
return this.#conversationActionItemsEnabled;
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
/** @param {boolean} v */
|
|
600
|
+
set conversationActionItemsEnabled(v){
|
|
601
|
+
if(!is.boolean(v)){
|
|
602
|
+
throw new Error('conversationActionItemsEnabled must be boolean');
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
this.#conversationActionItemsEnabled = v;
|
|
606
|
+
|
|
607
|
+
this.#persist();
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
|
|
611
|
+
|
|
612
|
+
/** @returns {number} */
|
|
613
|
+
get firstBootUp(){
|
|
614
|
+
return this.#firstBootUp;
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
/** @param {number} v */
|
|
618
|
+
set firstBootUp(v){
|
|
619
|
+
if(
|
|
620
|
+
typeof v!=='number'
|
|
621
|
+
||!Number.isFinite(v)
|
|
622
|
+
||!Number.isSafeInteger(v)
|
|
623
|
+
||v<0
|
|
624
|
+
){
|
|
625
|
+
throw new Error(
|
|
626
|
+
'firstBootUp must be a non-negative finite integer timestamp'
|
|
627
|
+
);
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
this.#firstBootUp = v;
|
|
631
|
+
|
|
632
|
+
this.#persist();
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
|
|
636
|
+
|
|
637
|
+
/** @returns {boolean} */
|
|
638
|
+
get questionnaireShown(){
|
|
639
|
+
return this.#questionnaireShown;
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
/** @param {boolean} v */
|
|
643
|
+
set questionnaireShown(v){
|
|
644
|
+
if(!is.boolean(v)){
|
|
645
|
+
throw new Error('questionnaireShown must be boolean');
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
this.#questionnaireShown = v;
|
|
649
|
+
|
|
650
|
+
this.#persist();
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
|
|
654
|
+
|
|
655
|
+
/** @returns {string|number} */
|
|
656
|
+
get skin(){
|
|
657
|
+
return this.#skin;
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
/** @param {string|number} v */
|
|
661
|
+
set skin(v){
|
|
662
|
+
if(!is.union(v,'string','number')){
|
|
663
|
+
throw new Error('skin must be string or number');
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
this.#skin = v;
|
|
667
|
+
|
|
668
|
+
this.#persist();
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
|
|
672
|
+
/** @returns {boolean} */
|
|
673
|
+
get developer(){
|
|
674
|
+
return this.#developer;
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
/** @param {boolean} v */
|
|
678
|
+
set developer(v){
|
|
679
|
+
if(!is.boolean(v)){
|
|
680
|
+
throw new Error('developer must be boolean');
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
this.#developer = v;
|
|
684
|
+
|
|
685
|
+
this.#persist();
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
|
|
689
|
+
|
|
690
|
+
/** @returns {boolean} */
|
|
691
|
+
get prefersLocal(){
|
|
692
|
+
return this.#prefersLocal;
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
/** @param {boolean} v */
|
|
696
|
+
set prefersLocal(v){
|
|
697
|
+
if(!is.boolean(v)){
|
|
698
|
+
throw new Error('prefersLocal must be boolean');
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
this.#prefersLocal = v;
|
|
702
|
+
|
|
703
|
+
this.#persist();
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
/** @returns {array} */
|
|
707
|
+
get preferredModels(){
|
|
708
|
+
return this.#preferredModels;
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
/** @param {array} v */
|
|
712
|
+
set preferredModels(v){
|
|
713
|
+
if(!is.array(v)){
|
|
714
|
+
throw new Error('preferredModels must be an array');
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
this.#preferredModels = v;
|
|
718
|
+
|
|
719
|
+
this.#persist();
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
/** @returns {{charts:Object<string,boolean>}} */
|
|
723
|
+
get dashboard(){
|
|
724
|
+
return {
|
|
725
|
+
charts:{...this.#dashboard.charts}
|
|
726
|
+
};
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
/** @param {{charts:Object<string,boolean>}} v */
|
|
730
|
+
set dashboard(v){
|
|
731
|
+
if(!v||typeof v!=='object'||Array.isArray(v)){
|
|
732
|
+
throw new Error('dashboard must be an object');
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
this.#dashboard=normalizeDashboard(v);
|
|
736
|
+
|
|
737
|
+
this.#persist();
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
/**
|
|
741
|
+
* Explicit schema representation
|
|
742
|
+
*
|
|
743
|
+
* @returns {UserEntityData}
|
|
744
|
+
*/
|
|
745
|
+
get explicit(){
|
|
746
|
+
const data={};
|
|
747
|
+
|
|
748
|
+
for(let i=0;i<this.#schema.length;i++){
|
|
749
|
+
const key=this.#schema[i];
|
|
750
|
+
data[key]=this[key];
|
|
751
|
+
}
|
|
752
|
+
|
|
753
|
+
return data;
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
|
|
757
|
+
|
|
758
|
+
/**
|
|
759
|
+
* Update entity fields from object or JSON
|
|
760
|
+
*
|
|
761
|
+
* @param {UserEntityData|string|Object} src
|
|
762
|
+
*/
|
|
763
|
+
set explicit(src){
|
|
764
|
+
if(!src){
|
|
765
|
+
src=this.explicit;
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
if(!is.union(src,'object','string')){
|
|
769
|
+
throw new Error('UserEntity.explicit setter expects object or JSON string');
|
|
770
|
+
}
|
|
771
|
+
|
|
772
|
+
if(is.string(src)){
|
|
773
|
+
src = JSON.parse(src);
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
if(!is.object(src)){
|
|
777
|
+
throw new Error('UserEntity.explicit parsed src must be object');
|
|
778
|
+
}
|
|
779
|
+
|
|
780
|
+
const persist=this.persist;
|
|
781
|
+
this.persist=false;
|
|
782
|
+
|
|
783
|
+
for(let i=0;i<this.#schema.length;i++){
|
|
784
|
+
const key=this.#schema[i];
|
|
785
|
+
|
|
786
|
+
if(is.undefined(src[key])){
|
|
787
|
+
continue;
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
try {
|
|
791
|
+
this[key] = src[key];
|
|
792
|
+
} catch(e){
|
|
793
|
+
console.warn(`UserEntity.explicit setter skipping invalid field ${key}: ${e.message}`);
|
|
794
|
+
}
|
|
795
|
+
}
|
|
796
|
+
|
|
797
|
+
this.persist=persist;
|
|
798
|
+
this.#persist();
|
|
799
|
+
}
|
|
800
|
+
|
|
801
|
+
/**
|
|
802
|
+
* Applies and durably saves an explicit profile update as one serialized
|
|
803
|
+
* operation. A failed write restores the prior in-memory values.
|
|
804
|
+
*
|
|
805
|
+
* @param {UserEntityData|string|Object} src
|
|
806
|
+
* @returns {Promise<UserEntityData>}
|
|
807
|
+
*/
|
|
808
|
+
updateExplicit(src){
|
|
809
|
+
const operation=this.#explicitUpdateQueue.then(()=>{
|
|
810
|
+
const applyUpdate=async()=>{
|
|
811
|
+
const prior=this.explicit;
|
|
812
|
+
const persist=this.persist;
|
|
813
|
+
let baseline=prior;
|
|
814
|
+
|
|
815
|
+
if(persist){
|
|
816
|
+
const durable=await dbopfs.get(
|
|
817
|
+
this.#tableName,
|
|
818
|
+
this.fileName,
|
|
819
|
+
true
|
|
820
|
+
);
|
|
821
|
+
if(durable){
|
|
822
|
+
baseline=durable;
|
|
823
|
+
}
|
|
824
|
+
}
|
|
825
|
+
|
|
826
|
+
this.persist=false;
|
|
827
|
+
try{
|
|
828
|
+
this.explicit=baseline;
|
|
829
|
+
this.explicit=src;
|
|
830
|
+
}finally{
|
|
831
|
+
this.persist=persist;
|
|
832
|
+
}
|
|
833
|
+
|
|
834
|
+
if(!persist){
|
|
835
|
+
return this.explicit;
|
|
836
|
+
}
|
|
837
|
+
|
|
838
|
+
try{
|
|
839
|
+
await this.save();
|
|
840
|
+
return this.explicit;
|
|
841
|
+
}catch(error){
|
|
842
|
+
this.persist=false;
|
|
843
|
+
try{
|
|
844
|
+
this.explicit=baseline;
|
|
845
|
+
}finally{
|
|
846
|
+
this.persist=persist;
|
|
847
|
+
}
|
|
848
|
+
throw error;
|
|
849
|
+
}
|
|
850
|
+
};
|
|
851
|
+
return this.#withExplicitLock(applyUpdate);
|
|
852
|
+
});
|
|
853
|
+
|
|
854
|
+
this.#explicitUpdateQueue=operation.catch(()=>false);
|
|
855
|
+
return operation;
|
|
856
|
+
}
|
|
857
|
+
|
|
858
|
+
withFreshExplicit(operation){
|
|
859
|
+
if(typeof operation!=='function'){
|
|
860
|
+
throw new TypeError('A fresh UserEntity operation is required.');
|
|
861
|
+
}
|
|
862
|
+
const result=this.#explicitUpdateQueue.then(()=>
|
|
863
|
+
this.#withExplicitLock(async()=>{
|
|
864
|
+
const durable=await dbopfs.get(
|
|
865
|
+
this.#tableName,
|
|
866
|
+
this.fileName,
|
|
867
|
+
true
|
|
868
|
+
);
|
|
869
|
+
if(durable){
|
|
870
|
+
const persist=this.persist;
|
|
871
|
+
this.persist=false;
|
|
872
|
+
try{
|
|
873
|
+
this.explicit=durable;
|
|
874
|
+
}finally{
|
|
875
|
+
this.persist=persist;
|
|
876
|
+
}
|
|
877
|
+
}
|
|
878
|
+
|
|
879
|
+
return operation(this.explicit);
|
|
880
|
+
})
|
|
881
|
+
);
|
|
882
|
+
|
|
883
|
+
this.#explicitUpdateQueue=result.catch(()=>false);
|
|
884
|
+
return result;
|
|
885
|
+
}
|
|
886
|
+
|
|
887
|
+
#withExplicitLock(operation){
|
|
888
|
+
const locks=globalThis.navigator?.locks;
|
|
889
|
+
|
|
890
|
+
if(typeof locks?.request==='function'){
|
|
891
|
+
return locks.request(
|
|
892
|
+
`user-entity:${this.fileName}`,
|
|
893
|
+
operation
|
|
894
|
+
);
|
|
895
|
+
}
|
|
896
|
+
if(typeof globalThis.window==='object'){
|
|
897
|
+
const error=new Error(
|
|
898
|
+
'This browser cannot safely coordinate profile changes across tabs.'
|
|
899
|
+
);
|
|
900
|
+
error.code='USER_ENTITY_LOCK_UNAVAILABLE';
|
|
901
|
+
return Promise.reject(error);
|
|
902
|
+
}
|
|
903
|
+
|
|
904
|
+
return operation();
|
|
905
|
+
}
|
|
906
|
+
|
|
907
|
+
/**
|
|
908
|
+
* Load entity from OPFS
|
|
909
|
+
*
|
|
910
|
+
* Reads serialized entity data using DBOPFS
|
|
911
|
+
* this is async so your code can be faster
|
|
912
|
+
* only await if you need to ensure data is loaded before proceeding
|
|
913
|
+
*/
|
|
914
|
+
async load(){
|
|
915
|
+
if(this.ready){
|
|
916
|
+
return this.explicit;
|
|
917
|
+
}
|
|
918
|
+
|
|
919
|
+
if(this.#loadPromise){
|
|
920
|
+
return this.#loadPromise;
|
|
921
|
+
}
|
|
922
|
+
|
|
923
|
+
this.#loadPromise=this.#load();
|
|
924
|
+
|
|
925
|
+
try{
|
|
926
|
+
return await this.#loadPromise;
|
|
927
|
+
}finally{
|
|
928
|
+
this.#loadPromise=null;
|
|
929
|
+
}
|
|
930
|
+
}
|
|
931
|
+
|
|
932
|
+
/**
|
|
933
|
+
* Force-reads the durable profile so an already-open tab observes changes
|
|
934
|
+
* made by another tab before making consent-sensitive decisions.
|
|
935
|
+
*
|
|
936
|
+
* @returns {Promise<UserEntityData>}
|
|
937
|
+
*/
|
|
938
|
+
async refresh(){
|
|
939
|
+
await this.#explicitUpdateQueue;
|
|
940
|
+
const user=await dbopfs.get(
|
|
941
|
+
this.#tableName,
|
|
942
|
+
this.fileName,
|
|
943
|
+
true
|
|
944
|
+
);
|
|
945
|
+
|
|
946
|
+
if(user){
|
|
947
|
+
const persist=this.persist;
|
|
948
|
+
this.persist=false;
|
|
949
|
+
try{
|
|
950
|
+
this.explicit=user;
|
|
951
|
+
}finally{
|
|
952
|
+
this.persist=persist;
|
|
953
|
+
}
|
|
954
|
+
}
|
|
955
|
+
|
|
956
|
+
return this.explicit;
|
|
957
|
+
}
|
|
958
|
+
|
|
959
|
+
async #load(){
|
|
960
|
+
const user=await dbopfs.get(
|
|
961
|
+
this.#tableName,
|
|
962
|
+
this.fileName
|
|
963
|
+
);
|
|
964
|
+
|
|
965
|
+
if(user){
|
|
966
|
+
const persist=this.persist;
|
|
967
|
+
this.persist=false;
|
|
968
|
+
|
|
969
|
+
try{
|
|
970
|
+
this.explicit=user;
|
|
971
|
+
}finally{
|
|
972
|
+
this.persist=persist;
|
|
973
|
+
}
|
|
974
|
+
}
|
|
975
|
+
|
|
976
|
+
this.ready=true;
|
|
977
|
+
|
|
978
|
+
window.dispatchEvent(
|
|
979
|
+
new CustomEvent(
|
|
980
|
+
'user-entity-loaded',
|
|
981
|
+
{
|
|
982
|
+
detail:{
|
|
983
|
+
user:this
|
|
984
|
+
}
|
|
985
|
+
}
|
|
986
|
+
)
|
|
987
|
+
);
|
|
988
|
+
|
|
989
|
+
return this.explicit;
|
|
990
|
+
}
|
|
991
|
+
|
|
992
|
+
/**
|
|
993
|
+
* Persist entity to OPFS
|
|
994
|
+
*
|
|
995
|
+
* Writes serialized entity data using DBOPFS
|
|
996
|
+
* this is async so your code can run faster
|
|
997
|
+
* only await if you need to ensure data is saved before proceeding
|
|
998
|
+
*/
|
|
999
|
+
async save(){
|
|
1000
|
+
return dbopfs.set(
|
|
1001
|
+
this.#tableName,
|
|
1002
|
+
this.fileName,
|
|
1003
|
+
JSON.stringify(this.explicit)
|
|
1004
|
+
);
|
|
1005
|
+
}
|
|
1006
|
+
|
|
1007
|
+
/**
|
|
1008
|
+
* Serialize entity to JSON
|
|
1009
|
+
*
|
|
1010
|
+
* @returns {string}
|
|
1011
|
+
*/
|
|
1012
|
+
toJSON(){
|
|
1013
|
+
return JSON.stringify(
|
|
1014
|
+
this.explicit
|
|
1015
|
+
);
|
|
1016
|
+
}
|
|
1017
|
+
|
|
1018
|
+
/**
|
|
1019
|
+
* Internal persistence trigger
|
|
1020
|
+
*/
|
|
1021
|
+
#persist(){
|
|
1022
|
+
if(this.persist){
|
|
1023
|
+
return this.save();
|
|
1024
|
+
}
|
|
1025
|
+
|
|
1026
|
+
return true;
|
|
1027
|
+
}
|
|
1028
|
+
}
|
|
1029
|
+
|
|
1030
|
+
window.addEventListener(
|
|
1031
|
+
'dbopfs-ready',
|
|
1032
|
+
initSingletonUserEntity
|
|
1033
|
+
);
|
|
1034
|
+
|
|
1035
|
+
if(window.dbopfs?.ready){
|
|
1036
|
+
initSingletonUserEntity();
|
|
1037
|
+
}
|
|
1038
|
+
|
|
1039
|
+
function initSingletonUserEntity(){
|
|
1040
|
+
if(!window.user){
|
|
1041
|
+
window.user = new UserEntity();
|
|
1042
|
+
}
|
|
1043
|
+
}
|
|
1044
|
+
|
|
1045
|
+
|
|
1046
|
+
export default UserEntity;
|