From 00137997cf6044b199bf4666e07cb5c2bba1e12d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fabr=C3=ADcio=20Bracht?= Date: Thu, 13 Aug 2026 14:28:40 -0300 Subject: [PATCH 1/2] add remote.autoConnect and bump stitch-wasm to 0.3.0 --- docs/configuration.md | 2 +- package-lock.json | 12 ++++---- package.json | 2 +- src/types.ts | 1 + tests/integration/auto-connect.test.ts | 40 ++++++++++++++++++++++++++ 5 files changed, 49 insertions(+), 8 deletions(-) create mode 100644 tests/integration/auto-connect.test.ts diff --git a/docs/configuration.md b/docs/configuration.md index 35d310d..dde0bcd 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -21,7 +21,7 @@ | Field | Type | Description | |---|---|---| | `persistence` | `{ dbName: string, passphrase?: string }` | Enable IndexedDB persistence. A `passphrase` turns on AES-GCM encryption at rest | -| `remote` | `{ url: string, clientId?: string, ticket?: string, username?: string, password?: string }` | Enable MQTT sync. `url` is a `ws://`/`wss://` MQTT endpoint; `ticket` is a JWT for MQTT v5 enhanced auth; `username`/`password` drive classic MQTT password auth | +| `remote` | `{ url: string, clientId?: string, ticket?: string, username?: string, password?: string, autoConnect?: boolean }` | Enable MQTT sync. `url` is a `ws://`/`wss://` MQTT endpoint; `ticket` is a JWT for MQTT v5 enhanced auth; `username`/`password` drive classic MQTT password auth. `autoConnect` (default `true`) → set `false` to skip the connect on `initialize()` and drive it yourself via `reconnect(url, ticket)`, the usual pattern when the JWT is minted dynamically per connection | ## Scope model diff --git a/package-lock.json b/package-lock.json index 7528259..b821ffe 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,15 +1,15 @@ { "name": "@laboverwire/stitch", - "version": "0.5.0", + "version": "0.5.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@laboverwire/stitch", - "version": "0.5.0", + "version": "0.5.1", "license": "Apache-2.0", "dependencies": { - "@laboverwire/stitch-wasm": "^0.2.2" + "@laboverwire/stitch-wasm": "^0.3.0" }, "devDependencies": { "@eslint/js": "^10.0.1", @@ -1031,9 +1031,9 @@ } }, "node_modules/@laboverwire/stitch-wasm": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/@laboverwire/stitch-wasm/-/stitch-wasm-0.2.2.tgz", - "integrity": "sha512-OiRh3sr7qAvAhfvOc5uAvOmiGBymzv00GNVmfBglZHZs2kWXivbV6bgfCVLPGyKGxQEHiWlXjDvQMM70MtK5xg==", + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@laboverwire/stitch-wasm/-/stitch-wasm-0.3.0.tgz", + "integrity": "sha512-DVzBdR+8+hUeRa7opRAnoMnWzmsattFpnZVMr0qqJVazL48LISuHz4AqD4DbS2gF0UD6wx3LsAnXaWOPXK0lBg==", "license": "Apache-2.0" }, "node_modules/@napi-rs/lzma-linux-x64-gnu": { diff --git a/package.json b/package.json index 5f83181..3cc747e 100644 --- a/package.json +++ b/package.json @@ -74,7 +74,7 @@ } }, "dependencies": { - "@laboverwire/stitch-wasm": "^0.2.2" + "@laboverwire/stitch-wasm": "^0.3.0" }, "peerDependencies": { "react": "^19.0.0", diff --git a/src/types.ts b/src/types.ts index 3323ac3..f52f4d7 100644 --- a/src/types.ts +++ b/src/types.ts @@ -65,6 +65,7 @@ export interface RemoteConfig { ticket?: string; username?: string; password?: string; + autoConnect?: boolean; } export interface StoreOptions { diff --git a/tests/integration/auto-connect.test.ts b/tests/integration/auto-connect.test.ts new file mode 100644 index 0000000..3172dc3 --- /dev/null +++ b/tests/integration/auto-connect.test.ts @@ -0,0 +1,40 @@ +import { afterEach, describe, it, expect } from 'vitest'; +import { createStore } from '../../src/store.ts'; +import { projectTaskConfig, uniqueDbName } from '../helpers/fixtures.ts'; +import type { RemoteConfig, Store } from '../../src/types.ts'; + +describe('remote.autoConnect', () => { + const stores: Store[] = []; + + afterEach(async () => { + while (stores.length > 0) { + const store = stores.pop(); + if (store) await store.destroy(); + } + }); + + function open(remote: RemoteConfig): Store { + const store = createStore(projectTaskConfig(), { + persistence: { dbName: uniqueDbName() }, + remote, + }); + stores.push(store); + return store; + } + + it('autoConnect:false skips the startup connect and stays offline', async () => { + const store = open({ url: 'ws://127.0.0.1:1', autoConnect: false }); + await store.initialize(); + + expect(store.connectionStatus).toBe('offline'); + expect(store.hasRemote).toBe(true); + }); + + it('default (autoConnect omitted) attempts the connect on initialize', async () => { + const store = open({ url: 'ws://127.0.0.1:1' }); + await store.initialize(); + + expect(store.connectionStatus).not.toBe('offline'); + expect(store.hasRemote).toBe(true); + }); +}); From 6a24f9b9c3b19fb60e5430ef61b1f6ee75321841 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fabr=C3=ADcio=20Bracht?= Date: Fri, 14 Aug 2026 10:55:42 -0300 Subject: [PATCH 2/2] harden autoConnect test against port-block and connect-timing flakiness --- tests/integration/auto-connect.test.ts | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/tests/integration/auto-connect.test.ts b/tests/integration/auto-connect.test.ts index 3172dc3..1710738 100644 --- a/tests/integration/auto-connect.test.ts +++ b/tests/integration/auto-connect.test.ts @@ -1,8 +1,10 @@ -import { afterEach, describe, it, expect } from 'vitest'; +import { afterEach, describe, it, expect, vi } from 'vitest'; import { createStore } from '../../src/store.ts'; import { projectTaskConfig, uniqueDbName } from '../helpers/fixtures.ts'; import type { RemoteConfig, Store } from '../../src/types.ts'; +const UNROUTABLE = 'ws://127.0.0.1:49871'; + describe('remote.autoConnect', () => { const stores: Store[] = []; @@ -22,19 +24,23 @@ describe('remote.autoConnect', () => { return store; } - it('autoConnect:false skips the startup connect and stays offline', async () => { - const store = open({ url: 'ws://127.0.0.1:1', autoConnect: false }); + it('autoConnect:false does not start a connection on initialize', async () => { + const store = open({ url: UNROUTABLE, autoConnect: false }); await store.initialize(); - expect(store.connectionStatus).toBe('offline'); + await new Promise((resolve) => setTimeout(resolve, 150)); + + expect(store.connectionStatus).not.toBe('connecting'); + expect(store.connectionStatus).not.toBe('connected'); + expect(store.isReconnecting).toBe(false); expect(store.hasRemote).toBe(true); }); - it('default (autoConnect omitted) attempts the connect on initialize', async () => { - const store = open({ url: 'ws://127.0.0.1:1' }); + it('default (autoConnect omitted) starts the connection on initialize', async () => { + const store = open({ url: UNROUTABLE }); await store.initialize(); - expect(store.connectionStatus).not.toBe('offline'); + await vi.waitFor(() => expect(store.connectionStatus).toBe('connecting')); expect(store.hasRemote).toBe(true); }); });