Skip to content

Commit 0bcc93e

Browse files
committed
refactor: update type definitions extraction logic and fix dist types
- Renamed VFS imports to avoid naming conflicts and created constructor interfaces for MemoryVFS, OPFSWriteAheadVFS, and OPFSCoopSyncVFS. - Updated type imports to reference the new type.ts file. - Added a script to generate SQLite type definitions and module declarations from upstream sources. - Created sqlite-types.d.ts and wa-sqlite-modules.d.ts to hold generated type definitions. - Enhanced InitSQLiteOptions and BaseStorageOptions interfaces for better type safety.
1 parent 016269a commit 0bcc93e

17 files changed

Lines changed: 379 additions & 88 deletions

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,9 +34,9 @@
3434
},
3535
"scripts": {
3636
"dev": "vite serve playground",
37-
"build": "tsdown",
37+
"build": "bun run scripts/generate-sqlite-types.ts && oxfmt --write src/sqlite-types.d.ts src/wa-sqlite-modules.d.ts && tsdown",
3838
"update": "bun run download.ts",
39-
"qa": "oxlint --fix ./src && oxfmt --write ./src && tsc --noEmit",
39+
"qa": "bun run scripts/generate-sqlite-types.ts && oxlint --fix ./src && oxfmt --write ./src && tsc --noEmit",
4040
"release": "bun run qa && bun run build && bumpp --all"
4141
},
4242
"devDependencies": {

scripts/generate-sqlite-types.ts

Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
import { readFileSync, writeFileSync } from 'node:fs'
2+
3+
const upstreamTypesPath = './wa-sqlite/src/types/index.d.ts'
4+
const facadeVFSPath = './wa-sqlite/src/FacadeVFS.js'
5+
const typesOutputPath = './src/sqlite-types.d.ts'
6+
const modulesOutputPath = './src/wa-sqlite-modules.d.ts'
7+
8+
const upstreamDeclarations = [
9+
'SQLiteCompatibleType',
10+
'SQLiteVFS',
11+
'SQLitePrepareOptions',
12+
'SQLiteAPI',
13+
] as const
14+
15+
const moduleDeclarations = String.raw`
16+
declare module 'wa-sqlite/src/examples/IDBBatchAtomicVFS.js' {
17+
export class IDBBatchAtomicVFS {
18+
static create(name: string, module: any, options: any): Promise<any>
19+
}
20+
}
21+
22+
declare module 'wa-sqlite/src/examples/IDBMirrorVFS.js' {
23+
export class IDBMirrorVFS {
24+
static create(name: string, module: any): Promise<any>
25+
}
26+
}
27+
28+
declare module 'wa-sqlite/src/examples/OPFSCoopSyncVFS.js' {
29+
export class OPFSCoopSyncVFS {
30+
static create(name: string, module: any): Promise<any>
31+
}
32+
}
33+
34+
declare module 'wa-sqlite/src/examples/OPFSWriteAheadVFS.js' {
35+
export class OPFSWriteAheadVFS {
36+
static create(name: string, module: any, options?: any): Promise<any>
37+
}
38+
}
39+
40+
declare module 'wa-sqlite/src/examples/MemoryVFS.js' {
41+
export class MemoryVFS {
42+
static create(name: string, module: any): Promise<any>
43+
}
44+
}
45+
46+
declare module 'wa-sqlite-fts5/wa-sqlite.mjs' {
47+
const SQLiteESMFactory: (moduleArg?: { locateFile?: (path: string) => string }) => Promise<any>
48+
49+
export default SQLiteESMFactory
50+
}
51+
52+
declare module 'wa-sqlite-fts5/wa-sqlite-async.mjs' {
53+
const SQLiteAsyncESMFactory: (moduleArg?: {
54+
locateFile?: (path: string) => string
55+
}) => Promise<any>
56+
57+
export default SQLiteAsyncESMFactory
58+
}
59+
`.trim()
60+
61+
function extractDeclaration(source: string, name: (typeof upstreamDeclarations)[number]): string {
62+
const typeStart = source.search(new RegExp(`(?:declare\\s+)?type\\s+${name}\\b`))
63+
if (typeStart >= 0) {
64+
const end = source.indexOf(';', typeStart)
65+
return source.slice(typeStart, end + 1)
66+
}
67+
68+
const interfaceStart = source.search(new RegExp(`(?:declare\\s+)?interface\\s+${name}\\b`))
69+
if (interfaceStart < 0) {
70+
throw new Error(`Unable to find ${name} in ${upstreamTypesPath}`)
71+
}
72+
73+
const bodyStart = source.indexOf('{', interfaceStart)
74+
let depth = 0
75+
for (let index = bodyStart; index < source.length; index++) {
76+
const char = source[index]
77+
if (char === '{') {
78+
depth++
79+
} else if (char === '}') {
80+
depth--
81+
if (depth === 0) {
82+
return source.slice(interfaceStart, index + 1)
83+
}
84+
}
85+
}
86+
87+
throw new Error(`Unable to parse ${name} in ${upstreamTypesPath}`)
88+
}
89+
90+
function toExportedDeclaration(declaration: string): string {
91+
return declaration.replace(/^(?:declare\s+)?(interface|type)\s+/, 'export $1 ')
92+
}
93+
94+
function patchUpstreamDeclaration(declaration: string): string {
95+
return declaration
96+
.replace('changes(db): number;', 'changes(db: number): number;')
97+
.replace('close(db): Promise<number>;', 'close(db: number): Promise<number>;')
98+
.replace(
99+
/progress_handler\(\s*db:\s*number,\s*nProgressOps:\s*number,\s*handler:\s*\(userData:\s*any\)\s*=>\s*number\s*\|\s*Promise<number>,\s*userData,?\s*\);?/,
100+
'progress_handler(db: number, nProgressOps: number, handler: (userData: any) => number | Promise<number>, userData: any): void;',
101+
)
102+
}
103+
104+
function toTypeAnnotation(jsDocType: string): string {
105+
return jsDocType
106+
.replace('string?', 'string | null')
107+
.replace('number|Promise<number>', 'number | Promise<number>')
108+
}
109+
110+
function generateFacadeVFSDeclaration(source: string): string {
111+
const methods: string[] = []
112+
const methodPattern = /\/\*\*([\s\S]*?)\*\/\n\s+([a-zA-Z]\w*)\(([^)]*)\)\s*\{/g
113+
114+
for (const match of source.matchAll(methodPattern)) {
115+
const [, jsdoc = '', methodName = '', rawParams = ''] = match
116+
if (!methodName || methodName === 'constructor' || methodName === 'hasAsyncMethod') {
117+
continue
118+
}
119+
if (!methodName.startsWith('j') && methodName !== 'getFilename') {
120+
continue
121+
}
122+
123+
const params = rawParams
124+
.split(',')
125+
.map((param) => param.trim())
126+
.filter(Boolean)
127+
.map((param) => {
128+
const paramType = jsdoc.match(new RegExp(`@param \\{([^}]+)\\} ${param}\\b`))?.[1]
129+
if (!paramType) {
130+
throw new Error(`Unable to find JSDoc type for FacadeVFS.${methodName}(${param})`)
131+
}
132+
return `${param}: ${toTypeAnnotation(paramType)}`
133+
})
134+
.join(', ')
135+
136+
const returnType = toTypeAnnotation(jsdoc.match(/@returns \{([^}]+)\}/)?.[1] ?? 'void')
137+
methods.push(` ${methodName}(${params}): ${returnType}`)
138+
}
139+
140+
if (methods.length === 0) {
141+
throw new Error(`Unable to generate FacadeVFS from ${facadeVFSPath}`)
142+
}
143+
144+
return `export interface FacadeVFS extends SQLiteVFS {\n${methods.join('\n\n')}\n}`
145+
}
146+
147+
function generateSQLiteTypes(): void {
148+
const upstreamTypes = readFileSync(upstreamTypesPath, 'utf8')
149+
const facadeVFS = readFileSync(facadeVFSPath, 'utf8')
150+
const sqliteDeclarations = upstreamDeclarations
151+
.map((name) =>
152+
patchUpstreamDeclaration(toExportedDeclaration(extractDeclaration(upstreamTypes, name))),
153+
)
154+
.join('\n\n')
155+
156+
writeFileSync(
157+
typesOutputPath,
158+
`// Generated from ${upstreamTypesPath} and ${facadeVFSPath}. Do not edit manually.\n/* eslint-disable */\n\n${sqliteDeclarations}\n\n${generateFacadeVFSDeclaration(facadeVFS)}\n`,
159+
)
160+
writeFileSync(
161+
modulesOutputPath,
162+
`// Generated module declarations for vendored wa-sqlite entry points. Do not edit manually.\n/* eslint-disable */\n\n${moduleDeclarations}\n`,
163+
)
164+
}
165+
166+
generateSQLiteTypes()
167+
168+
export { generateSQLiteTypes }

src/core.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { Factory } from 'wa-sqlite'
22

33
import { exportDatabase, importDatabase } from './io'
4-
import type { InitSQLiteOptions, Promisable, SQLiteDB, SQLiteDBCore } from './types'
4+
import type { InitSQLiteOptions, Promisable, SQLiteDB, SQLiteDBCore, SQLiteVFS } from './type'
55
import { changes, close, lastInsertRowId, parseOpenV2Flag, run, stream } from './utils'
66

77
/**

src/index.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
export * from './core'
22
export * from './io'
3-
export * from './types'
3+
export type * from './sqlite-types'
4+
export type * from './type'
45
export * from './utils'
56
export * from './vfs/memory'

src/io/common.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { SQLITE_OK } from '../constant'
2-
import type { FacadeVFS, Promisable } from '../types'
2+
import type { FacadeVFS, Promisable } from '../type'
33
import type { OPFSAnyContextVFS } from '../vfs/fs-handle'
44

55
export async function check(code: Promisable<number>): Promise<void> {

src/io/export.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import {
55
SQLITE_OPEN_READONLY,
66
} from '../constant'
77
// reference from https://github.com/rhashimoto/wa-sqlite/blob/master/demo/file/service-worker.js
8-
import type { FacadeVFS, Promisable } from '../types'
8+
import type { FacadeVFS, Promisable } from '../type'
99

1010
import { check, getHandle, ignoredDataView, isFsHandleVFS } from './common'
1111

src/io/import.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import {
1313
} from '../constant'
1414
/* eslint-disable antfu/consistent-list-newline */
1515
// reference from https://github.com/rhashimoto/wa-sqlite/blob/master/demo/file/index.js
16-
import type { FacadeVFS, Promisable } from '../types'
16+
import type { FacadeVFS, Promisable } from '../type'
1717

1818
import { check, getHandle, ignoredDataView, isFsHandleVFS } from './common'
1919

Lines changed: 146 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,104 @@
1-
// Copy from wa-sqlite@1.0.5/src/types/index.d.ts, fix some errors
1+
// Generated from ./wa-sqlite/src/types/index.d.ts and ./wa-sqlite/src/FacadeVFS.js. Do not edit manually.
22
/* eslint-disable */
33

4+
export type SQLiteCompatibleType = number | string | Uint8Array | Array<number> | bigint | null
5+
6+
export interface SQLiteVFS {
7+
/** Maximum length of a file path in UTF-8 bytes (default 64) */
8+
mxPathname?: number
9+
10+
name: string
11+
12+
close(): void | Promise<void>
13+
isReady(): boolean | Promise<boolean>
14+
hasAsyncMethod(methodName: string): boolean
15+
16+
/** @see https://sqlite.org/c3ref/vfs.html */
17+
xOpen(
18+
pVfs: number,
19+
zName: number,
20+
pFile: number,
21+
flags: number,
22+
pOutFlags: number,
23+
): number | Promise<number>
24+
25+
/** @see https://sqlite.org/c3ref/vfs.html */
26+
xDelete(pVfs: number, zName: number, syncDir: number): number | Promise<number>
27+
28+
/** @see https://sqlite.org/c3ref/vfs.html */
29+
xAccess(pVfs: number, zName: number, flags: number, pResOut: number): number | Promise<number>
30+
31+
/** @see https://sqlite.org/c3ref/vfs.html */
32+
xFullPathname(pVfs: number, zName: number, nOut: number, zOut: number): number | Promise<number>
33+
34+
/** @see https://sqlite.org/c3ref/vfs.html */
35+
xGetLastError(pVfs: number, nBuf: number, zBuf: number): number | Promise<number>
36+
37+
/** @see https://sqlite.org/c3ref/io_methods.html */
38+
xClose(pFile: number): number | Promise<number>
39+
40+
/** @see https://sqlite.org/c3ref/io_methods.html */
41+
xRead(
42+
pFile: number,
43+
pData: number,
44+
iAmt: number,
45+
iOffsetLo: number,
46+
iOffsetHi: number,
47+
): number | Promise<number>
48+
49+
/** @see https://sqlite.org/c3ref/io_methods.html */
50+
xWrite(
51+
pFile: number,
52+
pData: number,
53+
iAmt: number,
54+
iOffsetLo: number,
55+
iOffsetHi: number,
56+
): number | Promise<number>
57+
58+
/** @see https://sqlite.org/c3ref/io_methods.html */
59+
xTruncate(pFile: number, sizeLo: number, sizeHi: number): number | Promise<number>
60+
61+
/** @see https://sqlite.org/c3ref/io_methods.html */
62+
xSync(pFile: number, flags: number): number | Promise<number>
63+
64+
/** @see https://sqlite.org/c3ref/io_methods.html */
65+
xFileSize(pFile: number, pSize: number): number | Promise<number>
66+
67+
/** @see https://sqlite.org/c3ref/io_methods.html */
68+
xLock(pFile: number, lockType: number): number | Promise<number>
69+
70+
/** @see https://sqlite.org/c3ref/io_methods.html */
71+
xUnlock(pFile: number, lockType: number): number | Promise<number>
72+
73+
/** @see https://sqlite.org/c3ref/io_methods.html */
74+
xCheckReservedLock(pFile: number, pResOut: number): number | Promise<number>
75+
76+
/** @see https://sqlite.org/c3ref/io_methods.html */
77+
xFileControl(pFile: number, op: number, pArg: number): number | Promise<number>
78+
79+
/** @see https://sqlite.org/c3ref/io_methods.html */
80+
xSectorSize(pFile: number): number | Promise<number>
81+
82+
/** @see https://sqlite.org/c3ref/io_methods.html */
83+
xDeviceCharacteristics(pFile: number): number | Promise<number>
84+
}
85+
86+
export interface SQLitePrepareOptions {
87+
/**
88+
* Statement handles prepared and yielded by {@link SQLiteAPI.statements}
89+
* are normally valid only within the scope of an iteration.
90+
* Set `unscoped` to `true` to give iterated statements an arbitrary
91+
* lifetime.
92+
*/
93+
unscoped?: boolean
94+
95+
/**
96+
* SQLITE_PREPARE_* flags
97+
* @see https://sqlite.org/c3ref/c_prepare_dont_log.html#sqlitepreparepersistent
98+
*/
99+
flags?: number
100+
}
101+
4102
export interface SQLiteAPI {
5103
/**
6104
* Bind a collection of values to a statement
@@ -416,12 +514,12 @@ export interface SQLiteAPI {
416514
* @param handler
417515
* @param userData
418516
*/
419-
progress_handler<T extends number | Promise<number>>(
517+
progress_handler(
420518
db: number,
421519
nProgressOps: number,
422-
handler: (userData: any) => T,
423-
userData: unknown,
424-
): T
520+
handler: (userData: any) => number | Promise<number>,
521+
userData: any,
522+
): void
425523

426524
/**
427525
* Reset a prepared statement object
@@ -684,3 +782,46 @@ export interface SQLiteAPI {
684782
*/
685783
vfs_register(vfs: SQLiteVFS, makeDefault?: boolean): number
686784
}
785+
786+
export interface FacadeVFS extends SQLiteVFS {
787+
getFilename(pFile: number): string
788+
789+
jOpen(
790+
filename: string | null,
791+
pFile: number,
792+
flags: number,
793+
pOutFlags: DataView,
794+
): number | Promise<number>
795+
796+
jDelete(filename: string, syncDir: number): number | Promise<number>
797+
798+
jAccess(filename: string, flags: number, pResOut: DataView): number | Promise<number>
799+
800+
jFullPathname(filename: string, zOut: Uint8Array): number | Promise<number>
801+
802+
jGetLastError(zBuf: Uint8Array): number | Promise<number>
803+
804+
jClose(pFile: number): number | Promise<number>
805+
806+
jRead(pFile: number, pData: Uint8Array, iOffset: number): number | Promise<number>
807+
808+
jWrite(pFile: number, pData: Uint8Array, iOffset: number): number | Promise<number>
809+
810+
jTruncate(pFile: number, size: number): number | Promise<number>
811+
812+
jSync(pFile: number, flags: number): number | Promise<number>
813+
814+
jFileSize(pFile: number, pSize: DataView): number | Promise<number>
815+
816+
jLock(pFile: number, lockType: number): number | Promise<number>
817+
818+
jUnlock(pFile: number, lockType: number): number | Promise<number>
819+
820+
jCheckReservedLock(pFile: number, pResOut: DataView): number | Promise<number>
821+
822+
jFileControl(pFile: number, op: number, pArg: DataView): number | Promise<number>
823+
824+
jSectorSize(pFile: number): number | Promise<number>
825+
826+
jDeviceCharacteristics(pFile: number): number | Promise<number>
827+
}

0 commit comments

Comments
 (0)