@alchemy.run/turso 0.0.0 → 2.0.0-beta.84
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 +201 -0
- package/README.md +68 -4
- package/lib/ApiToken.d.ts +73 -0
- package/lib/ApiToken.d.ts.map +1 -0
- package/lib/ApiToken.js +120 -0
- package/lib/ApiToken.js.map +1 -0
- package/lib/AuthProvider.d.ts +36 -0
- package/lib/AuthProvider.d.ts.map +1 -0
- package/lib/AuthProvider.js +85 -0
- package/lib/AuthProvider.js.map +1 -0
- package/lib/Connect.d.ts +79 -0
- package/lib/Connect.d.ts.map +1 -0
- package/lib/Connect.js +22 -0
- package/lib/Connect.js.map +1 -0
- package/lib/Contributions.d.ts +7 -0
- package/lib/Contributions.d.ts.map +1 -0
- package/lib/Contributions.js +6 -0
- package/lib/Contributions.js.map +1 -0
- package/lib/Credentials.d.ts +39 -0
- package/lib/Credentials.d.ts.map +1 -0
- package/lib/Credentials.js +80 -0
- package/lib/Credentials.js.map +1 -0
- package/lib/Database.d.ts +217 -0
- package/lib/Database.d.ts.map +1 -0
- package/lib/Database.js +271 -0
- package/lib/Database.js.map +1 -0
- package/lib/DatabaseToken.d.ts +95 -0
- package/lib/DatabaseToken.d.ts.map +1 -0
- package/lib/DatabaseToken.js +128 -0
- package/lib/DatabaseToken.js.map +1 -0
- package/lib/Group.d.ts +93 -0
- package/lib/Group.d.ts.map +1 -0
- package/lib/Group.js +162 -0
- package/lib/Group.js.map +1 -0
- package/lib/GroupToken.d.ts +48 -0
- package/lib/GroupToken.d.ts.map +1 -0
- package/lib/GroupToken.js +83 -0
- package/lib/GroupToken.js.map +1 -0
- package/lib/Hrana.d.ts +42 -0
- package/lib/Hrana.d.ts.map +1 -0
- package/lib/Hrana.js +105 -0
- package/lib/Hrana.js.map +1 -0
- package/lib/ManageDatabases.d.ts +73 -0
- package/lib/ManageDatabases.d.ts.map +1 -0
- package/lib/ManageDatabases.js +81 -0
- package/lib/ManageDatabases.js.map +1 -0
- package/lib/OrganizationInvite.d.ts +46 -0
- package/lib/OrganizationInvite.d.ts.map +1 -0
- package/lib/OrganizationInvite.js +72 -0
- package/lib/OrganizationInvite.js.map +1 -0
- package/lib/OrganizationMember.d.ts +47 -0
- package/lib/OrganizationMember.d.ts.map +1 -0
- package/lib/OrganizationMember.js +81 -0
- package/lib/OrganizationMember.js.map +1 -0
- package/lib/OrganizationSettings.d.ts +52 -0
- package/lib/OrganizationSettings.d.ts.map +1 -0
- package/lib/OrganizationSettings.js +93 -0
- package/lib/OrganizationSettings.js.map +1 -0
- package/lib/Providers.d.ts +24 -0
- package/lib/Providers.d.ts.map +1 -0
- package/lib/Providers.js +42 -0
- package/lib/Providers.js.map +1 -0
- package/lib/index.d.ts +14 -0
- package/lib/index.d.ts.map +1 -0
- package/lib/index.js +14 -0
- package/lib/index.js.map +1 -0
- package/package.json +54 -3
- package/src/ApiToken.ts +197 -0
- package/src/AuthProvider.ts +113 -0
- package/src/Connect.ts +103 -0
- package/src/Contributions.ts +9 -0
- package/src/Credentials.ts +139 -0
- package/src/Database.ts +504 -0
- package/src/DatabaseToken.ts +203 -0
- package/src/Group.ts +256 -0
- package/src/GroupToken.ts +119 -0
- package/src/Hrana.ts +182 -0
- package/src/ManageDatabases.ts +195 -0
- package/src/OrganizationInvite.ts +118 -0
- package/src/OrganizationMember.ts +122 -0
- package/src/OrganizationSettings.ts +145 -0
- package/src/Providers.ts +67 -0
- package/src/index.ts +20 -0
package/lib/Group.js
ADDED
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import * as turso from "@distilled.cloud/turso/turso";
|
|
2
|
+
import { Unowned } from "alchemy/AdoptPolicy";
|
|
3
|
+
import { deepEqual, isResolved } from "alchemy/Diff";
|
|
4
|
+
import { createPhysicalName } from "alchemy/PhysicalName";
|
|
5
|
+
import * as Provider from "alchemy/Provider";
|
|
6
|
+
import { Resource } from "alchemy/Resource";
|
|
7
|
+
import * as Effect from "effect/Effect";
|
|
8
|
+
import { organization } from "./Credentials.js";
|
|
9
|
+
/**
|
|
10
|
+
* A Turso group. Every Turso database belongs to one group, and the group
|
|
11
|
+
* decides where its databases run.
|
|
12
|
+
* @see https://docs.turso.tech/concepts#groups
|
|
13
|
+
*
|
|
14
|
+
* ### Creating a Group
|
|
15
|
+
* **Example:** Group in a single location
|
|
16
|
+
* ```typescript
|
|
17
|
+
* const group = yield* Turso.Group("Group", {
|
|
18
|
+
* location: "aws-us-east-1",
|
|
19
|
+
* });
|
|
20
|
+
* ```
|
|
21
|
+
*
|
|
22
|
+
* **Example:** Protected group with all extensions enabled
|
|
23
|
+
* ```typescript
|
|
24
|
+
* const group = yield* Turso.Group("Group", {
|
|
25
|
+
* name: "production",
|
|
26
|
+
* location: "aws-eu-west-1",
|
|
27
|
+
* extensions: "all",
|
|
28
|
+
* deleteProtection: true,
|
|
29
|
+
* });
|
|
30
|
+
* ```
|
|
31
|
+
*
|
|
32
|
+
* ### Adding databases
|
|
33
|
+
* **Example:** Create a database in the group
|
|
34
|
+
* ```typescript
|
|
35
|
+
* const db = yield* Turso.Database("Db", { group: group.name });
|
|
36
|
+
* ```
|
|
37
|
+
*
|
|
38
|
+
* @resource
|
|
39
|
+
* @product Group
|
|
40
|
+
*/
|
|
41
|
+
export const Group = Resource("Turso.Group");
|
|
42
|
+
const DEFAULT_LOCATION = "aws-us-east-1";
|
|
43
|
+
export const GroupProvider = () => Provider.effect(Group, Effect.gen(function* () {
|
|
44
|
+
const groupName = (id, name) => name
|
|
45
|
+
? Effect.succeed(name)
|
|
46
|
+
: createPhysicalName({ id, lowercase: true, maxLength: 32, delimiter: "-" });
|
|
47
|
+
const observe = (org, name) => turso.getGroup({ organizationSlug: org, groupName: name }).pipe(Effect.map((res) => res.group), Effect.catchTag("NotFound", () => Effect.succeed(undefined)));
|
|
48
|
+
const toAttrs = (org, group, deleteProtection) => ({
|
|
49
|
+
name: group.name,
|
|
50
|
+
uuid: group.uuid ?? "",
|
|
51
|
+
organization: org,
|
|
52
|
+
location: group.primary ?? "",
|
|
53
|
+
locations: group.locations ?? [],
|
|
54
|
+
version: group.version ?? "",
|
|
55
|
+
deleteProtection,
|
|
56
|
+
});
|
|
57
|
+
return {
|
|
58
|
+
stables: ["name", "uuid", "organization", "location"],
|
|
59
|
+
diff: Effect.fn(function* ({ id, olds, news, output }) {
|
|
60
|
+
if (!isResolved(news))
|
|
61
|
+
return undefined;
|
|
62
|
+
const name = output?.name ?? (yield* groupName(id, olds?.name));
|
|
63
|
+
if (news.name !== undefined && news.name !== name) {
|
|
64
|
+
return { action: "replace" };
|
|
65
|
+
}
|
|
66
|
+
if ((news.location ?? DEFAULT_LOCATION) !== (olds?.location ?? DEFAULT_LOCATION)) {
|
|
67
|
+
return { action: "replace", deleteFirst: news.name !== undefined };
|
|
68
|
+
}
|
|
69
|
+
if (!deepEqual(news.extensions, olds?.extensions)) {
|
|
70
|
+
return { action: "replace", deleteFirst: news.name !== undefined };
|
|
71
|
+
}
|
|
72
|
+
}),
|
|
73
|
+
read: Effect.fn(function* ({ id, olds, output }) {
|
|
74
|
+
const org = output?.organization ?? (yield* organization);
|
|
75
|
+
const name = output?.name ?? (yield* groupName(id, olds?.name));
|
|
76
|
+
const group = yield* observe(org, name);
|
|
77
|
+
if (!group)
|
|
78
|
+
return undefined;
|
|
79
|
+
const attrs = toAttrs(org, group, group.delete_protection ?? false);
|
|
80
|
+
// Turso has no tags: a group found under an explicit, user-chosen
|
|
81
|
+
// name without prior state may belong to someone else.
|
|
82
|
+
return output === undefined && olds?.name !== undefined ? Unowned(attrs) : attrs;
|
|
83
|
+
}),
|
|
84
|
+
reconcile: Effect.fn(function* ({ id, news, output, session }) {
|
|
85
|
+
const org = output?.organization ?? (yield* organization);
|
|
86
|
+
const name = output?.name ?? (yield* groupName(id, news.name));
|
|
87
|
+
const location = news.location ?? DEFAULT_LOCATION;
|
|
88
|
+
// Observe
|
|
89
|
+
let group = yield* observe(org, name);
|
|
90
|
+
// Ensure
|
|
91
|
+
if (!group) {
|
|
92
|
+
yield* session.note(`Creating group ${name}...`);
|
|
93
|
+
group = yield* turso
|
|
94
|
+
.createGroup({
|
|
95
|
+
organizationSlug: org,
|
|
96
|
+
name,
|
|
97
|
+
location,
|
|
98
|
+
extensions: news.extensions,
|
|
99
|
+
})
|
|
100
|
+
.pipe(Effect.map((res) => res.group), Effect.catchTag("Conflict", () => observe(org, name)));
|
|
101
|
+
if (!group) {
|
|
102
|
+
return yield* Effect.fail(new Error(`Turso group ${name} vanished after create`));
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
// Sync replica locations against the observed set.
|
|
106
|
+
const primary = group.primary ?? location;
|
|
107
|
+
const desired = new Set([primary, ...(news.replicas ?? [])]);
|
|
108
|
+
const observed = new Set(group.locations ?? [primary]);
|
|
109
|
+
for (const loc of desired) {
|
|
110
|
+
if (!observed.has(loc)) {
|
|
111
|
+
yield* session.note(`Adding location ${loc}...`);
|
|
112
|
+
group =
|
|
113
|
+
(yield* turso.addLocationToGroup({
|
|
114
|
+
organizationSlug: org,
|
|
115
|
+
groupName: name,
|
|
116
|
+
location: loc,
|
|
117
|
+
})).group ?? group;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
for (const loc of observed) {
|
|
121
|
+
if (!desired.has(loc)) {
|
|
122
|
+
yield* session.note(`Removing location ${loc}...`);
|
|
123
|
+
group =
|
|
124
|
+
(yield* turso.removeLocationFromGroup({
|
|
125
|
+
organizationSlug: org,
|
|
126
|
+
groupName: name,
|
|
127
|
+
location: loc,
|
|
128
|
+
})).group ?? group;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
// Sync configuration.
|
|
132
|
+
const config = yield* turso.getGroupConfiguration({
|
|
133
|
+
organizationSlug: org,
|
|
134
|
+
groupName: name,
|
|
135
|
+
});
|
|
136
|
+
let deleteProtection = config.delete_protection ?? false;
|
|
137
|
+
const desiredProtection = news.deleteProtection ?? false;
|
|
138
|
+
if (deleteProtection !== desiredProtection) {
|
|
139
|
+
const updated = yield* turso.updateGroupConfiguration({
|
|
140
|
+
organizationSlug: org,
|
|
141
|
+
groupName: name,
|
|
142
|
+
delete_protection: desiredProtection,
|
|
143
|
+
});
|
|
144
|
+
deleteProtection = updated.delete_protection ?? desiredProtection;
|
|
145
|
+
}
|
|
146
|
+
return toAttrs(org, group, deleteProtection);
|
|
147
|
+
}),
|
|
148
|
+
delete: Effect.fn(function* ({ output }) {
|
|
149
|
+
yield* turso
|
|
150
|
+
.deleteGroup({ organizationSlug: output.organization, groupName: output.name })
|
|
151
|
+
.pipe(Effect.catchTag("NotFound", () => Effect.void));
|
|
152
|
+
}),
|
|
153
|
+
list: Effect.fn(function* () {
|
|
154
|
+
const org = yield* organization;
|
|
155
|
+
const { groups } = yield* turso.listGroups({ organizationSlug: org });
|
|
156
|
+
return (groups ?? [])
|
|
157
|
+
.filter((group) => group.name !== undefined)
|
|
158
|
+
.map((group) => toAttrs(org, group, group.delete_protection ?? false));
|
|
159
|
+
}),
|
|
160
|
+
};
|
|
161
|
+
}));
|
|
162
|
+
//# sourceMappingURL=Group.js.map
|
package/lib/Group.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Group.js","sourceRoot":"","sources":["../src/Group.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import * as turso from "@distilled.cloud/turso/turso";
|
|
2
|
+
import * as Provider from "alchemy/Provider";
|
|
3
|
+
import { Resource } from "alchemy/Resource";
|
|
4
|
+
import { type SqlTokenAttributes, type SqlTokenProps } from "./DatabaseToken.ts";
|
|
5
|
+
import type { Providers } from "./Providers.ts";
|
|
6
|
+
export interface GroupTokenProps extends SqlTokenProps {
|
|
7
|
+
/** Name of the group whose databases the token grants access to. */
|
|
8
|
+
group: string;
|
|
9
|
+
}
|
|
10
|
+
export interface GroupTokenAttributes extends SqlTokenAttributes {
|
|
11
|
+
/** Name of the group the token grants access to. */
|
|
12
|
+
group: string;
|
|
13
|
+
/** UUID of the group the token was minted for. */
|
|
14
|
+
groupId: string;
|
|
15
|
+
}
|
|
16
|
+
export type GroupToken = Resource<"Turso.GroupToken", GroupTokenProps, GroupTokenAttributes, never, Providers>;
|
|
17
|
+
/**
|
|
18
|
+
* A token that lets an app run SQL on every database in a Turso
|
|
19
|
+
* {@link Group}, even databases created later. Useful when each customer
|
|
20
|
+
* has their own database.
|
|
21
|
+
*
|
|
22
|
+
* Like {@link DatabaseToken}, you can't cancel a single group token. Changing
|
|
23
|
+
* a prop creates a new token, but the old one keeps working until it expires.
|
|
24
|
+
* @see https://docs.turso.tech/api-reference/groups/create-token
|
|
25
|
+
*
|
|
26
|
+
* ### Creating a Token
|
|
27
|
+
* **Example:** Full-access token for every database in a group
|
|
28
|
+
* ```typescript
|
|
29
|
+
* const token = yield* Turso.GroupToken("Token", {
|
|
30
|
+
* group: group.name,
|
|
31
|
+
* });
|
|
32
|
+
* ```
|
|
33
|
+
*
|
|
34
|
+
* **Example:** Read-only token that expires after a week
|
|
35
|
+
* ```typescript
|
|
36
|
+
* const token = yield* Turso.GroupToken("ReadOnly", {
|
|
37
|
+
* group: group.name,
|
|
38
|
+
* authorization: "read-only",
|
|
39
|
+
* expiration: "7d",
|
|
40
|
+
* });
|
|
41
|
+
* ```
|
|
42
|
+
*
|
|
43
|
+
* @resource
|
|
44
|
+
* @product Group
|
|
45
|
+
*/
|
|
46
|
+
export declare const GroupToken: import("alchemy/Resource").ResourceClass<GroupToken>;
|
|
47
|
+
export declare const GroupTokenProvider: () => import("effect/Layer").Layer<Provider.Provider<GroupToken>, never, import("./Credentials.ts").Organization | turso.TursoOpContext>;
|
|
48
|
+
//# sourceMappingURL=GroupToken.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"GroupToken.d.ts","sourceRoot":"","sources":["../src/GroupToken.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,8BAA8B,CAAC;AACtD,OAAO,KAAK,QAAQ,MAAM,kBAAkB,CAAC;AAC7C,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAG5C,OAAO,EAIL,KAAK,kBAAkB,EACvB,KAAK,aAAa,EACnB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAEhD,MAAM,WAAW,eAAgB,SAAQ,aAAa;IACpD,oEAAoE;IACpE,KAAK,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,oBAAqB,SAAQ,kBAAkB;IAC9D,oDAAoD;IACpD,KAAK,EAAE,MAAM,CAAC;IACd,kDAAkD;IAClD,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,MAAM,UAAU,GAAG,QAAQ,CAC/B,kBAAkB,EAClB,eAAe,EACf,oBAAoB,EACpB,KAAK,EACL,SAAS,CACV,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,eAAO,MAAM,UAAU,sDAA2C,CAAC;AAEnE,eAAO,MAAM,kBAAkB,0IAqD5B,CAAC"}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import * as turso from "@distilled.cloud/turso/turso";
|
|
2
|
+
import * as Provider from "alchemy/Provider";
|
|
3
|
+
import { Resource } from "alchemy/Resource";
|
|
4
|
+
import * as Effect from "effect/Effect";
|
|
5
|
+
import { organization } from "./Credentials.js";
|
|
6
|
+
import { diffSqlToken, isTokenCurrent, tokenExpiry, } from "./DatabaseToken.js";
|
|
7
|
+
/**
|
|
8
|
+
* A token that lets an app run SQL on every database in a Turso
|
|
9
|
+
* {@link Group}, even databases created later. Useful when each customer
|
|
10
|
+
* has their own database.
|
|
11
|
+
*
|
|
12
|
+
* Like {@link DatabaseToken}, you can't cancel a single group token. Changing
|
|
13
|
+
* a prop creates a new token, but the old one keeps working until it expires.
|
|
14
|
+
* @see https://docs.turso.tech/api-reference/groups/create-token
|
|
15
|
+
*
|
|
16
|
+
* ### Creating a Token
|
|
17
|
+
* **Example:** Full-access token for every database in a group
|
|
18
|
+
* ```typescript
|
|
19
|
+
* const token = yield* Turso.GroupToken("Token", {
|
|
20
|
+
* group: group.name,
|
|
21
|
+
* });
|
|
22
|
+
* ```
|
|
23
|
+
*
|
|
24
|
+
* **Example:** Read-only token that expires after a week
|
|
25
|
+
* ```typescript
|
|
26
|
+
* const token = yield* Turso.GroupToken("ReadOnly", {
|
|
27
|
+
* group: group.name,
|
|
28
|
+
* authorization: "read-only",
|
|
29
|
+
* expiration: "7d",
|
|
30
|
+
* });
|
|
31
|
+
* ```
|
|
32
|
+
*
|
|
33
|
+
* @resource
|
|
34
|
+
* @product Group
|
|
35
|
+
*/
|
|
36
|
+
export const GroupToken = Resource("Turso.GroupToken");
|
|
37
|
+
export const GroupTokenProvider = () => Provider.effect(GroupToken, Effect.gen(function* () {
|
|
38
|
+
return {
|
|
39
|
+
stables: ["organization"],
|
|
40
|
+
diff: diffSqlToken,
|
|
41
|
+
reconcile: Effect.fn(function* ({ news, olds, output }) {
|
|
42
|
+
const org = output?.organization ?? (yield* organization);
|
|
43
|
+
// Observe — the token is bound to the group's UUID, so a group
|
|
44
|
+
// replaced under the same name invalidates it.
|
|
45
|
+
const { group } = yield* turso.getGroup({
|
|
46
|
+
organizationSlug: org,
|
|
47
|
+
groupName: news.group,
|
|
48
|
+
});
|
|
49
|
+
const groupId = group?.uuid ?? "";
|
|
50
|
+
if (output !== undefined &&
|
|
51
|
+
output.group === news.group &&
|
|
52
|
+
output.groupId === groupId &&
|
|
53
|
+
(yield* isTokenCurrent(output, olds, news))) {
|
|
54
|
+
return output;
|
|
55
|
+
}
|
|
56
|
+
// Ensure — mint.
|
|
57
|
+
const authorization = news.authorization ?? "full-access";
|
|
58
|
+
const { jwt } = yield* turso.createGroupToken({
|
|
59
|
+
organizationSlug: org,
|
|
60
|
+
groupName: news.group,
|
|
61
|
+
authorization,
|
|
62
|
+
expiration: news.expiration,
|
|
63
|
+
permissions: news.readAttach
|
|
64
|
+
? { read_attach: { databases: news.readAttach } }
|
|
65
|
+
: undefined,
|
|
66
|
+
});
|
|
67
|
+
if (!jwt) {
|
|
68
|
+
return yield* Effect.fail(new Error(`Turso returned no token for group ${news.group}`));
|
|
69
|
+
}
|
|
70
|
+
return {
|
|
71
|
+
token: jwt,
|
|
72
|
+
organization: org,
|
|
73
|
+
authorization,
|
|
74
|
+
expiresAt: tokenExpiry(jwt),
|
|
75
|
+
group: news.group,
|
|
76
|
+
groupId,
|
|
77
|
+
};
|
|
78
|
+
}),
|
|
79
|
+
// Turso cannot revoke a single token; it simply stops being tracked.
|
|
80
|
+
delete: () => Effect.void,
|
|
81
|
+
};
|
|
82
|
+
}));
|
|
83
|
+
//# sourceMappingURL=GroupToken.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"GroupToken.js","sourceRoot":"","sources":["../src/GroupToken.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,8BAA8B,CAAC;AACtD,OAAO,KAAK,QAAQ,MAAM,kBAAkB,CAAC;AAC7C,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAC5C,OAAO,KAAK,MAAM,MAAM,eAAe,CAAC;AACxC,OAAO,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAChD,OAAO,EACL,YAAY,EACZ,cAAc,EACd,WAAW,GAGZ,MAAM,oBAAoB,CAAC;AAuB5B;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,MAAM,CAAC,MAAM,UAAU,GAAG,QAAQ,CAAa,kBAAkB,CAAC,CAAC;AAEnE,MAAM,CAAC,MAAM,kBAAkB,GAAG,GAAG,EAAE,CACrC,QAAQ,CAAC,MAAM,CACb,UAAU,EACV,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC;IAClB,OAAO;QACL,OAAO,EAAE,CAAC,cAAc,CAAC;QACzB,IAAI,EAAE,YAAY;QAClB,SAAS,EAAE,MAAM,CAAC,EAAE,CAAC,QAAQ,CAAC,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE;YACpD,MAAM,GAAG,GAAG,MAAM,EAAE,YAAY,IAAI,CAAC,KAAK,CAAC,CAAC,YAAY,CAAC,CAAC;YAE1D,+DAA+D;YAC/D,+CAA+C;YAC/C,MAAM,EAAE,KAAK,EAAE,GAAG,KAAK,CAAC,CAAC,KAAK,CAAC,QAAQ,CAAC;gBACtC,gBAAgB,EAAE,GAAG;gBACrB,SAAS,EAAE,IAAI,CAAC,KAAK;aACtB,CAAC,CAAC;YACH,MAAM,OAAO,GAAG,KAAK,EAAE,IAAI,IAAI,EAAE,CAAC;YAClC,IACE,MAAM,KAAK,SAAS;gBACpB,MAAM,CAAC,KAAK,KAAK,IAAI,CAAC,KAAK;gBAC3B,MAAM,CAAC,OAAO,KAAK,OAAO;gBAC1B,CAAC,KAAK,CAAC,CAAC,cAAc,CAAC,MAAM,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC,EAC3C,CAAC;gBACD,OAAO,MAAM,CAAC;YAChB,CAAC;YAED,iBAAiB;YACjB,MAAM,aAAa,GAAG,IAAI,CAAC,aAAa,IAAI,aAAa,CAAC;YAC1D,MAAM,EAAE,GAAG,EAAE,GAAG,KAAK,CAAC,CAAC,KAAK,CAAC,gBAAgB,CAAC;gBAC5C,gBAAgB,EAAE,GAAG;gBACrB,SAAS,EAAE,IAAI,CAAC,KAAK;gBACrB,aAAa;gBACb,UAAU,EAAE,IAAI,CAAC,UAAU;gBAC3B,WAAW,EAAE,IAAI,CAAC,UAAU;oBAC1B,CAAC,CAAC,EAAE,WAAW,EAAE,EAAE,SAAS,EAAE,IAAI,CAAC,UAAU,EAAE,EAAE;oBACjD,CAAC,CAAC,SAAS;aACd,CAAC,CAAC;YACH,IAAI,CAAC,GAAG,EAAE,CAAC;gBACT,OAAO,KAAK,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,KAAK,CAAC,qCAAqC,IAAI,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC;YAC1F,CAAC;YACD,OAAO;gBACL,KAAK,EAAE,GAAG;gBACV,YAAY,EAAE,GAAG;gBACjB,aAAa;gBACb,SAAS,EAAE,WAAW,CAAC,GAAG,CAAC;gBAC3B,KAAK,EAAE,IAAI,CAAC,KAAK;gBACjB,OAAO;aACR,CAAC;QACJ,CAAC,CAAC;QACF,qEAAqE;QACrE,MAAM,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,IAAI;KAC1B,CAAC;AACJ,CAAC,CAAC,CACH,CAAC"}
|
package/lib/Hrana.d.ts
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { type SqlExecutor } from "@alchemy.run/sql/Migrations";
|
|
2
|
+
import * as Effect from "effect/Effect";
|
|
3
|
+
import * as HttpClient from "effect/http/HttpClient";
|
|
4
|
+
import * as Redacted from "effect/Redacted";
|
|
5
|
+
declare const HranaError_base: new <A extends Record<string, any> = {}>(args: import("effect/Types").VoidIfEmpty<{ readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }>) => import("effect/Cause").YieldableError & {
|
|
6
|
+
readonly _tag: "Turso::HranaError";
|
|
7
|
+
} & Readonly<A>;
|
|
8
|
+
/**
|
|
9
|
+
* A minimal Hrana-over-HTTP client (`POST /v2/pipeline`) — the SQL wire
|
|
10
|
+
* protocol every Turso database speaks. Deploy-time work (migrations, SQL
|
|
11
|
+
* imports) uses it instead of `@libsql/client`, so the provider needs no
|
|
12
|
+
* driver and runs anywhere `HttpClient` does.
|
|
13
|
+
*
|
|
14
|
+
* @see https://github.com/tursodatabase/libsql/blob/main/docs/HRANA_3_SPEC.md
|
|
15
|
+
*/
|
|
16
|
+
export declare class HranaError extends HranaError_base<{
|
|
17
|
+
message: string;
|
|
18
|
+
code?: string;
|
|
19
|
+
cause?: unknown;
|
|
20
|
+
}> {
|
|
21
|
+
}
|
|
22
|
+
/** Connection details for one Turso database. */
|
|
23
|
+
export interface HranaConnection {
|
|
24
|
+
/** `https://<hostname>` of the database. */
|
|
25
|
+
url: string;
|
|
26
|
+
/** A database auth token (JWT). */
|
|
27
|
+
authToken: Redacted.Redacted<string>;
|
|
28
|
+
}
|
|
29
|
+
/** Execute one statement and return its rows as objects. */
|
|
30
|
+
export declare const query: (connection: HranaConnection, sql: string, params?: ReadonlyArray<unknown>) => Effect.Effect<Array<Record<string, unknown>>, HranaError, HttpClient.HttpClient>;
|
|
31
|
+
/**
|
|
32
|
+
* Execute statements atomically. Each entry may hold several
|
|
33
|
+
* `;`-separated statements (a whole migration file or seed script), so they
|
|
34
|
+
* run as one Hrana `sequence` wrapped in `BEGIN`/`COMMIT`. A failing
|
|
35
|
+
* statement stops the sequence before `COMMIT`, and closing the stream
|
|
36
|
+
* rolls the open transaction back.
|
|
37
|
+
*/
|
|
38
|
+
export declare const transaction: (connection: HranaConnection, statements: ReadonlyArray<string>) => Effect.Effect<void, HranaError, HttpClient.HttpClient>;
|
|
39
|
+
/** Adapt a Turso database connection into the migration registry's executor. */
|
|
40
|
+
export declare const makeMigrationExecutor: (connection: HranaConnection, client: HttpClient.HttpClient) => SqlExecutor;
|
|
41
|
+
export {};
|
|
42
|
+
//# sourceMappingURL=Hrana.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Hrana.d.ts","sourceRoot":"","sources":["../src/Hrana.ts"],"names":[],"mappings":"AAAA,OAAO,EAAkB,KAAK,WAAW,EAAE,MAAM,6BAA6B,CAAC;AAG/E,OAAO,KAAK,MAAM,MAAM,eAAe,CAAC;AACxC,OAAO,KAAK,UAAU,MAAM,wBAAwB,CAAC;AAErD,OAAO,KAAK,QAAQ,MAAM,iBAAiB,CAAC;;;;AAE5C;;;;;;;GAOG;AACH,qBAAa,UAAW,SAAQ,gBAAsC;IACpE,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB,CAAC;CAAG;AAoEL,iDAAiD;AACjD,MAAM,WAAW,eAAe;IAC9B,4CAA4C;IAC5C,GAAG,EAAE,MAAM,CAAC;IACZ,mCAAmC;IACnC,SAAS,EAAE,QAAQ,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;CACtC;AAyBD,4DAA4D;AAC5D,eAAO,MAAM,KAAK,eACJ,eAAe,OACtB,MAAM,WACF,aAAa,CAAC,OAAO,CAAC,KAC9B,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,EAAE,UAAU,EAAE,UAAU,CAAC,UAAU,CAW9E,CAAC;AAEL;;;;;;GAMG;AACH,eAAO,MAAM,WAAW,eACV,eAAe,cACf,aAAa,CAAC,MAAM,CAAC,KAChC,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,UAAU,EAAE,UAAU,CAAC,UAAU,CAYpD,CAAC;AAEL,gFAAgF;AAChF,eAAO,MAAM,qBAAqB,eACpB,eAAe,UACnB,UAAU,CAAC,UAAU,KAC5B,WAgBF,CAAC"}
|
package/lib/Hrana.js
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { MigrationError } from "@alchemy.run/sql/Migrations";
|
|
2
|
+
import { splitSqlStatements } from "@alchemy.run/sql/SqlFile";
|
|
3
|
+
import * as Data from "effect/Data";
|
|
4
|
+
import * as Effect from "effect/Effect";
|
|
5
|
+
import * as HttpClient from "effect/http/HttpClient";
|
|
6
|
+
import * as HttpClientRequest from "effect/http/HttpClientRequest";
|
|
7
|
+
import * as Redacted from "effect/Redacted";
|
|
8
|
+
/**
|
|
9
|
+
* A minimal Hrana-over-HTTP client (`POST /v2/pipeline`) — the SQL wire
|
|
10
|
+
* protocol every Turso database speaks. Deploy-time work (migrations, SQL
|
|
11
|
+
* imports) uses it instead of `@libsql/client`, so the provider needs no
|
|
12
|
+
* driver and runs anywhere `HttpClient` does.
|
|
13
|
+
*
|
|
14
|
+
* @see https://github.com/tursodatabase/libsql/blob/main/docs/HRANA_3_SPEC.md
|
|
15
|
+
*/
|
|
16
|
+
export class HranaError extends Data.TaggedError("Turso::HranaError") {
|
|
17
|
+
}
|
|
18
|
+
const toHranaValue = (value) => {
|
|
19
|
+
if (value === null || value === undefined)
|
|
20
|
+
return { type: "null" };
|
|
21
|
+
if (typeof value === "bigint")
|
|
22
|
+
return { type: "integer", value: value.toString() };
|
|
23
|
+
if (typeof value === "number") {
|
|
24
|
+
return Number.isInteger(value)
|
|
25
|
+
? { type: "integer", value: value.toString() }
|
|
26
|
+
: { type: "float", value };
|
|
27
|
+
}
|
|
28
|
+
if (typeof value === "boolean")
|
|
29
|
+
return { type: "integer", value: value ? "1" : "0" };
|
|
30
|
+
if (value instanceof Uint8Array) {
|
|
31
|
+
return { type: "blob", base64: Buffer.from(value).toString("base64") };
|
|
32
|
+
}
|
|
33
|
+
return { type: "text", value: String(value) };
|
|
34
|
+
};
|
|
35
|
+
const fromHranaValue = (value) => {
|
|
36
|
+
switch (value.type) {
|
|
37
|
+
case "null":
|
|
38
|
+
return null;
|
|
39
|
+
case "integer":
|
|
40
|
+
return Number(value.value);
|
|
41
|
+
case "float":
|
|
42
|
+
return value.value;
|
|
43
|
+
case "text":
|
|
44
|
+
return value.value;
|
|
45
|
+
case "blob":
|
|
46
|
+
return Buffer.from(value.base64, "base64");
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
const toRows = (result) => result.rows.map((row) => Object.fromEntries(result.cols.map((col, i) => [col.name ?? String(i), fromHranaValue(row[i])])));
|
|
50
|
+
const pipeline = (connection, requests) => Effect.gen(function* () {
|
|
51
|
+
const client = yield* HttpClient.HttpClient;
|
|
52
|
+
const response = yield* client
|
|
53
|
+
.execute(HttpClientRequest.post(`${connection.url.replace(/\/$/, "")}/v2/pipeline`).pipe(HttpClientRequest.bearerToken(Redacted.value(connection.authToken)), HttpClientRequest.bodyJsonUnsafe({ requests: [...requests, { type: "close" }] })))
|
|
54
|
+
.pipe(Effect.mapError((cause) => new HranaError({ message: String(cause), cause })));
|
|
55
|
+
if (response.status < 200 || response.status >= 300) {
|
|
56
|
+
const text = yield* response.text.pipe(Effect.orElseSucceed(() => ""));
|
|
57
|
+
return yield* new HranaError({
|
|
58
|
+
message: `Turso SQL request failed with HTTP ${response.status}: ${text}`,
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
const body = (yield* response.json.pipe(Effect.mapError((cause) => new HranaError({ message: String(cause), cause }))));
|
|
62
|
+
return body.results.slice(0, requests.length);
|
|
63
|
+
});
|
|
64
|
+
/** Execute one statement and return its rows as objects. */
|
|
65
|
+
export const query = (connection, sql, params) => Effect.gen(function* () {
|
|
66
|
+
const stmt = { sql, args: (params ?? []).map(toHranaValue) };
|
|
67
|
+
const [result] = yield* pipeline(connection, [{ type: "execute", stmt }]);
|
|
68
|
+
if (result === undefined) {
|
|
69
|
+
return yield* new HranaError({ message: "Turso returned no result" });
|
|
70
|
+
}
|
|
71
|
+
if (result.type === "error") {
|
|
72
|
+
return yield* new HranaError({ message: result.error.message, code: result.error.code });
|
|
73
|
+
}
|
|
74
|
+
return toRows(result.response.result);
|
|
75
|
+
});
|
|
76
|
+
/**
|
|
77
|
+
* Execute statements atomically. Each entry may hold several
|
|
78
|
+
* `;`-separated statements (a whole migration file or seed script), so they
|
|
79
|
+
* run as one Hrana `sequence` wrapped in `BEGIN`/`COMMIT`. A failing
|
|
80
|
+
* statement stops the sequence before `COMMIT`, and closing the stream
|
|
81
|
+
* rolls the open transaction back.
|
|
82
|
+
*/
|
|
83
|
+
export const transaction = (connection, statements) => Effect.gen(function* () {
|
|
84
|
+
const body = statements.flatMap(splitSqlStatements);
|
|
85
|
+
if (body.length === 0)
|
|
86
|
+
return;
|
|
87
|
+
const sql = ["BEGIN", ...body.map((s) => s.replace(/;\s*$/, "")), "COMMIT"].join(";\n");
|
|
88
|
+
const [result] = yield* pipeline(connection, [{ type: "sequence", sql }]);
|
|
89
|
+
if (result === undefined) {
|
|
90
|
+
return yield* new HranaError({ message: "Turso returned no result" });
|
|
91
|
+
}
|
|
92
|
+
if (result.type === "error") {
|
|
93
|
+
return yield* new HranaError({ message: result.error.message, code: result.error.code });
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
/** Adapt a Turso database connection into the migration registry's executor. */
|
|
97
|
+
export const makeMigrationExecutor = (connection, client) => {
|
|
98
|
+
const toMigrationError = (error) => new MigrationError({ message: `turso: ${error.message}`, cause: error });
|
|
99
|
+
return {
|
|
100
|
+
dialect: "sqlite",
|
|
101
|
+
query: (sql, params) => query(connection, sql, params).pipe(Effect.provideService(HttpClient.HttpClient, client), Effect.mapError(toMigrationError)),
|
|
102
|
+
batch: (statements) => transaction(connection, statements).pipe(Effect.provideService(HttpClient.HttpClient, client), Effect.mapError(toMigrationError)),
|
|
103
|
+
};
|
|
104
|
+
};
|
|
105
|
+
//# sourceMappingURL=Hrana.js.map
|
package/lib/Hrana.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Hrana.js","sourceRoot":"","sources":["../src/Hrana.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import * as turso from "@distilled.cloud/turso/turso";
|
|
2
|
+
import * as Binding from "alchemy/Binding";
|
|
3
|
+
import type { RuntimeContext } from "alchemy/RuntimeContext";
|
|
4
|
+
import * as Effect from "effect/Effect";
|
|
5
|
+
import * as Layer from "effect/Layer";
|
|
6
|
+
import type { ConnectClient } from "./Connect.ts";
|
|
7
|
+
import type { DatabaseSeed } from "./Database.ts";
|
|
8
|
+
import type { Group } from "./Group.ts";
|
|
9
|
+
/** A database in the managed group. */
|
|
10
|
+
export interface ManagedDatabase {
|
|
11
|
+
/** Database name. */
|
|
12
|
+
name: string;
|
|
13
|
+
/** Database UUID. */
|
|
14
|
+
dbId: string;
|
|
15
|
+
/** DNS hostname of the database. */
|
|
16
|
+
hostname: string;
|
|
17
|
+
/** libSQL URL (`libsql://<hostname>`). */
|
|
18
|
+
url: string;
|
|
19
|
+
}
|
|
20
|
+
export interface CreateManagedDatabaseOptions {
|
|
21
|
+
/** Seed the new database from an existing database (e.g. a template). */
|
|
22
|
+
seed?: DatabaseSeed;
|
|
23
|
+
/** Maximum size of the database, e.g. `"256mb"`. */
|
|
24
|
+
sizeLimit?: string;
|
|
25
|
+
}
|
|
26
|
+
export interface ManageDatabasesClient {
|
|
27
|
+
/** Create a database in the group. */
|
|
28
|
+
create(name: string, options?: CreateManagedDatabaseOptions): Effect.Effect<ManagedDatabase, turso.CreateDatabaseError, RuntimeContext>;
|
|
29
|
+
/** Look up a database by name; `undefined` when it doesn't exist. */
|
|
30
|
+
get(name: string): Effect.Effect<ManagedDatabase | undefined, turso.GetDatabaseError, RuntimeContext>;
|
|
31
|
+
/** List every database in the group. */
|
|
32
|
+
list(): Effect.Effect<ManagedDatabase[], turso.ListDatabasesError, RuntimeContext>;
|
|
33
|
+
/** Delete a database. Deleting a missing database succeeds. */
|
|
34
|
+
delete(name: string): Effect.Effect<void, turso.DeleteDatabaseError, RuntimeContext>;
|
|
35
|
+
/**
|
|
36
|
+
* libSQL connection accessors for a database in the group, authorized by
|
|
37
|
+
* a group-wide SQL token. Pass to `SQL.LibSQL` or `Drizzle.LibSQL`.
|
|
38
|
+
*/
|
|
39
|
+
connect(name: string): ConnectClient;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Let a Worker create, list, delete, and query databases in one Turso
|
|
43
|
+
* {@link Group}. Use it to give each customer their own database.
|
|
44
|
+
*
|
|
45
|
+
* The Worker gets an {@link ApiToken} that only works on that group, and a
|
|
46
|
+
* {@link GroupToken} for running SQL. It can't touch databases in any other
|
|
47
|
+
* group, and your own Turso API token stays on your machine.
|
|
48
|
+
*
|
|
49
|
+
* ### Database per tenant
|
|
50
|
+
* **Example:** Create a tenant database on signup and query it
|
|
51
|
+
* ```typescript
|
|
52
|
+
* Effect.gen(function* () {
|
|
53
|
+
* const tenants = yield* Turso.ManageDatabases(Tenants);
|
|
54
|
+
* return {
|
|
55
|
+
* fetch: Effect.gen(function* () {
|
|
56
|
+
* const db = yield* tenants.create("acme");
|
|
57
|
+
* const sql = yield* SQL.LibSQL(tenants.connect(db.name));
|
|
58
|
+
* yield* sql`CREATE TABLE IF NOT EXISTS notes (body TEXT)`;
|
|
59
|
+
* return HttpServerResponse.text(db.url);
|
|
60
|
+
* }),
|
|
61
|
+
* };
|
|
62
|
+
* }).pipe(Effect.provide(Turso.ManageDatabasesHttp));
|
|
63
|
+
* ```
|
|
64
|
+
*
|
|
65
|
+
* @binding
|
|
66
|
+
* @product Group
|
|
67
|
+
*/
|
|
68
|
+
export interface ManageDatabases extends Binding.Service<ManageDatabases, "Turso.ManageDatabases", (group: Group) => Effect.Effect<ManageDatabasesClient>> {
|
|
69
|
+
}
|
|
70
|
+
export declare const ManageDatabases: ManageDatabases;
|
|
71
|
+
/** Implementation over the Turso Platform API, usable from any Platform host. */
|
|
72
|
+
export declare const ManageDatabasesHttp: Layer.Layer<ManageDatabases, never, never>;
|
|
73
|
+
//# sourceMappingURL=ManageDatabases.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ManageDatabases.d.ts","sourceRoot":"","sources":["../src/ManageDatabases.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,KAAK,MAAM,8BAA8B,CAAC;AACtD,OAAO,KAAK,OAAO,MAAM,iBAAiB,CAAC;AAC3C,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AAC7D,OAAO,KAAK,MAAM,MAAM,eAAe,CAAC;AAGxC,OAAO,KAAK,KAAK,MAAM,cAAc,CAAC;AAEtC,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAClD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAClD,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AAGxC,uCAAuC;AACvC,MAAM,WAAW,eAAe;IAC9B,qBAAqB;IACrB,IAAI,EAAE,MAAM,CAAC;IACb,qBAAqB;IACrB,IAAI,EAAE,MAAM,CAAC;IACb,oCAAoC;IACpC,QAAQ,EAAE,MAAM,CAAC;IACjB,0CAA0C;IAC1C,GAAG,EAAE,MAAM,CAAC;CACb;AAED,MAAM,WAAW,4BAA4B;IAC3C,yEAAyE;IACzE,IAAI,CAAC,EAAE,YAAY,CAAC;IACpB,oDAAoD;IACpD,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,qBAAqB;IACpC,sCAAsC;IACtC,MAAM,CACJ,IAAI,EAAE,MAAM,EACZ,OAAO,CAAC,EAAE,4BAA4B,GACrC,MAAM,CAAC,MAAM,CAAC,eAAe,EAAE,KAAK,CAAC,mBAAmB,EAAE,cAAc,CAAC,CAAC;IAC7E,qEAAqE;IACrE,GAAG,CACD,IAAI,EAAE,MAAM,GACX,MAAM,CAAC,MAAM,CAAC,eAAe,GAAG,SAAS,EAAE,KAAK,CAAC,gBAAgB,EAAE,cAAc,CAAC,CAAC;IACtF,wCAAwC;IACxC,IAAI,IAAI,MAAM,CAAC,MAAM,CAAC,eAAe,EAAE,EAAE,KAAK,CAAC,kBAAkB,EAAE,cAAc,CAAC,CAAC;IACnF,+DAA+D;IAC/D,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,KAAK,CAAC,mBAAmB,EAAE,cAAc,CAAC,CAAC;IACrF;;;OAGG;IACH,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,aAAa,CAAC;CACtC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,MAAM,WAAW,eAAgB,SAAQ,OAAO,CAAC,OAAO,CACtD,eAAe,EACf,uBAAuB,EACvB,CAAC,KAAK,EAAE,KAAK,KAAK,MAAM,CAAC,MAAM,CAAC,qBAAqB,CAAC,CACvD;CAAG;AAEJ,eAAO,MAAM,eAAe,iBAA4D,CAAC;AAEzF,iFAAiF;AACjF,eAAO,MAAM,mBAAmB,4CAwG/B,CAAC"}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { Credentials, DEFAULT_API_BASE_URL } from "@distilled.cloud/turso/Credentials";
|
|
2
|
+
import * as turso from "@distilled.cloud/turso/turso";
|
|
3
|
+
import * as Binding from "alchemy/Binding";
|
|
4
|
+
import * as Effect from "effect/Effect";
|
|
5
|
+
import * as FetchHttpClient from "effect/http/FetchHttpClient";
|
|
6
|
+
import * as Layer from "effect/Layer";
|
|
7
|
+
import { ApiToken } from "./ApiToken.js";
|
|
8
|
+
import { GroupToken } from "./GroupToken.js";
|
|
9
|
+
export const ManageDatabases = Binding.Service("Turso.ManageDatabases");
|
|
10
|
+
/** Implementation over the Turso Platform API, usable from any Platform host. */
|
|
11
|
+
export const ManageDatabasesHttp = Layer.effect(ManageDatabases, Effect.gen(function* () {
|
|
12
|
+
const PlatformToken = yield* ApiToken;
|
|
13
|
+
const SqlToken = yield* GroupToken;
|
|
14
|
+
return Effect.fn(function* (group) {
|
|
15
|
+
const host = yield* Binding.Host;
|
|
16
|
+
const prefix = `${host?.LogicalId ?? ""}${group.LogicalId}`;
|
|
17
|
+
const apiToken = yield* PlatformToken(`${prefix}ManageToken`, {
|
|
18
|
+
group: group.name,
|
|
19
|
+
scopes: ["read", "db:create", "db:delete", "db:configure"],
|
|
20
|
+
});
|
|
21
|
+
const sqlToken = yield* SqlToken(`${prefix}SqlToken`, {
|
|
22
|
+
group: group.name,
|
|
23
|
+
});
|
|
24
|
+
const apiKey = yield* apiToken.token;
|
|
25
|
+
const authToken = yield* sqlToken.token;
|
|
26
|
+
const organization = yield* group.organization;
|
|
27
|
+
const groupName = yield* group.name;
|
|
28
|
+
const location = yield* group.location;
|
|
29
|
+
const authorize = (effect) => effect.pipe(Effect.provide(Layer.mergeAll(Layer.succeed(Credentials, Effect.map(apiKey, (key) => ({ apiKey: key, apiBaseUrl: DEFAULT_API_BASE_URL }))), FetchHttpClient.layer)));
|
|
30
|
+
const toManaged = (db) => ({
|
|
31
|
+
name: db.Name ?? "",
|
|
32
|
+
dbId: db.DbId ?? "",
|
|
33
|
+
hostname: db.Hostname ?? "",
|
|
34
|
+
url: `libsql://${db.Hostname}`,
|
|
35
|
+
});
|
|
36
|
+
const client = {
|
|
37
|
+
create: (name, options) => authorize(Effect.gen(function* () {
|
|
38
|
+
const { database } = yield* turso.createDatabase({
|
|
39
|
+
organizationSlug: yield* organization,
|
|
40
|
+
name,
|
|
41
|
+
group: yield* groupName,
|
|
42
|
+
seed: options?.seed,
|
|
43
|
+
size_limit: options?.sizeLimit,
|
|
44
|
+
});
|
|
45
|
+
return toManaged(database ?? {});
|
|
46
|
+
})),
|
|
47
|
+
get: (name) => authorize(Effect.gen(function* () {
|
|
48
|
+
const { database } = yield* turso.getDatabase({
|
|
49
|
+
organizationSlug: yield* organization,
|
|
50
|
+
databaseName: name,
|
|
51
|
+
});
|
|
52
|
+
return database && database.group === (yield* groupName)
|
|
53
|
+
? toManaged(database)
|
|
54
|
+
: undefined;
|
|
55
|
+
}).pipe(Effect.catchTag("NotFound", () => Effect.succeed(undefined)))),
|
|
56
|
+
list: () => authorize(Effect.gen(function* () {
|
|
57
|
+
const { databases } = yield* turso.listDatabases({
|
|
58
|
+
organizationSlug: yield* organization,
|
|
59
|
+
group: yield* groupName,
|
|
60
|
+
});
|
|
61
|
+
return (databases ?? []).map(toManaged);
|
|
62
|
+
})),
|
|
63
|
+
delete: (name) => authorize(Effect.gen(function* () {
|
|
64
|
+
yield* turso.deleteDatabase({
|
|
65
|
+
organizationSlug: yield* organization,
|
|
66
|
+
databaseName: name,
|
|
67
|
+
});
|
|
68
|
+
}).pipe(Effect.catchTag("NotFound", () => Effect.void))),
|
|
69
|
+
connect: (name) => ({
|
|
70
|
+
// Turso hostnames are `<database>-<organization>.<location>.turso.io`,
|
|
71
|
+
// so the URL is derived without a Platform API round-trip.
|
|
72
|
+
url: Effect.gen(function* () {
|
|
73
|
+
return `libsql://${name}-${yield* organization}.${yield* location}.turso.io`;
|
|
74
|
+
}),
|
|
75
|
+
authToken,
|
|
76
|
+
}),
|
|
77
|
+
};
|
|
78
|
+
return client;
|
|
79
|
+
});
|
|
80
|
+
}));
|
|
81
|
+
//# sourceMappingURL=ManageDatabases.js.map
|