@k2b/rsql 0.4.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/LICENSE +21 -0
- package/README.md +81 -0
- package/dist/client.d.ts +2 -0
- package/dist/client.js +12 -0
- package/dist/default.d.ts +2 -0
- package/dist/default.js +28 -0
- package/dist/http.d.ts +8 -0
- package/dist/http.js +134 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +2 -0
- package/dist/modules/namespace.d.ts +3 -0
- package/dist/modules/namespace.js +131 -0
- package/dist/modules/namespaces.d.ts +3 -0
- package/dist/modules/namespaces.js +90 -0
- package/dist/modules/sse.d.ts +2 -0
- package/dist/modules/sse.js +68 -0
- package/dist/modules/table.d.ts +3 -0
- package/dist/modules/table.js +160 -0
- package/dist/types.d.ts +345 -0
- package/dist/types.js +1 -0
- package/package.json +54 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Valentin Kolb
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
# @k2b/rsql
|
|
2
|
+
|
|
3
|
+
Type-safe, fetch-based client for the
|
|
4
|
+
[rsql](https://github.com/k2b-dev/rsql) HTTP API. It runs in Bun, Node, and
|
|
5
|
+
modern browsers without runtime dependencies.
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
bun add @k2b/rsql
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Usage
|
|
14
|
+
|
|
15
|
+
```ts
|
|
16
|
+
import { createRsqlClient } from "@k2b/rsql";
|
|
17
|
+
|
|
18
|
+
const client = createRsqlClient({
|
|
19
|
+
url: "http://127.0.0.1:8080",
|
|
20
|
+
token: "secret",
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
const contacts = client.ns("crm").table<{
|
|
24
|
+
id: number;
|
|
25
|
+
name: string;
|
|
26
|
+
status: "active" | "inactive";
|
|
27
|
+
}>("contacts");
|
|
28
|
+
|
|
29
|
+
const result = await contacts.rows.list({
|
|
30
|
+
status: "eq.active",
|
|
31
|
+
order: "name.asc",
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
if (result.ok) {
|
|
35
|
+
console.log(result.data);
|
|
36
|
+
}
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Modules are hierarchical and stateless:
|
|
40
|
+
|
|
41
|
+
```text
|
|
42
|
+
client.namespaces
|
|
43
|
+
client.ns(name).tables
|
|
44
|
+
client.ns(name).table(name).schema
|
|
45
|
+
client.ns(name).table(name).indexes
|
|
46
|
+
client.ns(name).table(name).rows
|
|
47
|
+
client.ns(name).query
|
|
48
|
+
client.ns(name).changelog
|
|
49
|
+
client.ns(name).overview
|
|
50
|
+
client.ns(name).events
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
HTTP errors resolve to `RsqlResult<T>`. Network and runtime failures may reject
|
|
54
|
+
the promise. Export methods return the native streaming `Response`; SSE
|
|
55
|
+
subscriptions expose an async iterable and explicit `close` function.
|
|
56
|
+
|
|
57
|
+
## Default Client
|
|
58
|
+
|
|
59
|
+
The `rsql` export reads `RSQL_URL` and `RSQL_API_TOKEN` lazily on first access:
|
|
60
|
+
|
|
61
|
+
```ts
|
|
62
|
+
import { rsql } from "@k2b/rsql";
|
|
63
|
+
|
|
64
|
+
await rsql.namespaces.list();
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
Imports remain browser-safe because environment variables are not read during
|
|
68
|
+
module evaluation. Browser applications should use an explicit client and must
|
|
69
|
+
not expose a server-wide administrative token.
|
|
70
|
+
|
|
71
|
+
## Development
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
bun install
|
|
75
|
+
bun run typecheck
|
|
76
|
+
bun test
|
|
77
|
+
bun run test:package
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
Performance profiles are available through `bun run perf:fast` and
|
|
81
|
+
`bun run perf:deep`.
|
package/dist/client.d.ts
ADDED
package/dist/client.js
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { createHttpClient } from "./http.js";
|
|
2
|
+
import { createNamespaceModule } from "./modules/namespace.js";
|
|
3
|
+
import { createNamespacesModule } from "./modules/namespaces.js";
|
|
4
|
+
export const createRsqlClient = (options) => {
|
|
5
|
+
const http = createHttpClient(options);
|
|
6
|
+
return {
|
|
7
|
+
namespaces: createNamespacesModule(http),
|
|
8
|
+
ns(name) {
|
|
9
|
+
return createNamespaceModule(http, name);
|
|
10
|
+
},
|
|
11
|
+
};
|
|
12
|
+
};
|
package/dist/default.js
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { createRsqlClient } from "./client.js";
|
|
2
|
+
let defaultClient = null;
|
|
3
|
+
const readEnv = (key) => {
|
|
4
|
+
const maybeProcess = globalThis.process;
|
|
5
|
+
return maybeProcess?.env?.[key];
|
|
6
|
+
};
|
|
7
|
+
const createDefaultClient = () => {
|
|
8
|
+
const url = readEnv("RSQL_URL");
|
|
9
|
+
const token = readEnv("RSQL_API_TOKEN");
|
|
10
|
+
if (!url || !token) {
|
|
11
|
+
throw new Error("RSQL_URL and RSQL_API_TOKEN environment variables are required.\n" +
|
|
12
|
+
"You can also create an explicit client:\n" +
|
|
13
|
+
" createRsqlClient({ url: \"http://127.0.0.1:8080\", token: \"secret\" })");
|
|
14
|
+
}
|
|
15
|
+
return createRsqlClient({ url, token });
|
|
16
|
+
};
|
|
17
|
+
export const rsql = new Proxy({}, {
|
|
18
|
+
get(_target, prop, receiver) {
|
|
19
|
+
if (defaultClient === null) {
|
|
20
|
+
defaultClient = createDefaultClient();
|
|
21
|
+
}
|
|
22
|
+
const value = Reflect.get(defaultClient, prop, receiver);
|
|
23
|
+
if (typeof value === "function") {
|
|
24
|
+
return value.bind(defaultClient);
|
|
25
|
+
}
|
|
26
|
+
return value;
|
|
27
|
+
},
|
|
28
|
+
});
|
package/dist/http.d.ts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { CreateRsqlClientOptions, QueryInput, RsqlResult } from "./types.js";
|
|
2
|
+
export interface HttpClient {
|
|
3
|
+
json<T>(path: string, init?: RequestInit): Promise<RsqlResult<T>>;
|
|
4
|
+
empty(path: string, init?: RequestInit): Promise<RsqlResult<void>>;
|
|
5
|
+
raw(path: string, init?: RequestInit): Promise<RsqlResult<Response>>;
|
|
6
|
+
withQuery(path: string, query?: QueryInput): string;
|
|
7
|
+
}
|
|
8
|
+
export declare const createHttpClient: (options: CreateRsqlClientOptions) => HttpClient;
|
package/dist/http.js
ADDED
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
const defaultHeaders = (token) => ({
|
|
2
|
+
Authorization: `Bearer ${token}`,
|
|
3
|
+
});
|
|
4
|
+
export const createHttpClient = (options) => {
|
|
5
|
+
const baseUrl = options.url.replace(/\/$/, "");
|
|
6
|
+
const doFetch = options.fetch ?? fetch;
|
|
7
|
+
const execute = async (path, init) => {
|
|
8
|
+
const headers = new Headers(defaultHeaders(options.token));
|
|
9
|
+
if (init?.headers) {
|
|
10
|
+
const incoming = new Headers(init.headers);
|
|
11
|
+
for (const [k, v] of incoming.entries()) {
|
|
12
|
+
headers.set(k, v);
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
return doFetch(`${baseUrl}${path}`, {
|
|
16
|
+
...init,
|
|
17
|
+
headers,
|
|
18
|
+
});
|
|
19
|
+
};
|
|
20
|
+
return {
|
|
21
|
+
async json(path, init) {
|
|
22
|
+
const res = await execute(path, init);
|
|
23
|
+
if (!res.ok) {
|
|
24
|
+
return errorResult(res);
|
|
25
|
+
}
|
|
26
|
+
if (res.status === 204) {
|
|
27
|
+
return {
|
|
28
|
+
ok: true,
|
|
29
|
+
data: undefined,
|
|
30
|
+
status: res.status,
|
|
31
|
+
headers: res.headers,
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
const data = (await res.json());
|
|
35
|
+
return {
|
|
36
|
+
ok: true,
|
|
37
|
+
data,
|
|
38
|
+
status: res.status,
|
|
39
|
+
headers: res.headers,
|
|
40
|
+
};
|
|
41
|
+
},
|
|
42
|
+
async empty(path, init) {
|
|
43
|
+
const res = await execute(path, init);
|
|
44
|
+
if (!res.ok) {
|
|
45
|
+
return errorResult(res);
|
|
46
|
+
}
|
|
47
|
+
return {
|
|
48
|
+
ok: true,
|
|
49
|
+
data: undefined,
|
|
50
|
+
status: res.status,
|
|
51
|
+
headers: res.headers,
|
|
52
|
+
};
|
|
53
|
+
},
|
|
54
|
+
async raw(path, init) {
|
|
55
|
+
const res = await execute(path, init);
|
|
56
|
+
if (!res.ok) {
|
|
57
|
+
return errorResult(res);
|
|
58
|
+
}
|
|
59
|
+
return {
|
|
60
|
+
ok: true,
|
|
61
|
+
data: res,
|
|
62
|
+
status: res.status,
|
|
63
|
+
headers: res.headers,
|
|
64
|
+
};
|
|
65
|
+
},
|
|
66
|
+
withQuery(path, query) {
|
|
67
|
+
if (!query) {
|
|
68
|
+
return path;
|
|
69
|
+
}
|
|
70
|
+
const search = toSearchParams(query);
|
|
71
|
+
const qs = search.toString();
|
|
72
|
+
if (!qs) {
|
|
73
|
+
return path;
|
|
74
|
+
}
|
|
75
|
+
return `${path}?${qs}`;
|
|
76
|
+
},
|
|
77
|
+
};
|
|
78
|
+
};
|
|
79
|
+
const errorResult = async (res) => {
|
|
80
|
+
let body = {
|
|
81
|
+
error: "unknown_error",
|
|
82
|
+
message: res.statusText || "Unknown error",
|
|
83
|
+
};
|
|
84
|
+
try {
|
|
85
|
+
const text = await res.text();
|
|
86
|
+
if (text.trim() !== "") {
|
|
87
|
+
try {
|
|
88
|
+
const maybeJson = JSON.parse(text);
|
|
89
|
+
body = {
|
|
90
|
+
error: maybeJson.error ?? "unknown_error",
|
|
91
|
+
message: maybeJson.message ?? (res.statusText || "Unknown error"),
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
catch {
|
|
95
|
+
body.message = text;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
catch {
|
|
100
|
+
// Keep the HTTP status fallback when the body cannot be read.
|
|
101
|
+
}
|
|
102
|
+
return {
|
|
103
|
+
ok: false,
|
|
104
|
+
error: body,
|
|
105
|
+
status: res.status,
|
|
106
|
+
headers: res.headers,
|
|
107
|
+
};
|
|
108
|
+
};
|
|
109
|
+
const toSearchParams = (input) => {
|
|
110
|
+
if (input instanceof URLSearchParams) {
|
|
111
|
+
return input;
|
|
112
|
+
}
|
|
113
|
+
const params = new URLSearchParams();
|
|
114
|
+
for (const [key, raw] of Object.entries(input)) {
|
|
115
|
+
if (Array.isArray(raw)) {
|
|
116
|
+
for (const v of raw) {
|
|
117
|
+
appendQueryValue(params, key, v);
|
|
118
|
+
}
|
|
119
|
+
continue;
|
|
120
|
+
}
|
|
121
|
+
appendQueryValue(params, key, raw);
|
|
122
|
+
}
|
|
123
|
+
return params;
|
|
124
|
+
};
|
|
125
|
+
const appendQueryValue = (params, key, value) => {
|
|
126
|
+
if (value === undefined) {
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
if (value === null) {
|
|
130
|
+
params.append(key, "null");
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
params.append(key, String(value));
|
|
134
|
+
};
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
export { createRsqlClient } from "./client.js";
|
|
2
|
+
export { rsql } from "./default.js";
|
|
3
|
+
export type { ApiErrorBody, ChangelogEntry, ColumnDefinition, CreateRsqlClientOptions, ImportFile, IndexCreateRequest, ListMeta, ListResponse, MutateOptions, NamespaceConfig, NamespaceDefinition, NamespaceModule, NamespaceOverview, NamespaceRecord, NamespacesModule, QueryInput, QueryRequest, QueryStatement, QueryValue, OverviewLatency, OverviewWindowName, RowsModule, RsqlClient, RsqlResult, SSEEvent, SSESubscribeOptions, SSESubscription, StorageSnapshot, TableCreateRequest, TableExportOptions, TableModule, TablesModule, TableUpdateRequest, } from "./types.js";
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import { createTableModule } from "./table.js";
|
|
2
|
+
import { createSSESubscription } from "./sse.js";
|
|
3
|
+
const version = "v1";
|
|
4
|
+
const jsonHeaders = {
|
|
5
|
+
"Content-Type": "application/json",
|
|
6
|
+
};
|
|
7
|
+
export const createNamespaceModule = (http, namespace) => {
|
|
8
|
+
const encoded = encodeURIComponent(namespace);
|
|
9
|
+
const base = `/${version}/${encoded}`;
|
|
10
|
+
return {
|
|
11
|
+
name: namespace,
|
|
12
|
+
tables: {
|
|
13
|
+
list() {
|
|
14
|
+
return http.json(`${base}/tables`);
|
|
15
|
+
},
|
|
16
|
+
create(request) {
|
|
17
|
+
return http.json(`${base}/tables`, {
|
|
18
|
+
method: "POST",
|
|
19
|
+
headers: jsonHeaders,
|
|
20
|
+
body: JSON.stringify(request),
|
|
21
|
+
});
|
|
22
|
+
},
|
|
23
|
+
get(name) {
|
|
24
|
+
return http.json(`${base}/tables/${encodeURIComponent(name)}`);
|
|
25
|
+
},
|
|
26
|
+
update(name, request) {
|
|
27
|
+
return http.json(`${base}/tables/${encodeURIComponent(name)}`, {
|
|
28
|
+
method: "PUT",
|
|
29
|
+
headers: jsonHeaders,
|
|
30
|
+
body: JSON.stringify(request),
|
|
31
|
+
});
|
|
32
|
+
},
|
|
33
|
+
delete(name, meta) {
|
|
34
|
+
if (!meta) {
|
|
35
|
+
return http.empty(`${base}/tables/${encodeURIComponent(name)}`, {
|
|
36
|
+
method: "DELETE",
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
return http.empty(`${base}/tables/${encodeURIComponent(name)}`, {
|
|
40
|
+
method: "DELETE",
|
|
41
|
+
headers: jsonHeaders,
|
|
42
|
+
body: JSON.stringify({ _meta: meta }),
|
|
43
|
+
});
|
|
44
|
+
},
|
|
45
|
+
},
|
|
46
|
+
table(name) {
|
|
47
|
+
return createTableModule(http, namespace, name);
|
|
48
|
+
},
|
|
49
|
+
query: {
|
|
50
|
+
run(request) {
|
|
51
|
+
return http.json(`${base}/query`, {
|
|
52
|
+
method: "POST",
|
|
53
|
+
headers: jsonHeaders,
|
|
54
|
+
body: JSON.stringify(request),
|
|
55
|
+
});
|
|
56
|
+
},
|
|
57
|
+
batch(statements) {
|
|
58
|
+
return http.json(`${base}/query`, {
|
|
59
|
+
method: "POST",
|
|
60
|
+
headers: jsonHeaders,
|
|
61
|
+
body: JSON.stringify({ statements }),
|
|
62
|
+
});
|
|
63
|
+
},
|
|
64
|
+
},
|
|
65
|
+
changelog: {
|
|
66
|
+
list(options) {
|
|
67
|
+
const path = http.withQuery(`${base}/changelog`, {
|
|
68
|
+
table: options?.table,
|
|
69
|
+
limit: options?.limit,
|
|
70
|
+
offset: options?.offset,
|
|
71
|
+
});
|
|
72
|
+
return http.json(path);
|
|
73
|
+
},
|
|
74
|
+
},
|
|
75
|
+
overview: {
|
|
76
|
+
get(options) {
|
|
77
|
+
return http.json(http.withQuery(`${base}/overview`, { window: options?.window }));
|
|
78
|
+
},
|
|
79
|
+
},
|
|
80
|
+
events: {
|
|
81
|
+
async subscribe(options) {
|
|
82
|
+
const controller = new AbortController();
|
|
83
|
+
const externalSignal = options?.signal;
|
|
84
|
+
if (externalSignal) {
|
|
85
|
+
if (externalSignal.aborted) {
|
|
86
|
+
controller.abort(externalSignal.reason);
|
|
87
|
+
}
|
|
88
|
+
else {
|
|
89
|
+
externalSignal.addEventListener("abort", () => {
|
|
90
|
+
controller.abort(externalSignal.reason);
|
|
91
|
+
}, { once: true });
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
const path = http.withQuery(`${base}/subscribe`, {
|
|
95
|
+
tables: options?.tables?.join(","),
|
|
96
|
+
});
|
|
97
|
+
const res = await http.raw(path, {
|
|
98
|
+
method: "GET",
|
|
99
|
+
signal: controller.signal,
|
|
100
|
+
headers: {
|
|
101
|
+
Accept: "text/event-stream",
|
|
102
|
+
},
|
|
103
|
+
});
|
|
104
|
+
if (!res.ok) {
|
|
105
|
+
return res;
|
|
106
|
+
}
|
|
107
|
+
try {
|
|
108
|
+
const subscription = createSSESubscription(res.data, controller);
|
|
109
|
+
return {
|
|
110
|
+
ok: true,
|
|
111
|
+
data: subscription,
|
|
112
|
+
status: res.status,
|
|
113
|
+
headers: res.headers,
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
catch (err) {
|
|
117
|
+
controller.abort();
|
|
118
|
+
return {
|
|
119
|
+
ok: false,
|
|
120
|
+
error: {
|
|
121
|
+
error: "invalid_sse_stream",
|
|
122
|
+
message: err instanceof Error ? err.message : "SSE stream is not available",
|
|
123
|
+
},
|
|
124
|
+
status: 500,
|
|
125
|
+
headers: res.headers,
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
},
|
|
129
|
+
},
|
|
130
|
+
};
|
|
131
|
+
};
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
const version = "v1";
|
|
2
|
+
const jsonHeaders = {
|
|
3
|
+
"Content-Type": "application/json",
|
|
4
|
+
};
|
|
5
|
+
export const createNamespacesModule = (http) => {
|
|
6
|
+
return {
|
|
7
|
+
create(request) {
|
|
8
|
+
const payload = {
|
|
9
|
+
...request,
|
|
10
|
+
config: withDefaultNamespaceConfig(request.config),
|
|
11
|
+
};
|
|
12
|
+
return http.json(`/${version}/namespaces`, {
|
|
13
|
+
method: "POST",
|
|
14
|
+
headers: jsonHeaders,
|
|
15
|
+
body: JSON.stringify(payload),
|
|
16
|
+
});
|
|
17
|
+
},
|
|
18
|
+
list() {
|
|
19
|
+
return http.json(`/${version}/namespaces`);
|
|
20
|
+
},
|
|
21
|
+
get(name) {
|
|
22
|
+
return http.json(`/${version}/namespaces/${encodeURIComponent(name)}`);
|
|
23
|
+
},
|
|
24
|
+
update(name, config) {
|
|
25
|
+
return http.json(`/${version}/namespaces/${encodeURIComponent(name)}`, {
|
|
26
|
+
method: "PUT",
|
|
27
|
+
headers: jsonHeaders,
|
|
28
|
+
body: JSON.stringify({ config }),
|
|
29
|
+
});
|
|
30
|
+
},
|
|
31
|
+
delete(name) {
|
|
32
|
+
return http.empty(`/${version}/namespaces/${encodeURIComponent(name)}`, {
|
|
33
|
+
method: "DELETE",
|
|
34
|
+
});
|
|
35
|
+
},
|
|
36
|
+
duplicate(source, target) {
|
|
37
|
+
return http.json(`/${version}/namespaces/${encodeURIComponent(source)}/duplicate`, {
|
|
38
|
+
method: "POST",
|
|
39
|
+
headers: jsonHeaders,
|
|
40
|
+
body: JSON.stringify({ name: target }),
|
|
41
|
+
});
|
|
42
|
+
},
|
|
43
|
+
exportDb(name) {
|
|
44
|
+
return http.raw(`/${version}/namespaces/${encodeURIComponent(name)}/export`);
|
|
45
|
+
},
|
|
46
|
+
importDb(name, file) {
|
|
47
|
+
const formData = new FormData();
|
|
48
|
+
formData.set("file", toBlob(file.content, file.contentType), file.filename || "namespace.db");
|
|
49
|
+
return http.json(`/${version}/namespaces/${encodeURIComponent(name)}/import`, {
|
|
50
|
+
method: "POST",
|
|
51
|
+
body: formData,
|
|
52
|
+
});
|
|
53
|
+
},
|
|
54
|
+
importCsv(name, table, file) {
|
|
55
|
+
const formData = new FormData();
|
|
56
|
+
formData.set("file", toBlob(file.content, file.contentType ?? "text/csv"), file.filename || "rows.csv");
|
|
57
|
+
return http.json(`/${version}/namespaces/${encodeURIComponent(name)}/import?table=${encodeURIComponent(table)}`, {
|
|
58
|
+
method: "POST",
|
|
59
|
+
body: formData,
|
|
60
|
+
});
|
|
61
|
+
},
|
|
62
|
+
};
|
|
63
|
+
};
|
|
64
|
+
const withDefaultNamespaceConfig = (config) => {
|
|
65
|
+
const fallback = {
|
|
66
|
+
journal_mode: "wal",
|
|
67
|
+
busy_timeout: 5000,
|
|
68
|
+
query_timeout: 10000,
|
|
69
|
+
foreign_keys: true,
|
|
70
|
+
read_only: false,
|
|
71
|
+
};
|
|
72
|
+
return {
|
|
73
|
+
...fallback,
|
|
74
|
+
...(config ?? {}),
|
|
75
|
+
};
|
|
76
|
+
};
|
|
77
|
+
const toBlob = (content, contentType) => {
|
|
78
|
+
if (content instanceof Blob) {
|
|
79
|
+
return content;
|
|
80
|
+
}
|
|
81
|
+
if (typeof content === "string") {
|
|
82
|
+
return new Blob([content], { type: contentType });
|
|
83
|
+
}
|
|
84
|
+
if (content instanceof Uint8Array) {
|
|
85
|
+
const copy = new Uint8Array(content.byteLength);
|
|
86
|
+
copy.set(content);
|
|
87
|
+
return new Blob([copy.buffer], { type: contentType });
|
|
88
|
+
}
|
|
89
|
+
return new Blob([new Uint8Array(content)], { type: contentType });
|
|
90
|
+
};
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
export const createSSESubscription = (response, controller) => {
|
|
2
|
+
if (!response.body) {
|
|
3
|
+
throw new Error("SSE response body is not readable");
|
|
4
|
+
}
|
|
5
|
+
return {
|
|
6
|
+
response,
|
|
7
|
+
close: () => {
|
|
8
|
+
controller.abort();
|
|
9
|
+
},
|
|
10
|
+
stream: parseEventStream(response.body, controller.signal),
|
|
11
|
+
};
|
|
12
|
+
};
|
|
13
|
+
async function* parseEventStream(stream, signal) {
|
|
14
|
+
const reader = stream.getReader();
|
|
15
|
+
const decoder = new TextDecoder();
|
|
16
|
+
let buffer = "";
|
|
17
|
+
let eventName = "message";
|
|
18
|
+
let dataLines = [];
|
|
19
|
+
try {
|
|
20
|
+
while (!signal.aborted) {
|
|
21
|
+
const { value, done } = await reader.read();
|
|
22
|
+
if (done) {
|
|
23
|
+
break;
|
|
24
|
+
}
|
|
25
|
+
buffer += decoder.decode(value, { stream: true });
|
|
26
|
+
while (true) {
|
|
27
|
+
const idx = buffer.indexOf("\n");
|
|
28
|
+
if (idx === -1) {
|
|
29
|
+
break;
|
|
30
|
+
}
|
|
31
|
+
const rawLine = buffer.slice(0, idx);
|
|
32
|
+
buffer = buffer.slice(idx + 1);
|
|
33
|
+
const line = rawLine.endsWith("\r") ? rawLine.slice(0, -1) : rawLine;
|
|
34
|
+
if (line === "") {
|
|
35
|
+
if (dataLines.length > 0) {
|
|
36
|
+
const rawData = dataLines.join("\n");
|
|
37
|
+
dataLines = [];
|
|
38
|
+
try {
|
|
39
|
+
const parsed = JSON.parse(rawData);
|
|
40
|
+
if (!parsed.action && eventName !== "message") {
|
|
41
|
+
parsed.action = eventName;
|
|
42
|
+
}
|
|
43
|
+
yield parsed;
|
|
44
|
+
}
|
|
45
|
+
catch {
|
|
46
|
+
// Ignore malformed event payloads.
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
eventName = "message";
|
|
50
|
+
continue;
|
|
51
|
+
}
|
|
52
|
+
if (line.startsWith(":")) {
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
if (line.startsWith("event:")) {
|
|
56
|
+
eventName = line.slice("event:".length).trim();
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
if (line.startsWith("data:")) {
|
|
60
|
+
dataLines.push(line.slice("data:".length).trim());
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
finally {
|
|
66
|
+
reader.releaseLock();
|
|
67
|
+
}
|
|
68
|
+
}
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
export const createTableModule = (http, namespace, table) => {
|
|
2
|
+
const ctx = {
|
|
3
|
+
http,
|
|
4
|
+
namespace: encodeURIComponent(namespace),
|
|
5
|
+
table: encodeURIComponent(table),
|
|
6
|
+
};
|
|
7
|
+
return {
|
|
8
|
+
rows: createRowsModule(ctx),
|
|
9
|
+
indexes: {
|
|
10
|
+
create(request) {
|
|
11
|
+
return ctx.http.json(`/${version}/${ctx.namespace}/tables/${ctx.table}/indexes`, {
|
|
12
|
+
method: "POST",
|
|
13
|
+
headers: jsonHeaders,
|
|
14
|
+
body: JSON.stringify(request),
|
|
15
|
+
});
|
|
16
|
+
},
|
|
17
|
+
delete(indexName, meta) {
|
|
18
|
+
return deleteWithOptionalMeta(ctx.http, `/${version}/${ctx.namespace}/tables/${ctx.table}/indexes/${encodeURIComponent(indexName)}`, meta);
|
|
19
|
+
},
|
|
20
|
+
},
|
|
21
|
+
schema: {
|
|
22
|
+
get() {
|
|
23
|
+
return ctx.http.json(`/${version}/${ctx.namespace}/tables/${ctx.table}`);
|
|
24
|
+
},
|
|
25
|
+
update(request) {
|
|
26
|
+
return ctx.http.json(`/${version}/${ctx.namespace}/tables/${ctx.table}`, {
|
|
27
|
+
method: "PUT",
|
|
28
|
+
headers: jsonHeaders,
|
|
29
|
+
body: JSON.stringify(request),
|
|
30
|
+
});
|
|
31
|
+
},
|
|
32
|
+
delete(meta) {
|
|
33
|
+
return deleteWithOptionalMeta(ctx.http, `/${version}/${ctx.namespace}/tables/${ctx.table}`, meta);
|
|
34
|
+
},
|
|
35
|
+
},
|
|
36
|
+
export(options) {
|
|
37
|
+
// The endpoint accepts the same filter grammar as rows.list. We add
|
|
38
|
+
// the required `format` (and optional `bom`) on top of the caller's
|
|
39
|
+
// own filters.
|
|
40
|
+
const query = { format: options.format };
|
|
41
|
+
if (options.bom) {
|
|
42
|
+
query.bom = "true";
|
|
43
|
+
}
|
|
44
|
+
const merged = mergeQuery(options.query, query);
|
|
45
|
+
const path = ctx.http.withQuery(`/${version}/${ctx.namespace}/tables/${ctx.table}/export`, merged);
|
|
46
|
+
return ctx.http.raw(path, { method: "GET" });
|
|
47
|
+
},
|
|
48
|
+
};
|
|
49
|
+
};
|
|
50
|
+
const mergeQuery = (base, extras) => {
|
|
51
|
+
const params = new URLSearchParams();
|
|
52
|
+
if (base instanceof URLSearchParams) {
|
|
53
|
+
for (const [k, v] of base.entries())
|
|
54
|
+
params.append(k, v);
|
|
55
|
+
}
|
|
56
|
+
else if (base) {
|
|
57
|
+
for (const [k, raw] of Object.entries(base)) {
|
|
58
|
+
const values = Array.isArray(raw) ? raw : [raw];
|
|
59
|
+
for (const v of values) {
|
|
60
|
+
if (v === undefined)
|
|
61
|
+
continue;
|
|
62
|
+
params.append(k, v === null ? "null" : String(v));
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
for (const [k, v] of Object.entries(extras)) {
|
|
67
|
+
if (v === undefined || v === null)
|
|
68
|
+
continue;
|
|
69
|
+
params.set(k, String(v));
|
|
70
|
+
}
|
|
71
|
+
return params;
|
|
72
|
+
};
|
|
73
|
+
const createRowsModule = (ctx) => {
|
|
74
|
+
const basePath = `/${version}/${ctx.namespace}/tables/${ctx.table}/rows`;
|
|
75
|
+
return {
|
|
76
|
+
list(query) {
|
|
77
|
+
return ctx.http.json(ctx.http.withQuery(basePath, query));
|
|
78
|
+
},
|
|
79
|
+
insert(rows, options) {
|
|
80
|
+
const body = Array.isArray(rows) ? { rows } : { ...rows };
|
|
81
|
+
if (options?.meta) {
|
|
82
|
+
body._meta = options.meta;
|
|
83
|
+
}
|
|
84
|
+
return ctx.http.json(basePath, {
|
|
85
|
+
method: "POST",
|
|
86
|
+
headers: withPrefer(options?.prefer),
|
|
87
|
+
body: JSON.stringify(body),
|
|
88
|
+
});
|
|
89
|
+
},
|
|
90
|
+
get(id) {
|
|
91
|
+
return ctx.http.json(`${basePath}/${id}`);
|
|
92
|
+
},
|
|
93
|
+
update(id, payload, options) {
|
|
94
|
+
return ctx.http.json(`${basePath}/${id}`, {
|
|
95
|
+
method: "PUT",
|
|
96
|
+
headers: withPrefer(options?.prefer),
|
|
97
|
+
body: JSON.stringify(withMeta(payload, options?.meta)),
|
|
98
|
+
});
|
|
99
|
+
},
|
|
100
|
+
delete(id, options) {
|
|
101
|
+
return deleteWithPreferAndMeta(ctx.http, `${basePath}/${id}`, options);
|
|
102
|
+
},
|
|
103
|
+
bulkUpdate(query, payload, options) {
|
|
104
|
+
return ctx.http.json(ctx.http.withQuery(basePath, query), {
|
|
105
|
+
method: "PATCH",
|
|
106
|
+
headers: withPrefer(options?.prefer),
|
|
107
|
+
body: JSON.stringify(withMeta(payload, options?.meta)),
|
|
108
|
+
});
|
|
109
|
+
},
|
|
110
|
+
bulkDelete(query, options) {
|
|
111
|
+
return deleteWithPreferAndMeta(ctx.http, ctx.http.withQuery(basePath, query), options);
|
|
112
|
+
},
|
|
113
|
+
};
|
|
114
|
+
};
|
|
115
|
+
const deleteWithOptionalMeta = (http, path, meta) => {
|
|
116
|
+
if (!meta) {
|
|
117
|
+
return http.empty(path, { method: "DELETE" });
|
|
118
|
+
}
|
|
119
|
+
return http.empty(path, {
|
|
120
|
+
method: "DELETE",
|
|
121
|
+
headers: jsonHeaders,
|
|
122
|
+
body: JSON.stringify({ _meta: meta }),
|
|
123
|
+
});
|
|
124
|
+
};
|
|
125
|
+
const deleteWithPreferAndMeta = (http, path, options) => {
|
|
126
|
+
const headers = withPrefer(options?.prefer);
|
|
127
|
+
const body = options?.meta ? JSON.stringify({ _meta: options.meta }) : undefined;
|
|
128
|
+
if (options?.prefer === "return=representation") {
|
|
129
|
+
return http.json(path, {
|
|
130
|
+
method: "DELETE",
|
|
131
|
+
headers,
|
|
132
|
+
body,
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
return http.json(path, {
|
|
136
|
+
method: "DELETE",
|
|
137
|
+
headers,
|
|
138
|
+
body,
|
|
139
|
+
});
|
|
140
|
+
};
|
|
141
|
+
const withMeta = (payload, meta) => {
|
|
142
|
+
if (!meta) {
|
|
143
|
+
return payload;
|
|
144
|
+
}
|
|
145
|
+
return {
|
|
146
|
+
...payload,
|
|
147
|
+
_meta: meta,
|
|
148
|
+
};
|
|
149
|
+
};
|
|
150
|
+
const withPrefer = (prefer) => {
|
|
151
|
+
const headers = new Headers(jsonHeaders);
|
|
152
|
+
if (prefer) {
|
|
153
|
+
headers.set("Prefer", prefer);
|
|
154
|
+
}
|
|
155
|
+
return headers;
|
|
156
|
+
};
|
|
157
|
+
const version = "v1";
|
|
158
|
+
const jsonHeaders = {
|
|
159
|
+
"Content-Type": "application/json",
|
|
160
|
+
};
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,345 @@
|
|
|
1
|
+
export interface ApiErrorBody {
|
|
2
|
+
error: string;
|
|
3
|
+
message: string;
|
|
4
|
+
}
|
|
5
|
+
export type RsqlResult<T> = {
|
|
6
|
+
ok: true;
|
|
7
|
+
data: T;
|
|
8
|
+
status: number;
|
|
9
|
+
headers: Headers;
|
|
10
|
+
} | {
|
|
11
|
+
ok: false;
|
|
12
|
+
error: ApiErrorBody;
|
|
13
|
+
status: number;
|
|
14
|
+
headers: Headers;
|
|
15
|
+
};
|
|
16
|
+
export interface CreateRsqlClientOptions {
|
|
17
|
+
url: string;
|
|
18
|
+
token: string;
|
|
19
|
+
fetch?: typeof fetch;
|
|
20
|
+
}
|
|
21
|
+
export interface NamespaceConfig {
|
|
22
|
+
journal_mode: string;
|
|
23
|
+
busy_timeout: number;
|
|
24
|
+
max_db_size?: number;
|
|
25
|
+
query_timeout: number;
|
|
26
|
+
/** Optional. Omit to apply the server default (true). Send false to
|
|
27
|
+
* explicitly disable foreign-key enforcement for the namespace. */
|
|
28
|
+
foreign_keys?: boolean;
|
|
29
|
+
read_only?: boolean;
|
|
30
|
+
}
|
|
31
|
+
export interface NamespaceDefinition {
|
|
32
|
+
name: string;
|
|
33
|
+
config?: NamespaceConfig;
|
|
34
|
+
}
|
|
35
|
+
export interface ColumnDefinition {
|
|
36
|
+
name: string;
|
|
37
|
+
type: string;
|
|
38
|
+
not_null?: boolean;
|
|
39
|
+
unique?: boolean;
|
|
40
|
+
default?: unknown;
|
|
41
|
+
index?: boolean;
|
|
42
|
+
pattern?: string;
|
|
43
|
+
max_length?: number;
|
|
44
|
+
min?: number;
|
|
45
|
+
max?: number;
|
|
46
|
+
auto?: boolean;
|
|
47
|
+
options?: string[];
|
|
48
|
+
formula?: string;
|
|
49
|
+
metadata?: Record<string, unknown>;
|
|
50
|
+
primary_key?: boolean;
|
|
51
|
+
read_only?: boolean;
|
|
52
|
+
}
|
|
53
|
+
export interface TableCreateRequest {
|
|
54
|
+
type: "table" | "view";
|
|
55
|
+
name: string;
|
|
56
|
+
metadata?: Record<string, unknown>;
|
|
57
|
+
columns?: ColumnDefinition[];
|
|
58
|
+
sql?: string;
|
|
59
|
+
/** Audit-meta passthrough. Wire key is `_meta` so it cannot collide with
|
|
60
|
+
* a user-defined column named `meta`. */
|
|
61
|
+
_meta?: Record<string, unknown>;
|
|
62
|
+
}
|
|
63
|
+
export interface TableUpdateRequest {
|
|
64
|
+
rename?: string;
|
|
65
|
+
add_columns?: ColumnDefinition[];
|
|
66
|
+
drop_columns?: string[];
|
|
67
|
+
rename_columns?: Record<string, string>;
|
|
68
|
+
sql?: string;
|
|
69
|
+
metadata?: Record<string, unknown>;
|
|
70
|
+
_meta?: Record<string, unknown>;
|
|
71
|
+
}
|
|
72
|
+
export interface IndexCreateRequest {
|
|
73
|
+
type: "index" | "unique" | "fts";
|
|
74
|
+
name?: string;
|
|
75
|
+
columns: string[];
|
|
76
|
+
_meta?: Record<string, unknown>;
|
|
77
|
+
}
|
|
78
|
+
export interface ListMeta {
|
|
79
|
+
total_count: number;
|
|
80
|
+
filter_count: number;
|
|
81
|
+
limit: number;
|
|
82
|
+
offset: number;
|
|
83
|
+
}
|
|
84
|
+
export interface ListResponse<T> {
|
|
85
|
+
data: T[];
|
|
86
|
+
meta: ListMeta;
|
|
87
|
+
}
|
|
88
|
+
export interface QueryStatement {
|
|
89
|
+
sql: string;
|
|
90
|
+
params?: unknown[];
|
|
91
|
+
}
|
|
92
|
+
export interface QueryRequest {
|
|
93
|
+
sql?: string;
|
|
94
|
+
params?: unknown[];
|
|
95
|
+
statements?: QueryStatement[];
|
|
96
|
+
}
|
|
97
|
+
export interface ChangelogEntry {
|
|
98
|
+
id: number;
|
|
99
|
+
timestamp: string;
|
|
100
|
+
action: string;
|
|
101
|
+
table: string;
|
|
102
|
+
detail: Record<string, unknown>;
|
|
103
|
+
_meta?: Record<string, unknown>;
|
|
104
|
+
}
|
|
105
|
+
export interface SSEEvent {
|
|
106
|
+
namespace?: string;
|
|
107
|
+
table: string;
|
|
108
|
+
action: string;
|
|
109
|
+
source_table?: string;
|
|
110
|
+
source_action?: string;
|
|
111
|
+
row?: Record<string, unknown>;
|
|
112
|
+
row_count?: number;
|
|
113
|
+
row_ids?: Array<string | number>;
|
|
114
|
+
detail?: Record<string, unknown>;
|
|
115
|
+
_meta?: Record<string, unknown>;
|
|
116
|
+
timestamp: string;
|
|
117
|
+
}
|
|
118
|
+
export interface SSESubscribeOptions {
|
|
119
|
+
tables?: string[];
|
|
120
|
+
signal?: AbortSignal;
|
|
121
|
+
}
|
|
122
|
+
export interface SSESubscription {
|
|
123
|
+
stream: AsyncIterable<SSEEvent>;
|
|
124
|
+
response: Response;
|
|
125
|
+
close: () => void;
|
|
126
|
+
}
|
|
127
|
+
export type QueryInput = URLSearchParams | Record<string, QueryValue | QueryValue[]>;
|
|
128
|
+
export type QueryValue = string | number | boolean | null | undefined;
|
|
129
|
+
export interface MutateOptions {
|
|
130
|
+
prefer?: string;
|
|
131
|
+
meta?: Record<string, unknown>;
|
|
132
|
+
}
|
|
133
|
+
export interface ImportFile {
|
|
134
|
+
filename: string;
|
|
135
|
+
content: Blob | ArrayBuffer | Uint8Array | string;
|
|
136
|
+
contentType?: string;
|
|
137
|
+
}
|
|
138
|
+
export interface NamespaceRecord {
|
|
139
|
+
name: string;
|
|
140
|
+
created_at?: string;
|
|
141
|
+
db_path?: string;
|
|
142
|
+
config?: NamespaceConfig;
|
|
143
|
+
last_activity_at?: string;
|
|
144
|
+
storage?: StorageSnapshot;
|
|
145
|
+
}
|
|
146
|
+
export type OverviewWindowName = "1h" | "24h" | "7d" | "30d";
|
|
147
|
+
export interface StorageSnapshot {
|
|
148
|
+
used_bytes: number;
|
|
149
|
+
allocated_bytes: number;
|
|
150
|
+
wal_bytes: number;
|
|
151
|
+
disk_bytes: number;
|
|
152
|
+
}
|
|
153
|
+
export interface OverviewLatency {
|
|
154
|
+
count: number;
|
|
155
|
+
average_ms: number;
|
|
156
|
+
p50_ms: number;
|
|
157
|
+
p95_ms: number;
|
|
158
|
+
p99_ms: number;
|
|
159
|
+
}
|
|
160
|
+
export interface NamespaceOverview {
|
|
161
|
+
generated_at: string;
|
|
162
|
+
window: {
|
|
163
|
+
name: OverviewWindowName;
|
|
164
|
+
from: string;
|
|
165
|
+
to: string;
|
|
166
|
+
resolution: "5m" | "1h";
|
|
167
|
+
};
|
|
168
|
+
health: {
|
|
169
|
+
status: "healthy" | "degraded";
|
|
170
|
+
mode: "read_write" | "read_only";
|
|
171
|
+
issues: string[];
|
|
172
|
+
};
|
|
173
|
+
storage: StorageSnapshot & {
|
|
174
|
+
reclaimable_bytes: number;
|
|
175
|
+
quota_bytes: number;
|
|
176
|
+
remaining_bytes: number | null;
|
|
177
|
+
usage_ratio: number | null;
|
|
178
|
+
database_file_bytes: number;
|
|
179
|
+
shm_bytes: number;
|
|
180
|
+
};
|
|
181
|
+
activity: {
|
|
182
|
+
requests: number;
|
|
183
|
+
reads: number;
|
|
184
|
+
writes: number;
|
|
185
|
+
errors: number;
|
|
186
|
+
error_rate: number;
|
|
187
|
+
response_bytes: number;
|
|
188
|
+
last_activity_at: string | null;
|
|
189
|
+
series: Array<{
|
|
190
|
+
timestamp: string;
|
|
191
|
+
reads: number;
|
|
192
|
+
writes: number;
|
|
193
|
+
errors: number;
|
|
194
|
+
}>;
|
|
195
|
+
};
|
|
196
|
+
read_latency: OverviewLatency;
|
|
197
|
+
write_latency: OverviewLatency;
|
|
198
|
+
write_contention: {
|
|
199
|
+
wait: OverviewLatency;
|
|
200
|
+
busy_errors: number;
|
|
201
|
+
timeouts: number;
|
|
202
|
+
quota_rejections: number;
|
|
203
|
+
};
|
|
204
|
+
schema: {
|
|
205
|
+
tables: number;
|
|
206
|
+
views: number;
|
|
207
|
+
indexes: number;
|
|
208
|
+
fts_indexes: number;
|
|
209
|
+
};
|
|
210
|
+
realtime: {
|
|
211
|
+
subscribers: number;
|
|
212
|
+
};
|
|
213
|
+
top_operations: Array<{
|
|
214
|
+
name: string;
|
|
215
|
+
count: number;
|
|
216
|
+
errors: number;
|
|
217
|
+
error_rate: number;
|
|
218
|
+
latency: OverviewLatency;
|
|
219
|
+
}>;
|
|
220
|
+
growth?: {
|
|
221
|
+
bytes_per_day: number;
|
|
222
|
+
sample_count: number;
|
|
223
|
+
projected_full_at: string | null;
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
export interface NamespacesModule {
|
|
227
|
+
create(request: NamespaceDefinition): Promise<RsqlResult<NamespaceRecord>>;
|
|
228
|
+
list(): Promise<RsqlResult<NamespaceRecord[]>>;
|
|
229
|
+
get(name: string): Promise<RsqlResult<NamespaceRecord>>;
|
|
230
|
+
update(name: string, config: NamespaceConfig): Promise<RsqlResult<NamespaceRecord>>;
|
|
231
|
+
delete(name: string): Promise<RsqlResult<void>>;
|
|
232
|
+
duplicate(source: string, target: string): Promise<RsqlResult<{
|
|
233
|
+
source: string;
|
|
234
|
+
target: string;
|
|
235
|
+
}>>;
|
|
236
|
+
/** Stream a consistent SQLite snapshot. The caller consumes response.body. */
|
|
237
|
+
exportDb(name: string): Promise<RsqlResult<Response>>;
|
|
238
|
+
importDb(name: string, file: ImportFile): Promise<RsqlResult<{
|
|
239
|
+
imported: boolean;
|
|
240
|
+
type: string;
|
|
241
|
+
}>>;
|
|
242
|
+
importCsv(name: string, table: string, file: ImportFile): Promise<RsqlResult<{
|
|
243
|
+
inserted: number;
|
|
244
|
+
}>>;
|
|
245
|
+
}
|
|
246
|
+
export interface TablesModule {
|
|
247
|
+
list(): Promise<RsqlResult<Array<Record<string, unknown>>>>;
|
|
248
|
+
create(request: TableCreateRequest): Promise<RsqlResult<{
|
|
249
|
+
created: string;
|
|
250
|
+
type: string;
|
|
251
|
+
}>>;
|
|
252
|
+
get(name: string): Promise<RsqlResult<Record<string, unknown>>>;
|
|
253
|
+
update(name: string, request: TableUpdateRequest): Promise<RsqlResult<{
|
|
254
|
+
updated: boolean;
|
|
255
|
+
}>>;
|
|
256
|
+
delete(name: string, meta?: Record<string, unknown>): Promise<RsqlResult<void>>;
|
|
257
|
+
}
|
|
258
|
+
export interface RowsModule<Row extends Record<string, unknown>> {
|
|
259
|
+
list(query?: QueryInput): Promise<RsqlResult<ListResponse<Row> | {
|
|
260
|
+
data: Row[];
|
|
261
|
+
}>>;
|
|
262
|
+
insert(rows: Partial<Row> | Array<Partial<Row>>, options?: MutateOptions): Promise<RsqlResult<{
|
|
263
|
+
data: Row[];
|
|
264
|
+
} | {
|
|
265
|
+
inserted: number;
|
|
266
|
+
}>>;
|
|
267
|
+
get(id: number): Promise<RsqlResult<Row>>;
|
|
268
|
+
update(id: number, payload: Record<string, unknown>, options?: MutateOptions): Promise<RsqlResult<{
|
|
269
|
+
data: Row[];
|
|
270
|
+
} | {
|
|
271
|
+
updated: number;
|
|
272
|
+
}>>;
|
|
273
|
+
delete(id: number, options?: MutateOptions): Promise<RsqlResult<{
|
|
274
|
+
data: Row[];
|
|
275
|
+
} | {
|
|
276
|
+
deleted: number;
|
|
277
|
+
} | void>>;
|
|
278
|
+
bulkUpdate(query: QueryInput, payload: Record<string, unknown>, options?: MutateOptions): Promise<RsqlResult<{
|
|
279
|
+
data: Row[];
|
|
280
|
+
} | {
|
|
281
|
+
updated: number;
|
|
282
|
+
}>>;
|
|
283
|
+
bulkDelete(query: QueryInput, options?: MutateOptions): Promise<RsqlResult<{
|
|
284
|
+
data: Row[];
|
|
285
|
+
} | {
|
|
286
|
+
deleted: number;
|
|
287
|
+
} | void>>;
|
|
288
|
+
}
|
|
289
|
+
export interface TableExportOptions {
|
|
290
|
+
format: "csv";
|
|
291
|
+
/** Filter / select / order / search params, identical to rows.list. */
|
|
292
|
+
query?: QueryInput;
|
|
293
|
+
/** Prepend the UTF-8 BOM (0xEF 0xBB 0xBF) for Excel compatibility. */
|
|
294
|
+
bom?: boolean;
|
|
295
|
+
}
|
|
296
|
+
export interface TableModule<Row extends Record<string, unknown>> {
|
|
297
|
+
rows: RowsModule<Row>;
|
|
298
|
+
indexes: {
|
|
299
|
+
create(request: IndexCreateRequest): Promise<RsqlResult<{
|
|
300
|
+
created: boolean;
|
|
301
|
+
}>>;
|
|
302
|
+
delete(indexName: string, meta?: Record<string, unknown>): Promise<RsqlResult<void>>;
|
|
303
|
+
};
|
|
304
|
+
schema: {
|
|
305
|
+
get(): Promise<RsqlResult<Record<string, unknown>>>;
|
|
306
|
+
update(request: TableUpdateRequest): Promise<RsqlResult<{
|
|
307
|
+
updated: boolean;
|
|
308
|
+
}>>;
|
|
309
|
+
delete(meta?: Record<string, unknown>): Promise<RsqlResult<void>>;
|
|
310
|
+
};
|
|
311
|
+
/**
|
|
312
|
+
* Stream the table contents in the requested format. Returns the underlying
|
|
313
|
+
* Response so callers can decide how to consume the body (text(), blob(),
|
|
314
|
+
* getReader(), pipe to disk).
|
|
315
|
+
*/
|
|
316
|
+
export(options: TableExportOptions): Promise<RsqlResult<Response>>;
|
|
317
|
+
}
|
|
318
|
+
export interface NamespaceModule {
|
|
319
|
+
readonly name: string;
|
|
320
|
+
tables: TablesModule;
|
|
321
|
+
table<Row extends Record<string, unknown> = Record<string, unknown>>(name: string): TableModule<Row>;
|
|
322
|
+
query: {
|
|
323
|
+
run(request: QueryRequest): Promise<RsqlResult<Record<string, unknown>>>;
|
|
324
|
+
batch(statements: QueryStatement[]): Promise<RsqlResult<Record<string, unknown>>>;
|
|
325
|
+
};
|
|
326
|
+
changelog: {
|
|
327
|
+
list(options?: {
|
|
328
|
+
table?: string;
|
|
329
|
+
limit?: number;
|
|
330
|
+
offset?: number;
|
|
331
|
+
}): Promise<RsqlResult<ChangelogEntry[]>>;
|
|
332
|
+
};
|
|
333
|
+
overview: {
|
|
334
|
+
get(options?: {
|
|
335
|
+
window?: OverviewWindowName;
|
|
336
|
+
}): Promise<RsqlResult<NamespaceOverview>>;
|
|
337
|
+
};
|
|
338
|
+
events: {
|
|
339
|
+
subscribe(options?: SSESubscribeOptions): Promise<RsqlResult<SSESubscription>>;
|
|
340
|
+
};
|
|
341
|
+
}
|
|
342
|
+
export interface RsqlClient {
|
|
343
|
+
namespaces: NamespacesModule;
|
|
344
|
+
ns(name: string): NamespaceModule;
|
|
345
|
+
}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/package.json
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@k2b/rsql",
|
|
3
|
+
"version": "0.4.0",
|
|
4
|
+
"description": "Type-safe fetch client for rsql",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"sideEffects": false,
|
|
8
|
+
"main": "./dist/index.js",
|
|
9
|
+
"module": "./dist/index.js",
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"files": [
|
|
12
|
+
"dist",
|
|
13
|
+
"LICENSE",
|
|
14
|
+
"README.md",
|
|
15
|
+
"package.json"
|
|
16
|
+
],
|
|
17
|
+
"exports": {
|
|
18
|
+
".": {
|
|
19
|
+
"types": "./dist/index.d.ts",
|
|
20
|
+
"import": "./dist/index.js",
|
|
21
|
+
"default": "./dist/index.js"
|
|
22
|
+
}
|
|
23
|
+
},
|
|
24
|
+
"repository": {
|
|
25
|
+
"type": "git",
|
|
26
|
+
"url": "git+https://github.com/k2b-dev/rsql.git",
|
|
27
|
+
"directory": "client"
|
|
28
|
+
},
|
|
29
|
+
"homepage": "https://github.com/k2b-dev/rsql#readme",
|
|
30
|
+
"bugs": {
|
|
31
|
+
"url": "https://github.com/k2b-dev/rsql/issues"
|
|
32
|
+
},
|
|
33
|
+
"publishConfig": {
|
|
34
|
+
"access": "public",
|
|
35
|
+
"registry": "https://registry.npmjs.org/"
|
|
36
|
+
},
|
|
37
|
+
"scripts": {
|
|
38
|
+
"build": "rm -rf dist && bunx tsc -p tsconfig.build.json",
|
|
39
|
+
"typecheck": "bunx tsc --noEmit",
|
|
40
|
+
"test": "bun test ./tests",
|
|
41
|
+
"test:package": "./scripts/test-package.sh",
|
|
42
|
+
"test:e2e": "bun test ./tests/e2e.test.ts",
|
|
43
|
+
"test:perf": "bun test ./tests/performance.test.ts",
|
|
44
|
+
"perf:fast": "bun run ./perf/cli.ts fast",
|
|
45
|
+
"perf:deep": "bun run ./perf/cli.ts deep",
|
|
46
|
+
"perf:report": "bun run ./perf/cli.ts report",
|
|
47
|
+
"prepack": "bun run build"
|
|
48
|
+
},
|
|
49
|
+
"devDependencies": {
|
|
50
|
+
"@types/bun": "1.3.14",
|
|
51
|
+
"typescript": "5.9.3"
|
|
52
|
+
},
|
|
53
|
+
"packageManager": "bun@1.3.14"
|
|
54
|
+
}
|