t3code-cli 0.3.0 → 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.
Files changed (46) hide show
  1. package/README.md +1 -1
  2. package/dist/bin.js +412 -87
  3. package/dist/index.js +1 -1
  4. package/dist/{runtime-CMPZpQaG.js → runtime-Cq64iuZr.js} +4768 -2040
  5. package/dist/src/application/layer.d.ts +3 -3
  6. package/dist/src/application/models.d.ts +1 -1
  7. package/dist/src/application/projects.d.ts +1 -1
  8. package/dist/src/application/threads.d.ts +1 -1
  9. package/dist/src/auth/error.d.ts +20 -1
  10. package/dist/src/auth/layer.d.ts +9 -22
  11. package/dist/src/auth/local.d.ts +23 -15
  12. package/dist/src/auth/pairing.d.ts +20 -2
  13. package/dist/src/auth/schema.d.ts +25 -34
  14. package/dist/src/auth/service.d.ts +2 -2
  15. package/dist/src/auth/transport.d.ts +13 -20
  16. package/dist/src/auth/type.d.ts +0 -1
  17. package/dist/src/domain/model-config.d.ts +3 -3
  18. package/dist/src/orchestration/layer.d.ts +3 -3
  19. package/dist/src/rpc/layer.d.ts +6 -6
  20. package/dist/src/rpc/ws-group.d.ts +3 -3
  21. package/dist/src/runtime.d.ts +2 -2
  22. package/dist/src/sql/node-sqlite-client.d.ts +10 -0
  23. package/dist/src/sql/service.d.ts +17 -0
  24. package/dist/upstream-t3code/packages/contracts/src/auth.d.ts +14 -12
  25. package/dist/upstream-t3code/packages/contracts/src/environmentHttp.d.ts +167 -7
  26. package/dist/upstream-t3code/packages/contracts/src/index.d.ts +1 -0
  27. package/dist/upstream-t3code/packages/contracts/src/ipc.d.ts +24 -0
  28. package/dist/upstream-t3code/packages/contracts/src/providerRuntime.d.ts +90 -0
  29. package/dist/upstream-t3code/packages/contracts/src/relay.d.ts +1262 -0
  30. package/dist/upstream-t3code/packages/contracts/src/relayClient.d.ts +48 -0
  31. package/dist/upstream-t3code/packages/contracts/src/rpc.d.ts +78 -9
  32. package/dist/upstream-t3code/packages/contracts/src/server.d.ts +3 -3
  33. package/package.json +3 -3
  34. package/src/auth/error.ts +33 -1
  35. package/src/auth/layer.ts +11 -76
  36. package/src/auth/local.ts +342 -208
  37. package/src/auth/pairing.ts +44 -2
  38. package/src/auth/schema.ts +21 -28
  39. package/src/auth/service.ts +2 -2
  40. package/src/auth/transport.ts +59 -22
  41. package/src/auth/type.ts +0 -1
  42. package/src/cli/auth.ts +1 -3
  43. package/src/rpc/layer.ts +2 -2
  44. package/src/runtime.ts +14 -1
  45. package/src/sql/node-sqlite-client.ts +141 -0
  46. package/src/sql/service.ts +21 -0
@@ -1,13 +1,21 @@
1
1
  import * as Schema from "effect/Schema";
2
2
 
3
- export const AuthBearerBootstrapResultSchema = Schema.Struct({
4
- authenticated: Schema.Literal(true),
5
- role: Schema.Literals(["owner", "client"]),
6
- sessionMethod: Schema.Literal("bearer-session-token"),
7
- expiresAt: Schema.String,
8
- sessionToken: Schema.String,
3
+ export const AuthAccessTokenResultSchema = Schema.Struct({
4
+ access_token: Schema.String,
5
+ issued_token_type: Schema.String,
6
+ token_type: Schema.Literal("Bearer"),
7
+ expires_in: Schema.Number,
8
+ scope: Schema.String,
9
9
  });
10
- export type AuthBearerBootstrapResult = typeof AuthBearerBootstrapResultSchema.Type;
10
+ export type AuthAccessTokenResult = typeof AuthAccessTokenResultSchema.Type;
11
+
12
+ export type AuthBearerBootstrapResult = {
13
+ readonly authenticated: true;
14
+ readonly role: "owner" | "client";
15
+ readonly sessionMethod: "bearer-access-token";
16
+ readonly expiresAt: string;
17
+ readonly sessionToken: string;
18
+ };
11
19
 
12
20
  export const AuthSessionStateSchema = Schema.Struct({
13
21
  authenticated: Schema.Boolean,
@@ -17,18 +25,11 @@ export const AuthSessionStateSchema = Schema.Struct({
17
25
  });
18
26
  export type AuthSessionState = typeof AuthSessionStateSchema.Type;
19
27
 
20
- export const AuthWebSocketTokenResultSchema = Schema.Struct({
21
- token: Schema.String,
22
- expiresAt: Schema.String,
23
- });
24
- export type AuthWebSocketTokenResult = typeof AuthWebSocketTokenResultSchema.Type;
25
-
26
- export const AuthLocalSessionIssueResultSchema = Schema.Struct({
27
- token: Schema.String,
28
- role: Schema.Literals(["owner", "client"]),
28
+ export const AuthWebSocketTicketResultSchema = Schema.Struct({
29
+ ticket: Schema.String,
29
30
  expiresAt: Schema.String,
30
31
  });
31
- export type AuthLocalSessionIssueResult = typeof AuthLocalSessionIssueResultSchema.Type;
32
+ export type AuthWebSocketTicketResult = typeof AuthWebSocketTicketResultSchema.Type;
32
33
 
33
34
  export const AuthLocalRuntimeStateSchema = Schema.Struct({
34
35
  version: Schema.Literal(1),
@@ -36,20 +37,12 @@ export const AuthLocalRuntimeStateSchema = Schema.Struct({
36
37
  });
37
38
  export type AuthLocalRuntimeState = typeof AuthLocalRuntimeStateSchema.Type;
38
39
 
39
- export const decodeAuthBearerBootstrapResult = Schema.decodeUnknownEffect(
40
- AuthBearerBootstrapResultSchema,
41
- );
40
+ export const decodeAuthAccessTokenResult = Schema.decodeUnknownEffect(AuthAccessTokenResultSchema);
42
41
  export const decodeAuthSessionState = Schema.decodeUnknownEffect(AuthSessionStateSchema);
43
- export const decodeAuthWebSocketTokenResult = Schema.decodeUnknownEffect(
44
- AuthWebSocketTokenResultSchema,
45
- );
46
- export const decodeAuthLocalSessionIssueResult = Schema.decodeUnknownEffect(
47
- AuthLocalSessionIssueResultSchema,
42
+ export const decodeAuthWebSocketTicketResult = Schema.decodeUnknownEffect(
43
+ AuthWebSocketTicketResultSchema,
48
44
  );
49
45
  export const decodeAuthLocalRuntimeState = Schema.decodeUnknownEffect(AuthLocalRuntimeStateSchema);
50
- export const decodeAuthLocalSessionIssueResultFromJson = Schema.decodeUnknownEffect(
51
- Schema.fromJsonString(AuthLocalSessionIssueResultSchema),
52
- );
53
46
  export const decodeAuthLocalRuntimeStateFromJson = Schema.decodeUnknownEffect(
54
47
  Schema.fromJsonString(AuthLocalRuntimeStateSchema),
55
48
  );
@@ -1,7 +1,7 @@
1
1
  import * as Context from "effect/Context";
2
2
  import type * as Effect from "effect/Effect";
3
3
 
4
- import type { AuthSessionState, AuthWebSocketTokenResult } from "./schema.ts";
4
+ import type { AuthSessionState, AuthWebSocketTicketResult } from "./schema.ts";
5
5
  import type { AuthError } from "./error.ts";
6
6
  import type { LocalAuthInput, LocalAuthResult, PairResult } from "./type.ts";
7
7
 
@@ -11,6 +11,6 @@ export class T3Auth extends Context.Service<
11
11
  readonly pair: (value: string) => Effect.Effect<PairResult, AuthError>;
12
12
  readonly local: (input: LocalAuthInput) => Effect.Effect<LocalAuthResult, AuthError>;
13
13
  readonly status: () => Effect.Effect<AuthSessionState, AuthError>;
14
- readonly issueWebSocketToken: () => Effect.Effect<AuthWebSocketTokenResult, AuthError>;
14
+ readonly issueWebSocketTicket: () => Effect.Effect<AuthWebSocketTicketResult, AuthError>;
15
15
  }
16
16
  >()("t3cli/T3Auth") {}
@@ -1,26 +1,58 @@
1
+ import {
2
+ AuthAccessTokenType,
3
+ AuthEnvironmentBootstrapTokenType,
4
+ AuthTokenExchangeGrantType,
5
+ } from "#t3tools/contracts";
6
+ import * as Context from "effect/Context";
7
+ import * as DateTime from "effect/DateTime";
1
8
  import * as Effect from "effect/Effect";
9
+ import * as Layer from "effect/Layer";
2
10
  import { HttpClient, HttpClientError, HttpClientRequest } from "effect/unstable/http";
3
11
 
4
12
  import type { ResolvedConfig } from "../config/service.ts";
5
13
  import { toHttpEndpointUrl } from "../config/url.ts";
6
14
  import { AuthTransportError } from "./error.ts";
7
15
  import {
8
- decodeAuthBearerBootstrapResult,
16
+ type AuthBearerBootstrapResult,
17
+ decodeAuthAccessTokenResult,
18
+ type AuthSessionState,
9
19
  decodeAuthSessionState,
10
- decodeAuthWebSocketTokenResult,
20
+ type AuthWebSocketTicketResult,
21
+ decodeAuthWebSocketTicketResult,
11
22
  } from "./schema.ts";
12
23
 
13
- export const makeAuthTransport = Effect.fn("makeAuthTransport")(function* () {
24
+ export class T3AuthTransport extends Context.Service<
25
+ T3AuthTransport,
26
+ {
27
+ readonly bootstrapBearer: (input: {
28
+ readonly baseUrl: string;
29
+ readonly credential: string;
30
+ }) => Effect.Effect<AuthBearerBootstrapResult, AuthTransportError>;
31
+ readonly getSession: (
32
+ config: ResolvedConfig,
33
+ ) => Effect.Effect<AuthSessionState, AuthTransportError>;
34
+ readonly issueWebSocketTicket: (
35
+ config: ResolvedConfig,
36
+ ) => Effect.Effect<AuthWebSocketTicketResult, AuthTransportError>;
37
+ }
38
+ >()("t3cli/T3AuthTransport") {}
39
+
40
+ const makeT3AuthTransport = Effect.fn("makeT3AuthTransport")(function* () {
14
41
  const client = HttpClient.filterStatusOk(yield* HttpClient.HttpClient);
15
42
 
16
43
  const bootstrapBearer = Effect.fn("AuthTransport.bootstrapBearer")(function* (input: {
17
44
  readonly baseUrl: string;
18
45
  readonly credential: string;
19
46
  }) {
20
- const url = yield* makeHttpEndpointUrl(input.baseUrl, "/api/auth/bootstrap/bearer");
47
+ const url = yield* makeHttpEndpointUrl(input.baseUrl, "/oauth/token");
21
48
  const request = HttpClientRequest.post(url).pipe(
22
49
  HttpClientRequest.acceptJson,
23
- HttpClientRequest.bodyJsonUnsafe({ credential: input.credential }),
50
+ HttpClientRequest.bodyUrlParams({
51
+ grant_type: AuthTokenExchangeGrantType,
52
+ subject_token: input.credential,
53
+ subject_token_type: AuthEnvironmentBootstrapTokenType,
54
+ requested_token_type: AuthAccessTokenType,
55
+ }),
24
56
  );
25
57
  const response = yield* client.execute(request).pipe(
26
58
  Effect.catchTags({
@@ -33,8 +65,8 @@ export const makeAuthTransport = Effect.fn("makeAuthTransport")(function* () {
33
65
  ),
34
66
  }),
35
67
  );
36
- return yield* response.json.pipe(
37
- Effect.flatMap(decodeAuthBearerBootstrapResult),
68
+ const result = yield* response.json.pipe(
69
+ Effect.flatMap(decodeAuthAccessTokenResult),
38
70
  Effect.catchTags({
39
71
  HttpClientError: (error) =>
40
72
  Effect.fail(
@@ -49,10 +81,19 @@ export const makeAuthTransport = Effect.fn("makeAuthTransport")(function* () {
49
81
  ),
50
82
  }),
51
83
  );
84
+ const now = yield* DateTime.now;
85
+ return {
86
+ authenticated: true,
87
+ role: result.scope.split(/\s+/u).includes("access:write") ? "owner" : "client",
88
+ sessionMethod: "bearer-access-token",
89
+ expiresAt: DateTime.formatIso(DateTime.add(now, { seconds: result.expires_in })),
90
+ sessionToken: result.access_token,
91
+ } satisfies AuthBearerBootstrapResult;
52
92
  });
53
93
 
54
94
  const getSession = Effect.fn("AuthTransport.getSession")(function* (config: ResolvedConfig) {
55
- const request = yield* authenticatedRequest(config, "/api/auth/session", "get");
95
+ const url = yield* makeHttpEndpointUrl(config.url, "/api/auth/session");
96
+ const request = HttpClientRequest.get(url).pipe(authenticatedRequest(config));
56
97
  const response = yield* client.execute(request).pipe(
57
98
  Effect.catchTags({
58
99
  HttpClientError: (error) =>
@@ -82,10 +123,11 @@ export const makeAuthTransport = Effect.fn("makeAuthTransport")(function* () {
82
123
  );
83
124
  });
84
125
 
85
- const issueWebSocketToken = Effect.fn("AuthTransport.issueWebSocketToken")(function* (
126
+ const issueWebSocketTicket = Effect.fn("AuthTransport.issueWebSocketTicket")(function* (
86
127
  config: ResolvedConfig,
87
128
  ) {
88
- const request = yield* authenticatedRequest(config, "/api/auth/ws-token", "post");
129
+ const url = yield* makeHttpEndpointUrl(config.url, "/api/auth/websocket-ticket");
130
+ const request = HttpClientRequest.post(url).pipe(authenticatedRequest(config));
89
131
  const response = yield* client.execute(request).pipe(
90
132
  Effect.catchTags({
91
133
  HttpClientError: (error) =>
@@ -98,7 +140,7 @@ export const makeAuthTransport = Effect.fn("makeAuthTransport")(function* () {
98
140
  }),
99
141
  );
100
142
  return yield* response.json.pipe(
101
- Effect.flatMap(decodeAuthWebSocketTokenResult),
143
+ Effect.flatMap(decodeAuthWebSocketTicketResult),
102
144
  Effect.catchTags({
103
145
  HttpClientError: (error) =>
104
146
  Effect.fail(
@@ -118,20 +160,15 @@ export const makeAuthTransport = Effect.fn("makeAuthTransport")(function* () {
118
160
  return {
119
161
  bootstrapBearer,
120
162
  getSession,
121
- issueWebSocketToken,
163
+ issueWebSocketTicket,
122
164
  };
123
165
  });
124
166
 
125
- function authenticatedRequest(config: ResolvedConfig, path: string, method: "get" | "post") {
126
- return makeHttpEndpointUrl(config.url, path).pipe(
127
- Effect.map((url) =>
128
- method === "get" ? HttpClientRequest.get(url) : HttpClientRequest.post(url),
129
- ),
130
- Effect.map((request) =>
131
- request.pipe(HttpClientRequest.acceptJson, HttpClientRequest.bearerToken(config.token)),
132
- ),
133
- );
134
- }
167
+ export const T3AuthTransportLive = Layer.effect(T3AuthTransport, makeT3AuthTransport());
168
+
169
+ const authenticatedRequest =
170
+ (config: ResolvedConfig) => (request: HttpClientRequest.HttpClientRequest) =>
171
+ request.pipe(HttpClientRequest.acceptJson, HttpClientRequest.bearerToken(config.token));
135
172
 
136
173
  function makeHttpEndpointUrl(baseUrl: string, path: string) {
137
174
  return toHttpEndpointUrl(baseUrl, path).pipe(
package/src/auth/type.ts CHANGED
@@ -15,7 +15,6 @@ export type PairResult = {
15
15
 
16
16
  export type LocalAuthInput = {
17
17
  readonly baseDir?: string;
18
- readonly t3Command?: string;
19
18
  readonly origin?: string;
20
19
  readonly role: AuthSessionRole;
21
20
  readonly label: string;
package/src/cli/auth.ts CHANGED
@@ -47,14 +47,13 @@ const localCommand = Command.make(
47
47
  "local",
48
48
  {
49
49
  baseDir: Flag.string("base-dir").pipe(Flag.optional),
50
- t3Command: Flag.string("t3-command").pipe(Flag.optional),
51
50
  origin: Flag.string("origin").pipe(Flag.optional),
52
51
  role: Flag.choice("role", ["owner", "client"] as const).pipe(Flag.withDefault("owner")),
53
52
  label: Flag.string("label").pipe(Flag.withDefault("t3cli")),
54
53
  subject: Flag.string("subject").pipe(Flag.withDefault("t3cli-local")),
55
54
  format: Flag.choice("format", humanJsonFormatChoices).pipe(Flag.withDefault("auto")),
56
55
  },
57
- ({ baseDir, t3Command, origin, role, label, subject, format }) =>
56
+ ({ baseDir, origin, role, label, subject, format }) =>
58
57
  Effect.gen(function* () {
59
58
  const auth = yield* T3Auth;
60
59
  const environment = yield* Environment;
@@ -64,7 +63,6 @@ const localCommand = Command.make(
64
63
  role,
65
64
  label,
66
65
  subject,
67
- ...(Option.isSome(t3Command) ? { t3Command: t3Command.value } : {}),
68
66
  ...(Option.isSome(baseDir) ? { baseDir: baseDir.value } : {}),
69
67
  ...(Option.isSome(origin) ? { origin: origin.value } : {}),
70
68
  });
package/src/rpc/layer.ts CHANGED
@@ -76,10 +76,10 @@ const makeWsUrl = Effect.fn("makeWsUrl")(function* (input: {
76
76
  readonly auth: T3Auth["Service"];
77
77
  }) {
78
78
  const resolved = yield* input.config.resolve();
79
- const wsToken = yield* input.auth.issueWebSocketToken();
79
+ const wsTicket = yield* input.auth.issueWebSocketTicket();
80
80
  const wsUrl = yield* toWebSocketEndpointUrl(resolved.url, "/ws");
81
81
  const request = HttpClientRequest.get(wsUrl).pipe(
82
- HttpClientRequest.setUrlParam("wsToken", wsToken.token),
82
+ HttpClientRequest.setUrlParam("wsTicket", wsTicket.ticket),
83
83
  );
84
84
  return Option.getOrThrow(HttpClientRequest.toUrl(request)).toString();
85
85
  });
package/src/runtime.ts CHANGED
@@ -3,13 +3,26 @@ import * as NodeHttpClient from "@effect/platform-node/NodeHttpClient";
3
3
  import * as NodeSocket from "@effect/platform-node/NodeSocket";
4
4
 
5
5
  import { T3AuthLive } from "./auth/layer.ts";
6
+ import { T3LocalAuthLive } from "./auth/local.ts";
7
+ import { T3AuthPairingLive } from "./auth/pairing.ts";
8
+ import { T3AuthTransportLive } from "./auth/transport.ts";
6
9
  import { T3ConfigLive } from "./config/layer.ts";
7
10
  import { T3ApplicationLive } from "./application/layer.ts";
8
11
  import { T3OrchestrationLive } from "./orchestration/layer.ts";
9
12
  import { T3RpcLive } from "./rpc/layer.ts";
13
+ import { NodeSqlClientFactoryLive } from "./sql/node-sqlite-client.ts";
10
14
 
15
+ const T3AuthTransportLayer = T3AuthTransportLive.pipe(Layer.provide(NodeHttpClient.layerUndici));
16
+ const T3LocalAuthLayer = T3LocalAuthLive.pipe(
17
+ Layer.provide(Layer.mergeAll(T3ConfigLive, NodeSqlClientFactoryLive)),
18
+ );
19
+ const T3AuthPairingLayer = T3AuthPairingLive.pipe(
20
+ Layer.provide(Layer.mergeAll(T3ConfigLive, T3AuthTransportLayer)),
21
+ );
11
22
  const T3AuthLayer = T3AuthLive.pipe(
12
- Layer.provide(Layer.mergeAll(T3ConfigLive, NodeHttpClient.layerUndici)),
23
+ Layer.provide(
24
+ Layer.mergeAll(T3ConfigLive, T3AuthTransportLayer, T3LocalAuthLayer, T3AuthPairingLayer),
25
+ ),
13
26
  );
14
27
  const T3RpcLayer = T3RpcLive.pipe(
15
28
  Layer.provide(Layer.mergeAll(T3ConfigLive, T3AuthLayer, NodeSocket.layerWebSocketConstructor)),
@@ -0,0 +1,141 @@
1
+ import { DatabaseSync, type SQLInputValue } from "node:sqlite";
2
+ import * as Context from "effect/Context";
3
+ import * as Effect from "effect/Effect";
4
+ import { identity } from "effect/Function";
5
+ import * as Layer from "effect/Layer";
6
+ import * as Scope from "effect/Scope";
7
+ import * as Stream from "effect/Stream";
8
+ import * as Reactivity from "effect/unstable/reactivity/Reactivity";
9
+ import * as SqlClient from "effect/unstable/sql/SqlClient";
10
+ import type { Connection } from "effect/unstable/sql/SqlConnection";
11
+ import { classifySqliteError, SqlError } from "effect/unstable/sql/SqlError";
12
+ import * as Statement from "effect/unstable/sql/Statement";
13
+
14
+ import { SqlClientFactory, type SqliteClientConfig } from "./service.ts";
15
+
16
+ const ATTR_DB_SYSTEM_NAME = "db.system.name";
17
+
18
+ const classifyError = (cause: unknown, message: string, operation: string) =>
19
+ classifySqliteError(cause, { message, operation });
20
+
21
+ export const makeNodeSqliteClient = Effect.fn("makeNodeSqliteClient")(function* (
22
+ config: SqliteClientConfig,
23
+ ) {
24
+ const compiler = Statement.makeCompilerSqlite(config.transformQueryNames);
25
+ const transformRows =
26
+ config.transformResultNames === undefined
27
+ ? undefined
28
+ : Statement.defaultTransforms(config.transformResultNames).array;
29
+ const scope = yield* Effect.scope;
30
+ const db = yield* Effect.try({
31
+ try: () => new DatabaseSync(config.filename),
32
+ catch: (cause) =>
33
+ new SqlError({
34
+ reason: classifyError(cause, "Failed to open sqlite database", "connect"),
35
+ }),
36
+ });
37
+ yield* Scope.addFinalizer(
38
+ scope,
39
+ Effect.sync(() => {
40
+ try {
41
+ db.close();
42
+ } catch {
43
+ // Ignore close failures after the scoped client is done.
44
+ }
45
+ }),
46
+ );
47
+
48
+ const runRows = (sql: string, params: ReadonlyArray<unknown>) =>
49
+ Effect.try({
50
+ try: () => {
51
+ const statement = db.prepare(sql);
52
+ const bound = params.map(toSqlInputValue);
53
+ if (statement.columns().length > 0) {
54
+ return statement.all(...bound);
55
+ }
56
+ statement.run(...bound);
57
+ return [];
58
+ },
59
+ catch: (cause) =>
60
+ new SqlError({
61
+ reason: classifyError(cause, "Failed to execute sqlite statement", "execute"),
62
+ }),
63
+ });
64
+
65
+ const runRaw = (sql: string, params: ReadonlyArray<unknown>) =>
66
+ Effect.try({
67
+ try: () => {
68
+ const statement = db.prepare(sql);
69
+ const bound = params.map(toSqlInputValue);
70
+ return statement.columns().length > 0 ? statement.all(...bound) : statement.run(...bound);
71
+ },
72
+ catch: (cause) =>
73
+ new SqlError({
74
+ reason: classifyError(cause, "Failed to execute sqlite statement", "execute"),
75
+ }),
76
+ });
77
+
78
+ const runValues = (sql: string, params: ReadonlyArray<unknown>) =>
79
+ Effect.map(runRows(sql, params), (rows) =>
80
+ rows.map((row) => Object.values(row as Record<string, unknown>)),
81
+ );
82
+
83
+ const connection = identity<Connection>({
84
+ execute(sql, params, rowTransform) {
85
+ const effect = runRows(sql, params);
86
+ return rowTransform === undefined ? effect : Effect.map(effect, rowTransform);
87
+ },
88
+ executeRaw(sql, params) {
89
+ return runRaw(sql, params);
90
+ },
91
+ executeValues(sql, params) {
92
+ return runValues(sql, params);
93
+ },
94
+ executeUnprepared(sql, params, rowTransform) {
95
+ const effect = runRows(sql, params);
96
+ return rowTransform === undefined ? effect : Effect.map(effect, rowTransform);
97
+ },
98
+ executeStream(sql, params, rowTransform) {
99
+ return Stream.fromIterableEffect(this.execute(sql, params, rowTransform));
100
+ },
101
+ });
102
+
103
+ const acquirer = Effect.succeed(connection);
104
+ return yield* SqlClient.make({
105
+ acquirer,
106
+ compiler,
107
+ spanAttributes: [[ATTR_DB_SYSTEM_NAME, "sqlite"]],
108
+ transformRows,
109
+ });
110
+ });
111
+
112
+ export const NodeSqliteClientLive = (config: SqliteClientConfig) =>
113
+ Layer.effectContext(
114
+ Effect.map(makeNodeSqliteClient(config), (client) => Context.make(SqlClient.SqlClient, client)),
115
+ ).pipe(Layer.provide(Reactivity.layer));
116
+
117
+ export const NodeSqlClientFactoryLive = Layer.succeed(SqlClientFactory, {
118
+ sqliteClient: (config) => makeNodeSqliteClient(config).pipe(Effect.provide(Reactivity.layer)),
119
+ });
120
+
121
+ function toSqlInputValue(value: unknown): SQLInputValue {
122
+ if (value === undefined || value === null) {
123
+ return null;
124
+ }
125
+ if (typeof value === "string" || typeof value === "number" || typeof value === "bigint") {
126
+ return value;
127
+ }
128
+ if (typeof value === "boolean") {
129
+ return value ? 1 : 0;
130
+ }
131
+ if (value instanceof Date) {
132
+ return value.toISOString();
133
+ }
134
+ if (ArrayBuffer.isView(value)) {
135
+ const bytes = new Uint8Array(value.buffer, value.byteOffset, value.byteLength);
136
+ const copy = new Uint8Array(bytes.byteLength);
137
+ copy.set(bytes);
138
+ return copy;
139
+ }
140
+ return JSON.stringify(value) ?? null;
141
+ }
@@ -0,0 +1,21 @@
1
+ import type * as Effect from "effect/Effect";
2
+ import * as Context from "effect/Context";
3
+ import type * as Scope from "effect/Scope";
4
+ import type * as SqlClient from "effect/unstable/sql/SqlClient";
5
+ import type { SqlError } from "effect/unstable/sql/SqlError";
6
+
7
+ export type SqliteClientConfig = {
8
+ readonly filename: string;
9
+ readonly transformResultNames?: ((str: string) => string) | undefined;
10
+ readonly transformQueryNames?: ((str: string) => string) | undefined;
11
+ };
12
+
13
+ export type SqlClientFactoryShape = {
14
+ readonly sqliteClient: (
15
+ config: SqliteClientConfig,
16
+ ) => Effect.Effect<SqlClient.SqlClient, SqlError, Scope.Scope>;
17
+ };
18
+
19
+ export class SqlClientFactory extends Context.Service<SqlClientFactory, SqlClientFactoryShape>()(
20
+ "t3cli/SqlClientFactory",
21
+ ) {}