@pikku/kysely-sqlite 0.12.12 → 0.12.14

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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,135 @@
1
1
  # @pikku/kysely-sqlite
2
2
 
3
+ ## 0.12.14
4
+
5
+ ### Patch Changes
6
+
7
+ - 4c7a1b5: Run the monorepo's own scripts through bun instead of yarn. What moves is the
8
+ package manager each package's `prepublishOnly` and build scripts invoke, plus
9
+ the two manifest fixes bun needs to resolve the tree: `uWebSockets.js` is
10
+ declared with an explicit `github:` specifier, and `@pikku/uws-handler` marks
11
+ its `uWebSockets.js` peer optional so a bun install of a consumer that brings
12
+ its own uWS app does not try to fetch it from the registry.
13
+
14
+ Three published behaviours change, all of them cases where an isolated
15
+ `node_modules` or bun as the runtime had been papered over by yarn's hoisting:
16
+
17
+ - `@pikku/migrator-sql` turns foreign keys on when it opens a sqlite database
18
+ through bun. `node:sqlite` enforces them by default and `bun:sqlite` does not,
19
+ which silently turned every `ON DELETE CASCADE` into a no-op under
20
+ `bunx --bun pikku`.
21
+ - `@pikku/cli` resolves a deploy provider against the project being deployed
22
+ rather than against wherever the CLI itself is installed, which is what its
23
+ own "is not installed" error asks the user to arrange.
24
+ - `@pikku/cli` treats a specifier a runtime hands straight back — bun does this
25
+ for the modules it implements itself — as not resolved from the project, so
26
+ it falls back rather than loading the runtime's own copy.
27
+
28
+ - Updated dependencies [4c7a1b5]
29
+ - @pikku/core@0.12.108
30
+ - @pikku/kysely@0.13.25
31
+
32
+ ## 0.12.13
33
+
34
+ ### Patch Changes
35
+
36
+ - 266e3bc: One door per name: `@pikku/core/ecosystem/*` and the package root are gone
37
+
38
+ `@pikku/core` published every module twice. `ecosystem/http` re-exported
39
+ `./http`, `ecosystem/services` re-exported `./services`, and a name was
40
+ reachable through either — so every addition had to be made in two places, and a
41
+ consumer's import said nothing about what it actually used. The package root was
42
+ the same problem at a larger scale: a single barrel of 206 names that no bundler
43
+ could take apart, and the one specifier that revealed nothing at all.
44
+
45
+ Both are deleted. Every name now lives on the subpath that owns it, and every
46
+ import carries that subpath — `@pikku/core/http`, `@pikku/core/services`,
47
+ `@pikku/core/errors`, `@pikku/core/types`.
48
+
49
+ Deleting the facades meant the raw subpaths had to become a superset of them,
50
+ which they were not: the facade tree had accumulated 25 names with no raw home
51
+ and about 26 more filed under a different area than the module they came from.
52
+ Those names moved to the area that owns them, and three areas were published as
53
+ new entry points rather than left on a root that is going away — `./types` (the
54
+ shared type surface, the largest single destination), `./state` and
55
+ `./classification`.
56
+
57
+ `./classification` is one door onto one subject: what a value is and how it must
58
+ be handled. Its three halves would each have been an entry point — the brands
59
+ and manifest types, the stored-form helpers (`hashToken`, `unsafeAsSealed` and
60
+ friends), and `SecretValue` — split by whether a name happens to be a type or a
61
+ value, which is the same defect as the facades. The duration and versioned-id
62
+ helpers went to `./utils`, which already published, and `PikkuRequest` went to
63
+ `./function`: it is the transport-agnostic request base, not an HTTP one — HTTP
64
+ has `PikkuHTTPAbstractRequest`, and the only thing outside core that extends
65
+ `PikkuRequest` is Azure's timer request.
66
+
67
+ `./types` inherited the root barrel's habit before it inherited its contents, so
68
+ the names with an owner elsewhere were moved off it. The middleware types and the
69
+ five middleware factories — `pikkuMiddleware`, `pikkuMiddlewareFactory`,
70
+ `pikkuChannelMiddleware`, `pikkuChannelMiddlewareFactory` and
71
+ `pikkuAgentMiddleware`, runtime values on a types entry point — are now
72
+ `@pikku/core/middleware`; the function meta types are `@pikku/core/function`;
73
+ `SerializedError` is `@pikku/core/errors`; and the generic TypeScript helpers
74
+ (`MakeRequired`, `PickRequired`, `PickOptional`, `RequireAtLeastOne`,
75
+ `JSONPrimitive`, `JSONValue`) are `@pikku/core/utils`. What is left on `./types`
76
+ is the vocabulary the wirings share, which no single module owns.
77
+
78
+ `pikku` was itself a root barrel — `export * from '@pikku/core'` — and
79
+ now exports only the services it bundles.
80
+
81
+ One module survives at the old specifier, and only for the bootstrap:
82
+ `packages/cli` is generated by the _published_ CLI pinned in its `build.sh`, and
83
+ that CLI still writes a bare `@pikku/core` into the files it generates for the
84
+ CLI itself. `bootstrap-compat/root.ts` carries the eight types it names, a test
85
+ in core fails if that list grows, and it goes when the pin moves to a CLI
86
+ released from this branch. The adapter names the pinned CLI reaches for —
87
+ `pikkuState` and `CreateWireServices` — are rewritten to `@pikku/core/state` and
88
+ `@pikku/core/types` by the same `build.sh` patch pass, so no second shim is
89
+ needed for them.
90
+
91
+ A guard test keeps the root shut: it parses imports and rejects a bare
92
+ `@pikku/core` rather than grepping for it, because several tests hold a user's
93
+ file as a template literal, where `import … from '@pikku/core'` is fixture text
94
+ rather than an import this repo makes.
95
+
96
+ An agent scaffold a project generated under an older CLI is refreshed rather
97
+ than left to fail: `pikku all` already deleted one importing an entry point
98
+ `@pikku/core` no longer publishes, and the `#pikku` hub joins that list.
99
+ Without it a project that scaffolds the agent endpoint but
100
+ declares no agents keeps the old file forever — the generator that would rewrite
101
+ it only runs when agents exist, and the file being present is what stops it
102
+ being regenerated as missing.
103
+
104
+ `pikku new addon` also wrote a tsconfig `paths` map naming only the deleted hub.
105
+ An addon's `imports` map points into `dist`, so `paths` is what resolves
106
+ `#pikku/<leaf>` for the addon's own source build — it now names the two leaf
107
+ patterns, in both the addon and its test harness.
108
+
109
+ - 6eef0a0: Bump every dependency to its latest compatible minor/patch across the monorepo.
110
+ - Updated dependencies [7722ceb]
111
+ - Updated dependencies [375c1ff]
112
+ - Updated dependencies [02a70cd]
113
+ - Updated dependencies [aeef159]
114
+ - Updated dependencies [a281de6]
115
+ - Updated dependencies [266e3bc]
116
+ - Updated dependencies [02a70cd]
117
+ - Updated dependencies [786dae5]
118
+ - Updated dependencies [6eef0a0]
119
+ - Updated dependencies [3561d67]
120
+ - Updated dependencies [a91c433]
121
+ - Updated dependencies [02a70cd]
122
+ - Updated dependencies [9537f74]
123
+ - Updated dependencies [2b57ca8]
124
+ - Updated dependencies [266e3bc]
125
+ - Updated dependencies [9fce0f1]
126
+ - Updated dependencies [83683a0]
127
+ - Updated dependencies [456c88b]
128
+ - Updated dependencies [456c88b]
129
+ - Updated dependencies [c127273]
130
+ - @pikku/core@0.12.85
131
+ - @pikku/kysely@0.13.17
132
+
3
133
  ## 0.12.12
4
134
 
5
135
  ### Patch Changes
@@ -10,5 +10,5 @@ export { SQLiteKyselySecretService } from './sqlite-kysely-secret-service.js';
10
10
  export { SqliteFunctionsUnsupportedError, type SqliteFunctionMap, } from './sqlite-functions.js';
11
11
  export { LibsqlWebDialect } from './libsql-web-dialect.js';
12
12
  export type { KyselyPikkuDB } from '@pikku/kysely';
13
- export type { WorkflowRunService } from '@pikku/core/ecosystem/workflow';
14
- export type { AgentRunService, AgentRunRow, } from '@pikku/core/ecosystem/agent';
13
+ export type { WorkflowRunService } from '@pikku/core/workflow';
14
+ export type { AgentRunService, AgentRunRow } from '@pikku/core/agent';
package/package.json CHANGED
@@ -1,6 +1,11 @@
1
1
  {
2
2
  "name": "@pikku/kysely-sqlite",
3
- "version": "0.12.12",
3
+ "version": "0.12.14",
4
+ "repository": {
5
+ "type": "git",
6
+ "url": "git+https://github.com/pikkujs/pikku.git",
7
+ "directory": "packages/services/kysely-sqlite"
8
+ },
4
9
  "description": "SQLite dialect services and libSQL web dialect for Pikku, backed by Kysely",
5
10
  "author": "yasser.fadl@gmail.com",
6
11
  "license": "MIT",
@@ -14,18 +19,17 @@
14
19
  "test": "bash run-tests.sh",
15
20
  "test:watch": "bash run-tests.sh --watch",
16
21
  "test:coverage": "bash run-tests.sh --coverage",
17
- "prepublishOnly": "yarn build"
22
+ "prepublishOnly": "bun run build"
18
23
  },
19
24
  "dependencies": {
20
- "@pikku/kysely": "^0.13.16",
21
- "kysely": "^0.29.0"
25
+ "@pikku/kysely": "^0.13.25",
26
+ "kysely": "^0.29.5"
22
27
  },
23
28
  "peerDependencies": {
24
- "@pikku/core": "^0.12.84"
29
+ "@pikku/core": "^0.12.108"
25
30
  },
26
31
  "devDependencies": {
27
- "@pikku/core": "^0.12.84",
28
- "typescript": "^6.0.3"
32
+ "@pikku/core": "^0.12.108"
29
33
  },
30
34
  "exports": {
31
35
  ".": "./dist/src/index.js"
package/run-tests.sh CHANGED
@@ -1,33 +1,68 @@
1
1
  #!/bin/bash
2
2
 
3
- # Parse command line arguments
4
- WATCH_MODE=false
5
- COVERAGE_MODE=false
6
-
7
- for arg in "$@"
8
- do
9
- case $arg in
10
- --watch)
11
- WATCH_MODE=true
12
- shift
13
- ;;
14
- --coverage)
15
- COVERAGE_MODE=true
16
- shift
17
- ;;
18
- esac
3
+ # Enable nullglob to handle cases where no files match the pattern
4
+ shopt -s nullglob
5
+
6
+ # Initialize variables for options
7
+ watch_mode=false
8
+ coverage_mode=false
9
+
10
+ # Parse command-line options
11
+ while [[ $# -gt 0 ]]; do
12
+ case $1 in
13
+ --watch)
14
+ watch_mode=true
15
+ shift
16
+ ;;
17
+ --coverage)
18
+ coverage_mode=true
19
+ shift
20
+ ;;
21
+ *)
22
+ echo "Unknown option: $1"
23
+ exit 1
24
+ ;;
25
+ esac
19
26
  done
20
27
 
21
- # Build the command
22
- CMD="node --import tsx --test --test-force-exit src/**/*.test.ts"
28
+ # Define the pattern to match your test files
29
+ pattern="src/*.test.ts"
30
+
31
+ # Expand the pattern into an array of files
32
+ files=($(find src -type f -name "*.test.ts"))
33
+
34
+ # Check if any files matched the pattern
35
+ if [ ${#files[@]} -eq 0 ]; then
36
+ echo "No test files found matching pattern: $pattern"
37
+ if [ "${STRICT_TEST_DISCOVERY}" = "1" ]; then
38
+ exit 1
39
+ fi
40
+ exit 0
41
+ fi
42
+
43
+ # Construct the bun command
44
+ #
45
+ # --parallel gives every test file its own worker process, which is what node's
46
+ # `--test` did. Several suites keep module-level caches (a TypeScript program, a
47
+ # temp fixture directory) that a shared process would leak between files.
48
+ #
49
+ # --timeout because bun caps a test at 5s by default where node had no cap, and
50
+ # the suites that build real TypeScript programs run well past that.
51
+ bun_cmd=(bun test --parallel --timeout 120000)
23
52
 
24
- if [ "$WATCH_MODE" = true ]; then
25
- CMD="$CMD --watch"
53
+ # Append options based on flags
54
+ if [ "$watch_mode" = true ]; then
55
+ # --watch reruns in one process; parallel workers have nothing to watch.
56
+ bun_cmd=(bun test --watch --timeout 120000)
26
57
  fi
27
58
 
28
- if [ "$COVERAGE_MODE" = true ]; then
29
- CMD="$CMD --experimental-test-coverage"
59
+ if [ "$coverage_mode" = true ]; then
60
+ # --coverage-dir=. keeps lcov.info at the package root: CI merges every
61
+ # package's report by prefixing its SF paths with the file's own directory,
62
+ # so a nested coverage/ directory would re-root them one level too deep.
63
+ bun_cmd+=(--coverage --coverage-reporter=lcov --coverage-reporter=text --coverage-dir=.)
64
+ export PIKKU_TEST_COVERAGE=1
30
65
  fi
31
66
 
32
- # Execute the command
33
- eval $CMD
67
+ # Execute the bun command with the expanded list of files
68
+ "${bun_cmd[@]}" "${files[@]}"
@@ -1,3 +0,0 @@
1
- import { KyselyAIStorageService } from '@pikku/kysely';
2
- export declare class SQLiteKyselyAIStorageService extends KyselyAIStorageService {
3
- }
@@ -1,3 +0,0 @@
1
- import { KyselyAIStorageService } from '@pikku/kysely';
2
- export class SQLiteKyselyAIStorageService extends KyselyAIStorageService {
3
- }
@@ -1 +0,0 @@
1
- {"root":["../src/create-sqlite-kysely.ts","../src/index.ts","../src/libsql-web-dialect.ts","../src/sqlite-functions.ts","../src/sqlite-kysely-agent-run-service.ts","../src/sqlite-kysely-agent-storage-service.ts","../src/sqlite-kysely-channel-store.ts","../src/sqlite-kysely-deployment-service.ts","../src/sqlite-kysely-eventhub-store.ts","../src/sqlite-kysely-secret-service.ts","../src/sqlite-kysely-workflow-run-service.ts","../src/sqlite-kysely-workflow-service.ts"],"version":"6.0.3"}
@@ -1,12 +0,0 @@
1
- import { Kysely, SqliteDialect, type SqliteDatabase } from 'kysely'
2
- import { SerializePlugin } from '@pikku/kysely'
3
- import type { KyselyPikkuDB } from '@pikku/kysely'
4
-
5
- export function createSQLiteKysely(
6
- database: SqliteDatabase | (() => Promise<SqliteDatabase>)
7
- ): Kysely<KyselyPikkuDB> {
8
- return new Kysely<KyselyPikkuDB>({
9
- dialect: new SqliteDialect({ database }),
10
- plugins: [new SerializePlugin()],
11
- })
12
- }
package/src/index.ts DELETED
@@ -1,21 +0,0 @@
1
- export { createSQLiteKysely } from './create-sqlite-kysely.js'
2
- export { SQLiteKyselyWorkflowService } from './sqlite-kysely-workflow-service.js'
3
- export { SQLiteKyselyDeploymentService } from './sqlite-kysely-deployment-service.js'
4
- export { SQLiteKyselyAgentStorageService } from './sqlite-kysely-agent-storage-service.js'
5
- export { SQLiteKyselyAgentRunService } from './sqlite-kysely-agent-run-service.js'
6
- export { SQLiteKyselyWorkflowRunService } from './sqlite-kysely-workflow-run-service.js'
7
- export { SQLiteKyselyChannelStore } from './sqlite-kysely-channel-store.js'
8
- export { SQLiteKyselyEventHubStore } from './sqlite-kysely-eventhub-store.js'
9
- export { SQLiteKyselySecretService } from './sqlite-kysely-secret-service.js'
10
- export {
11
- SqliteFunctionsUnsupportedError,
12
- type SqliteFunctionMap,
13
- } from './sqlite-functions.js'
14
- export { LibsqlWebDialect } from './libsql-web-dialect.js'
15
-
16
- export type { KyselyPikkuDB } from '@pikku/kysely'
17
- export type { WorkflowRunService } from '@pikku/core/ecosystem/workflow'
18
- export type {
19
- AgentRunService,
20
- AgentRunRow,
21
- } from '@pikku/core/ecosystem/agent'
@@ -1,151 +0,0 @@
1
- import { describe, test, beforeEach, afterEach } from 'node:test'
2
- import assert from 'node:assert/strict'
3
- import { Kysely } from 'kysely'
4
- import { LibsqlWebDialect } from './libsql-web-dialect.js'
5
-
6
- interface TestDB {
7
- entry: {
8
- id: string
9
- createdAt: string
10
- payload: string
11
- archived: number
12
- }
13
- }
14
-
15
- /** Args of the last `execute` request the dialect put on the wire. */
16
- let sentArgs: Array<Record<string, unknown>>
17
- let realFetch: typeof globalThis.fetch
18
-
19
- const okResponse = () =>
20
- new Response(
21
- JSON.stringify({
22
- baton: null,
23
- base_url: null,
24
- results: [
25
- {
26
- type: 'ok',
27
- response: {
28
- type: 'execute',
29
- result: {
30
- cols: [],
31
- rows: [],
32
- affected_row_count: 1,
33
- last_insert_rowid: '1',
34
- },
35
- },
36
- },
37
- ],
38
- }),
39
- { status: 200, headers: { 'content-type': 'application/json' } }
40
- )
41
-
42
- const db = () =>
43
- new Kysely<TestDB>({
44
- dialect: new LibsqlWebDialect({ url: 'https://example.turso.io' }),
45
- })
46
-
47
- describe('LibsqlWebDialect bind-parameter encoding', () => {
48
- beforeEach(() => {
49
- sentArgs = []
50
- realFetch = globalThis.fetch
51
- globalThis.fetch = (async (_url: string, init: RequestInit) => {
52
- const body = JSON.parse(init.body as string)
53
- for (const req of body.requests) {
54
- if (req.type === 'execute') sentArgs = req.stmt.args
55
- }
56
- return okResponse()
57
- }) as unknown as typeof globalThis.fetch
58
- })
59
-
60
- afterEach(() => {
61
- globalThis.fetch = realFetch
62
- })
63
-
64
- test('encodes a Date as an ISO-8601 text value', async () => {
65
- const createdAt = new Date('2026-07-29T21:00:00.000Z')
66
- await db()
67
- .insertInto('entry')
68
- .values({ id: 'a', createdAt: createdAt as never } as never)
69
- .execute()
70
-
71
- assert.deepEqual(sentArgs[1], {
72
- type: 'text',
73
- value: '2026-07-29T21:00:00.000Z',
74
- })
75
- })
76
-
77
- test('encodes a plain object as JSON text', async () => {
78
- await db()
79
- .insertInto('entry')
80
- .values({ id: 'a', payload: { tags: [1, 2] } as never } as never)
81
- .execute()
82
-
83
- assert.deepEqual(sentArgs[1], {
84
- type: 'text',
85
- value: '{"tags":[1,2]}',
86
- })
87
- })
88
-
89
- test('encodes an array bound directly as JSON text', async () => {
90
- await db()
91
- .insertInto('entry')
92
- .values({ id: 'a', payload: [1, 2] as never } as never)
93
- .execute()
94
-
95
- assert.deepEqual(sentArgs[1], { type: 'text', value: '[1,2]' })
96
- })
97
-
98
- test('encodes a null-prototype record as JSON text', async () => {
99
- const payload = Object.assign(Object.create(null), { tags: [1] })
100
- await db()
101
- .insertInto('entry')
102
- .values({ id: 'a', payload: payload as never } as never)
103
- .execute()
104
-
105
- assert.deepEqual(sentArgs[1], { type: 'text', value: '{"tags":[1]}' })
106
- })
107
-
108
- test('keeps the primitive encodings intact', async () => {
109
- await db()
110
- .insertInto('entry')
111
- .values({ id: 'a', archived: true as never } as never)
112
- .execute()
113
-
114
- assert.deepEqual(sentArgs, [
115
- { type: 'text', value: 'a' },
116
- { type: 'integer', value: '1' },
117
- ])
118
- })
119
-
120
- test('still rejects a value it cannot represent', async () => {
121
- await assert.rejects(
122
- db()
123
- .insertInto('entry')
124
- .values({ id: 'a', payload: Symbol('nope') as never } as never)
125
- .execute(),
126
- /unsupported argument type symbol/
127
- )
128
- })
129
-
130
- // A Map or a class instance stringifies to "{}" or to a partial view of
131
- // itself, so it must fail loudly rather than persist an empty JSON blob.
132
- test('rejects an object JSON cannot faithfully represent', async () => {
133
- class Money {
134
- constructor(public cents: number) {}
135
- }
136
-
137
- for (const [value, expected] of [
138
- [new Map([['a', 1]]), /unsupported argument type Map/],
139
- [/abc/i, /unsupported argument type RegExp/],
140
- [new Money(100), /unsupported argument type Money/],
141
- ] as const) {
142
- await assert.rejects(
143
- db()
144
- .insertInto('entry')
145
- .values({ id: 'a', payload: value as never } as never)
146
- .execute(),
147
- expected
148
- )
149
- }
150
- })
151
- })
@@ -1,283 +0,0 @@
1
- import {
2
- SqliteAdapter,
3
- SqliteIntrospector,
4
- SqliteQueryCompiler,
5
- type DatabaseConnection,
6
- type Dialect,
7
- type Driver,
8
- type CompiledQuery,
9
- type QueryResult,
10
- type Kysely,
11
- type DatabaseIntrospector,
12
- type DialectAdapter,
13
- type QueryCompiler,
14
- } from 'kysely'
15
-
16
- /**
17
- * Kysely dialect for libsql over the *HTTP pipeline* transport (v2/pipeline).
18
- *
19
- * Why this file exists at all:
20
- * - @libsql/kysely-libsql imports @libsql/client (Node), which uses node:http.
21
- * CF Workers nodejs_compat_v2 doesn't ship node:http -> upload error 10021.
22
- * - @libsql/client/web imports both ./http.js and ./ws.js at module top, and
23
- * ./ws.js pulls in `ws` which does `require('node:events')` at load time.
24
- * CF Workers also doesn't ship node:events -> upload error 10021.
25
- * - @libsql/client/http re-uses @libsql/hrana-client's index which still
26
- * imports ws transitively for the same reason.
27
- *
28
- * So we talk to the libsql/Turso HTTP pipeline directly with fetch. Single
29
- * endpoint, single round-trip per execute. Transactions use the `baton`
30
- * mechanism: every response returns a baton, and resending it on the next
31
- * request keeps the same server-side stream (== same SQL connection ==
32
- * same transaction).
33
- *
34
- * Pipeline reference: https://github.com/tursodatabase/libsql/blob/main/docs/HTTP_V2_SPEC.md
35
- */
36
- export interface LibsqlWebDialectConfig {
37
- url: string
38
- authToken?: string
39
- }
40
-
41
- export class LibsqlWebDialect implements Dialect {
42
- constructor(private readonly config: LibsqlWebDialectConfig) {}
43
- createAdapter(): DialectAdapter {
44
- return new SqliteAdapter()
45
- }
46
- createDriver(): Driver {
47
- return new LibsqlWebDriver(this.config)
48
- }
49
- createQueryCompiler(): QueryCompiler {
50
- return new SqliteQueryCompiler()
51
- }
52
- createIntrospector(db: Kysely<unknown>): DatabaseIntrospector {
53
- return new SqliteIntrospector(db)
54
- }
55
- }
56
-
57
- type HranaValue =
58
- | { type: 'null' }
59
- | { type: 'integer'; value: string }
60
- | { type: 'float'; value: number }
61
- | { type: 'text'; value: string }
62
- | { type: 'blob'; base64: string }
63
-
64
- interface HranaStmtResult {
65
- cols: Array<{ name: string | null; decltype: string | null }>
66
- rows: HranaValue[][]
67
- affected_row_count: number
68
- last_insert_rowid: string | null
69
- }
70
-
71
- interface PipelineResponse {
72
- baton: string | null
73
- base_url: string | null
74
- results: Array<
75
- | {
76
- type: 'ok'
77
- response:
78
- | { type: 'execute'; result: HranaStmtResult }
79
- | { type: 'close' }
80
- }
81
- | { type: 'error'; error: { message: string; code?: string } }
82
- >
83
- }
84
-
85
- class LibsqlWebDriver implements Driver {
86
- private readonly endpoint: string
87
- private readonly authHeader: string | undefined
88
- constructor(config: LibsqlWebDialectConfig) {
89
- // Accept libsql://, https://, http://. The pipeline is HTTP only.
90
- // Turso embeds the JWT in the URL as ?authToken=... — honor that, but let
91
- // an explicit config.authToken win.
92
- const u = new URL(config.url)
93
- const scheme = u.protocol === 'libsql:' ? 'https:' : u.protocol
94
- const tokenFromUrl = u.searchParams.get('authToken') ?? undefined
95
- this.endpoint = `${scheme}//${u.host}/v2/pipeline`
96
- const token = config.authToken ?? tokenFromUrl
97
- this.authHeader = token ? `Bearer ${token}` : undefined
98
- }
99
- async init(): Promise<void> {}
100
- async acquireConnection(): Promise<DatabaseConnection> {
101
- return new LibsqlWebConnection(this.endpoint, this.authHeader)
102
- }
103
- async beginTransaction(conn: DatabaseConnection): Promise<void> {
104
- await (conn as LibsqlWebConnection).executeRaw('BEGIN')
105
- }
106
- async commitTransaction(conn: DatabaseConnection): Promise<void> {
107
- await (conn as LibsqlWebConnection).executeRaw('COMMIT')
108
- }
109
- async rollbackTransaction(conn: DatabaseConnection): Promise<void> {
110
- await (conn as LibsqlWebConnection).executeRaw('ROLLBACK')
111
- }
112
- async releaseConnection(conn: DatabaseConnection): Promise<void> {
113
- await (conn as LibsqlWebConnection).close()
114
- }
115
- async destroy(): Promise<void> {}
116
- }
117
-
118
- class LibsqlWebConnection implements DatabaseConnection {
119
- private baton: string | null = null
120
- private endpoint: string
121
- constructor(
122
- initialEndpoint: string,
123
- private readonly authHeader: string | undefined
124
- ) {
125
- this.endpoint = initialEndpoint
126
- }
127
-
128
- async executeQuery<R>(query: CompiledQuery): Promise<QueryResult<R>> {
129
- const result = await this.runStmt(query.sql, query.parameters)
130
- return decodeResult<R>(result)
131
- }
132
-
133
- async *streamQuery<R>(
134
- query: CompiledQuery
135
- ): AsyncIterableIterator<QueryResult<R>> {
136
- yield this.executeQuery<R>(query)
137
- }
138
-
139
- async executeRaw(sql: string): Promise<void> {
140
- await this.runStmt(sql, [])
141
- }
142
-
143
- async close(): Promise<void> {
144
- if (this.baton === null) return
145
- await this.send([{ type: 'close' }])
146
- this.baton = null
147
- }
148
-
149
- private async runStmt(
150
- sql: string,
151
- args: readonly unknown[]
152
- ): Promise<HranaStmtResult> {
153
- const resp = await this.send([
154
- {
155
- type: 'execute',
156
- stmt: { sql, args: args.map(encodeArg) },
157
- },
158
- ])
159
- const r = resp.results[0]
160
- if (!r) throw new Error('libsql: empty pipeline response')
161
- if (r.type === 'error')
162
- throw new Error(
163
- `libsql: ${r.error.message}${r.error.code ? ` (${r.error.code})` : ''}`
164
- )
165
- if (r.response.type !== 'execute')
166
- throw new Error(`libsql: unexpected response type ${r.response.type}`)
167
- return r.response.result
168
- }
169
-
170
- private async send(requests: unknown[]): Promise<PipelineResponse> {
171
- const headers: Record<string, string> = {
172
- 'content-type': 'application/json',
173
- }
174
- if (this.authHeader) headers.authorization = this.authHeader
175
- const res = await fetch(this.endpoint, {
176
- method: 'POST',
177
- headers,
178
- body: JSON.stringify({ baton: this.baton, requests }),
179
- })
180
- if (!res.ok) {
181
- const body = await res.text().catch(() => '')
182
- throw new Error(`libsql HTTP ${res.status}: ${body}`)
183
- }
184
- const json = (await res.json()) as PipelineResponse
185
- this.baton = json.baton
186
- if (json.base_url)
187
- this.endpoint = `${json.base_url.replace(/\/$/, '')}/v2/pipeline`
188
- return json
189
- }
190
- }
191
-
192
- /**
193
- * Whether a value is a JSON column's worth of data rather than some other
194
- * object that merely happens to be one.
195
- *
196
- * A `Map`, a `RegExp` or a class instance all stringify to `"{}"` or to a
197
- * partial view of themselves, so accepting every object would silently persist
198
- * an empty JSON blob where the caller meant something. Those fall through to
199
- * the unsupported-type error instead. Kept in lockstep with the `coerce()` in
200
- * the CLI's node:sqlite dev runtime (`db/sqlite/sqlite-kysely.ts`).
201
- */
202
- function isJsonEncodable(v: object): boolean {
203
- if (Array.isArray(v)) return true
204
- const proto = Object.getPrototypeOf(v)
205
- return proto === Object.prototype || proto === null
206
- }
207
-
208
- /**
209
- * Encodes one Kysely bind parameter as a Hrana value.
210
- *
211
- * The Date and plain-object branches exist for parity with the `coerce()` in
212
- * the CLI's node:sqlite dev runtime (`db/sqlite/sqlite-kysely.ts`). Without
213
- * them a query that binds a timestamp or a JSON column passes under
214
- * `pikku dev` and throws `unsupported argument type object` once deployed to
215
- * a Worker — so every write in a deployed libsql app fails while the same
216
- * code works locally.
217
- */
218
- function encodeArg(v: unknown): HranaValue {
219
- if (v === null || v === undefined) return { type: 'null' }
220
- if (typeof v === 'string') return { type: 'text', value: v }
221
- if (typeof v === 'number')
222
- return Number.isInteger(v)
223
- ? { type: 'integer', value: String(v) }
224
- : { type: 'float', value: v }
225
- if (typeof v === 'bigint') return { type: 'integer', value: v.toString() }
226
- if (typeof v === 'boolean') return { type: 'integer', value: v ? '1' : '0' }
227
- if (v instanceof ArrayBuffer)
228
- return { type: 'blob', base64: bytesToBase64(new Uint8Array(v)) }
229
- if (v instanceof Uint8Array) return { type: 'blob', base64: bytesToBase64(v) }
230
- if (v instanceof Date) return { type: 'text', value: v.toISOString() }
231
- if (typeof v === 'object' && isJsonEncodable(v))
232
- return { type: 'text', value: JSON.stringify(v) }
233
- // Name the constructor for objects: "unsupported argument type object" is
234
- // what made the missing Date branch so hard to place in the first place.
235
- const kind =
236
- typeof v === 'object' ? (v.constructor?.name ?? 'object') : typeof v
237
- throw new Error(`libsql: unsupported argument type ${kind}`)
238
- }
239
-
240
- function decodeValue(v: HranaValue): unknown {
241
- switch (v.type) {
242
- case 'null':
243
- return null
244
- case 'integer': {
245
- const n = Number(v.value)
246
- return Number.isSafeInteger(n) ? n : BigInt(v.value)
247
- }
248
- case 'float':
249
- return v.value
250
- case 'text':
251
- return v.value
252
- case 'blob':
253
- return base64ToBytes(v.base64)
254
- }
255
- }
256
-
257
- function decodeResult<R>(r: HranaStmtResult): QueryResult<R> {
258
- const colNames = r.cols.map((c, i) => c.name ?? `c${i}`)
259
- const rows = r.rows.map((row) => {
260
- const o: Record<string, unknown> = {}
261
- for (let i = 0; i < colNames.length; i++)
262
- o[colNames[i]!] = decodeValue(row[i]!)
263
- return o as R
264
- })
265
- return {
266
- rows,
267
- numAffectedRows: BigInt(r.affected_row_count),
268
- insertId:
269
- r.last_insert_rowid !== null ? BigInt(r.last_insert_rowid) : undefined,
270
- }
271
- }
272
-
273
- function bytesToBase64(b: Uint8Array): string {
274
- let s = ''
275
- for (let i = 0; i < b.length; i++) s += String.fromCharCode(b[i]!)
276
- return btoa(s)
277
- }
278
- function base64ToBytes(s: string): Uint8Array {
279
- const bin = atob(s)
280
- const out = new Uint8Array(bin.length)
281
- for (let i = 0; i < bin.length; i++) out[i] = bin.charCodeAt(i)
282
- return out
283
- }
@@ -1,47 +0,0 @@
1
- /**
2
- * User-defined SQL functions, shared between the runtime-specific SQLite
3
- * drivers so that `functions: { … }` means the same thing whichever one an app
4
- * is wired to.
5
- *
6
- * Only `@pikku/kysely-node-sqlite` can actually register them. bun:sqlite has
7
- * no equivalent of `db.function()`, so `@pikku/kysely-bun-sqlite` rejects this
8
- * option outright rather than accepting it and letting the queries fail. That
9
- * asymmetry is the whole reason this type is declared once, in a package both
10
- * depend on: an app that names its UDFs against this type finds out at wiring
11
- * time — or at the type level — that it cannot move to bun, instead of finding
12
- * out as `no such function: …` from whichever endpoint happens to use it.
13
- */
14
-
15
- /**
16
- * A scalar SQL function. Arguments arrive as the values SQLite stores — string,
17
- * number, bigint, Uint8Array or null — and the return value must be one SQLite
18
- * can store, so no Date, boolean or object.
19
- */
20
- export type SqliteFunction = (
21
- ...args: Array<string | number | bigint | Uint8Array | null>
22
- ) => string | number | bigint | Uint8Array | null
23
-
24
- /** Scalar SQL functions to register, keyed by the name SQL will call them by. */
25
- export type SqliteFunctionMap = Record<string, SqliteFunction>
26
-
27
- /**
28
- * Thrown when UDFs are requested on a runtime that cannot provide them.
29
- *
30
- * Its own class rather than a plain Error because the one sensible reaction —
31
- * fall back to doing the work outside SQL — is worth being able to catch for.
32
- */
33
- export class SqliteFunctionsUnsupportedError extends Error {
34
- constructor(
35
- readonly runtime: string,
36
- readonly functionNames: string[]
37
- ) {
38
- super(
39
- `${runtime} cannot register user-defined SQL functions, but ${functionNames.length} were requested: ${functionNames.join(', ')}.\n\n` +
40
- `Every query calling them would fail with "no such function". Options:\n` +
41
- ` - run on Node with @pikku/kysely-node-sqlite, which supports them; or\n` +
42
- ` - do the work outside SQL, narrowing rows with an index first so the\n` +
43
- ` computation runs over a candidate set rather than the whole table.`
44
- )
45
- this.name = 'SqliteFunctionsUnsupportedError'
46
- }
47
- }
@@ -1,3 +0,0 @@
1
- import { KyselyAgentRunService } from '@pikku/kysely'
2
-
3
- export class SQLiteKyselyAgentRunService extends KyselyAgentRunService {}
@@ -1,3 +0,0 @@
1
- import { KyselyAgentStorageService } from '@pikku/kysely'
2
-
3
- export class SQLiteKyselyAgentStorageService extends KyselyAgentStorageService {}
@@ -1,3 +0,0 @@
1
- import { KyselyChannelStore } from '@pikku/kysely'
2
-
3
- export class SQLiteKyselyChannelStore extends KyselyChannelStore {}
@@ -1,3 +0,0 @@
1
- import { KyselyDeploymentService } from '@pikku/kysely'
2
-
3
- export class SQLiteKyselyDeploymentService extends KyselyDeploymentService {}
@@ -1,3 +0,0 @@
1
- import { KyselyEventHubStore } from '@pikku/kysely'
2
-
3
- export class SQLiteKyselyEventHubStore extends KyselyEventHubStore {}
@@ -1,3 +0,0 @@
1
- import { KyselySecretService } from '@pikku/kysely'
2
-
3
- export class SQLiteKyselySecretService extends KyselySecretService {}
@@ -1,3 +0,0 @@
1
- import { KyselyWorkflowRunService } from '@pikku/kysely'
2
-
3
- export class SQLiteKyselyWorkflowRunService extends KyselyWorkflowRunService {}
@@ -1,3 +0,0 @@
1
- import { KyselyWorkflowService } from '@pikku/kysely'
2
-
3
- export class SQLiteKyselyWorkflowService extends KyselyWorkflowService {}
package/tsconfig.json DELETED
@@ -1,12 +0,0 @@
1
- {
2
- "extends": "../../tsconfig.json",
3
- "compilerOptions": {
4
- "rootDir": ".",
5
- "module": "Node18",
6
- "outDir": "dist",
7
- "target": "esnext",
8
- "declaration": true
9
- },
10
- "include": ["src/**/*.ts"],
11
- "exclude": ["**/*.test.ts", "node_modules"]
12
- }