@happyvertical/smrt-svelte 0.37.8 → 0.37.10

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 (90) hide show
  1. package/AGENTS.md +22 -86
  2. package/README.md +14 -10
  3. package/dist/components/workspace/MIGRATION.md +55 -0
  4. package/dist/components/workspace/README.md +22 -25
  5. package/dist/components/workspace/__tests__/AdminShell.test.js +55 -0
  6. package/dist/components/workspace/__tests__/admin-shell-hotkeys.test.js +34 -0
  7. package/dist/components/workspace/__tests__/admin-shell-settings.test.js +25 -0
  8. package/dist/components/workspace/__tests__/admin-shell-state.test.js +110 -0
  9. package/dist/components/workspace/__tests__/index.test.js +15 -24
  10. package/dist/components/workspace/__tests__/typed-tool-fixture/register-typed-tool.d.ts +2 -1
  11. package/dist/components/workspace/__tests__/typed-tool-fixture/register-typed-tool.d.ts.map +1 -1
  12. package/dist/components/workspace/__tests__/typed-tool-fixture/register-typed-tool.js +1 -1
  13. package/dist/components/workspace/admin-shell/ActivityBadge.svelte +25 -0
  14. package/dist/components/workspace/admin-shell/ActivityBadge.svelte.d.ts +9 -0
  15. package/dist/components/workspace/admin-shell/ActivityBadge.svelte.d.ts.map +1 -0
  16. package/dist/components/workspace/admin-shell/ActivityItem.svelte +85 -0
  17. package/dist/components/workspace/admin-shell/ActivityItem.svelte.d.ts +8 -0
  18. package/dist/components/workspace/admin-shell/ActivityItem.svelte.d.ts.map +1 -0
  19. package/dist/components/workspace/admin-shell/ActivityList.svelte +41 -0
  20. package/dist/components/workspace/admin-shell/ActivityList.svelte.d.ts +10 -0
  21. package/dist/components/workspace/admin-shell/ActivityList.svelte.d.ts.map +1 -0
  22. package/dist/components/workspace/admin-shell/ActivityToasts.svelte +140 -0
  23. package/dist/components/workspace/admin-shell/ActivityToasts.svelte.d.ts +16 -0
  24. package/dist/components/workspace/admin-shell/ActivityToasts.svelte.d.ts.map +1 -0
  25. package/dist/components/workspace/admin-shell/AdminShell.svelte +691 -0
  26. package/dist/components/workspace/admin-shell/AdminShell.svelte.d.ts +24 -0
  27. package/dist/components/workspace/admin-shell/AdminShell.svelte.d.ts.map +1 -0
  28. package/dist/components/workspace/admin-shell/AppScopePanel.svelte +94 -0
  29. package/dist/components/workspace/admin-shell/AppScopePanel.svelte.d.ts +14 -0
  30. package/dist/components/workspace/admin-shell/AppScopePanel.svelte.d.ts.map +1 -0
  31. package/dist/components/workspace/admin-shell/HotkeyInput.svelte +74 -0
  32. package/dist/components/workspace/admin-shell/HotkeyInput.svelte.d.ts +11 -0
  33. package/dist/components/workspace/admin-shell/HotkeyInput.svelte.d.ts.map +1 -0
  34. package/dist/components/workspace/admin-shell/ShellCorner.svelte +25 -0
  35. package/dist/components/workspace/admin-shell/ShellCorner.svelte.d.ts +9 -0
  36. package/dist/components/workspace/admin-shell/ShellCorner.svelte.d.ts.map +1 -0
  37. package/dist/components/workspace/admin-shell/ShellDockTool.svelte +19 -0
  38. package/dist/components/workspace/admin-shell/ShellDockTool.svelte.d.ts +7 -0
  39. package/dist/components/workspace/admin-shell/ShellDockTool.svelte.d.ts.map +1 -0
  40. package/dist/components/workspace/admin-shell/ShellSettingsPanel.svelte +146 -0
  41. package/dist/components/workspace/admin-shell/ShellSettingsPanel.svelte.d.ts +4 -0
  42. package/dist/components/workspace/admin-shell/ShellSettingsPanel.svelte.d.ts.map +1 -0
  43. package/dist/components/workspace/admin-shell/ShortcutsOverlay.svelte +81 -0
  44. package/dist/components/workspace/admin-shell/ShortcutsOverlay.svelte.d.ts +7 -0
  45. package/dist/components/workspace/admin-shell/ShortcutsOverlay.svelte.d.ts.map +1 -0
  46. package/dist/components/workspace/admin-shell/SystemScopePanel.svelte +118 -0
  47. package/dist/components/workspace/admin-shell/SystemScopePanel.svelte.d.ts +9 -0
  48. package/dist/components/workspace/admin-shell/SystemScopePanel.svelte.d.ts.map +1 -0
  49. package/dist/components/workspace/admin-shell/SystemStatusChips.svelte +71 -0
  50. package/dist/components/workspace/admin-shell/SystemStatusChips.svelte.d.ts +8 -0
  51. package/dist/components/workspace/admin-shell/SystemStatusChips.svelte.d.ts.map +1 -0
  52. package/dist/components/workspace/admin-shell/TenantNav.svelte +101 -0
  53. package/dist/components/workspace/admin-shell/TenantNav.svelte.d.ts +10 -0
  54. package/dist/components/workspace/admin-shell/TenantNav.svelte.d.ts.map +1 -0
  55. package/dist/components/workspace/admin-shell/context.d.ts +6 -0
  56. package/dist/components/workspace/admin-shell/context.d.ts.map +1 -0
  57. package/dist/components/workspace/admin-shell/context.js +16 -0
  58. package/dist/components/workspace/admin-shell/hotkeys.d.ts +14 -0
  59. package/dist/components/workspace/admin-shell/hotkeys.d.ts.map +1 -0
  60. package/dist/components/workspace/admin-shell/hotkeys.js +62 -0
  61. package/dist/components/workspace/admin-shell/settings.d.ts +14 -0
  62. package/dist/components/workspace/admin-shell/settings.d.ts.map +1 -0
  63. package/dist/components/workspace/admin-shell/settings.js +139 -0
  64. package/dist/components/workspace/admin-shell/state.svelte.d.ts +47 -0
  65. package/dist/components/workspace/admin-shell/state.svelte.d.ts.map +1 -0
  66. package/dist/components/workspace/admin-shell/state.svelte.js +252 -0
  67. package/dist/components/workspace/admin-shell/types.d.ts +137 -0
  68. package/dist/components/workspace/admin-shell/types.d.ts.map +1 -0
  69. package/dist/components/workspace/admin-shell/types.js +13 -0
  70. package/dist/components/workspace/index.d.ts +25 -11
  71. package/dist/components/workspace/index.d.ts.map +1 -1
  72. package/dist/components/workspace/index.js +24 -10
  73. package/dist/i18n/strings.workspace.d.ts +30 -0
  74. package/dist/i18n/strings.workspace.d.ts.map +1 -1
  75. package/dist/i18n/strings.workspace.js +31 -0
  76. package/dist/internal/logger.d.ts +13 -6
  77. package/dist/internal/logger.d.ts.map +1 -1
  78. package/dist/internal/logger.js +33 -8
  79. package/dist/internal/logger.test.js +54 -0
  80. package/dist/web/__tests__/harness.svelte +25 -0
  81. package/dist/web/__tests__/harness.svelte.d.ts +15 -0
  82. package/dist/web/__tests__/harness.svelte.d.ts.map +1 -0
  83. package/dist/web/__tests__/live-collection.svelte.test.js +184 -0
  84. package/dist/web/index.d.ts +13 -0
  85. package/dist/web/index.d.ts.map +1 -0
  86. package/dist/web/index.js +12 -0
  87. package/dist/web/live-collection.svelte.d.ts +132 -0
  88. package/dist/web/live-collection.svelte.d.ts.map +1 -0
  89. package/dist/web/live-collection.svelte.js +173 -0
  90. package/package.json +13 -4
@@ -1,12 +1,37 @@
1
1
  /**
2
2
  * Shared package logger for `@happyvertical/smrt-svelte`.
3
3
  *
4
- * Browser-safe: the `@happyvertical/logger` console adapter pulls no Node
5
- * built-ins and its `@sentry/node` integration is an optional peer that is
6
- * never imported here. Centralising the instance keeps voice/AI error reporting
7
- * consistent across the form components (mic permission denials, STT init
8
- * failures) instead of swallowing them in empty `catch` blocks or scattering
9
- * raw `console.*` calls.
4
+ * This module is browser-reachable (Provider + the form components import it),
5
+ * so it must never touch Node globals at module scope. `createLogger()` reads
6
+ * `HAVE_LOGGER_LEVEL` from `process.env`, and `process` does not exist in
7
+ * browsers — calling it during module init threw
8
+ * `ReferenceError: process is not defined` under `vite dev` and killed
9
+ * client-side hydration for every consuming app. The logger is therefore
10
+ * created lazily on first use, and the env-config path is only consulted when
11
+ * a usable `process.env` actually exists; browsers get the plain console
12
+ * logger at the same level.
13
+ *
14
+ * Centralising the instance keeps voice/AI error reporting consistent across
15
+ * the form components (mic permission denials, STT init failures) instead of
16
+ * swallowing them in empty `catch` blocks or scattering raw `console.*` calls.
10
17
  */
11
- import { createLogger } from '@happyvertical/logger';
12
- export const logger = createLogger({ level: 'warn' });
18
+ import { ConsoleLogger, createLogger, } from '@happyvertical/logger';
19
+ const LEVEL = 'warn';
20
+ let instance;
21
+ function resolveLogger() {
22
+ if (!instance) {
23
+ const hasProcessEnv = typeof process !== 'undefined' &&
24
+ typeof process.env === 'object' &&
25
+ process.env !== null;
26
+ instance = hasProcessEnv
27
+ ? createLogger({ level: LEVEL })
28
+ : new ConsoleLogger(LEVEL);
29
+ }
30
+ return instance;
31
+ }
32
+ export const logger = {
33
+ debug: (message, context) => resolveLogger().debug(message, context),
34
+ info: (message, context) => resolveLogger().info(message, context),
35
+ warn: (message, context) => resolveLogger().warn(message, context),
36
+ error: (message, context) => resolveLogger().error(message, context),
37
+ };
@@ -0,0 +1,54 @@
1
+ /**
2
+ * Regression coverage for the "process is not defined" hydration killer.
3
+ *
4
+ * `internal/logger` is browser-reachable through Provider and the form
5
+ * components, and `@happyvertical/logger`'s `createLogger()` eagerly reads
6
+ * `process.env` (HAVE_LOGGER_LEVEL). Constructing the logger at module scope
7
+ * therefore threw during Vite dev pre-bundle evaluation in browsers and killed
8
+ * client-side hydration for every consuming app.
9
+ *
10
+ * vite-node's module runner itself needs `process.platform` to load inline
11
+ * modules, so the test cannot delete `process` outright the way a real browser
12
+ * lacks it. Stubbing it to an object without a usable `env` reproduces the
13
+ * same failure mode: the pre-fix module init still blows up at import time
14
+ * (`Object.keys(process.env)`), and the fixed guard must route to the plain
15
+ * console logger.
16
+ */
17
+ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
18
+ describe('internal/logger', () => {
19
+ beforeEach(() => {
20
+ vi.resetModules();
21
+ });
22
+ afterEach(() => {
23
+ vi.unstubAllGlobals();
24
+ vi.restoreAllMocks();
25
+ });
26
+ it('imports and logs without a usable process.env (browser)', async () => {
27
+ vi.stubGlobal('process', {});
28
+ const { logger } = await import('./logger.js');
29
+ const warn = vi.spyOn(console, 'warn').mockImplementation(() => { });
30
+ logger.warn('mic permission denied');
31
+ expect(warn).toHaveBeenCalledTimes(1);
32
+ expect(warn.mock.calls[0][0]).toContain('mic permission denied');
33
+ });
34
+ it('filters below the warn level in the browser fallback', async () => {
35
+ vi.stubGlobal('process', {});
36
+ const { logger } = await import('./logger.js');
37
+ const debug = vi.spyOn(console, 'debug').mockImplementation(() => { });
38
+ const info = vi.spyOn(console, 'info').mockImplementation(() => { });
39
+ const error = vi.spyOn(console, 'error').mockImplementation(() => { });
40
+ logger.debug('below threshold');
41
+ logger.info('below threshold');
42
+ logger.error('stt init failed');
43
+ expect(debug).not.toHaveBeenCalled();
44
+ expect(info).not.toHaveBeenCalled();
45
+ expect(error).toHaveBeenCalledTimes(1);
46
+ });
47
+ it('logs through the env-aware logger when process exists (node/SSR)', async () => {
48
+ const { logger } = await import('./logger.js');
49
+ const warn = vi.spyOn(console, 'warn').mockImplementation(() => { });
50
+ logger.warn('stt init failed', { adapter: 'whisper-cpp' });
51
+ expect(warn).toHaveBeenCalledTimes(1);
52
+ expect(warn.mock.calls[0][0]).toContain('stt init failed');
53
+ });
54
+ });
@@ -0,0 +1,25 @@
1
+ <script lang="ts">
2
+ /**
3
+ * Test harness — invokes liveCollection() inside a component init scope so the
4
+ * `$effect` that `useLiveQuery` installs wires up correctly, then hands the
5
+ * returned reactive view back to the test via the `onReady` callback prop.
6
+ */
7
+ import type { SmrtWebCollection } from '@happyvertical/smrt-web';
8
+ import {
9
+ type LiveCollection,
10
+ liveCollection,
11
+ } from '../live-collection.svelte.js';
12
+
13
+ interface Props {
14
+ handle: SmrtWebCollection<Record<string, unknown>>;
15
+ onReady: (view: LiveCollection<Record<string, unknown>>) => void;
16
+ }
17
+
18
+ const props: Props = $props();
19
+ // This harness intentionally snapshots the handle during setup.
20
+ // svelte-ignore state_referenced_locally
21
+ const view = liveCollection(props.handle);
22
+ // This harness intentionally snapshots the callback during setup.
23
+ // svelte-ignore state_referenced_locally
24
+ props.onReady(view);
25
+ </script>
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Test harness — invokes liveCollection() inside a component init scope so the
3
+ * `$effect` that `useLiveQuery` installs wires up correctly, then hands the
4
+ * returned reactive view back to the test via the `onReady` callback prop.
5
+ */
6
+ import type { SmrtWebCollection } from '@happyvertical/smrt-web';
7
+ import { type LiveCollection } from '../live-collection.svelte.js';
8
+ interface Props {
9
+ handle: SmrtWebCollection<Record<string, unknown>>;
10
+ onReady: (view: LiveCollection<Record<string, unknown>>) => void;
11
+ }
12
+ declare const Harness: import("svelte").Component<Props, {}, "">;
13
+ type Harness = ReturnType<typeof Harness>;
14
+ export default Harness;
15
+ //# sourceMappingURL=harness.svelte.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"harness.svelte.d.ts","sourceRoot":"","sources":["../../../src/web/__tests__/harness.svelte.ts"],"names":[],"mappings":"AAGA;;;;GAIG;AACH,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;AACjE,OAAO,EACL,KAAK,cAAc,EAEpB,MAAM,8BAA8B,CAAC;AAGtC,UAAU,KAAK;IACb,MAAM,EAAE,iBAAiB,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;IACnD,OAAO,EAAE,CAAC,IAAI,EAAE,cAAc,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,KAAK,IAAI,CAAC;CAClE;AAkBD,QAAA,MAAM,OAAO,2CAAwC,CAAC;AACtD,KAAK,OAAO,GAAG,UAAU,CAAC,OAAO,OAAO,CAAC,CAAC;AAC1C,eAAe,OAAO,CAAC"}
@@ -0,0 +1,184 @@
1
+ /**
2
+ * Behavior tests for the Svelte 5 live-query binding over `@happyvertical/smrt-web`
3
+ * (#1761, slice A).
4
+ *
5
+ * Per repo policy only the network boundary is mocked: the generated REST
6
+ * client fetchers passed to `createSmrtCollection`. Everything else — the
7
+ * client-data engine collection, the real `useLiveQuery` live view, its
8
+ * transactions, and the runes reactivity — is real. The binding is exercised
9
+ * through a thin host component (`harness.svelte`) so `useLiveQuery`'s internal
10
+ * `$effect` has a genuine component-init scope, matching this package's other
11
+ * runes-composable tests.
12
+ */
13
+ import { createSmrtCollection, } from '@happyvertical/smrt-web';
14
+ import { flushSync, mount, unmount } from 'svelte';
15
+ import { afterEach, describe, expect, it } from 'vitest';
16
+ import Harness from './harness.svelte';
17
+ function productDefinition(name) {
18
+ return {
19
+ name,
20
+ className: 'Product',
21
+ endpoint: `/${name}`,
22
+ idField: 'id',
23
+ actions: ['create', 'get', 'list', 'update'],
24
+ fields: {
25
+ name: { type: 'text', required: true },
26
+ price: { type: 'decimal' },
27
+ },
28
+ };
29
+ }
30
+ /**
31
+ * Scripted stand-in for `createClient('/api/v1').products`: resolves like the
32
+ * generated fetchers (JSON payloads, `{ error }` bodies on failure, never
33
+ * rejects on HTTP status) so the engine's optimistic/rollback path is real.
34
+ */
35
+ function makeScriptedFetchers(initialRows) {
36
+ const serverRows = [...initialRows];
37
+ let failNextCreate = null;
38
+ const fetchers = {
39
+ list: async () => serverRows.map((row) => ({ ...row })),
40
+ create: async (data) => {
41
+ if (failNextCreate) {
42
+ const error = failNextCreate;
43
+ failNextCreate = null;
44
+ return { error };
45
+ }
46
+ const created = { ...data, id: `server-${serverRows.length + 1}` };
47
+ serverRows.push(created);
48
+ return { ...created };
49
+ },
50
+ };
51
+ return {
52
+ fetchers,
53
+ failCreateWith(message) {
54
+ failNextCreate = message;
55
+ },
56
+ };
57
+ }
58
+ function mountView(handle) {
59
+ const target = document.createElement('div');
60
+ document.body.appendChild(target);
61
+ let exposed = undefined;
62
+ const component = mount(Harness, {
63
+ target,
64
+ props: {
65
+ // The harness is typed against the base object shape; ProductData is a
66
+ // structural subtype, so this cross-cast is purely nominal.
67
+ handle: handle,
68
+ onReady: (view) => {
69
+ exposed = view;
70
+ },
71
+ },
72
+ });
73
+ return {
74
+ view: exposed,
75
+ teardown: () => {
76
+ unmount(component);
77
+ target.remove();
78
+ },
79
+ };
80
+ }
81
+ describe('liveCollection', () => {
82
+ const cleanup = [];
83
+ afterEach(async () => {
84
+ for (const fn of cleanup.splice(0))
85
+ fn();
86
+ });
87
+ function track(mounted) {
88
+ cleanup.push(mounted.teardown);
89
+ return mounted.view;
90
+ }
91
+ it('exposes live rows that reflect the collection as plain DTOs (no $-prefixed keys)', async () => {
92
+ const scripted = makeScriptedFetchers([
93
+ { id: 'p1', name: 'Widget', price: 9.99 },
94
+ ]);
95
+ const collection = createSmrtCollection(productDefinition('live-dto'), {
96
+ fetchers: scripted.fetchers,
97
+ staleTimeMs: 60_000,
98
+ });
99
+ const view = track(mountView(collection));
100
+ await collection.preload();
101
+ flushSync();
102
+ expect(view.isReady).toBe(true);
103
+ expect(view.status).toBe('ready');
104
+ expect(view.rows).toHaveLength(1);
105
+ const [row] = view.rows;
106
+ // Exact equality (not toMatchObject): must be a plain DTO — no
107
+ // $synced/$origin/$key/$collectionId leaking through the engine live view.
108
+ expect(row).toEqual({ id: 'p1', name: 'Widget', price: 9.99 });
109
+ expect(Object.keys(row).some((key) => key.startsWith('$'))).toBe(false);
110
+ expect(JSON.stringify(row)).not.toContain('$');
111
+ });
112
+ it('reports loading before the first read resolves', () => {
113
+ const scripted = makeScriptedFetchers([{ id: 'p1', name: 'Widget' }]);
114
+ const collection = createSmrtCollection(productDefinition('live-loading'), {
115
+ fetchers: scripted.fetchers,
116
+ staleTimeMs: 60_000,
117
+ // Do not eagerly preload — observe the initial loading status.
118
+ });
119
+ const view = track(mountView(collection));
120
+ flushSync();
121
+ expect(view.isLoading).toBe(true);
122
+ expect(view.status).toBe('loading');
123
+ expect(view.error).toBeNull();
124
+ });
125
+ it('reflects an optimistic insert reactively and settles its pending state', async () => {
126
+ const scripted = makeScriptedFetchers([{ id: 'p1', name: 'Widget' }]);
127
+ const collection = createSmrtCollection(productDefinition('live-insert'), {
128
+ fetchers: scripted.fetchers,
129
+ staleTimeMs: 60_000,
130
+ });
131
+ const view = track(mountView(collection));
132
+ await collection.preload();
133
+ flushSync();
134
+ expect(view.rows).toHaveLength(1);
135
+ const mutation = view.insert({
136
+ id: 'local-1',
137
+ name: 'Gadget',
138
+ price: 19.99,
139
+ });
140
+ flushSync();
141
+ // Optimistic row is visible in the live rows synchronously, and pending.
142
+ expect(view.rows.map((row) => row.name).sort()).toEqual([
143
+ 'Gadget',
144
+ 'Widget',
145
+ ]);
146
+ expect(mutation.pending).toBe(true);
147
+ expect(mutation.settled).toBe(false);
148
+ await mutation.done;
149
+ flushSync();
150
+ // Persisted: pending cleared, no error, and the server-assigned row is in.
151
+ expect(mutation.pending).toBe(false);
152
+ expect(mutation.settled).toBe(true);
153
+ expect(mutation.error).toBeNull();
154
+ const gadget = view.rows.find((row) => row.name === 'Gadget');
155
+ expect(gadget?.id).toBe('server-2');
156
+ // Still plain DTOs after the mutation reconciles.
157
+ expect(Object.keys(gadget ?? {}).some((key) => key.startsWith('$'))).toBe(false);
158
+ });
159
+ it('rolls the optimistic row back and surfaces the error when the create fails', async () => {
160
+ const scripted = makeScriptedFetchers([{ id: 'p1', name: 'Widget' }]);
161
+ const collection = createSmrtCollection(productDefinition('live-rollback'), {
162
+ fetchers: scripted.fetchers,
163
+ staleTimeMs: 60_000,
164
+ });
165
+ const view = track(mountView(collection));
166
+ await collection.preload();
167
+ flushSync();
168
+ scripted.failCreateWith('Validation failed: name is reserved');
169
+ const mutation = view.insert({ id: 'local-2', name: 'FAIL Gadget' });
170
+ flushSync();
171
+ expect(view.rows.some((row) => row.name === 'FAIL Gadget')).toBe(true);
172
+ // `done` resolves (never rejects) on settle — the failure is surfaced only
173
+ // reactively via `error`, so a reactive-only consumer risks no unhandled
174
+ // rejection.
175
+ await expect(mutation.done).resolves.toBeUndefined();
176
+ flushSync();
177
+ // Optimistic state rolled back reactively; the mutation carries the error.
178
+ expect(view.rows.some((row) => row.name === 'FAIL Gadget')).toBe(false);
179
+ expect(view.rows).toHaveLength(1);
180
+ expect(mutation.pending).toBe(false);
181
+ expect(mutation.settled).toBe(true);
182
+ expect(String(mutation.error?.message)).toContain('Validation failed: name is reserved');
183
+ });
184
+ });
@@ -0,0 +1,13 @@
1
+ /**
2
+ * `@happyvertical/smrt-svelte/web` — Svelte 5 live-query bindings for the
3
+ * `@happyvertical/smrt-web` browser client data runtime (#1761, slice A).
4
+ *
5
+ * Consumers turn a `SmrtWebCollection<T>` into runes-reactive live-query state
6
+ * plus mutation helpers WITHOUT importing TanStack. The client-data engine
7
+ * stays an implementation detail of the runtime + this binding; nothing here
8
+ * re-exports an `@tanstack/*` type.
9
+ *
10
+ * @packageDocumentation
11
+ */
12
+ export { type LiveCollection, type LiveCollectionMutation, type LiveCollectionOptions, type LiveCollectionStatus, liveCollection, } from './live-collection.svelte.js';
13
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/web/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,EACL,KAAK,cAAc,EACnB,KAAK,sBAAsB,EAC3B,KAAK,qBAAqB,EAC1B,KAAK,oBAAoB,EACzB,cAAc,GACf,MAAM,6BAA6B,CAAC"}
@@ -0,0 +1,12 @@
1
+ /**
2
+ * `@happyvertical/smrt-svelte/web` — Svelte 5 live-query bindings for the
3
+ * `@happyvertical/smrt-web` browser client data runtime (#1761, slice A).
4
+ *
5
+ * Consumers turn a `SmrtWebCollection<T>` into runes-reactive live-query state
6
+ * plus mutation helpers WITHOUT importing TanStack. The client-data engine
7
+ * stays an implementation detail of the runtime + this binding; nothing here
8
+ * re-exports an `@tanstack/*` type.
9
+ *
10
+ * @packageDocumentation
11
+ */
12
+ export { liveCollection, } from './live-collection.svelte.js';
@@ -0,0 +1,132 @@
1
+ /**
2
+ * Svelte 5 live-query bindings for `@happyvertical/smrt-web` (#1761, slice A).
3
+ *
4
+ * Turns a SMRT-owned {@link SmrtWebCollection} into runes-reactive live-query
5
+ * state plus mutation helpers, so a consumer writes reactive components WITHOUT
6
+ * importing TanStack. The client-data engine (`@tanstack/db` +
7
+ * `@tanstack/svelte-db`) is used only inside this module — its types never
8
+ * appear on the public surface, mirroring smrt-web's own engine-absorption
9
+ * boundary (ratified condition for #1761).
10
+ *
11
+ * Two engine facts drive the shape of this file:
12
+ *
13
+ * 1. `useLiveQuery` internally uses `$state`/`$derived`/`$effect`, so it (and
14
+ * therefore {@link liveCollection}) MUST be called during Svelte component
15
+ * initialization — exactly like this package's other `use*` composables.
16
+ * Its internal `$effect` binds to the calling component's lifecycle, so the
17
+ * live subscription is torn down automatically when the component unmounts.
18
+ *
19
+ * 2. `@tanstack/svelte-db` live-query rows carry ENUMERABLE engine virtual
20
+ * props (`$key`/`$origin`/`$synced`/`$collectionId`) that would otherwise
21
+ * escape through spread or JSON. Every exposed row is projected to a plain
22
+ * DTO by stripping `$`-prefixed keys (the `$` prefix is reserved for the
23
+ * engine; SMRT columns never begin with it), mirroring smrt-web's internal
24
+ * `toPlainRow`.
25
+ */
26
+ import { type SmrtWebCollection, type SmrtWebRow } from '@happyvertical/smrt-web';
27
+ /**
28
+ * Lifecycle status of a {@link LiveCollection}'s underlying read.
29
+ *
30
+ * - `loading` — the first load has not completed yet (also covers the engine's
31
+ * pre-start `idle` and post-teardown `cleaned-up` phases).
32
+ * - `ready` — data has arrived and the collection is live.
33
+ * - `error` — the read failed during sync initialization.
34
+ */
35
+ export type LiveCollectionStatus = 'loading' | 'ready' | 'error';
36
+ /**
37
+ * A live, runes-reactive view over a {@link SmrtWebCollection}. Read `rows`,
38
+ * `status`, and `error` directly in markup — they update as the collection
39
+ * changes. Do NOT destructure this object (Svelte 5 reactivity is lost on
40
+ * destructure); read through the object, e.g. `view.rows`, or wrap in
41
+ * `$derived`.
42
+ */
43
+ export interface LiveCollection<TData extends object> {
44
+ /** Live rows as plain DTOs (no `$`-prefixed engine props), insertion order. */
45
+ readonly rows: ReadonlyArray<SmrtWebRow<TData>>;
46
+ /** Coarse lifecycle status of the underlying read. */
47
+ readonly status: LiveCollectionStatus;
48
+ /**
49
+ * The most recent error surfaced by the underlying read, or `null`. Mutation
50
+ * errors are surfaced on the {@link LiveCollectionMutation} returned by the
51
+ * mutation helpers, not here.
52
+ */
53
+ readonly error: unknown;
54
+ /** True while the first load has not completed (`status === 'loading'`). */
55
+ readonly isLoading: boolean;
56
+ /** True once data has arrived (`status === 'ready'`). */
57
+ readonly isReady: boolean;
58
+ /** True when the underlying read failed (`status === 'error'`). */
59
+ readonly isError: boolean;
60
+ /**
61
+ * Optimistically insert a row and persist it through the collection's create
62
+ * surface. The row is visible in `rows` synchronously; the returned handle's
63
+ * reactive `pending`/`error` track the server outcome and a failed create
64
+ * rolls the optimistic row back automatically.
65
+ */
66
+ insert(row: SmrtWebRow<TData>): LiveCollectionMutation;
67
+ }
68
+ /**
69
+ * Reactive handle to one in-flight mutation. Read `pending`/`error`/`settled`
70
+ * in markup to drive optimistic UI; await {@link done} to observe completion
71
+ * imperatively. All fields are SMRT-owned — no engine transaction leaks.
72
+ */
73
+ export interface LiveCollectionMutation {
74
+ /** True while the write is persisting; flips to false on success or failure. */
75
+ readonly pending: boolean;
76
+ /** True once the write has settled (persisted or rolled back). */
77
+ readonly settled: boolean;
78
+ /** The rollback error if the write failed, else `null`. */
79
+ readonly error: unknown;
80
+ /**
81
+ * Resolves when the write settles (persisted or rolled back); read
82
+ * {@link error} (or {@link settled}) for the outcome. Never rejects, so
83
+ * reactive-only consumers never risk an unhandled rejection.
84
+ */
85
+ readonly done: Promise<void>;
86
+ }
87
+ /** Options for {@link liveCollection}. Reserved for forward-compatible growth. */
88
+ export interface LiveCollectionOptions {
89
+ /**
90
+ * Trigger the collection's first load eagerly when the binding is created,
91
+ * rather than waiting for the engine's first read. Defaults to `true`.
92
+ */
93
+ preload?: boolean;
94
+ }
95
+ /**
96
+ * Create a runes-reactive live view over a {@link SmrtWebCollection}.
97
+ *
98
+ * MUST be called during Svelte component initialization (it delegates to
99
+ * `@tanstack/svelte-db`'s `useLiveQuery`, which sets up a `$effect`). The live
100
+ * subscription is torn down automatically when the calling component unmounts.
101
+ *
102
+ * @example
103
+ * ```svelte
104
+ * <script lang="ts">
105
+ * import { createSmrtCollection } from '@happyvertical/smrt-web';
106
+ * import { liveCollection } from '@happyvertical/smrt-svelte/web';
107
+ * import { productsDefinition } from '@happyvertical/smrt-virt-web';
108
+ *
109
+ * const products = createSmrtCollection(productsDefinition, {});
110
+ * const view = liveCollection(products);
111
+ *
112
+ * async function add() {
113
+ * const m = view.insert({ id: crypto.randomUUID(), name: 'New' });
114
+ * await m.done.catch(() => {}); // error also reflected on m.error
115
+ * }
116
+ * </script>
117
+ *
118
+ * {#if view.isLoading}
119
+ * <p>Loading…</p>
120
+ * {:else if view.isError}
121
+ * <p>Failed: {String(view.error)}</p>
122
+ * {:else}
123
+ * <ul>
124
+ * {#each view.rows as row (row.id)}
125
+ * <li>{row.name}</li>
126
+ * {/each}
127
+ * </ul>
128
+ * {/if}
129
+ * ```
130
+ */
131
+ export declare function liveCollection<TData extends object>(handle: SmrtWebCollection<TData>, options?: LiveCollectionOptions): LiveCollection<TData>;
132
+ //# sourceMappingURL=live-collection.svelte.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"live-collection.svelte.d.ts","sourceRoot":"","sources":["../../src/web/live-collection.svelte.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAEH,OAAO,EAEL,KAAK,iBAAiB,EACtB,KAAK,UAAU,EAChB,MAAM,yBAAyB,CAAC;AASjC;;;;;;;GAOG;AACH,MAAM,MAAM,oBAAoB,GAAG,SAAS,GAAG,OAAO,GAAG,OAAO,CAAC;AAEjE;;;;;;GAMG;AACH,MAAM,WAAW,cAAc,CAAC,KAAK,SAAS,MAAM;IAClD,+EAA+E;IAC/E,QAAQ,CAAC,IAAI,EAAE,aAAa,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC,CAAC;IAChD,sDAAsD;IACtD,QAAQ,CAAC,MAAM,EAAE,oBAAoB,CAAC;IACtC;;;;OAIG;IACH,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC;IACxB,4EAA4E;IAC5E,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC;IAC5B,yDAAyD;IACzD,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,mEAAmE;IACnE,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B;;;;;OAKG;IACH,MAAM,CAAC,GAAG,EAAE,UAAU,CAAC,KAAK,CAAC,GAAG,sBAAsB,CAAC;CACxD;AAED;;;;GAIG;AACH,MAAM,WAAW,sBAAsB;IACrC,gFAAgF;IAChF,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,kEAAkE;IAClE,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,2DAA2D;IAC3D,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC;IACxB;;;;OAIG;IACH,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC,IAAI,CAAC,CAAC;CAC9B;AAED,kFAAkF;AAClF,MAAM,WAAW,qBAAqB;IACpC;;;OAGG;IACH,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAkCD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AACH,wBAAgB,cAAc,CAAC,KAAK,SAAS,MAAM,EACjD,MAAM,EAAE,iBAAiB,CAAC,KAAK,CAAC,EAChC,OAAO,GAAE,qBAA0B,GAClC,cAAc,CAAC,KAAK,CAAC,CA6DvB"}