@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.
Files changed (83) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +68 -4
  3. package/lib/ApiToken.d.ts +73 -0
  4. package/lib/ApiToken.d.ts.map +1 -0
  5. package/lib/ApiToken.js +120 -0
  6. package/lib/ApiToken.js.map +1 -0
  7. package/lib/AuthProvider.d.ts +36 -0
  8. package/lib/AuthProvider.d.ts.map +1 -0
  9. package/lib/AuthProvider.js +85 -0
  10. package/lib/AuthProvider.js.map +1 -0
  11. package/lib/Connect.d.ts +79 -0
  12. package/lib/Connect.d.ts.map +1 -0
  13. package/lib/Connect.js +22 -0
  14. package/lib/Connect.js.map +1 -0
  15. package/lib/Contributions.d.ts +7 -0
  16. package/lib/Contributions.d.ts.map +1 -0
  17. package/lib/Contributions.js +6 -0
  18. package/lib/Contributions.js.map +1 -0
  19. package/lib/Credentials.d.ts +39 -0
  20. package/lib/Credentials.d.ts.map +1 -0
  21. package/lib/Credentials.js +80 -0
  22. package/lib/Credentials.js.map +1 -0
  23. package/lib/Database.d.ts +217 -0
  24. package/lib/Database.d.ts.map +1 -0
  25. package/lib/Database.js +271 -0
  26. package/lib/Database.js.map +1 -0
  27. package/lib/DatabaseToken.d.ts +95 -0
  28. package/lib/DatabaseToken.d.ts.map +1 -0
  29. package/lib/DatabaseToken.js +128 -0
  30. package/lib/DatabaseToken.js.map +1 -0
  31. package/lib/Group.d.ts +93 -0
  32. package/lib/Group.d.ts.map +1 -0
  33. package/lib/Group.js +162 -0
  34. package/lib/Group.js.map +1 -0
  35. package/lib/GroupToken.d.ts +48 -0
  36. package/lib/GroupToken.d.ts.map +1 -0
  37. package/lib/GroupToken.js +83 -0
  38. package/lib/GroupToken.js.map +1 -0
  39. package/lib/Hrana.d.ts +42 -0
  40. package/lib/Hrana.d.ts.map +1 -0
  41. package/lib/Hrana.js +105 -0
  42. package/lib/Hrana.js.map +1 -0
  43. package/lib/ManageDatabases.d.ts +73 -0
  44. package/lib/ManageDatabases.d.ts.map +1 -0
  45. package/lib/ManageDatabases.js +81 -0
  46. package/lib/ManageDatabases.js.map +1 -0
  47. package/lib/OrganizationInvite.d.ts +46 -0
  48. package/lib/OrganizationInvite.d.ts.map +1 -0
  49. package/lib/OrganizationInvite.js +72 -0
  50. package/lib/OrganizationInvite.js.map +1 -0
  51. package/lib/OrganizationMember.d.ts +47 -0
  52. package/lib/OrganizationMember.d.ts.map +1 -0
  53. package/lib/OrganizationMember.js +81 -0
  54. package/lib/OrganizationMember.js.map +1 -0
  55. package/lib/OrganizationSettings.d.ts +52 -0
  56. package/lib/OrganizationSettings.d.ts.map +1 -0
  57. package/lib/OrganizationSettings.js +93 -0
  58. package/lib/OrganizationSettings.js.map +1 -0
  59. package/lib/Providers.d.ts +24 -0
  60. package/lib/Providers.d.ts.map +1 -0
  61. package/lib/Providers.js +42 -0
  62. package/lib/Providers.js.map +1 -0
  63. package/lib/index.d.ts +14 -0
  64. package/lib/index.d.ts.map +1 -0
  65. package/lib/index.js +14 -0
  66. package/lib/index.js.map +1 -0
  67. package/package.json +54 -3
  68. package/src/ApiToken.ts +197 -0
  69. package/src/AuthProvider.ts +113 -0
  70. package/src/Connect.ts +103 -0
  71. package/src/Contributions.ts +9 -0
  72. package/src/Credentials.ts +139 -0
  73. package/src/Database.ts +504 -0
  74. package/src/DatabaseToken.ts +203 -0
  75. package/src/Group.ts +256 -0
  76. package/src/GroupToken.ts +119 -0
  77. package/src/Hrana.ts +182 -0
  78. package/src/ManageDatabases.ts +195 -0
  79. package/src/OrganizationInvite.ts +118 -0
  80. package/src/OrganizationMember.ts +122 -0
  81. package/src/OrganizationSettings.ts +145 -0
  82. package/src/Providers.ts +67 -0
  83. 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
@@ -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
@@ -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