libsql-search 0.1.3 → 0.1.5
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/README.md +79 -431
- package/dist/index.cjs +354 -0
- package/dist/index.d.ts +0 -4
- package/dist/index.esm.js +249 -295
- package/docs/API.md +167 -0
- package/docs/INDEXING.md +68 -0
- package/docs/INTEGRATIONS.md +143 -0
- package/docs/PROVIDERS.md +100 -0
- package/docs/README.md +16 -0
- package/docs/RELEASING.md +91 -0
- package/docs/TROUBLESHOOTING-SHARP.md +64 -0
- package/docs/TROUBLESHOOTING.md +12 -0
- package/package.json +31 -19
- package/dist/index.js +0 -400
package/docs/API.md
ADDED
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
# API Reference
|
|
2
|
+
|
|
3
|
+
## Exports
|
|
4
|
+
|
|
5
|
+
`libsql-search` exports:
|
|
6
|
+
|
|
7
|
+
- `createTable`
|
|
8
|
+
- `indexContent`
|
|
9
|
+
- `search`
|
|
10
|
+
- `getAllArticles`
|
|
11
|
+
- `getArticleBySlug`
|
|
12
|
+
- `getArticlesByFolder`
|
|
13
|
+
- `getFolders`
|
|
14
|
+
- `generateEmbedding`
|
|
15
|
+
- `padEmbedding`
|
|
16
|
+
- `prepareTextForEmbedding`
|
|
17
|
+
|
|
18
|
+
It also exports these types:
|
|
19
|
+
|
|
20
|
+
- `EmbeddingProvider`
|
|
21
|
+
- `EmbeddingOptions`
|
|
22
|
+
- `IndexerOptions`
|
|
23
|
+
- `IndexedDocument`
|
|
24
|
+
- `SearchOptions`
|
|
25
|
+
- `SearchResult`
|
|
26
|
+
|
|
27
|
+
## `createTable(client, tableName?, dimensions?)`
|
|
28
|
+
|
|
29
|
+
Creates the table and supporting indexes used by search.
|
|
30
|
+
|
|
31
|
+
```ts
|
|
32
|
+
await createTable(client, "articles", 768);
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Defaults:
|
|
36
|
+
|
|
37
|
+
- `tableName`: `"articles"`
|
|
38
|
+
- `dimensions`: `768`
|
|
39
|
+
|
|
40
|
+
The created schema includes:
|
|
41
|
+
|
|
42
|
+
- `id` primary key
|
|
43
|
+
- `slug`
|
|
44
|
+
- `title`
|
|
45
|
+
- `content`
|
|
46
|
+
- `folder`
|
|
47
|
+
- `tags`
|
|
48
|
+
- `embedding`
|
|
49
|
+
- `created_at`
|
|
50
|
+
- `updated_at`
|
|
51
|
+
|
|
52
|
+
## `indexContent(options)`
|
|
53
|
+
|
|
54
|
+
Indexes Markdown files from a directory on disk.
|
|
55
|
+
|
|
56
|
+
```ts
|
|
57
|
+
interface IndexerOptions {
|
|
58
|
+
client: Client;
|
|
59
|
+
contentPath: string;
|
|
60
|
+
embeddingOptions?: EmbeddingOptions;
|
|
61
|
+
fileExtensions?: string[];
|
|
62
|
+
exclude?: string[];
|
|
63
|
+
tableName?: string;
|
|
64
|
+
onProgress?: (current: number, total: number, file: string) => void;
|
|
65
|
+
}
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
Defaults:
|
|
69
|
+
|
|
70
|
+
- `fileExtensions`: [".md", ".markdown"]
|
|
71
|
+
- `exclude`: ["node_modules", ".git", "dist", "build"]
|
|
72
|
+
- `tableName`: `"articles"`
|
|
73
|
+
|
|
74
|
+
Return shape:
|
|
75
|
+
|
|
76
|
+
```ts
|
|
77
|
+
{
|
|
78
|
+
success: number;
|
|
79
|
+
failed: number;
|
|
80
|
+
total: number;
|
|
81
|
+
}
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
Behavior notes:
|
|
85
|
+
|
|
86
|
+
- `indexContent()` deletes existing rows in the target table before rebuilding
|
|
87
|
+
- frontmatter `title`, `description`, and `tags` are folded into the embedding
|
|
88
|
+
text
|
|
89
|
+
- if a file has no frontmatter title, the filename becomes the title
|
|
90
|
+
|
|
91
|
+
## `search(options)`
|
|
92
|
+
|
|
93
|
+
Generates a query embedding and performs vector similarity search.
|
|
94
|
+
|
|
95
|
+
```ts
|
|
96
|
+
interface SearchOptions {
|
|
97
|
+
client: Client;
|
|
98
|
+
query: string;
|
|
99
|
+
limit?: number;
|
|
100
|
+
tableName?: string;
|
|
101
|
+
embeddingOptions?: EmbeddingOptions;
|
|
102
|
+
}
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
Defaults:
|
|
106
|
+
|
|
107
|
+
- `limit`: `10`
|
|
108
|
+
- `tableName`: `"articles"`
|
|
109
|
+
|
|
110
|
+
Result shape:
|
|
111
|
+
|
|
112
|
+
```ts
|
|
113
|
+
interface SearchResult {
|
|
114
|
+
id: number;
|
|
115
|
+
slug: string;
|
|
116
|
+
title: string;
|
|
117
|
+
content: string;
|
|
118
|
+
folder: string;
|
|
119
|
+
tags: string[];
|
|
120
|
+
distance: number;
|
|
121
|
+
created_at: string;
|
|
122
|
+
}
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
Lower `distance` values are better matches.
|
|
126
|
+
|
|
127
|
+
## Article Retrieval Helpers
|
|
128
|
+
|
|
129
|
+
### `getAllArticles(client, tableName?)`
|
|
130
|
+
|
|
131
|
+
Returns all indexed articles ordered by title.
|
|
132
|
+
|
|
133
|
+
### `getArticleBySlug(client, slug, tableName?)`
|
|
134
|
+
|
|
135
|
+
Returns one article or `null`.
|
|
136
|
+
|
|
137
|
+
### `getArticlesByFolder(client, folder, tableName?)`
|
|
138
|
+
|
|
139
|
+
Returns articles in a specific folder.
|
|
140
|
+
|
|
141
|
+
### `getFolders(client, tableName?)`
|
|
142
|
+
|
|
143
|
+
Returns distinct folder names from the index.
|
|
144
|
+
|
|
145
|
+
## Embedding Helpers
|
|
146
|
+
|
|
147
|
+
### `generateEmbedding(text, options?)`
|
|
148
|
+
|
|
149
|
+
Generates an embedding for arbitrary text using the selected provider.
|
|
150
|
+
|
|
151
|
+
### `padEmbedding(embedding, targetDimensions)`
|
|
152
|
+
|
|
153
|
+
Pads or truncates an embedding array to the requested length.
|
|
154
|
+
|
|
155
|
+
### `prepareTextForEmbedding(fields)`
|
|
156
|
+
|
|
157
|
+
Combines title, description, tags, and content into the text sent to the
|
|
158
|
+
embedding model.
|
|
159
|
+
|
|
160
|
+
```ts
|
|
161
|
+
const text = prepareTextForEmbedding({
|
|
162
|
+
title: "My Article",
|
|
163
|
+
description: "How semantic search works",
|
|
164
|
+
tags: ["search", "turso"],
|
|
165
|
+
content: "# Content",
|
|
166
|
+
});
|
|
167
|
+
```
|
package/docs/INDEXING.md
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# Indexing And Operations
|
|
2
|
+
|
|
3
|
+
## Content Shape
|
|
4
|
+
|
|
5
|
+
`indexContent()` walks a directory tree, reads Markdown files, parses
|
|
6
|
+
frontmatter with `gray-matter`, and stores:
|
|
7
|
+
|
|
8
|
+
- `slug`
|
|
9
|
+
- `title`
|
|
10
|
+
- `content`
|
|
11
|
+
- `folder`
|
|
12
|
+
- `tags`
|
|
13
|
+
- `embedding`
|
|
14
|
+
|
|
15
|
+
The slug is derived from the file path relative to `contentPath`.
|
|
16
|
+
|
|
17
|
+
## Rebuild Behavior
|
|
18
|
+
|
|
19
|
+
`indexContent()` clears the target table before rebuilding:
|
|
20
|
+
|
|
21
|
+
```ts
|
|
22
|
+
await indexContent({
|
|
23
|
+
client,
|
|
24
|
+
contentPath: "./content",
|
|
25
|
+
tableName: "articles",
|
|
26
|
+
embeddingOptions: {
|
|
27
|
+
provider: "local",
|
|
28
|
+
dimensions: 768,
|
|
29
|
+
},
|
|
30
|
+
});
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
That keeps the implementation simple, but it also means a failed rebuild can
|
|
34
|
+
leave the index partially repopulated.
|
|
35
|
+
|
|
36
|
+
## Quality Guidelines
|
|
37
|
+
|
|
38
|
+
- include descriptive frontmatter titles
|
|
39
|
+
- add meaningful `tags` when they help retrieval
|
|
40
|
+
- use the same embedding provider and dimensions at index and query time
|
|
41
|
+
- keep `maxLength` intentional if your content is very large
|
|
42
|
+
- start with a smaller search `limit` and tune from real query behavior
|
|
43
|
+
|
|
44
|
+
## Build Integration
|
|
45
|
+
|
|
46
|
+
Many projects wire indexing into a dedicated script and call it before their
|
|
47
|
+
site build:
|
|
48
|
+
|
|
49
|
+
```json
|
|
50
|
+
{
|
|
51
|
+
"scripts": {
|
|
52
|
+
"index": "node ./scripts/index.js",
|
|
53
|
+
"build": "pnpm index && astro build"
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## Table Names
|
|
59
|
+
|
|
60
|
+
`tableName` is interpolated into SQL. Treat it as a trusted identifier coming
|
|
61
|
+
from your own configuration, not from user input.
|
|
62
|
+
|
|
63
|
+
## Runtime Notes
|
|
64
|
+
|
|
65
|
+
- local embeddings may download a model on the first run
|
|
66
|
+
- Node users need `@libsql/client` installed alongside the package
|
|
67
|
+
- the repository validates both the npm package build and `deno check`, but the
|
|
68
|
+
indexing flow itself still depends on filesystem access
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
# Integration Examples
|
|
2
|
+
|
|
3
|
+
These examples show the current exported API wired into typical server-side
|
|
4
|
+
routes. They are intentionally small so you can adapt them to your app.
|
|
5
|
+
|
|
6
|
+
## Astro Search Endpoint
|
|
7
|
+
|
|
8
|
+
```ts
|
|
9
|
+
import type { APIRoute } from "astro";
|
|
10
|
+
import { createClient } from "@libsql/client";
|
|
11
|
+
import { search } from "libsql-search";
|
|
12
|
+
|
|
13
|
+
export const prerender = false;
|
|
14
|
+
|
|
15
|
+
const client = createClient({
|
|
16
|
+
url: import.meta.env.TURSO_DB_URL,
|
|
17
|
+
authToken: import.meta.env.TURSO_AUTH_TOKEN,
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
export const POST: APIRoute = async ({ request }) => {
|
|
21
|
+
const { query, limit = 10 } = await request.json();
|
|
22
|
+
|
|
23
|
+
const results = await search({
|
|
24
|
+
client,
|
|
25
|
+
query,
|
|
26
|
+
limit,
|
|
27
|
+
embeddingOptions: {
|
|
28
|
+
provider: "local",
|
|
29
|
+
dimensions: 768,
|
|
30
|
+
},
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
return new Response(JSON.stringify({ results }), {
|
|
34
|
+
headers: { "Content-Type": "application/json" },
|
|
35
|
+
});
|
|
36
|
+
};
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## Astro Static Paths
|
|
40
|
+
|
|
41
|
+
```ts
|
|
42
|
+
import { createClient } from "@libsql/client";
|
|
43
|
+
import { getAllArticles, getArticleBySlug } from "libsql-search";
|
|
44
|
+
|
|
45
|
+
const client = createClient({
|
|
46
|
+
url: import.meta.env.TURSO_DB_URL,
|
|
47
|
+
authToken: import.meta.env.TURSO_AUTH_TOKEN,
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
export async function getStaticPaths() {
|
|
51
|
+
const articles = await getAllArticles(client);
|
|
52
|
+
|
|
53
|
+
return articles.map((article) => ({
|
|
54
|
+
params: { slug: article.slug },
|
|
55
|
+
}));
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const article = await getArticleBySlug(client, "guides/getting-started");
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## Next.js Route Handler
|
|
62
|
+
|
|
63
|
+
```ts
|
|
64
|
+
import { createClient } from "@libsql/client";
|
|
65
|
+
import { search } from "libsql-search";
|
|
66
|
+
import { NextRequest } from "next/server";
|
|
67
|
+
|
|
68
|
+
const client = createClient({
|
|
69
|
+
url: process.env.TURSO_DB_URL!,
|
|
70
|
+
authToken: process.env.TURSO_AUTH_TOKEN!,
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
export async function POST(request: NextRequest) {
|
|
74
|
+
const { query, limit = 10 } = await request.json();
|
|
75
|
+
|
|
76
|
+
const results = await search({
|
|
77
|
+
client,
|
|
78
|
+
query,
|
|
79
|
+
limit,
|
|
80
|
+
embeddingOptions: {
|
|
81
|
+
provider: "local",
|
|
82
|
+
dimensions: 768,
|
|
83
|
+
},
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
return Response.json({ results });
|
|
87
|
+
}
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
## Next.js Static Params
|
|
91
|
+
|
|
92
|
+
```tsx
|
|
93
|
+
import { createClient } from "@libsql/client";
|
|
94
|
+
import { getAllArticles, getArticleBySlug } from "libsql-search";
|
|
95
|
+
|
|
96
|
+
const client = createClient({
|
|
97
|
+
url: process.env.TURSO_DB_URL!,
|
|
98
|
+
authToken: process.env.TURSO_AUTH_TOKEN!,
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
export async function generateStaticParams() {
|
|
102
|
+
const articles = await getAllArticles(client);
|
|
103
|
+
|
|
104
|
+
return articles.map((article) => ({
|
|
105
|
+
slug: article.slug,
|
|
106
|
+
}));
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export default async function Page({ params }: { params: Promise<{ slug: string }> }) {
|
|
110
|
+
const { slug } = await params;
|
|
111
|
+
const article = await getArticleBySlug(client, slug);
|
|
112
|
+
|
|
113
|
+
return <article>{article?.title}</article>;
|
|
114
|
+
}
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
## Build-Time Index Script
|
|
118
|
+
|
|
119
|
+
A small script is usually enough to rebuild the index before a site build.
|
|
120
|
+
|
|
121
|
+
```ts
|
|
122
|
+
import { createClient } from "@libsql/client";
|
|
123
|
+
import { createTable, indexContent } from "libsql-search";
|
|
124
|
+
|
|
125
|
+
const client = createClient({
|
|
126
|
+
url: process.env.TURSO_DB_URL!,
|
|
127
|
+
authToken: process.env.TURSO_AUTH_TOKEN!,
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
await createTable(client, "articles", 768);
|
|
131
|
+
|
|
132
|
+
await indexContent({
|
|
133
|
+
client,
|
|
134
|
+
contentPath: "./content",
|
|
135
|
+
embeddingOptions: {
|
|
136
|
+
provider: process.env.EMBEDDING_PROVIDER as "local" | "gemini" | "openai" | undefined,
|
|
137
|
+
dimensions: 768,
|
|
138
|
+
},
|
|
139
|
+
});
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
Pair this with your framework build command so indexed content and deployed code
|
|
143
|
+
stay in sync.
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
# Embedding Providers
|
|
2
|
+
|
|
3
|
+
`libsql-search` currently supports three embedding providers:
|
|
4
|
+
|
|
5
|
+
- `local`
|
|
6
|
+
- `gemini`
|
|
7
|
+
- `openai`
|
|
8
|
+
|
|
9
|
+
Use the same provider and dimensions for both indexing and querying. A mismatch
|
|
10
|
+
between stored vectors and query vectors will break search quality or fail at
|
|
11
|
+
query time.
|
|
12
|
+
|
|
13
|
+
## Shared Options
|
|
14
|
+
|
|
15
|
+
```ts
|
|
16
|
+
interface EmbeddingOptions {
|
|
17
|
+
provider?: "local" | "gemini" | "openai";
|
|
18
|
+
apiKey?: string;
|
|
19
|
+
dimensions?: number;
|
|
20
|
+
maxLength?: number;
|
|
21
|
+
}
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
- `provider` defaults to `"local"`
|
|
25
|
+
- `dimensions` defaults to `768`
|
|
26
|
+
- `maxLength` defaults to `8000`
|
|
27
|
+
- `apiKey` is optional in code, but required for hosted providers unless the
|
|
28
|
+
matching environment variable is available
|
|
29
|
+
|
|
30
|
+
## Local
|
|
31
|
+
|
|
32
|
+
Provider value: `local`
|
|
33
|
+
|
|
34
|
+
The local provider loads `Xenova/all-MiniLM-L6-v2` through
|
|
35
|
+
`@xenova/transformers`.
|
|
36
|
+
|
|
37
|
+
```ts
|
|
38
|
+
embeddingOptions: {
|
|
39
|
+
provider: "local",
|
|
40
|
+
dimensions: 768,
|
|
41
|
+
}
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Notes:
|
|
45
|
+
|
|
46
|
+
- the model emits 384 dimensions and `libsql-search` pads or truncates to your
|
|
47
|
+
requested size
|
|
48
|
+
- the first run downloads the model and can take longer on a fresh machine
|
|
49
|
+
- no API key is required
|
|
50
|
+
|
|
51
|
+
## Gemini
|
|
52
|
+
|
|
53
|
+
Provider value: `gemini`
|
|
54
|
+
|
|
55
|
+
Gemini uses Google `text-embedding-004`.
|
|
56
|
+
|
|
57
|
+
```ts
|
|
58
|
+
embeddingOptions: {
|
|
59
|
+
provider: "gemini",
|
|
60
|
+
apiKey: process.env.GEMINI_API_KEY,
|
|
61
|
+
}
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
Behavior:
|
|
65
|
+
|
|
66
|
+
- if `apiKey` is omitted, the library reads `GEMINI_API_KEY`
|
|
67
|
+
- Gemini returns 768 dimensions natively
|
|
68
|
+
- the current implementation does not expose model selection
|
|
69
|
+
|
|
70
|
+
## OpenAI
|
|
71
|
+
|
|
72
|
+
Provider value: `openai`
|
|
73
|
+
|
|
74
|
+
OpenAI uses `text-embedding-3-small` when `dimensions <= 1536` and
|
|
75
|
+
`text-embedding-3-large` when `dimensions > 1536`.
|
|
76
|
+
|
|
77
|
+
```ts
|
|
78
|
+
embeddingOptions: {
|
|
79
|
+
provider: "openai",
|
|
80
|
+
apiKey: process.env.OPENAI_API_KEY,
|
|
81
|
+
dimensions: 1536,
|
|
82
|
+
}
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
Behavior:
|
|
86
|
+
|
|
87
|
+
- if `apiKey` is omitted, the library reads `OPENAI_API_KEY`
|
|
88
|
+
- the request sends the `dimensions` value to the OpenAI embeddings API
|
|
89
|
+
- use the same dimension count in `createTable()`
|
|
90
|
+
|
|
91
|
+
## Dimension Guidelines
|
|
92
|
+
|
|
93
|
+
- `768` is the easiest cross-provider target in the current implementation
|
|
94
|
+
- local embeddings are padded from 384 to your target size
|
|
95
|
+
- Gemini stays at 768
|
|
96
|
+
- OpenAI can be used at 1536 or 3072, or another supported OpenAI dimension
|
|
97
|
+
value you explicitly set
|
|
98
|
+
|
|
99
|
+
If you switch provider or dimensions for an existing table, recreate the table
|
|
100
|
+
or rebuild the index into a separate table so stored vectors stay consistent.
|
package/docs/README.md
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# Documentation
|
|
2
|
+
|
|
3
|
+
This directory holds the longer-form reference material for `libsql-search`.
|
|
4
|
+
Start with the page that matches the job you are doing:
|
|
5
|
+
|
|
6
|
+
- [Provider guide](./PROVIDERS.md): local, Gemini, and OpenAI embedding options,
|
|
7
|
+
dimensions, and API key behavior
|
|
8
|
+
- [API reference](./API.md): exported functions, option shapes, and result data
|
|
9
|
+
- [Integration examples](./INTEGRATIONS.md): Astro and Next.js server-side usage
|
|
10
|
+
- [Indexing and operations](./INDEXING.md): content layout, rebuild scripts,
|
|
11
|
+
search quality tips, and indexing gotchas
|
|
12
|
+
- [Troubleshooting](./TROUBLESHOOTING.md): known install/runtime issues
|
|
13
|
+
- [Releasing](./RELEASING.md): maintainer release workflow
|
|
14
|
+
|
|
15
|
+
For the shortest first-use path, go back to the repository
|
|
16
|
+
[README](../README.md).
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
# Releasing
|
|
2
|
+
|
|
3
|
+
Normal releases are automatic. Merge a reviewed PR to `main`; the `Publish
|
|
4
|
+
Package` workflow decides the next stable SemVer version, creates any required
|
|
5
|
+
manifest-only release commit, creates an annotated `vX.Y.Z` tag, publishes npm
|
|
6
|
+
and JSR, then creates GitHub Release notes after both registries succeed.
|
|
7
|
+
|
|
8
|
+
## Automatic release flow
|
|
9
|
+
|
|
10
|
+
1. Every push to `main` starts `.github/workflows/publish.yml`.
|
|
11
|
+
2. The workflow ignores self-generated `chore(release): ...` commits.
|
|
12
|
+
3. A serialized release-writer job fetches the current `origin/main` after it
|
|
13
|
+
has the release slot. If `origin/main` moved beyond the triggering commit,
|
|
14
|
+
that older run exits and the newer run handles the accumulated changes.
|
|
15
|
+
4. The release planner compares commits since the highest stable `vX.Y.Z` tag
|
|
16
|
+
merged into the release commit. Higher tags that are not reachable from
|
|
17
|
+
`main` are ignored so a stray or divergent tag cannot make the default
|
|
18
|
+
release line jump. Commits whose changed files are confined to `docs/**` or
|
|
19
|
+
`.github/**` do not count as release-eligible and do not affect the bump. If
|
|
20
|
+
the newest `main` commit is docs-only but an earlier untagged code or README
|
|
21
|
+
commit is still pending, that newest run releases the accumulated eligible
|
|
22
|
+
changes. Breaking changes bump major, `feat:` bumps minor, and all other
|
|
23
|
+
eligible commits bump patch.
|
|
24
|
+
5. `package.json`, `jsr.json`, and `deno.json` are synchronized to the chosen
|
|
25
|
+
version. If they already match the chosen version, no release commit is
|
|
26
|
+
created.
|
|
27
|
+
6. The workflow validates the candidate, creates an annotated tag, and atomically
|
|
28
|
+
pushes the release commit plus tag.
|
|
29
|
+
7. npm publishes `libsql-search` through trusted publishing OIDC with the GitHub
|
|
30
|
+
environment named `npm`.
|
|
31
|
+
8. JSR publishes `@logan/libsql-search` through a separate OIDC job.
|
|
32
|
+
9. GitHub Release notes are generated only after both registry jobs complete.
|
|
33
|
+
|
|
34
|
+
The current bootstrap case is supported: if the latest tag is `v0.1.3` and the
|
|
35
|
+
manifests already say `0.1.4`, the first qualifying `main` release tags and
|
|
36
|
+
publishes `v0.1.4` without creating an empty release commit.
|
|
37
|
+
|
|
38
|
+
## Manual overrides
|
|
39
|
+
|
|
40
|
+
Manual version and tag commands are overrides, not the default path. Use them
|
|
41
|
+
only when you intentionally need to publish a specific already-reviewed `main`
|
|
42
|
+
commit.
|
|
43
|
+
|
|
44
|
+
1. Synchronize `package.json`, `jsr.json`, and `deno.json` to the target
|
|
45
|
+
`X.Y.Z` version.
|
|
46
|
+
2. Merge that manifest change to `main`.
|
|
47
|
+
3. Create annotated tag `vX.Y.Z` on the merged `main` commit.
|
|
48
|
+
4. Push the tag.
|
|
49
|
+
|
|
50
|
+
The same `Publish Package` workflow validates manual tags before publishing. It
|
|
51
|
+
checks that the tag target is on `origin/main`, that the tag and manifests agree,
|
|
52
|
+
and that the remote tag still resolves to the checked-out commit immediately
|
|
53
|
+
before each registry publish.
|
|
54
|
+
|
|
55
|
+
## Recovery dispatch
|
|
56
|
+
|
|
57
|
+
Use the guarded workflow dispatch path when an existing `vX.Y.Z` tag needs a
|
|
58
|
+
registry or GitHub Release recovery without creating a new version. Example:
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
gh workflow run publish.yml --ref main -f release_tag=v0.1.4
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
Dispatch validates that `release_tag` is strict `vX.Y.Z`, fetches the remote tag,
|
|
65
|
+
peels it to a commit, checks that commit is on `origin/main`, checks manifests
|
|
66
|
+
against the tag, and then publishes from the tag target SHA. Unlike a tag-push
|
|
67
|
+
run, dispatch does not require the tag target to equal the workflow dispatch
|
|
68
|
+
SHA; this allows a newer `main` workflow fix to recover an older release tag.
|
|
69
|
+
|
|
70
|
+
npm and GitHub Release recovery steps remain idempotent and skip when the
|
|
71
|
+
version or release already exists. JSR recovery always invokes
|
|
72
|
+
`pnpm dlx jsr@0.14.3 publish`; do not use `deno info jsr:...` as an existence
|
|
73
|
+
gate, because it can resolve successfully even when the public JSR package
|
|
74
|
+
version metadata is not actually published.
|
|
75
|
+
|
|
76
|
+
## GitHub and registry settings
|
|
77
|
+
|
|
78
|
+
- npm trusted publishing must point at workflow filename `publish.yml` and use
|
|
79
|
+
the GitHub Actions environment named `npm`.
|
|
80
|
+
- The workflow does not use `NPM_TOKEN`.
|
|
81
|
+
- JSR publishing uses its own OIDC job and does not depend on the npm
|
|
82
|
+
environment.
|
|
83
|
+
- The repository settings must allow GitHub Actions to push the generated
|
|
84
|
+
`chore(release): ...` commit to `main`; otherwise the automatic release job
|
|
85
|
+
will validate successfully and then fail at the atomic push step.
|
|
86
|
+
- Configure a repository ruleset or tag protection rule for `v*` tags.
|
|
87
|
+
- Restrict the `npm` environment to the `main` deployment branch for automatic
|
|
88
|
+
releases and `v*` tags for intentional manual overrides.
|
|
89
|
+
|
|
90
|
+
GitHub Releases are an after-publish record. They are not a prerequisite for npm
|
|
91
|
+
or JSR publishing.
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# Troubleshooting: Transitive `sharp` Install Errors
|
|
2
|
+
|
|
3
|
+
`libsql-search` does not directly depend on `sharp`. If you see an install error
|
|
4
|
+
mentioning `sharp`, it is coming from another dependency in your application or
|
|
5
|
+
toolchain.
|
|
6
|
+
|
|
7
|
+
This page exists because the error can show up in environments that also use
|
|
8
|
+
`libsql-search`, and it is easy to misattribute the failure to this package.
|
|
9
|
+
|
|
10
|
+
## Typical Error
|
|
11
|
+
|
|
12
|
+
```text
|
|
13
|
+
Cannot find module '../build/Release/sharp-*.node'
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
Or:
|
|
17
|
+
|
|
18
|
+
```text
|
|
19
|
+
Error: Something went wrong installing the "sharp" module
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Why It Happens
|
|
23
|
+
|
|
24
|
+
With pnpm, native packages may need explicit build-script approval. If the
|
|
25
|
+
relevant install script is blocked, the native binary is never downloaded or
|
|
26
|
+
built.
|
|
27
|
+
|
|
28
|
+
## What To Do
|
|
29
|
+
|
|
30
|
+
First inspect which build scripts pnpm blocked:
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
pnpm ignored-builds
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Then approve the package that is actually failing and reinstall:
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
pnpm approve-builds
|
|
40
|
+
pnpm install
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
In the interactive `pnpm approve-builds` prompt, select `sharp` if that is the
|
|
44
|
+
package reporting the native-module failure.
|
|
45
|
+
|
|
46
|
+
For a committed repository-level fix, you can also allow the package explicitly
|
|
47
|
+
in `pnpm-workspace.yaml` with `onlyBuiltDependencies`.
|
|
48
|
+
|
|
49
|
+
## Relation To `libsql-search`
|
|
50
|
+
|
|
51
|
+
- local embeddings use `@xenova/transformers`
|
|
52
|
+
- the first local embedding run may download a model at runtime
|
|
53
|
+
- that runtime model download is separate from a pnpm native-module install
|
|
54
|
+
failure
|
|
55
|
+
|
|
56
|
+
## Verification
|
|
57
|
+
|
|
58
|
+
After reinstalling, rerun the command that originally failed. If your app uses
|
|
59
|
+
`sharp` directly, verify that import in your own project context.
|
|
60
|
+
|
|
61
|
+
## Additional Resources
|
|
62
|
+
|
|
63
|
+
- [pnpm approve-builds](https://pnpm.io/10.x/cli/approve-builds)
|
|
64
|
+
- [Sharp installation docs](https://sharp.pixelplumbing.com/install)
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# Troubleshooting
|
|
2
|
+
|
|
3
|
+
Use the page that matches the failure mode:
|
|
4
|
+
|
|
5
|
+
- [Sharp native module issues](./TROUBLESHOOTING-SHARP.md)
|
|
6
|
+
|
|
7
|
+
Common operational checks:
|
|
8
|
+
|
|
9
|
+
- verify you called `createTable()` before indexing or searching
|
|
10
|
+
- verify the table dimension matches the embedding dimension in your code
|
|
11
|
+
- verify the same provider is used for indexing and querying
|
|
12
|
+
- verify hosted providers have `GEMINI_API_KEY` or `OPENAI_API_KEY` available
|