also looking at this
fix(svelte): expose public store declaration types
#310
3 files
+58
-2
Review threads live on the whole diff, not on one commit, so none are shown here - a thread's line means something in the branch's final form, and painting it into an intermediate step would put it on code it is not about.
| @@ -1,4 +1,5 @@ | ||
| 1 | 1 | import { writable, derived } from 'svelte/store'; |
| 2 | import type { Readable } from 'svelte/store'; | |
| 2 | 3 | |
| 3 | 4 | interface CraftAPI { |
| 4 | 5 | getPlatform(): Promise<{ platform: string; version: string }>; |
| @@ -18,7 +19,7 @@ declare global { | ||
| 18 | 19 | } |
| 19 | 20 | } |
| 20 | 21 | |
| 21 | function createCraftStore() { | |
| 22 | function createCraftStore(): Readable<CraftAPI | null> { | |
| 22 | 23 | const { subscribe, set } = writable<CraftAPI | null>(null); |
| 23 | 24 | |
| 24 | 25 | if (typeof window !== 'undefined') { |
| @@ -1,7 +1,8 @@ | ||
| 1 | 1 | import { writable } from 'svelte/store'; |
| 2 | import type { Readable } from 'svelte/store'; | |
| 2 | 3 | import { craft } from './craft'; |
| 3 | 4 | |
| 4 | export function createPlatformStore() { | |
| 5 | export function createPlatformStore(): Readable<{ platform: string; version: string } | null> { | |
| 5 | 6 | const { subscribe, set } = writable<{ platform: string; version: string } | null>(null); |
| 6 | 7 | |
| 7 | 8 | craft.subscribe(async (_$craft) => { |
| @@ -31,3 +31,57 @@ test('packed Svelte condition resolves outside the workspace', async () => { | ||
| 31 | 31 | rmSync(temp, { recursive: true, force: true }) |
| 32 | 32 | } |
| 33 | 33 | }) |
| 34 | ||
| 35 | test('store declarations expose the public Readable contract', () => { | |
| 36 | const root = resolve(import.meta.dir, '..') | |
| 37 | const temp = mkdtempSync(join(tmpdir(), 'craft-svelte-types-')) | |
| 38 | const run = (args: string[], cwd = temp): void => { | |
| 39 | const result = Bun.spawnSync([ | |
| 40 | process.execPath, join(root, 'packages/typescript/scripts/tsc.ts'), ...args, | |
| 41 | ], { cwd, stdout: 'pipe', stderr: 'pipe' }) | |
| 42 | expect(`${result.stdout}\n${result.stderr}`).not.toContain('error TS') | |
| 43 | expect(result.exitCode).toBe(0) | |
| 44 | } | |
| 45 | try { | |
| 46 | run([ | |
| 47 | '-p', join(root, 'packages/svelte/tsconfig.json'), '--emitDeclarationOnly', | |
| 48 | '--outDir', join(temp, 'declarations'), | |
| 49 | ]) | |
| 50 | // Consumers only need Svelte's public store interface. Inferred subscribe | |
| 51 | // signatures must not leak helper exports from the build-time peer. | |
| 52 | writeFileSync(join(temp, 'store-contract.d.ts'), ` | |
| 53 | declare module 'svelte/store' { | |
| 54 | export interface Readable<T> { | |
| 55 | subscribe(run: (value: T) => void, invalidate?: (value?: T) => void): () => void | |
| 56 | } | |
| 57 | } | |
| 58 | `) | |
| 59 | writeFileSync(join(temp, 'consumer.mts'), ` | |
| 60 | import type { Readable } from 'svelte/store' | |
| 61 | import { craft, isReady } from './declarations/stores/craft.js' | |
| 62 | import { platform, createPlatformStore } from './declarations/stores/platform.js' | |
| 63 | const ready: Readable<boolean> = isReady | |
| 64 | const info: Readable<{ platform: string; version: string } | null> = platform | |
| 65 | const another: typeof info = createPlatformStore() | |
| 66 | const unsubscribe: () => void = craft.subscribe(api => { | |
| 67 | if (api) { | |
| 68 | const result: Promise<{ platform: string; version: string }> = api.getPlatform() | |
| 69 | void result | |
| 70 | } | |
| 71 | }) | |
| 72 | // @ts-expect-error the Craft store is read-only to consumers | |
| 73 | craft.set(null) | |
| 74 | // @ts-expect-error readiness remains boolean | |
| 75 | const wrong: Readable<string> = isReady | |
| 76 | void [ready, info, another, unsubscribe, wrong] | |
| 77 | `) | |
| 78 | run([ | |
| 79 | 'consumer.mts', 'store-contract.d.ts', '--noEmit', '--strict', | |
| 80 | '--module', 'nodenext', '--moduleResolution', 'nodenext', | |
| 81 | '--target', 'esnext', '--ignoreConfig', | |
| 82 | ]) | |
| 83 | } | |
| 84 | finally { | |
| 85 | rmSync(temp, { recursive: true, force: true }) | |
| 86 | } | |
| 87 | }, 60_000) | |