@hearthlog/runtime 0.0.0-stage → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (120) hide show
  1. package/LICENSE +235 -0
  2. package/package.json +51 -3
  3. package/src/access/session.ts +223 -0
  4. package/src/access/user-session.ts +61 -0
  5. package/src/ai/decisions.ts +89 -0
  6. package/src/calendar/draft.ts +84 -0
  7. package/src/calendar/feeds.ts +91 -0
  8. package/src/calendar/groups.ts +198 -0
  9. package/src/calendar/logic.ts +141 -0
  10. package/src/calendar/preferences.ts +40 -0
  11. package/src/calendar/weeks.ts +56 -0
  12. package/src/commands/index.ts +184 -0
  13. package/src/commands/local-commit.ts +214 -0
  14. package/src/commands/standard.ts +8 -0
  15. package/src/commands/validation.ts +206 -0
  16. package/src/db/bootstrap.ts +72 -0
  17. package/src/db/calendar.ts +638 -0
  18. package/src/db/dynamic-ddl.ts +127 -0
  19. package/src/db/entities.ts +208 -0
  20. package/src/db/history.ts +194 -0
  21. package/src/db/index.ts +1240 -0
  22. package/src/db/kv.ts +33 -0
  23. package/src/db/local-statistics.ts +106 -0
  24. package/src/db/module-projections.ts +46 -0
  25. package/src/db/raw-browser.ts +361 -0
  26. package/src/db/read-repositories.ts +29 -0
  27. package/src/db/read-session.ts +47 -0
  28. package/src/db/schema-version.ts +76 -0
  29. package/src/db/session-context.ts +33 -0
  30. package/src/event/code-export.ts +65 -0
  31. package/src/event/dynamic-entities.ts +503 -0
  32. package/src/event/dynamic-materializer.ts +288 -0
  33. package/src/event/handlers.ts +490 -0
  34. package/src/event/index.ts +53 -0
  35. package/src/event/meta-entity-types.ts +165 -0
  36. package/src/event/projection-builder.ts +384 -0
  37. package/src/event/queries.ts +126 -0
  38. package/src/event/rebuild.ts +176 -0
  39. package/src/event/reference-lookup.ts +93 -0
  40. package/src/event/registry.ts +158 -0
  41. package/src/event/schema-effects.ts +45 -0
  42. package/src/event/types.ts +20 -0
  43. package/src/history/action-boundary.ts +18 -0
  44. package/src/history/action-detail.ts +23 -0
  45. package/src/history/change-comparison.ts +29 -0
  46. package/src/history/change-display.ts +41 -0
  47. package/src/history/contracts.ts +63 -0
  48. package/src/history/history.ts +372 -0
  49. package/src/history/legacy-replay.ts +69 -0
  50. package/src/history/legacy-url.ts +9 -0
  51. package/src/history/lineage.ts +123 -0
  52. package/src/history/log-format.ts +17 -0
  53. package/src/history/log-map.ts +110 -0
  54. package/src/history/navigation.ts +14 -0
  55. package/src/history/point-snapshot.ts +229 -0
  56. package/src/history/timeline.ts +215 -0
  57. package/src/history/url.ts +18 -0
  58. package/src/ledger/README.md +118 -0
  59. package/src/ledger/admin-client.ts +616 -0
  60. package/src/ledger/client.ts +10 -0
  61. package/src/ledger/config.ts +117 -0
  62. package/src/ledger/entity-id.ts +115 -0
  63. package/src/ledger/errors.ts +144 -0
  64. package/src/ledger/http-client.ts +590 -0
  65. package/src/ledger/index.ts +67 -0
  66. package/src/ledger/socket.ts +400 -0
  67. package/src/ledger/types.ts +202 -0
  68. package/src/ledger/version-tracker-singleton.ts +12 -0
  69. package/src/ledger/version-tracker.ts +259 -0
  70. package/src/log-management.ts +123 -0
  71. package/src/log-validation.ts +19 -0
  72. package/src/native/reminders.ts +2 -0
  73. package/src/native/shell.svelte.ts +136 -0
  74. package/src/native/shell.ts +102 -0
  75. package/src/pwa/registration.ts +169 -0
  76. package/src/recovery/local-recovery.ts +59 -0
  77. package/src/recovery/reset-sequence.ts +13 -0
  78. package/src/runtime/clock.ts +43 -0
  79. package/src/runtime/kept-page-data.svelte.ts +42 -0
  80. package/src/runtime/page-data.svelte.ts +160 -0
  81. package/src/runtime/phone-header.svelte.ts +17 -0
  82. package/src/runtime/read-coordinator.ts +103 -0
  83. package/src/runtime/refresh.ts +15 -0
  84. package/src/runtime/start-page.ts +56 -0
  85. package/src/runtime/view.ts +83 -0
  86. package/src/runtime/workspace.ts +62 -0
  87. package/src/storage/indexeddb-statistics.ts +153 -0
  88. package/src/store/attention.svelte.ts +51 -0
  89. package/src/store/conflict-merge.ts +169 -0
  90. package/src/store/entity-sync-state.svelte.ts +36 -0
  91. package/src/store/event-log.svelte.ts +109 -0
  92. package/src/store/events.svelte.ts +14 -0
  93. package/src/store/local-context.ts +93 -0
  94. package/src/store/local-store.svelte.ts +58 -0
  95. package/src/store/log-version.svelte.ts +35 -0
  96. package/src/store/logs.svelte.ts +60 -0
  97. package/src/store/moved.svelte.ts +43 -0
  98. package/src/store/offline-readiness.svelte.ts +188 -0
  99. package/src/store/outbox-request.ts +61 -0
  100. package/src/store/outbox.ts +426 -0
  101. package/src/store/pending-work-transfer.ts +49 -0
  102. package/src/store/pending-work.ts +143 -0
  103. package/src/store/sync-coordinator.ts +170 -0
  104. package/src/store/sync-invalidation.ts +10 -0
  105. package/src/store/sync-issues.ts +406 -0
  106. package/src/store/sync-progress.ts +54 -0
  107. package/src/store/sync-state.ts +127 -0
  108. package/src/store/sync.svelte.ts +1010 -0
  109. package/src/store/tab-coordination.ts +164 -0
  110. package/src/testing/e2e-seed.ts +40 -0
  111. package/src/testing/local-database.ts +120 -0
  112. package/src/testing/projection-database.ts +105 -0
  113. package/src/testing/test-entities.ts +28 -0
  114. package/src/theme/appearance.svelte.ts +111 -0
  115. package/src/utils/address.ts +5 -0
  116. package/src/utils/async.ts +30 -0
  117. package/src/utils/long-press.ts +70 -0
  118. package/src/utils/perf-dataset.ts +405 -0
  119. package/src/utils/perf.ts +627 -0
  120. package/README.md +0 -3
@@ -0,0 +1,405 @@
1
+ import { requireActiveContext, setActiveContext } from '../db/index.js';
2
+ import { set as setKv } from '../db/kv.js';
3
+ import { handleIncomingStoredEvents, replaceAllStoredEvents } from '../event/index.js';
4
+ import type { DynamicFieldDefinition } from '../event/meta-entity-types.js';
5
+ import type { StoredEvent } from '../ledger/types.js';
6
+ import { notifyEventsChanged } from '../store/events.svelte.js';
7
+ import {
8
+ addSyncLogEntry,
9
+ stopRealtime,
10
+ } from '../store/sync.svelte.js';
11
+
12
+ export const PERFORMANCE_DATASET_LOG_ID = 'perf-benchmark-local';
13
+
14
+ export interface PerformanceDatasetConfig {
15
+ vendorCount: number;
16
+ categoryCount: number;
17
+ itemCount: number;
18
+ updateCount: number;
19
+ replayCount: number;
20
+ }
21
+
22
+ export interface PerformanceDatasetManifest {
23
+ config: PerformanceDatasetConfig;
24
+ entityTypeCount: number;
25
+ dynamicEntityCount: number;
26
+ seedEventCount: number;
27
+ replayEventCount: number;
28
+ seedHeadVersion: number;
29
+ replayHeadVersion: number;
30
+ referenceFieldCount: number;
31
+ }
32
+
33
+ export interface PerformanceDataset {
34
+ manifest: PerformanceDatasetManifest;
35
+ seedEvents: StoredEvent[];
36
+ replayEvents: StoredEvent[];
37
+ }
38
+
39
+ const ENTITY_NAMES = {
40
+ vendor: 'perf_vendor',
41
+ category: 'perf_category',
42
+ item: 'perf_item',
43
+ } as const;
44
+
45
+ export const DEFAULT_PERFORMANCE_DATASET_CONFIG: PerformanceDatasetConfig = {
46
+ vendorCount: 40,
47
+ categoryCount: 120,
48
+ itemCount: 1200,
49
+ updateCount: 320,
50
+ replayCount: 250,
51
+ };
52
+
53
+ function assertPositiveInteger(value: number, label: string): void {
54
+ if (!Number.isInteger(value) || value <= 0) {
55
+ throw new Error(`${label} must be a positive integer`);
56
+ }
57
+ }
58
+
59
+ function validateConfig(config: PerformanceDatasetConfig): void {
60
+ assertPositiveInteger(config.vendorCount, 'vendorCount');
61
+ assertPositiveInteger(config.categoryCount, 'categoryCount');
62
+ assertPositiveInteger(config.itemCount, 'itemCount');
63
+ assertPositiveInteger(config.updateCount, 'updateCount');
64
+ assertPositiveInteger(config.replayCount, 'replayCount');
65
+ }
66
+
67
+ function padIndex(index: number): string {
68
+ return String(index).padStart(4, '0');
69
+ }
70
+
71
+ function createId(kind: string, index: number): string {
72
+ return `perf-${kind}-${padIndex(index)}`;
73
+ }
74
+
75
+ function createEntityId(entityName: string, id: string): string {
76
+ return `${entityName}:${id}`;
77
+ }
78
+
79
+ function createEntityTypeFields(
80
+ entityName:
81
+ | typeof ENTITY_NAMES.vendor
82
+ | typeof ENTITY_NAMES.category
83
+ | typeof ENTITY_NAMES.item
84
+ ): DynamicFieldDefinition[] {
85
+ if (entityName === ENTITY_NAMES.vendor) {
86
+ return [
87
+ { name: 'name', type: 'string', payloadKey: 'name', required: true },
88
+ { name: 'tier', type: 'string', payloadKey: 'tier', required: true },
89
+ { name: 'rating', type: 'number', payloadKey: 'rating', required: true },
90
+ { name: 'is_preferred', type: 'boolean', payloadKey: 'isPreferred', default: false },
91
+ ];
92
+ }
93
+
94
+ if (entityName === ENTITY_NAMES.category) {
95
+ return [
96
+ { name: 'name', type: 'string', payloadKey: 'name', required: true },
97
+ { name: 'group_code', type: 'string', payloadKey: 'groupCode', required: true },
98
+ { name: 'sort_order', type: 'number', payloadKey: 'sortOrder', required: true },
99
+ ];
100
+ }
101
+
102
+ return [
103
+ { name: 'name', type: 'string', payloadKey: 'name', required: true },
104
+ { name: 'sku', type: 'string', payloadKey: 'sku', required: true },
105
+ { name: 'status', type: 'string', payloadKey: 'status', required: true },
106
+ { name: 'amount', type: 'number', payloadKey: 'amount', required: true },
107
+ { name: 'score', type: 'number', payloadKey: 'score', required: true },
108
+ {
109
+ name: 'vendor_id',
110
+ type: 'reference',
111
+ payloadKey: 'vendorId',
112
+ required: true,
113
+ referenceTo: ENTITY_NAMES.vendor,
114
+ },
115
+ {
116
+ name: 'backup_vendor_id',
117
+ type: 'reference',
118
+ payloadKey: 'backupVendorId',
119
+ required: true,
120
+ referenceTo: ENTITY_NAMES.vendor,
121
+ },
122
+ {
123
+ name: 'category_id',
124
+ type: 'reference',
125
+ payloadKey: 'categoryId',
126
+ required: true,
127
+ referenceTo: ENTITY_NAMES.category,
128
+ },
129
+ { name: 'captured_at', type: 'datetime', payloadKey: 'capturedAt', required: true },
130
+ { name: 'is_active', type: 'boolean', payloadKey: 'isActive', default: true },
131
+ ];
132
+ }
133
+
134
+ function buildEntityTypeDefinitionEvents(): Array<{
135
+ entityName: string;
136
+ typeId: string;
137
+ fields: DynamicFieldDefinition[];
138
+ }> {
139
+ return [
140
+ {
141
+ entityName: ENTITY_NAMES.vendor,
142
+ typeId: createId('entity-type-vendor', 1),
143
+ fields: createEntityTypeFields(ENTITY_NAMES.vendor),
144
+ },
145
+ {
146
+ entityName: ENTITY_NAMES.category,
147
+ typeId: createId('entity-type-category', 1),
148
+ fields: createEntityTypeFields(ENTITY_NAMES.category),
149
+ },
150
+ {
151
+ entityName: ENTITY_NAMES.item,
152
+ typeId: createId('entity-type-item', 1),
153
+ fields: createEntityTypeFields(ENTITY_NAMES.item),
154
+ },
155
+ ];
156
+ }
157
+
158
+ function pushStoredEvent(
159
+ events: StoredEvent[],
160
+ options: {
161
+ version: number;
162
+ entityVersion: number;
163
+ type: string;
164
+ entityId: string;
165
+ timestamp: number;
166
+ payload: Record<string, unknown>;
167
+ }
168
+ ): void {
169
+ events.push({
170
+ version: options.version,
171
+ id: `perf-event-${String(options.version).padStart(6, '0')}`,
172
+ type: options.type,
173
+ entityId: options.entityId,
174
+ entityVersion: options.entityVersion,
175
+ timestamp: options.timestamp,
176
+ payload: options.payload,
177
+ });
178
+ }
179
+
180
+ export function buildPerformanceDataset(
181
+ config: PerformanceDatasetConfig = DEFAULT_PERFORMANCE_DATASET_CONFIG
182
+ ): PerformanceDataset {
183
+ validateConfig(config);
184
+
185
+ const seedEvents: StoredEvent[] = [];
186
+ const replayEvents: StoredEvent[] = [];
187
+ const entityVersions = new Map<string, number>();
188
+ const vendorIds = Array.from({ length: config.vendorCount }, (_, index) =>
189
+ createId('vendor', index + 1)
190
+ );
191
+ const categoryIds = Array.from({ length: config.categoryCount }, (_, index) =>
192
+ createId('category', index + 1)
193
+ );
194
+ const itemIds = Array.from({ length: config.itemCount }, (_, index) =>
195
+ createId('item', index + 1)
196
+ );
197
+ const entityTypeDefinitions = buildEntityTypeDefinitionEvents();
198
+
199
+ let version = 1;
200
+ const baseTimestamp = Date.UTC(2026, 2, 11, 12, 0, 0);
201
+
202
+ for (const [index, definition] of entityTypeDefinitions.entries()) {
203
+ pushStoredEvent(seedEvents, {
204
+ version,
205
+ entityVersion: 1,
206
+ type: 'ENTITY_TYPE_DEFINED',
207
+ entityId: `entity_type:${definition.typeId}`,
208
+ timestamp: baseTimestamp + index,
209
+ payload: {
210
+ id: definition.typeId,
211
+ name: definition.entityName,
212
+ displayName: definition.entityName
213
+ .split('_')
214
+ .map((part) => part.charAt(0).toUpperCase() + part.slice(1))
215
+ .join(' '),
216
+ tableName: `${definition.entityName}s`,
217
+ fields: definition.fields,
218
+ },
219
+ });
220
+ version += 1;
221
+ }
222
+
223
+ for (const [index, vendorId] of vendorIds.entries()) {
224
+ const entityId = createEntityId(ENTITY_NAMES.vendor, vendorId);
225
+ entityVersions.set(entityId, 1);
226
+ pushStoredEvent(seedEvents, {
227
+ version,
228
+ entityVersion: 1,
229
+ type: 'PERF_VENDOR_ADDED',
230
+ entityId,
231
+ timestamp: baseTimestamp + version,
232
+ payload: {
233
+ id: vendorId,
234
+ name: `Vendor ${index + 1}`,
235
+ tier: ['bronze', 'silver', 'gold'][index % 3],
236
+ rating: Number((3.2 + ((index % 15) * 0.1)).toFixed(1)),
237
+ isPreferred: index % 5 === 0,
238
+ },
239
+ });
240
+ version += 1;
241
+ }
242
+
243
+ for (const [index, categoryId] of categoryIds.entries()) {
244
+ const entityId = createEntityId(ENTITY_NAMES.category, categoryId);
245
+ entityVersions.set(entityId, 1);
246
+ pushStoredEvent(seedEvents, {
247
+ version,
248
+ entityVersion: 1,
249
+ type: 'PERF_CATEGORY_ADDED',
250
+ entityId,
251
+ timestamp: baseTimestamp + version,
252
+ payload: {
253
+ id: categoryId,
254
+ name: `Category ${index + 1}`,
255
+ groupCode: `G-${String((index % 12) + 1).padStart(2, '0')}`,
256
+ sortOrder: index + 1,
257
+ },
258
+ });
259
+ version += 1;
260
+ }
261
+
262
+ for (const [index, itemId] of itemIds.entries()) {
263
+ const entityId = createEntityId(ENTITY_NAMES.item, itemId);
264
+ const vendorId = vendorIds[index % vendorIds.length];
265
+ const backupVendorId =
266
+ vendorIds[(index + 7) % vendorIds.length];
267
+ const categoryId = categoryIds[index % categoryIds.length];
268
+
269
+ entityVersions.set(entityId, 1);
270
+ pushStoredEvent(seedEvents, {
271
+ version,
272
+ entityVersion: 1,
273
+ type: 'PERF_ITEM_ADDED',
274
+ entityId,
275
+ timestamp: baseTimestamp + version,
276
+ payload: {
277
+ id: itemId,
278
+ name: `Benchmark Item ${index + 1}`,
279
+ sku: `SKU-${String(index + 1).padStart(5, '0')}`,
280
+ status: ['draft', 'active', 'review'][index % 3],
281
+ amount: 100 + (index % 50) * 17,
282
+ score: Number((50 + (index % 300) * 0.25).toFixed(2)),
283
+ vendorId,
284
+ backupVendorId,
285
+ categoryId,
286
+ capturedAt: new Date(baseTimestamp + index * 60_000).toISOString(),
287
+ isActive: index % 17 !== 0,
288
+ },
289
+ });
290
+ version += 1;
291
+ }
292
+
293
+ for (let index = 0; index < config.updateCount; index += 1) {
294
+ const itemId = itemIds[index % itemIds.length];
295
+ const entityId = createEntityId(ENTITY_NAMES.item, itemId);
296
+ const entityVersion = (entityVersions.get(entityId) ?? 1) + 1;
297
+ entityVersions.set(entityId, entityVersion);
298
+
299
+ pushStoredEvent(seedEvents, {
300
+ version,
301
+ entityVersion,
302
+ type: 'PERF_ITEM_UPDATED',
303
+ entityId,
304
+ timestamp: baseTimestamp + version,
305
+ payload: {
306
+ id: itemId,
307
+ status: ['active', 'review', 'archived'][index % 3],
308
+ amount: 250 + (index % 60) * 23,
309
+ score: Number((60 + (index % 200) * 0.37).toFixed(2)),
310
+ backupVendorId: vendorIds[(index + 11) % vendorIds.length],
311
+ capturedAt: new Date(baseTimestamp + (index + 10_000) * 30_000).toISOString(),
312
+ isActive: index % 9 !== 0,
313
+ },
314
+ });
315
+ version += 1;
316
+ }
317
+
318
+ const seedHeadVersion = version - 1;
319
+
320
+ for (let index = 0; index < config.replayCount; index += 1) {
321
+ const itemId = itemIds[(index * 7) % itemIds.length];
322
+ const entityId = createEntityId(ENTITY_NAMES.item, itemId);
323
+ const entityVersion = (entityVersions.get(entityId) ?? 1) + 1;
324
+ entityVersions.set(entityId, entityVersion);
325
+
326
+ pushStoredEvent(replayEvents, {
327
+ version,
328
+ entityVersion,
329
+ type: 'PERF_ITEM_UPDATED',
330
+ entityId,
331
+ timestamp: baseTimestamp + version,
332
+ payload: {
333
+ id: itemId,
334
+ status: ['review', 'active', 'archived', 'active'][index % 4],
335
+ amount: 400 + (index % 75) * 19,
336
+ score: Number((80 + (index % 250) * 0.19).toFixed(2)),
337
+ vendorId: vendorIds[(index + 3) % vendorIds.length],
338
+ backupVendorId: vendorIds[(index + 17) % vendorIds.length],
339
+ categoryId: categoryIds[(index + 5) % categoryIds.length],
340
+ capturedAt: new Date(baseTimestamp + (index + 20_000) * 15_000).toISOString(),
341
+ isActive: index % 13 !== 0,
342
+ },
343
+ });
344
+ version += 1;
345
+ }
346
+
347
+ const manifest: PerformanceDatasetManifest = {
348
+ config: { ...config },
349
+ entityTypeCount: entityTypeDefinitions.length,
350
+ dynamicEntityCount:
351
+ config.vendorCount + config.categoryCount + config.itemCount,
352
+ seedEventCount: seedEvents.length,
353
+ replayEventCount: replayEvents.length,
354
+ seedHeadVersion,
355
+ replayHeadVersion: version - 1,
356
+ referenceFieldCount: 3,
357
+ };
358
+
359
+ return {
360
+ manifest,
361
+ seedEvents,
362
+ replayEvents,
363
+ };
364
+ }
365
+
366
+ export async function loadPerformanceDataset(
367
+ config: PerformanceDatasetConfig = DEFAULT_PERFORMANCE_DATASET_CONFIG
368
+ ): Promise<PerformanceDatasetManifest> {
369
+ const dataset = buildPerformanceDataset(config);
370
+ await stopRealtime();
371
+ const context = await requireActiveContext();
372
+ // setActiveContext updates this tab's session + last-used defaults
373
+ await setActiveContext({ ...context, logId: PERFORMANCE_DATASET_LOG_ID });
374
+ await replaceAllStoredEvents(dataset.seedEvents);
375
+ await setKv('db-log-id', PERFORMANCE_DATASET_LOG_ID);
376
+ await setKv('log-head-version', dataset.manifest.seedHeadVersion);
377
+ notifyEventsChanged();
378
+ await addSyncLogEntry(
379
+ 'download',
380
+ 'Loaded local performance benchmark dataset',
381
+ `${dataset.manifest.seedEventCount} stored events`
382
+ );
383
+
384
+ return dataset.manifest;
385
+ }
386
+
387
+ export async function replayPerformanceDataset(
388
+ config: PerformanceDatasetConfig = DEFAULT_PERFORMANCE_DATASET_CONFIG
389
+ ): Promise<PerformanceDatasetManifest> {
390
+ const dataset = buildPerformanceDataset(config);
391
+ await stopRealtime();
392
+ const context = await requireActiveContext();
393
+ await setActiveContext({ ...context, logId: PERFORMANCE_DATASET_LOG_ID });
394
+ await handleIncomingStoredEvents(dataset.replayEvents);
395
+ await setKv('db-log-id', PERFORMANCE_DATASET_LOG_ID);
396
+ await setKv('log-head-version', dataset.manifest.replayHeadVersion);
397
+ notifyEventsChanged();
398
+ await addSyncLogEntry(
399
+ 'download',
400
+ 'Replayed local performance sync burst',
401
+ `${dataset.manifest.replayEventCount} stored events`
402
+ );
403
+
404
+ return dataset.manifest;
405
+ }