awaitly-libsql 0.1.0
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 +113 -0
- package/dist/index.cjs +32 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +135 -0
- package/dist/index.d.ts +135 -0
- package/dist/index.js +32 -0
- package/dist/index.js.map +1 -0
- package/package.json +72 -0
package/README.md
ADDED
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
# awaitly-libsql
|
|
2
|
+
|
|
3
|
+
libSQL / SQLite persistence adapter for [awaitly](https://github.com/jagreehal/awaitly) workflows.
|
|
4
|
+
|
|
5
|
+
Provides a `StatePersistence` backed by [libSQL](https://docs.turso.tech/libsql) (SQLite-compatible),
|
|
6
|
+
suitable for local development (`file:` URLs, `:memory:`) and remote deployments (e.g. Turso).
|
|
7
|
+
|
|
8
|
+
## Installation
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
npm install awaitly-libsql @libsql/client
|
|
12
|
+
# or
|
|
13
|
+
pnpm add awaitly-libsql @libsql/client
|
|
14
|
+
# or
|
|
15
|
+
yarn add awaitly-libsql @libsql/client
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## Quick Start
|
|
19
|
+
|
|
20
|
+
```ts
|
|
21
|
+
import { createLibSqlPersistence } from "awaitly-libsql";
|
|
22
|
+
import { durable } from "awaitly/durable";
|
|
23
|
+
|
|
24
|
+
const store = await createLibSqlPersistence({
|
|
25
|
+
// Local file database (good for dev)
|
|
26
|
+
url: "file:./awaitly.db",
|
|
27
|
+
// Optional: custom table name (default: "awaitly_workflow_state")
|
|
28
|
+
// tableName: "awaitly_workflow_state",
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
const result = await durable.run(
|
|
32
|
+
{ fetchUser, createOrder },
|
|
33
|
+
async (step, { fetchUser, createOrder }) => {
|
|
34
|
+
const user = await step(() => fetchUser("123"), { key: "fetch-user" });
|
|
35
|
+
const order = await step(() => createOrder(user), { key: "create-order" });
|
|
36
|
+
return order;
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
id: "checkout-123",
|
|
40
|
+
store,
|
|
41
|
+
}
|
|
42
|
+
);
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## Remote libSQL / Turso
|
|
46
|
+
|
|
47
|
+
```ts
|
|
48
|
+
const store = await createLibSqlPersistence({
|
|
49
|
+
url: process.env.LIBSQL_URL!, // e.g. "libsql://your-db.turso.io"
|
|
50
|
+
authToken: process.env.LIBSQL_AUTH_TOKEN,
|
|
51
|
+
tableName: "awaitly_workflow_state",
|
|
52
|
+
});
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## Tenant-Aware Keying (Recommended)
|
|
56
|
+
|
|
57
|
+
To make it easier to avoid cross-tenant leaks in multi-tenant setups, use a
|
|
58
|
+
tenant-specific key prefix:
|
|
59
|
+
|
|
60
|
+
```ts
|
|
61
|
+
const tenantId = "acme-tenant-123";
|
|
62
|
+
|
|
63
|
+
const store = await createLibSqlPersistence({
|
|
64
|
+
url: "file:./awaitly.db",
|
|
65
|
+
prefix: `tenant:${tenantId}:workflow:state:`,
|
|
66
|
+
});
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
All workflow keys will be stored with the configured prefix.
|
|
70
|
+
|
|
71
|
+
## Table Schema
|
|
72
|
+
|
|
73
|
+
The adapter automatically creates a table with the following schema:
|
|
74
|
+
|
|
75
|
+
```sql
|
|
76
|
+
CREATE TABLE awaitly_workflow_state (
|
|
77
|
+
key TEXT PRIMARY KEY,
|
|
78
|
+
value TEXT NOT NULL,
|
|
79
|
+
expires_at TEXT
|
|
80
|
+
);
|
|
81
|
+
|
|
82
|
+
CREATE INDEX idx_awaitly_workflow_state_expires_at
|
|
83
|
+
ON awaitly_workflow_state(expires_at);
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
The `expires_at` column stores ISO 8601 timestamps and is used for TTL support.
|
|
87
|
+
|
|
88
|
+
## Using the KeyValueStore Directly
|
|
89
|
+
|
|
90
|
+
If you want lower-level control, you can use `LibSqlKeyValueStore` directly
|
|
91
|
+
and wire it into `createStatePersistence`:
|
|
92
|
+
|
|
93
|
+
```ts
|
|
94
|
+
import { LibSqlKeyValueStore } from "awaitly-libsql";
|
|
95
|
+
import { createStatePersistence } from "awaitly/persistence";
|
|
96
|
+
|
|
97
|
+
const store = new LibSqlKeyValueStore({
|
|
98
|
+
url: "file:./awaitly.db",
|
|
99
|
+
tableName: "awaitly_workflow_state",
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
const persistence = createStatePersistence(store, "custom:prefix:");
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
## Requirements
|
|
106
|
+
|
|
107
|
+
- Node.js >= 22
|
|
108
|
+
- `@libsql/client` package
|
|
109
|
+
|
|
110
|
+
## License
|
|
111
|
+
|
|
112
|
+
MIT
|
|
113
|
+
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"use strict";var l=Object.defineProperty;var u=Object.getOwnPropertyDescriptor;var p=Object.getOwnPropertyNames;var m=Object.prototype.hasOwnProperty;var h=(r,e)=>{for(var t in e)l(r,t,{get:e[t],enumerable:!0})},w=(r,e,t,i)=>{if(e&&typeof e=="object"||typeof e=="function")for(let s of p(e))!m.call(r,s)&&s!==t&&l(r,s,{get:()=>e[s],enumerable:!(i=u(e,s))||i.enumerable});return r};var S=r=>w(l({},"__esModule",{value:!0}),r);var g={};h(g,{LibSqlKeyValueStore:()=>a,createLibSqlPersistence:()=>d});module.exports=S(g);var o=require("@libsql/client"),a=class{client;tableName;initialized=!1;initPromise=null;constructor(e={}){if(e.client)this.client=e.client;else{let i=e.url??"file:./awaitly.db";this.client=(0,o.createClient)({url:i,authToken:e.authToken})}let t=e.tableName??"awaitly_workflow_state";if(!/^[A-Za-z0-9_]+$/.test(t))throw new Error(`Invalid table name '${t}'. Only alphanumeric and underscore characters are allowed.`);this.tableName=t}async ensureInitialized(){if(!this.initialized)return this.initPromise?this.initPromise:(this.initPromise=(async()=>{try{await this.createTable(),this.initialized=!0}finally{this.initialized||(this.initPromise=null)}})(),this.initPromise)}async createTable(){await this.client.execute({sql:`
|
|
2
|
+
CREATE TABLE IF NOT EXISTS ${this.tableName} (
|
|
3
|
+
key TEXT PRIMARY KEY,
|
|
4
|
+
value TEXT NOT NULL,
|
|
5
|
+
expires_at TEXT
|
|
6
|
+
);
|
|
7
|
+
`,args:[]}),await this.client.execute({sql:`
|
|
8
|
+
CREATE INDEX IF NOT EXISTS idx_${this.tableName}_expires_at
|
|
9
|
+
ON ${this.tableName}(expires_at);
|
|
10
|
+
`,args:[]})}patternToLike(e){return e.replace(/%/g,"\\%").replace(/_/g,"\\_").replace(/\*/g,"%")}async get(e){await this.ensureInitialized();let t=new Date().toISOString(),i=await this.client.execute({sql:`
|
|
11
|
+
SELECT value, expires_at
|
|
12
|
+
FROM ${this.tableName}
|
|
13
|
+
WHERE key = ?
|
|
14
|
+
`,args:[e]});if(i.rows.length===0)return null;let s=i.rows[0],n=s.expires_at;return n&&n<=t?null:s.value??null}async set(e,t,i){await this.ensureInitialized();let s=i?.ttl&&i.ttl>0?new Date(Date.now()+i.ttl*1e3).toISOString():null;await this.client.execute({sql:`
|
|
15
|
+
INSERT INTO ${this.tableName} (key, value, expires_at)
|
|
16
|
+
VALUES (?, ?, ?)
|
|
17
|
+
ON CONFLICT(key) DO UPDATE SET
|
|
18
|
+
value = excluded.value,
|
|
19
|
+
expires_at = excluded.expires_at
|
|
20
|
+
`,args:[e,t,s]})}async delete(e){await this.ensureInitialized();let i=(await this.client.execute({sql:`DELETE FROM ${this.tableName} WHERE key = ?`,args:[e]})).rowsAffected;return typeof i=="number"?i>0:await this.get(e)===null}async exists(e){await this.ensureInitialized();let t=new Date().toISOString();return(await this.client.execute({sql:`
|
|
21
|
+
SELECT 1
|
|
22
|
+
FROM ${this.tableName}
|
|
23
|
+
WHERE key = ?
|
|
24
|
+
AND (expires_at IS NULL OR expires_at > ?)
|
|
25
|
+
LIMIT 1
|
|
26
|
+
`,args:[e,t]})).rows.length>0}async keys(e){await this.ensureInitialized();let t=this.patternToLike(e),i=new Date().toISOString();return(await this.client.execute({sql:`
|
|
27
|
+
SELECT key
|
|
28
|
+
FROM ${this.tableName}
|
|
29
|
+
WHERE key LIKE ? ESCAPE '\\'
|
|
30
|
+
AND (expires_at IS NULL OR expires_at > ?)
|
|
31
|
+
`,args:[t,i]})).rows.map(n=>n.key)}async close(){}};var c=require("awaitly/persistence");async function d(r={}){let{prefix:e,...t}=r,i=new a(t);return(0,c.createStatePersistence)(i,e)}0&&(module.exports={LibSqlKeyValueStore,createLibSqlPersistence});
|
|
32
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/libsql-store.ts"],"sourcesContent":["/**\n * awaitly-libsql\n *\n * libSQL / SQLite persistence adapter for awaitly workflows.\n * Provides ready-to-use StatePersistence backed by libSQL.\n */\n\nimport { LibSqlKeyValueStore, type LibSqlKeyValueStoreOptions } from \"./libsql-store\";\nimport { createStatePersistence, type StatePersistence } from \"awaitly/persistence\";\n\n/**\n * Options for creating libSQL persistence.\n */\nexport interface LibSqlPersistenceOptions extends LibSqlKeyValueStoreOptions {\n /**\n * Key prefix for state entries.\n * @default \"workflow:state:\"\n */\n prefix?: string;\n}\n\n/**\n * Create a StatePersistence instance backed by libSQL / SQLite.\n *\n * The table is automatically created on first use.\n *\n * @param options - libSQL connection and configuration options\n * @returns StatePersistence instance ready to use with durable.run()\n *\n * @example\n * ```typescript\n * import { createLibSqlPersistence } from \"awaitly-libsql\";\n * import { durable } from \"awaitly/durable\";\n *\n * const store = await createLibSqlPersistence({\n * url: \"file:./awaitly.db\",\n * });\n *\n * const result = await durable.run(\n * { fetchUser, createOrder },\n * async (step, { fetchUser, createOrder }) => {\n * const user = await step(() => fetchUser(\"123\"), { key: \"fetch-user\" });\n * const order = await step(() => createOrder(user), { key: \"create-order\" });\n * return order;\n * },\n * {\n * id: \"checkout-123\",\n * store,\n * }\n * );\n * ```\n *\n * @example\n * ```typescript\n * // Using remote Turso (libSQL) instance\n * const store = await createLibSqlPersistence({\n * url: process.env.LIBSQL_URL!,\n * authToken: process.env.LIBSQL_AUTH_TOKEN,\n * tableName: \"awaitly_workflow_state\",\n * });\n * ```\n */\nexport async function createLibSqlPersistence(\n options: LibSqlPersistenceOptions = {}\n): Promise<\n StatePersistence & {\n loadRaw(runId: string): Promise<import(\"awaitly/persistence\").SerializedState | undefined>;\n }\n> {\n const { prefix, ...storeOptions } = options;\n\n const store = new LibSqlKeyValueStore(storeOptions);\n return createStatePersistence(store, prefix);\n}\n\n/**\n * libSQL KeyValueStore implementation.\n * Use this directly if you need more control over the store.\n *\n * @example\n * ```typescript\n * import { LibSqlKeyValueStore } from \"awaitly-libsql\";\n * import { createStatePersistence } from \"awaitly/persistence\";\n *\n * const store = new LibSqlKeyValueStore({\n * url: \"file:./awaitly.db\",\n * });\n *\n * const persistence = createStatePersistence(store, \"custom:prefix:\");\n * ```\n */\nexport { LibSqlKeyValueStore, type LibSqlKeyValueStoreOptions };\n\n","/**\n * awaitly-libsql\n *\n * libSQL / SQLite KeyValueStore implementation for awaitly persistence.\n */\n\nimport { createClient, type Client } from \"@libsql/client\";\nimport type { KeyValueStore } from \"awaitly/persistence\";\n\n/**\n * Options for libSQL / SQLite KeyValueStore.\n */\nexport interface LibSqlKeyValueStoreOptions {\n /**\n * libSQL database URL.\n *\n * Examples:\n * - \"file:./awaitly.db\" (local file)\n * - \":memory:\" (in-memory, for tests)\n * - \"libsql://your-db.turso.io\" (remote)\n *\n * @default \"file:./awaitly.db\"\n */\n url?: string;\n\n /**\n * Authentication token for remote libSQL databases (e.g. Turso).\n */\n authToken?: string;\n\n /**\n * Table name for storing key-value pairs.\n * @default \"awaitly_workflow_state\"\n */\n tableName?: string;\n\n /**\n * Existing libSQL client to use.\n * If provided, url/authToken options are ignored.\n */\n client?: Client;\n}\n\n/**\n * libSQL / SQLite implementation of KeyValueStore.\n *\n * Automatically creates the required table on first use.\n * Supports TTL via ISO 8601 `expires_at` column.\n */\nexport class LibSqlKeyValueStore implements KeyValueStore {\n private client: Client;\n private tableName: string;\n private initialized = false;\n private initPromise: Promise<void> | null = null;\n\n constructor(options: LibSqlKeyValueStoreOptions = {}) {\n if (options.client) {\n this.client = options.client;\n } else {\n const url = options.url ?? \"file:./awaitly.db\";\n this.client = createClient({\n url,\n authToken: options.authToken,\n });\n }\n\n const tableName = options.tableName ?? \"awaitly_workflow_state\";\n if (!/^[A-Za-z0-9_]+$/.test(tableName)) {\n throw new Error(\n `Invalid table name '${tableName}'. Only alphanumeric and underscore characters are allowed.`\n );\n }\n this.tableName = tableName;\n }\n\n /**\n * Initialize the store by creating the table and index if they don't exist.\n * This is called automatically on first use.\n */\n private async ensureInitialized(): Promise<void> {\n if (this.initialized) {\n return;\n }\n\n if (this.initPromise) {\n return this.initPromise;\n }\n\n this.initPromise = (async () => {\n try {\n await this.createTable();\n this.initialized = true;\n } finally {\n if (!this.initialized) {\n this.initPromise = null;\n }\n }\n })();\n\n return this.initPromise;\n }\n\n /**\n * Create the table and index if they don't exist.\n */\n private async createTable(): Promise<void> {\n // SQLite / libSQL: execute schema changes as separate statements\n await this.client.execute({\n sql: `\n CREATE TABLE IF NOT EXISTS ${this.tableName} (\n key TEXT PRIMARY KEY,\n value TEXT NOT NULL,\n expires_at TEXT\n );\n `,\n args: [],\n });\n\n await this.client.execute({\n sql: `\n CREATE INDEX IF NOT EXISTS idx_${this.tableName}_expires_at\n ON ${this.tableName}(expires_at);\n `,\n args: [],\n });\n }\n\n /**\n * Convert glob pattern to SQL LIKE pattern.\n * Supports * wildcard (matches any characters).\n */\n private patternToLike(pattern: string): string {\n // Escape LIKE special characters and convert * to %\n return pattern.replace(/%/g, \"\\\\%\").replace(/_/g, \"\\\\_\").replace(/\\*/g, \"%\");\n }\n\n async get(key: string): Promise<string | null> {\n await this.ensureInitialized();\n\n const nowIso = new Date().toISOString();\n const result = await this.client.execute({\n sql: `\n SELECT value, expires_at\n FROM ${this.tableName}\n WHERE key = ?\n `,\n args: [key],\n });\n\n if (result.rows.length === 0) {\n return null;\n }\n\n const row = result.rows[0] as Record<string, unknown>;\n const expiresAt = row[\"expires_at\"] as string | null | undefined;\n\n if (expiresAt && expiresAt <= nowIso) {\n // Expired - behave as if key doesn't exist\n return null;\n }\n\n return (row[\"value\"] as string) ?? null;\n }\n\n async set(key: string, value: string, options?: { ttl?: number }): Promise<void> {\n await this.ensureInitialized();\n\n const expiresAt =\n options?.ttl && options.ttl > 0\n ? new Date(Date.now() + options.ttl * 1000).toISOString()\n : null;\n\n await this.client.execute({\n sql: `\n INSERT INTO ${this.tableName} (key, value, expires_at)\n VALUES (?, ?, ?)\n ON CONFLICT(key) DO UPDATE SET\n value = excluded.value,\n expires_at = excluded.expires_at\n `,\n args: [key, value, expiresAt],\n });\n }\n\n async delete(key: string): Promise<boolean> {\n await this.ensureInitialized();\n\n const result = await this.client.execute({\n sql: `DELETE FROM ${this.tableName} WHERE key = ?`,\n args: [key],\n });\n\n // libSQL .rowsAffected is available on hrana responses; fall back to > 0 check\n const affected: number | undefined = result.rowsAffected;\n if (typeof affected === \"number\") {\n return affected > 0;\n }\n\n // If rowsAffected is not available, perform an existence check as a fallback\n const after = await this.get(key);\n return after === null;\n }\n\n async exists(key: string): Promise<boolean> {\n await this.ensureInitialized();\n\n const nowIso = new Date().toISOString();\n const result = await this.client.execute({\n sql: `\n SELECT 1\n FROM ${this.tableName}\n WHERE key = ?\n AND (expires_at IS NULL OR expires_at > ?)\n LIMIT 1\n `,\n args: [key, nowIso],\n });\n\n return result.rows.length > 0;\n }\n\n async keys(pattern: string): Promise<string[]> {\n await this.ensureInitialized();\n\n const likePattern = this.patternToLike(pattern);\n const nowIso = new Date().toISOString();\n\n const result = await this.client.execute({\n sql: `\n SELECT key\n FROM ${this.tableName}\n WHERE key LIKE ? ESCAPE '\\\\'\n AND (expires_at IS NULL OR expires_at > ?)\n `,\n args: [likePattern, nowIso],\n });\n\n return result.rows.map((row) => (row as Record<string, unknown>)[\"key\"] as string);\n }\n\n /**\n * Close the underlying client if it supports close().\n */\n async close(): Promise<void> {\n // libSQL client doesn't expose a close API in all runtimes; no-op for now.\n // If a future version adds client.close(), it can be wired here.\n }\n}\n\n"],"mappings":"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","names":["index_exports","__export","LibSqlKeyValueStore","createLibSqlPersistence","__toCommonJS","import_client","LibSqlKeyValueStore","options","url","tableName","pattern","key","nowIso","result","row","expiresAt","value","affected","likePattern","import_persistence","createLibSqlPersistence","options","prefix","storeOptions","store","LibSqlKeyValueStore"]}
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import * as awaitly_persistence from 'awaitly/persistence';
|
|
2
|
+
import { KeyValueStore, StatePersistence } from 'awaitly/persistence';
|
|
3
|
+
import { Client } from '@libsql/client';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* awaitly-libsql
|
|
7
|
+
*
|
|
8
|
+
* libSQL / SQLite KeyValueStore implementation for awaitly persistence.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Options for libSQL / SQLite KeyValueStore.
|
|
13
|
+
*/
|
|
14
|
+
interface LibSqlKeyValueStoreOptions {
|
|
15
|
+
/**
|
|
16
|
+
* libSQL database URL.
|
|
17
|
+
*
|
|
18
|
+
* Examples:
|
|
19
|
+
* - "file:./awaitly.db" (local file)
|
|
20
|
+
* - ":memory:" (in-memory, for tests)
|
|
21
|
+
* - "libsql://your-db.turso.io" (remote)
|
|
22
|
+
*
|
|
23
|
+
* @default "file:./awaitly.db"
|
|
24
|
+
*/
|
|
25
|
+
url?: string;
|
|
26
|
+
/**
|
|
27
|
+
* Authentication token for remote libSQL databases (e.g. Turso).
|
|
28
|
+
*/
|
|
29
|
+
authToken?: string;
|
|
30
|
+
/**
|
|
31
|
+
* Table name for storing key-value pairs.
|
|
32
|
+
* @default "awaitly_workflow_state"
|
|
33
|
+
*/
|
|
34
|
+
tableName?: string;
|
|
35
|
+
/**
|
|
36
|
+
* Existing libSQL client to use.
|
|
37
|
+
* If provided, url/authToken options are ignored.
|
|
38
|
+
*/
|
|
39
|
+
client?: Client;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* libSQL / SQLite implementation of KeyValueStore.
|
|
43
|
+
*
|
|
44
|
+
* Automatically creates the required table on first use.
|
|
45
|
+
* Supports TTL via ISO 8601 `expires_at` column.
|
|
46
|
+
*/
|
|
47
|
+
declare class LibSqlKeyValueStore implements KeyValueStore {
|
|
48
|
+
private client;
|
|
49
|
+
private tableName;
|
|
50
|
+
private initialized;
|
|
51
|
+
private initPromise;
|
|
52
|
+
constructor(options?: LibSqlKeyValueStoreOptions);
|
|
53
|
+
/**
|
|
54
|
+
* Initialize the store by creating the table and index if they don't exist.
|
|
55
|
+
* This is called automatically on first use.
|
|
56
|
+
*/
|
|
57
|
+
private ensureInitialized;
|
|
58
|
+
/**
|
|
59
|
+
* Create the table and index if they don't exist.
|
|
60
|
+
*/
|
|
61
|
+
private createTable;
|
|
62
|
+
/**
|
|
63
|
+
* Convert glob pattern to SQL LIKE pattern.
|
|
64
|
+
* Supports * wildcard (matches any characters).
|
|
65
|
+
*/
|
|
66
|
+
private patternToLike;
|
|
67
|
+
get(key: string): Promise<string | null>;
|
|
68
|
+
set(key: string, value: string, options?: {
|
|
69
|
+
ttl?: number;
|
|
70
|
+
}): Promise<void>;
|
|
71
|
+
delete(key: string): Promise<boolean>;
|
|
72
|
+
exists(key: string): Promise<boolean>;
|
|
73
|
+
keys(pattern: string): Promise<string[]>;
|
|
74
|
+
/**
|
|
75
|
+
* Close the underlying client if it supports close().
|
|
76
|
+
*/
|
|
77
|
+
close(): Promise<void>;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Options for creating libSQL persistence.
|
|
82
|
+
*/
|
|
83
|
+
interface LibSqlPersistenceOptions extends LibSqlKeyValueStoreOptions {
|
|
84
|
+
/**
|
|
85
|
+
* Key prefix for state entries.
|
|
86
|
+
* @default "workflow:state:"
|
|
87
|
+
*/
|
|
88
|
+
prefix?: string;
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Create a StatePersistence instance backed by libSQL / SQLite.
|
|
92
|
+
*
|
|
93
|
+
* The table is automatically created on first use.
|
|
94
|
+
*
|
|
95
|
+
* @param options - libSQL connection and configuration options
|
|
96
|
+
* @returns StatePersistence instance ready to use with durable.run()
|
|
97
|
+
*
|
|
98
|
+
* @example
|
|
99
|
+
* ```typescript
|
|
100
|
+
* import { createLibSqlPersistence } from "awaitly-libsql";
|
|
101
|
+
* import { durable } from "awaitly/durable";
|
|
102
|
+
*
|
|
103
|
+
* const store = await createLibSqlPersistence({
|
|
104
|
+
* url: "file:./awaitly.db",
|
|
105
|
+
* });
|
|
106
|
+
*
|
|
107
|
+
* const result = await durable.run(
|
|
108
|
+
* { fetchUser, createOrder },
|
|
109
|
+
* async (step, { fetchUser, createOrder }) => {
|
|
110
|
+
* const user = await step(() => fetchUser("123"), { key: "fetch-user" });
|
|
111
|
+
* const order = await step(() => createOrder(user), { key: "create-order" });
|
|
112
|
+
* return order;
|
|
113
|
+
* },
|
|
114
|
+
* {
|
|
115
|
+
* id: "checkout-123",
|
|
116
|
+
* store,
|
|
117
|
+
* }
|
|
118
|
+
* );
|
|
119
|
+
* ```
|
|
120
|
+
*
|
|
121
|
+
* @example
|
|
122
|
+
* ```typescript
|
|
123
|
+
* // Using remote Turso (libSQL) instance
|
|
124
|
+
* const store = await createLibSqlPersistence({
|
|
125
|
+
* url: process.env.LIBSQL_URL!,
|
|
126
|
+
* authToken: process.env.LIBSQL_AUTH_TOKEN,
|
|
127
|
+
* tableName: "awaitly_workflow_state",
|
|
128
|
+
* });
|
|
129
|
+
* ```
|
|
130
|
+
*/
|
|
131
|
+
declare function createLibSqlPersistence(options?: LibSqlPersistenceOptions): Promise<StatePersistence & {
|
|
132
|
+
loadRaw(runId: string): Promise<awaitly_persistence.SerializedState | undefined>;
|
|
133
|
+
}>;
|
|
134
|
+
|
|
135
|
+
export { LibSqlKeyValueStore, type LibSqlKeyValueStoreOptions, type LibSqlPersistenceOptions, createLibSqlPersistence };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import * as awaitly_persistence from 'awaitly/persistence';
|
|
2
|
+
import { KeyValueStore, StatePersistence } from 'awaitly/persistence';
|
|
3
|
+
import { Client } from '@libsql/client';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* awaitly-libsql
|
|
7
|
+
*
|
|
8
|
+
* libSQL / SQLite KeyValueStore implementation for awaitly persistence.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Options for libSQL / SQLite KeyValueStore.
|
|
13
|
+
*/
|
|
14
|
+
interface LibSqlKeyValueStoreOptions {
|
|
15
|
+
/**
|
|
16
|
+
* libSQL database URL.
|
|
17
|
+
*
|
|
18
|
+
* Examples:
|
|
19
|
+
* - "file:./awaitly.db" (local file)
|
|
20
|
+
* - ":memory:" (in-memory, for tests)
|
|
21
|
+
* - "libsql://your-db.turso.io" (remote)
|
|
22
|
+
*
|
|
23
|
+
* @default "file:./awaitly.db"
|
|
24
|
+
*/
|
|
25
|
+
url?: string;
|
|
26
|
+
/**
|
|
27
|
+
* Authentication token for remote libSQL databases (e.g. Turso).
|
|
28
|
+
*/
|
|
29
|
+
authToken?: string;
|
|
30
|
+
/**
|
|
31
|
+
* Table name for storing key-value pairs.
|
|
32
|
+
* @default "awaitly_workflow_state"
|
|
33
|
+
*/
|
|
34
|
+
tableName?: string;
|
|
35
|
+
/**
|
|
36
|
+
* Existing libSQL client to use.
|
|
37
|
+
* If provided, url/authToken options are ignored.
|
|
38
|
+
*/
|
|
39
|
+
client?: Client;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* libSQL / SQLite implementation of KeyValueStore.
|
|
43
|
+
*
|
|
44
|
+
* Automatically creates the required table on first use.
|
|
45
|
+
* Supports TTL via ISO 8601 `expires_at` column.
|
|
46
|
+
*/
|
|
47
|
+
declare class LibSqlKeyValueStore implements KeyValueStore {
|
|
48
|
+
private client;
|
|
49
|
+
private tableName;
|
|
50
|
+
private initialized;
|
|
51
|
+
private initPromise;
|
|
52
|
+
constructor(options?: LibSqlKeyValueStoreOptions);
|
|
53
|
+
/**
|
|
54
|
+
* Initialize the store by creating the table and index if they don't exist.
|
|
55
|
+
* This is called automatically on first use.
|
|
56
|
+
*/
|
|
57
|
+
private ensureInitialized;
|
|
58
|
+
/**
|
|
59
|
+
* Create the table and index if they don't exist.
|
|
60
|
+
*/
|
|
61
|
+
private createTable;
|
|
62
|
+
/**
|
|
63
|
+
* Convert glob pattern to SQL LIKE pattern.
|
|
64
|
+
* Supports * wildcard (matches any characters).
|
|
65
|
+
*/
|
|
66
|
+
private patternToLike;
|
|
67
|
+
get(key: string): Promise<string | null>;
|
|
68
|
+
set(key: string, value: string, options?: {
|
|
69
|
+
ttl?: number;
|
|
70
|
+
}): Promise<void>;
|
|
71
|
+
delete(key: string): Promise<boolean>;
|
|
72
|
+
exists(key: string): Promise<boolean>;
|
|
73
|
+
keys(pattern: string): Promise<string[]>;
|
|
74
|
+
/**
|
|
75
|
+
* Close the underlying client if it supports close().
|
|
76
|
+
*/
|
|
77
|
+
close(): Promise<void>;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Options for creating libSQL persistence.
|
|
82
|
+
*/
|
|
83
|
+
interface LibSqlPersistenceOptions extends LibSqlKeyValueStoreOptions {
|
|
84
|
+
/**
|
|
85
|
+
* Key prefix for state entries.
|
|
86
|
+
* @default "workflow:state:"
|
|
87
|
+
*/
|
|
88
|
+
prefix?: string;
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Create a StatePersistence instance backed by libSQL / SQLite.
|
|
92
|
+
*
|
|
93
|
+
* The table is automatically created on first use.
|
|
94
|
+
*
|
|
95
|
+
* @param options - libSQL connection and configuration options
|
|
96
|
+
* @returns StatePersistence instance ready to use with durable.run()
|
|
97
|
+
*
|
|
98
|
+
* @example
|
|
99
|
+
* ```typescript
|
|
100
|
+
* import { createLibSqlPersistence } from "awaitly-libsql";
|
|
101
|
+
* import { durable } from "awaitly/durable";
|
|
102
|
+
*
|
|
103
|
+
* const store = await createLibSqlPersistence({
|
|
104
|
+
* url: "file:./awaitly.db",
|
|
105
|
+
* });
|
|
106
|
+
*
|
|
107
|
+
* const result = await durable.run(
|
|
108
|
+
* { fetchUser, createOrder },
|
|
109
|
+
* async (step, { fetchUser, createOrder }) => {
|
|
110
|
+
* const user = await step(() => fetchUser("123"), { key: "fetch-user" });
|
|
111
|
+
* const order = await step(() => createOrder(user), { key: "create-order" });
|
|
112
|
+
* return order;
|
|
113
|
+
* },
|
|
114
|
+
* {
|
|
115
|
+
* id: "checkout-123",
|
|
116
|
+
* store,
|
|
117
|
+
* }
|
|
118
|
+
* );
|
|
119
|
+
* ```
|
|
120
|
+
*
|
|
121
|
+
* @example
|
|
122
|
+
* ```typescript
|
|
123
|
+
* // Using remote Turso (libSQL) instance
|
|
124
|
+
* const store = await createLibSqlPersistence({
|
|
125
|
+
* url: process.env.LIBSQL_URL!,
|
|
126
|
+
* authToken: process.env.LIBSQL_AUTH_TOKEN,
|
|
127
|
+
* tableName: "awaitly_workflow_state",
|
|
128
|
+
* });
|
|
129
|
+
* ```
|
|
130
|
+
*/
|
|
131
|
+
declare function createLibSqlPersistence(options?: LibSqlPersistenceOptions): Promise<StatePersistence & {
|
|
132
|
+
loadRaw(runId: string): Promise<awaitly_persistence.SerializedState | undefined>;
|
|
133
|
+
}>;
|
|
134
|
+
|
|
135
|
+
export { LibSqlKeyValueStore, type LibSqlKeyValueStoreOptions, type LibSqlPersistenceOptions, createLibSqlPersistence };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import{createClient as l}from"@libsql/client";var a=class{client;tableName;initialized=!1;initPromise=null;constructor(e={}){if(e.client)this.client=e.client;else{let t=e.url??"file:./awaitly.db";this.client=l({url:t,authToken:e.authToken})}let i=e.tableName??"awaitly_workflow_state";if(!/^[A-Za-z0-9_]+$/.test(i))throw new Error(`Invalid table name '${i}'. Only alphanumeric and underscore characters are allowed.`);this.tableName=i}async ensureInitialized(){if(!this.initialized)return this.initPromise?this.initPromise:(this.initPromise=(async()=>{try{await this.createTable(),this.initialized=!0}finally{this.initialized||(this.initPromise=null)}})(),this.initPromise)}async createTable(){await this.client.execute({sql:`
|
|
2
|
+
CREATE TABLE IF NOT EXISTS ${this.tableName} (
|
|
3
|
+
key TEXT PRIMARY KEY,
|
|
4
|
+
value TEXT NOT NULL,
|
|
5
|
+
expires_at TEXT
|
|
6
|
+
);
|
|
7
|
+
`,args:[]}),await this.client.execute({sql:`
|
|
8
|
+
CREATE INDEX IF NOT EXISTS idx_${this.tableName}_expires_at
|
|
9
|
+
ON ${this.tableName}(expires_at);
|
|
10
|
+
`,args:[]})}patternToLike(e){return e.replace(/%/g,"\\%").replace(/_/g,"\\_").replace(/\*/g,"%")}async get(e){await this.ensureInitialized();let i=new Date().toISOString(),t=await this.client.execute({sql:`
|
|
11
|
+
SELECT value, expires_at
|
|
12
|
+
FROM ${this.tableName}
|
|
13
|
+
WHERE key = ?
|
|
14
|
+
`,args:[e]});if(t.rows.length===0)return null;let s=t.rows[0],r=s.expires_at;return r&&r<=i?null:s.value??null}async set(e,i,t){await this.ensureInitialized();let s=t?.ttl&&t.ttl>0?new Date(Date.now()+t.ttl*1e3).toISOString():null;await this.client.execute({sql:`
|
|
15
|
+
INSERT INTO ${this.tableName} (key, value, expires_at)
|
|
16
|
+
VALUES (?, ?, ?)
|
|
17
|
+
ON CONFLICT(key) DO UPDATE SET
|
|
18
|
+
value = excluded.value,
|
|
19
|
+
expires_at = excluded.expires_at
|
|
20
|
+
`,args:[e,i,s]})}async delete(e){await this.ensureInitialized();let t=(await this.client.execute({sql:`DELETE FROM ${this.tableName} WHERE key = ?`,args:[e]})).rowsAffected;return typeof t=="number"?t>0:await this.get(e)===null}async exists(e){await this.ensureInitialized();let i=new Date().toISOString();return(await this.client.execute({sql:`
|
|
21
|
+
SELECT 1
|
|
22
|
+
FROM ${this.tableName}
|
|
23
|
+
WHERE key = ?
|
|
24
|
+
AND (expires_at IS NULL OR expires_at > ?)
|
|
25
|
+
LIMIT 1
|
|
26
|
+
`,args:[e,i]})).rows.length>0}async keys(e){await this.ensureInitialized();let i=this.patternToLike(e),t=new Date().toISOString();return(await this.client.execute({sql:`
|
|
27
|
+
SELECT key
|
|
28
|
+
FROM ${this.tableName}
|
|
29
|
+
WHERE key LIKE ? ESCAPE '\\'
|
|
30
|
+
AND (expires_at IS NULL OR expires_at > ?)
|
|
31
|
+
`,args:[i,t]})).rows.map(r=>r.key)}async close(){}};import{createStatePersistence as o}from"awaitly/persistence";async function h(n={}){let{prefix:e,...i}=n,t=new a(i);return o(t,e)}export{a as LibSqlKeyValueStore,h as createLibSqlPersistence};
|
|
32
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/libsql-store.ts","../src/index.ts"],"sourcesContent":["/**\n * awaitly-libsql\n *\n * libSQL / SQLite KeyValueStore implementation for awaitly persistence.\n */\n\nimport { createClient, type Client } from \"@libsql/client\";\nimport type { KeyValueStore } from \"awaitly/persistence\";\n\n/**\n * Options for libSQL / SQLite KeyValueStore.\n */\nexport interface LibSqlKeyValueStoreOptions {\n /**\n * libSQL database URL.\n *\n * Examples:\n * - \"file:./awaitly.db\" (local file)\n * - \":memory:\" (in-memory, for tests)\n * - \"libsql://your-db.turso.io\" (remote)\n *\n * @default \"file:./awaitly.db\"\n */\n url?: string;\n\n /**\n * Authentication token for remote libSQL databases (e.g. Turso).\n */\n authToken?: string;\n\n /**\n * Table name for storing key-value pairs.\n * @default \"awaitly_workflow_state\"\n */\n tableName?: string;\n\n /**\n * Existing libSQL client to use.\n * If provided, url/authToken options are ignored.\n */\n client?: Client;\n}\n\n/**\n * libSQL / SQLite implementation of KeyValueStore.\n *\n * Automatically creates the required table on first use.\n * Supports TTL via ISO 8601 `expires_at` column.\n */\nexport class LibSqlKeyValueStore implements KeyValueStore {\n private client: Client;\n private tableName: string;\n private initialized = false;\n private initPromise: Promise<void> | null = null;\n\n constructor(options: LibSqlKeyValueStoreOptions = {}) {\n if (options.client) {\n this.client = options.client;\n } else {\n const url = options.url ?? \"file:./awaitly.db\";\n this.client = createClient({\n url,\n authToken: options.authToken,\n });\n }\n\n const tableName = options.tableName ?? \"awaitly_workflow_state\";\n if (!/^[A-Za-z0-9_]+$/.test(tableName)) {\n throw new Error(\n `Invalid table name '${tableName}'. Only alphanumeric and underscore characters are allowed.`\n );\n }\n this.tableName = tableName;\n }\n\n /**\n * Initialize the store by creating the table and index if they don't exist.\n * This is called automatically on first use.\n */\n private async ensureInitialized(): Promise<void> {\n if (this.initialized) {\n return;\n }\n\n if (this.initPromise) {\n return this.initPromise;\n }\n\n this.initPromise = (async () => {\n try {\n await this.createTable();\n this.initialized = true;\n } finally {\n if (!this.initialized) {\n this.initPromise = null;\n }\n }\n })();\n\n return this.initPromise;\n }\n\n /**\n * Create the table and index if they don't exist.\n */\n private async createTable(): Promise<void> {\n // SQLite / libSQL: execute schema changes as separate statements\n await this.client.execute({\n sql: `\n CREATE TABLE IF NOT EXISTS ${this.tableName} (\n key TEXT PRIMARY KEY,\n value TEXT NOT NULL,\n expires_at TEXT\n );\n `,\n args: [],\n });\n\n await this.client.execute({\n sql: `\n CREATE INDEX IF NOT EXISTS idx_${this.tableName}_expires_at\n ON ${this.tableName}(expires_at);\n `,\n args: [],\n });\n }\n\n /**\n * Convert glob pattern to SQL LIKE pattern.\n * Supports * wildcard (matches any characters).\n */\n private patternToLike(pattern: string): string {\n // Escape LIKE special characters and convert * to %\n return pattern.replace(/%/g, \"\\\\%\").replace(/_/g, \"\\\\_\").replace(/\\*/g, \"%\");\n }\n\n async get(key: string): Promise<string | null> {\n await this.ensureInitialized();\n\n const nowIso = new Date().toISOString();\n const result = await this.client.execute({\n sql: `\n SELECT value, expires_at\n FROM ${this.tableName}\n WHERE key = ?\n `,\n args: [key],\n });\n\n if (result.rows.length === 0) {\n return null;\n }\n\n const row = result.rows[0] as Record<string, unknown>;\n const expiresAt = row[\"expires_at\"] as string | null | undefined;\n\n if (expiresAt && expiresAt <= nowIso) {\n // Expired - behave as if key doesn't exist\n return null;\n }\n\n return (row[\"value\"] as string) ?? null;\n }\n\n async set(key: string, value: string, options?: { ttl?: number }): Promise<void> {\n await this.ensureInitialized();\n\n const expiresAt =\n options?.ttl && options.ttl > 0\n ? new Date(Date.now() + options.ttl * 1000).toISOString()\n : null;\n\n await this.client.execute({\n sql: `\n INSERT INTO ${this.tableName} (key, value, expires_at)\n VALUES (?, ?, ?)\n ON CONFLICT(key) DO UPDATE SET\n value = excluded.value,\n expires_at = excluded.expires_at\n `,\n args: [key, value, expiresAt],\n });\n }\n\n async delete(key: string): Promise<boolean> {\n await this.ensureInitialized();\n\n const result = await this.client.execute({\n sql: `DELETE FROM ${this.tableName} WHERE key = ?`,\n args: [key],\n });\n\n // libSQL .rowsAffected is available on hrana responses; fall back to > 0 check\n const affected: number | undefined = result.rowsAffected;\n if (typeof affected === \"number\") {\n return affected > 0;\n }\n\n // If rowsAffected is not available, perform an existence check as a fallback\n const after = await this.get(key);\n return after === null;\n }\n\n async exists(key: string): Promise<boolean> {\n await this.ensureInitialized();\n\n const nowIso = new Date().toISOString();\n const result = await this.client.execute({\n sql: `\n SELECT 1\n FROM ${this.tableName}\n WHERE key = ?\n AND (expires_at IS NULL OR expires_at > ?)\n LIMIT 1\n `,\n args: [key, nowIso],\n });\n\n return result.rows.length > 0;\n }\n\n async keys(pattern: string): Promise<string[]> {\n await this.ensureInitialized();\n\n const likePattern = this.patternToLike(pattern);\n const nowIso = new Date().toISOString();\n\n const result = await this.client.execute({\n sql: `\n SELECT key\n FROM ${this.tableName}\n WHERE key LIKE ? ESCAPE '\\\\'\n AND (expires_at IS NULL OR expires_at > ?)\n `,\n args: [likePattern, nowIso],\n });\n\n return result.rows.map((row) => (row as Record<string, unknown>)[\"key\"] as string);\n }\n\n /**\n * Close the underlying client if it supports close().\n */\n async close(): Promise<void> {\n // libSQL client doesn't expose a close API in all runtimes; no-op for now.\n // If a future version adds client.close(), it can be wired here.\n }\n}\n\n","/**\n * awaitly-libsql\n *\n * libSQL / SQLite persistence adapter for awaitly workflows.\n * Provides ready-to-use StatePersistence backed by libSQL.\n */\n\nimport { LibSqlKeyValueStore, type LibSqlKeyValueStoreOptions } from \"./libsql-store\";\nimport { createStatePersistence, type StatePersistence } from \"awaitly/persistence\";\n\n/**\n * Options for creating libSQL persistence.\n */\nexport interface LibSqlPersistenceOptions extends LibSqlKeyValueStoreOptions {\n /**\n * Key prefix for state entries.\n * @default \"workflow:state:\"\n */\n prefix?: string;\n}\n\n/**\n * Create a StatePersistence instance backed by libSQL / SQLite.\n *\n * The table is automatically created on first use.\n *\n * @param options - libSQL connection and configuration options\n * @returns StatePersistence instance ready to use with durable.run()\n *\n * @example\n * ```typescript\n * import { createLibSqlPersistence } from \"awaitly-libsql\";\n * import { durable } from \"awaitly/durable\";\n *\n * const store = await createLibSqlPersistence({\n * url: \"file:./awaitly.db\",\n * });\n *\n * const result = await durable.run(\n * { fetchUser, createOrder },\n * async (step, { fetchUser, createOrder }) => {\n * const user = await step(() => fetchUser(\"123\"), { key: \"fetch-user\" });\n * const order = await step(() => createOrder(user), { key: \"create-order\" });\n * return order;\n * },\n * {\n * id: \"checkout-123\",\n * store,\n * }\n * );\n * ```\n *\n * @example\n * ```typescript\n * // Using remote Turso (libSQL) instance\n * const store = await createLibSqlPersistence({\n * url: process.env.LIBSQL_URL!,\n * authToken: process.env.LIBSQL_AUTH_TOKEN,\n * tableName: \"awaitly_workflow_state\",\n * });\n * ```\n */\nexport async function createLibSqlPersistence(\n options: LibSqlPersistenceOptions = {}\n): Promise<\n StatePersistence & {\n loadRaw(runId: string): Promise<import(\"awaitly/persistence\").SerializedState | undefined>;\n }\n> {\n const { prefix, ...storeOptions } = options;\n\n const store = new LibSqlKeyValueStore(storeOptions);\n return createStatePersistence(store, prefix);\n}\n\n/**\n * libSQL KeyValueStore implementation.\n * Use this directly if you need more control over the store.\n *\n * @example\n * ```typescript\n * import { LibSqlKeyValueStore } from \"awaitly-libsql\";\n * import { createStatePersistence } from \"awaitly/persistence\";\n *\n * const store = new LibSqlKeyValueStore({\n * url: \"file:./awaitly.db\",\n * });\n *\n * const persistence = createStatePersistence(store, \"custom:prefix:\");\n * ```\n */\nexport { LibSqlKeyValueStore, type LibSqlKeyValueStoreOptions };\n\n"],"mappings":"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","names":["createClient","LibSqlKeyValueStore","options","url","tableName","pattern","key","nowIso","result","row","expiresAt","value","affected","likePattern","createStatePersistence","createLibSqlPersistence","options","prefix","storeOptions","store","LibSqlKeyValueStore"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "awaitly-libsql",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "libSQL / SQLite persistence adapter for awaitly workflows",
|
|
6
|
+
"main": "./dist/index.cjs",
|
|
7
|
+
"module": "./dist/index.js",
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"sideEffects": false,
|
|
10
|
+
"exports": {
|
|
11
|
+
".": {
|
|
12
|
+
"types": "./dist/index.d.ts",
|
|
13
|
+
"import": "./dist/index.js",
|
|
14
|
+
"require": "./dist/index.cjs"
|
|
15
|
+
}
|
|
16
|
+
},
|
|
17
|
+
"files": [
|
|
18
|
+
"dist",
|
|
19
|
+
"README.md"
|
|
20
|
+
],
|
|
21
|
+
"scripts": {
|
|
22
|
+
"build": "tsup",
|
|
23
|
+
"type-check": "tsc --noEmit",
|
|
24
|
+
"test": "vitest run",
|
|
25
|
+
"test:watch": "vitest watch",
|
|
26
|
+
"lint": "eslint .",
|
|
27
|
+
"clean": "rm -rf dist",
|
|
28
|
+
"quality": "pnpm type-check && pnpm test && pnpm lint"
|
|
29
|
+
},
|
|
30
|
+
"keywords": [
|
|
31
|
+
"awaitly",
|
|
32
|
+
"workflow",
|
|
33
|
+
"persistence",
|
|
34
|
+
"sqlite",
|
|
35
|
+
"libsql",
|
|
36
|
+
"turso",
|
|
37
|
+
"database",
|
|
38
|
+
"durable-execution"
|
|
39
|
+
],
|
|
40
|
+
"author": "Jag Reehal <jag@jagreehal.com>",
|
|
41
|
+
"homepage": "https://github.com/jagreehal/awaitly#readme",
|
|
42
|
+
"bugs": {
|
|
43
|
+
"url": "https://github.com/jagreehal/awaitly/issues"
|
|
44
|
+
},
|
|
45
|
+
"repository": {
|
|
46
|
+
"type": "git",
|
|
47
|
+
"url": "git+https://github.com/jagreehal/awaitly.git"
|
|
48
|
+
},
|
|
49
|
+
"license": "MIT",
|
|
50
|
+
"peerDependencies": {
|
|
51
|
+
"awaitly": "workspace:^"
|
|
52
|
+
},
|
|
53
|
+
"dependencies": {
|
|
54
|
+
"@libsql/client": "^0.17.0"
|
|
55
|
+
},
|
|
56
|
+
"devDependencies": {
|
|
57
|
+
"@total-typescript/ts-reset": "^0.6.1",
|
|
58
|
+
"@total-typescript/tsconfig": "^1.0.4",
|
|
59
|
+
"@types/node": "^25.1.0",
|
|
60
|
+
"awaitly": "workspace:^",
|
|
61
|
+
"tsup": "^8.5.1",
|
|
62
|
+
"typescript": "^5.9.3",
|
|
63
|
+
"vitest": "^4.0.18"
|
|
64
|
+
},
|
|
65
|
+
"publishConfig": {
|
|
66
|
+
"access": "public",
|
|
67
|
+
"registry": "https://registry.npmjs.org/"
|
|
68
|
+
},
|
|
69
|
+
"engines": {
|
|
70
|
+
"node": ">=22"
|
|
71
|
+
}
|
|
72
|
+
}
|