acrylblends 1.0.9 → 1.0.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.
@@ -119,6 +119,8 @@ export async function apply(ctx: Context, config: { path?: string } = {}): Promi
119
119
 
120
120
  The suite is the contract: it runs the same cases against every provider. A test gives it a fresh store for each case and a way to reopen the same storage.
121
121
 
122
+ The suite imports `vitest` from your project, where `--suite` put it as a dev dependency. The same suite is in the package as `acrylblends/kv/conformance` (and `acrylblends/db/conformance`), and there it runs on the vitest your test hands it: `import * as vitest from 'vitest'` and `describeKvConformance('my-provider', open, vitest)`. The package does not declare `vitest` as a peer, so a production install of an app that uses `acrylblends` carries no vitest, vite or native build tools (1.0.10 and later; before that, `pnpm deploy --prod` pulled them in).
123
+
122
124
  ```ts no-check
123
125
  // test/kv-lowdb.spec.ts
124
126
  import { mkdir, mkdtemp } from 'node:fs/promises'
@@ -4,7 +4,7 @@ import { r as loopbackAuthority } from "../loopback-only-BbasoF7R.js";
4
4
  import "../helpers-BEjcdbgw.js";
5
5
  import { n as clientModules } from "../client-modules-drj4mjhj.js";
6
6
  import { a as MAX_WAIT_MS, d as parseChannelToHost, l as UiControlError, p as parseUiRequest, r as MAX_SNAPSHOT_NODES, u as WINDOW_ID } from "../protocol-CQ_if7Dg.js";
7
- import { a as INVALID_JSON, c as operatorOnly, d as AuditLog, f as auditPath, i as DOOR_PREFIX, l as privateApi, o as error, p as UiChannel, r as pageChannelRoutes, s as jsonBody, t as renderSnapshot, u as readJson } from "../snapshot-text-CBi3EMnj.js";
7
+ import { a as INVALID_JSON, c as operatorOnly, d as AuditLog, f as auditPath, i as DOOR_PREFIX, l as privateApi, o as error, p as UiChannel, r as pageChannelRoutes, s as jsonBody, t as renderSnapshot, u as readJson } from "../snapshot-text-BfEbEOjA.js";
8
8
  import "../src-BTBm4OPD.js";
9
9
  import "../middleware-BfG_fp8P.js";
10
10
  import { isAbsolute, join, relative, resolve } from "node:path";
@@ -1,4 +1,5 @@
1
1
  import { t as Db } from "../database-DBHdwefi.js";
2
+ import { t as VitestApi } from "../vitest-binding-CctNu3jT.js";
2
3
 
3
4
  //#region ../../plugins/acryl-db/src/conformance.d.ts
4
5
  /** A fresh, empty database from the provider under test, and how to close it. */
@@ -6,6 +7,9 @@ interface DbUnderTest {
6
7
  readonly db: Db;
7
8
  close(): Promise<void>;
8
9
  }
9
- declare function describeDbConformance(provider: string, open: () => Promise<DbUnderTest>): void;
10
+ declare function describeDbConformance$1(provider: string, open: () => Promise<DbUnderTest>): void;
10
11
  //#endregion
11
- export { DbUnderTest, describeDbConformance };
12
+ //#region src/db-conformance.d.ts
13
+ declare function describeDbConformance(provider: string, open: Parameters<typeof describeDbConformance$1>[1], vitest: VitestApi): void;
14
+ //#endregion
15
+ export { type DbUnderTest, type VitestApi, describeDbConformance };
@@ -1,8 +1,8 @@
1
1
  import { i as applyMigrations, n as MIGRATIONS_TABLE, r as readMigrationFiles } from "../src-MWcsMS8i.js";
2
+ import { a as it, i as expect, n as bind, r as describe, t as afterEach } from "../vitest-binding-CrH7rKg2.js";
2
3
  import { mkdtemp, rm, writeFile } from "node:fs/promises";
3
4
  import { join } from "node:path";
4
5
  import { tmpdir } from "node:os";
5
- import { afterEach, describe, expect, it } from "vitest";
6
6
  //#region ../../plugins/acryl-db/src/conformance.ts
7
7
  /**
8
8
  * The `db` conformance suite: what every provider of the port must do, as Vitest tests. A provider passes it to be accepted
@@ -24,7 +24,7 @@ function gate() {
24
24
  open
25
25
  };
26
26
  }
27
- function describeDbConformance(provider, open) {
27
+ function describeDbConformance$1(provider, open) {
28
28
  describe(`db conformance: ${provider}`, () => {
29
29
  const opened = [];
30
30
  const fresh = async () => {
@@ -264,4 +264,16 @@ function describeDbConformance(provider, open) {
264
264
  });
265
265
  }
266
266
  //#endregion
267
+ //#region src/db-conformance.ts
268
+ /**
269
+ * `acrylblends/db/conformance`: the `db` conformance suite a provider of the port passes. It runs on the vitest your test hands it, so the package declares no vitest:
270
+ *
271
+ * import * as vitest from 'vitest'
272
+ * describeDbConformance('my-provider', open, vitest)
273
+ */
274
+ function describeDbConformance(provider, open, vitest) {
275
+ bind(vitest);
276
+ describeDbConformance$1(provider, open);
277
+ }
278
+ //#endregion
267
279
  export { describeDbConformance };
package/lib/door/host.js CHANGED
@@ -1,2 +1,2 @@
1
- import { d as AuditLog, f as auditPath, n as HEARTBEAT_MS, p as UiChannel, r as pageChannelRoutes, t as renderSnapshot } from "../snapshot-text-CBi3EMnj.js";
1
+ import { d as AuditLog, f as auditPath, n as HEARTBEAT_MS, p as UiChannel, r as pageChannelRoutes, t as renderSnapshot } from "../snapshot-text-BfEbEOjA.js";
2
2
  export { AuditLog, HEARTBEAT_MS, UiChannel, auditPath, pageChannelRoutes, renderSnapshot };
@@ -1,3 +1,4 @@
1
+ import { t as VitestApi } from "../vitest-binding-CctNu3jT.js";
1
2
  import { n as Kv } from "../kv-9f-AlRKN.js";
2
3
 
3
4
  //#region ../../plugins/acryl-kv/src/conformance.d.ts
@@ -8,6 +9,9 @@ interface KvUnderTest {
8
9
  /** Close this store (if it is still open) and open the same storage again, to prove what was written is durable. */
9
10
  reopen(): Promise<KvUnderTest>;
10
11
  }
11
- declare function describeKvConformance(provider: string, open: () => Promise<KvUnderTest>): void;
12
+ declare function describeKvConformance$1(provider: string, open: () => Promise<KvUnderTest>): void;
12
13
  //#endregion
13
- export { KvUnderTest, describeKvConformance };
14
+ //#region src/kv-conformance.d.ts
15
+ declare function describeKvConformance(provider: string, open: Parameters<typeof describeKvConformance$1>[1], vitest: VitestApi): void;
16
+ //#endregion
17
+ export { type KvUnderTest, type VitestApi, describeKvConformance };
@@ -1,10 +1,10 @@
1
- import { afterEach, describe, expect, it } from "vitest";
1
+ import { a as it, i as expect, n as bind, r as describe, t as afterEach } from "../vitest-binding-CrH7rKg2.js";
2
2
  //#region ../../plugins/acryl-kv/src/conformance.ts
3
3
  /**
4
4
  * The `kv` conformance suite: what every provider of the port must do, as Vitest tests. A provider passes it to be accepted
5
5
  * (`describeKvConformance('acryl-kv-json', open)` in its own tests), so the same cases run on a file and on SQL.
6
6
  */
7
- function describeKvConformance(provider, open) {
7
+ function describeKvConformance$1(provider, open) {
8
8
  describe(`kv conformance: ${provider}`, () => {
9
9
  const opened = [];
10
10
  const track = (subject) => {
@@ -298,4 +298,16 @@ function describeKvConformance(provider, open) {
298
298
  });
299
299
  }
300
300
  //#endregion
301
+ //#region src/kv-conformance.ts
302
+ /**
303
+ * `acrylblends/kv/conformance`: the `kv` conformance suite a provider of the port passes. It runs on the vitest your test hands it, so the package declares no vitest:
304
+ *
305
+ * import * as vitest from 'vitest'
306
+ * describeKvConformance('my-provider', open, vitest)
307
+ */
308
+ function describeKvConformance(provider, open, vitest) {
309
+ bind(vitest);
310
+ describeKvConformance$1(provider, open);
311
+ }
312
+ //#endregion
301
313
  export { describeKvConformance };
@@ -142,7 +142,7 @@ var UiChannel = class {
142
142
  const fresh = await this.expectNewPage(this.attachWaitMs);
143
143
  if (fresh !== void 0 && !tried.has(fresh)) return await this.call(request, options, true);
144
144
  }
145
- throw new UiControlError("timeout", `no page of the app answered within ${String(Math.round(this.ackMs / 1e3))} s: the app's pages are open but none responded. Likely causes: a tab in the background, a page that is still loading after a navigation (look again), or a page that holds the browser's six connections with event streams of its own (a page should keep to two). Bring the app's tab to the front, or navigate it, then look again`);
145
+ throw new UiControlError("timeout", `no page of the app answered within ${String(Math.round(this.ackMs / 1e3))} s: the app's pages are open but none responded. Likely causes: a tab in the background, a page that is still loading after a navigation (look again), a page busy running a long script, or a page that holds the browser's six connections with event streams of its own (a page should keep to two). Bring the app's tab to the front, or navigate it, then look again`);
146
146
  }
147
147
  callOne(target, request, options, failOver) {
148
148
  const signal = options.signal;
@@ -0,0 +1,15 @@
1
+ //#region src/vitest-binding.d.ts
2
+ /**
3
+ * The conformance suites (`acrylblends/db/conformance`, `acrylblends/kv/conformance`) run on the test runner's own vitest, which the test hands them. In the package build their
4
+ * `import ... from 'vitest'` is pointed here (`tsdown.config.ts`), so the package imports no vitest and does not declare one: it cannot put vitest, vite, rolldown or lightningcss into
5
+ * a production install, and the suite can never see a second copy of vitest that is not the one running the test.
6
+ */
7
+ /** The four functions of vitest the suites use; a test passes `import * as vitest from 'vitest'`. */
8
+ interface VitestApi {
9
+ readonly afterEach: (...args: never[]) => unknown;
10
+ readonly describe: (...args: never[]) => unknown;
11
+ readonly expect: (...args: never[]) => unknown;
12
+ readonly it: (...args: never[]) => unknown;
13
+ }
14
+ //#endregion
15
+ export { VitestApi as t };
@@ -0,0 +1,17 @@
1
+ //#region src/vitest-binding.ts
2
+ let bound;
3
+ /** Use `api` for the suites that run next. They register their cases synchronously, so one call per suite is enough. */
4
+ function bind(api) {
5
+ if (api === void 0 || typeof api.describe !== "function" || typeof api.it !== "function" || typeof api.expect !== "function" || typeof api.afterEach !== "function") throw new Error("the conformance suite needs the vitest your tests run on: pass it as the third argument, `import * as vitest from 'vitest'` then `describeKvConformance(name, open, vitest)`");
6
+ bound = api;
7
+ }
8
+ const use = () => {
9
+ if (bound === void 0) throw new Error("the conformance suite ran before it was given vitest");
10
+ return bound;
11
+ };
12
+ const afterEach = (...args) => Reflect.apply(use().afterEach, void 0, args);
13
+ const describe = (...args) => Reflect.apply(use().describe, void 0, args);
14
+ const expect = (...args) => Reflect.apply(use().expect, void 0, args);
15
+ const it = (...args) => Reflect.apply(use().it, void 0, args);
16
+ //#endregion
17
+ export { it as a, expect as i, bind as n, describe as r, afterEach as t };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "acrylblends",
3
- "version": "1.0.9",
3
+ "version": "1.0.10",
4
4
  "description": "The Acryl web framework (import { Acryl } from \"acrylblends\") and the acrylblends command that scaffolds Cordis apps.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -436,7 +436,6 @@
436
436
  "@deepseek-ai/cordis": "^4.0.2",
437
437
  "@deepseek-ai/schemastery": "~3.18.4",
438
438
  "react": ">=18",
439
- "vitest": ">=3",
440
439
  "ws": ">=8"
441
440
  },
442
441
  "peerDependenciesMeta": {
@@ -449,9 +448,6 @@
449
448
  "react": {
450
449
  "optional": true
451
450
  },
452
- "vitest": {
453
- "optional": true
454
- },
455
451
  "ws": {
456
452
  "optional": true
457
453
  }
@@ -179,7 +179,7 @@ export class UiChannel {
179
179
  const fresh = await this.expectNewPage(this.attachWaitMs)
180
180
  if (fresh !== undefined && !tried.has(fresh)) return await this.call(request, options, true)
181
181
  }
182
- throw new UiControlError('timeout', `no page of the app answered within ${String(Math.round(this.ackMs / 1000))} s: the app's pages are open but none responded. Likely causes: a tab in the background, a page that is still loading after a navigation (look again), or a page that holds the browser's six connections with event streams of its own (a page should keep to two). Bring the app's tab to the front, or navigate it, then look again`)
182
+ throw new UiControlError('timeout', `no page of the app answered within ${String(Math.round(this.ackMs / 1000))} s: the app's pages are open but none responded. Likely causes: a tab in the background, a page that is still loading after a navigation (look again), a page busy running a long script, or a page that holds the browser's six connections with event streams of its own (a page should keep to two). Bring the app's tab to the front, or navigate it, then look again`)
183
183
  }
184
184
 
185
185
  private callOne(target: Connection, request: UiRequest, options: { signal?: AbortSignal; timeoutMs?: number }, failOver: boolean): Promise<UiResult> {
@@ -91,6 +91,8 @@ if (!app.requestSingleInstanceLock()) {
91
91
  }
92
92
  const origin = new URL(url).origin
93
93
  /** One window on a path of the app. Navigation stays on the app's own server; any other address opens in the system browser. The user agent names the window to the door (`AcrylWindow/<id>`), so an agent can drive it with `--window <id>`. */
94
+ // Where each window was opened: `window list` shows it until the page reports its own address (Electron gives none while the first load has not started).
95
+ const openedOn = new Map<number, string>()
94
96
  const createWindow = (path: string): BrowserWindow => {
95
97
  // A window opened while another is open sits a step down and to the right of it, not exactly on top.
96
98
  const beside =
@@ -112,6 +114,7 @@ if (!app.requestSingleInstanceLock()) {
112
114
  )
113
115
  })()
114
116
  const created = new BrowserWindow(windowOptions({ title: chosen.name, ...where }))
117
+ openedOn.set(created.id, path)
115
118
  created.webContents.setUserAgent(
116
119
  `${created.webContents.getUserAgent()} AcrylWindow/${String(created.id)}`,
117
120
  )
@@ -139,7 +142,8 @@ if (!app.requestSingleInstanceLock()) {
139
142
  const port = (candidate: BrowserWindow): WindowPort => ({
140
143
  id: candidate.id,
141
144
  getTitle: () => candidate.getTitle(),
142
- getURL: () => candidate.webContents.getURL(),
145
+ getURL: () =>
146
+ candidate.webContents.getURL() || new URL(openedOn.get(candidate.id) ?? '/', url).href,
143
147
  isFocused: () => candidate.isFocused(),
144
148
  isMinimized: () => candidate.isMinimized(),
145
149
  getSize: () => candidate.getSize(),