airfoil 0.4.0 → 0.4.1

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 (38) hide show
  1. package/dist/index.js +401 -739
  2. package/package.json +9 -6
  3. package/src/commands/cluster/index.ts +13 -37
  4. package/src/commands/cluster/namespace/create.ts +278 -61
  5. package/src/commands/cluster/namespace/delete.ts +9 -13
  6. package/src/commands/cluster/namespace/get.ts +12 -14
  7. package/src/commands/cluster/namespace/list.ts +16 -34
  8. package/src/commands/cluster/{topic → table}/create.ts +79 -96
  9. package/src/commands/cluster/{tenant → table}/delete.ts +15 -17
  10. package/src/commands/cluster/table/get.ts +62 -0
  11. package/src/commands/cluster/table/list.ts +61 -0
  12. package/src/commands/dev.ts +17 -13
  13. package/src/commands/sql.ts +13 -23
  14. package/src/index.ts +6 -6
  15. package/src/utils/client.ts +3 -3
  16. package/src/utils/docker.ts +101 -7
  17. package/src/utils/options.ts +1 -1
  18. package/src/utils/wings.ts +18 -14
  19. package/src/commands/cluster/data-lake/create-iceberg.ts +0 -59
  20. package/src/commands/cluster/data-lake/create-parquet.ts +0 -59
  21. package/src/commands/cluster/data-lake/create.ts +0 -10
  22. package/src/commands/cluster/data-lake/delete.ts +0 -54
  23. package/src/commands/cluster/data-lake/get.ts +0 -40
  24. package/src/commands/cluster/data-lake/list.ts +0 -62
  25. package/src/commands/cluster/object-store/create-aws.ts +0 -104
  26. package/src/commands/cluster/object-store/create-azure.ts +0 -96
  27. package/src/commands/cluster/object-store/create-google.ts +0 -87
  28. package/src/commands/cluster/object-store/create-s3.ts +0 -114
  29. package/src/commands/cluster/object-store/create.ts +0 -21
  30. package/src/commands/cluster/object-store/delete.ts +0 -56
  31. package/src/commands/cluster/object-store/get.ts +0 -49
  32. package/src/commands/cluster/object-store/list.ts +0 -62
  33. package/src/commands/cluster/tenant/create.ts +0 -44
  34. package/src/commands/cluster/tenant/get.ts +0 -40
  35. package/src/commands/cluster/tenant/list.ts +0 -53
  36. package/src/commands/cluster/topic/delete.ts +0 -65
  37. package/src/commands/cluster/topic/get.ts +0 -51
  38. package/src/commands/cluster/topic/list.ts +0 -61
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "airfoil",
3
- "version": "0.4.0",
3
+ "version": "0.4.1",
4
4
  "repository": "useairfoil/typescript-sdk",
5
5
  "bin": {
6
6
  "airfoil": "./dist/index.js"
@@ -11,7 +11,10 @@
11
11
  "README.md"
12
12
  ],
13
13
  "type": "module",
14
- "module": "index.ts",
14
+ "main": "./dist/index.js",
15
+ "exports": {
16
+ ".": "./dist/index.js"
17
+ },
15
18
  "scripts": {
16
19
  "build": "tsdown",
17
20
  "typecheck": "tsc --noEmit",
@@ -19,11 +22,11 @@
19
22
  },
20
23
  "dependencies": {
21
24
  "@clack/prompts": "^0.11.0",
22
- "@effect/platform-node": "4.0.0-beta.54",
23
- "@useairfoil/flight": "0.4.0",
24
- "@useairfoil/wings": "0.4.0",
25
+ "@effect/platform-node": "4.0.0-beta.83",
26
+ "@useairfoil/flight": "0.4.3",
27
+ "@useairfoil/wings": "0.4.1",
25
28
  "console-table-printer": "^2.15.0",
26
- "effect": "4.0.0-beta.54",
29
+ "effect": "4.0.0-beta.83",
27
30
  "nice-grpc": "^2.1.13",
28
31
  "nice-grpc-common": "^2.0.2"
29
32
  },
@@ -1,49 +1,25 @@
1
1
  import { Effect } from "effect";
2
2
  import { Command } from "effect/unstable/cli";
3
3
 
4
- import { createDataLakeCommand } from "./data-lake/create.js";
5
- import { deleteDataLakeCommand } from "./data-lake/delete.js";
6
- import { getDataLakeCommand } from "./data-lake/get.js";
7
- import { listDataLakesCommand } from "./data-lake/list.js";
8
- import { createNamespaceCommand } from "./namespace/create.js";
9
- import { deleteNamespaceCommand } from "./namespace/delete.js";
10
- import { getNamespaceCommand } from "./namespace/get.js";
11
- import { listNamespacesCommand } from "./namespace/list.js";
12
- import { createObjectStoreCommand } from "./object-store/create.js";
13
- import { deleteObjectStoreCommand } from "./object-store/delete.js";
14
- import { getObjectStoreCommand } from "./object-store/get.js";
15
- import { listObjectStoresCommand } from "./object-store/list.js";
16
- import { createTenantCommand } from "./tenant/create.js";
17
- import { deleteTenantCommand } from "./tenant/delete.js";
18
- import { getTenantCommand } from "./tenant/get.js";
19
- import { listTenantsCommand } from "./tenant/list.js";
20
- import { createTopicCommand } from "./topic/create.js";
21
- import { deleteTopicCommand } from "./topic/delete.js";
22
- import { getTopicCommand } from "./topic/get.js";
23
- import { listTopicsCommand } from "./topic/list.js";
4
+ import { createNamespaceCommand } from "./namespace/create";
5
+ import { deleteNamespaceCommand } from "./namespace/delete";
6
+ import { getNamespaceCommand } from "./namespace/get";
7
+ import { listNamespacesCommand } from "./namespace/list";
8
+ import { createTableCommand } from "./table/create";
9
+ import { deleteTableCommand } from "./table/delete";
10
+ import { getTableCommand } from "./table/get";
11
+ import { listTablesCommand } from "./table/list";
24
12
 
25
13
  export const clusterCommand = Command.make("cluster", {}, () => Effect.void).pipe(
26
- Command.withDescription("Interact with the cluster metadata server"),
14
+ Command.withDescription("Manage Wings namespaces and tables"),
27
15
  Command.withSubcommands([
28
- createTenantCommand,
29
- listTenantsCommand,
30
- getTenantCommand,
31
- deleteTenantCommand,
32
16
  createNamespaceCommand,
33
17
  listNamespacesCommand,
34
18
  getNamespaceCommand,
35
19
  deleteNamespaceCommand,
36
- createTopicCommand,
37
- listTopicsCommand,
38
- getTopicCommand,
39
- deleteTopicCommand,
40
- createObjectStoreCommand,
41
- listObjectStoresCommand,
42
- getObjectStoreCommand,
43
- deleteObjectStoreCommand,
44
- createDataLakeCommand,
45
- listDataLakesCommand,
46
- getDataLakeCommand,
47
- deleteDataLakeCommand,
20
+ createTableCommand,
21
+ listTablesCommand,
22
+ getTableCommand,
23
+ deleteTableCommand,
48
24
  ]),
49
25
  );
@@ -1,97 +1,314 @@
1
1
  import * as p from "@clack/prompts";
2
- import { WingsClusterMetadata } from "@useairfoil/wings";
2
+ import { Cluster, ClusterClient } from "@useairfoil/wings";
3
3
  import { printTable } from "console-table-printer";
4
- import { Effect } from "effect";
4
+ import { Config, Effect, Option, Redacted } from "effect";
5
5
  import { Command, Flag } from "effect/unstable/cli";
6
6
 
7
- import { makeClusterMetadataLayer } from "../../../utils/client.js";
8
- import { hostOption, portOption } from "../../../utils/options.js";
7
+ import { makeClusterClientLayer } from "../../../utils/client";
8
+ import { hostOption, portOption } from "../../../utils/options";
9
9
 
10
- const parentOption = Flag.string("parent").pipe(
11
- Flag.withDescription("Parent tenant in format: tenants/{tenant} (e.g., 'tenants/default')"),
12
- );
10
+ type LakeTag = Cluster.Lake.LakeApp["lakeConfig"]["_tag"];
13
11
 
14
12
  const namespaceIdOption = Flag.string("namespace-id").pipe(
15
13
  Flag.withDescription("Unique identifier for the namespace (e.g., 'production')"),
16
14
  );
17
15
 
18
- const flushSizeBytesOption = Flag.integer("flush-size-bytes").pipe(
19
- Flag.withDescription("Size at which the current segment is flushed to object storage"),
20
- Flag.withDefault(0),
16
+ const lakeOption = Flag.string("lake").pipe(
17
+ Flag.withDescription("Lake format: iceberg, parquet, or delta"),
18
+ Flag.withDefault("iceberg" as LakeTag),
21
19
  );
22
20
 
23
- const flushIntervalMillisOption = Flag.integer("flush-interval-millis").pipe(
24
- Flag.withDescription("Maximum interval at which the current segment is flushed (milliseconds)"),
25
- Flag.withDefault(0),
21
+ const prefixOption = Flag.string("prefix").pipe(
22
+ Flag.withDescription("Storage prefix (optional)"),
23
+ Flag.optional,
26
24
  );
27
25
 
28
- const objectStoreOption = Flag.string("object-store").pipe(
29
- Flag.withDescription(
30
- "Object store used by this namespace (format: tenants/{tenant}/object-stores/{object-store})",
31
- ),
26
+ function makeLakeConfig(lake: string): Cluster.Lake.LakeApp {
27
+ switch (lake) {
28
+ case "iceberg":
29
+ return { lakeConfig: { _tag: "iceberg" as const, iceberg: {} } };
30
+ case "parquet":
31
+ return { lakeConfig: { _tag: "parquet" as const, parquet: {} } };
32
+ case "delta":
33
+ return { lakeConfig: { _tag: "delta" as const, delta: {} } };
34
+ default:
35
+ throw new Error(`Invalid lake type: "${lake}". Must be iceberg, parquet, or delta.`);
36
+ }
37
+ }
38
+
39
+ function displayNamespace(namespace: Cluster.Namespace.Namespace) {
40
+ printTable([
41
+ {
42
+ name: namespace.name,
43
+ object_store: namespace.objectStore?.objectStoreConfig._tag ?? "-",
44
+ lake: namespace.lake?.lakeConfig._tag ?? "-",
45
+ },
46
+ ]);
47
+ p.outro("✓ Done");
48
+ }
49
+
50
+ // ─── AWS ──────────────────────────────────────────────────────────────────────
51
+
52
+ const createNamespaceAwsCommand = Command.make(
53
+ "aws",
54
+ {
55
+ namespaceId: namespaceIdOption,
56
+ lake: lakeOption,
57
+ bucketName: Flag.string("bucket-name").pipe(Flag.withDescription("AWS S3 bucket name")),
58
+ prefix: prefixOption,
59
+ accessKeyId: Flag.redacted("access-key-id").pipe(
60
+ Flag.withDescription("AWS access key ID (or set AWS_ACCESS_KEY_ID env var)"),
61
+ Flag.withFallbackConfig(Config.redacted("AWS_ACCESS_KEY_ID")),
62
+ ),
63
+ secretAccessKey: Flag.redacted("secret-access-key").pipe(
64
+ Flag.withDescription("AWS secret access key (or set AWS_SECRET_ACCESS_KEY env var)"),
65
+ Flag.withFallbackConfig(Config.redacted("AWS_SECRET_ACCESS_KEY")),
66
+ ),
67
+ region: Flag.string("region").pipe(
68
+ Flag.withDescription("AWS region (e.g., us-east-1)"),
69
+ Flag.optional,
70
+ ),
71
+ host: hostOption,
72
+ port: portOption,
73
+ },
74
+ ({ namespaceId, lake, bucketName, prefix, accessKeyId, secretAccessKey, region }) =>
75
+ Effect.gen(function* () {
76
+ p.intro("📁 Create Namespace (AWS S3)");
77
+
78
+ const lakeConfig = yield* Effect.try({
79
+ try: () => makeLakeConfig(lake),
80
+ catch: (e) => (e instanceof Error ? e : new Error(String(e))),
81
+ });
82
+
83
+ const s = p.spinner();
84
+ s.start("Creating namespace...");
85
+
86
+ const namespace = yield* ClusterClient.createNamespace({
87
+ namespaceId,
88
+ objectStore: {
89
+ objectStoreConfig: {
90
+ _tag: "aws",
91
+ aws: {
92
+ bucketName,
93
+ prefix: Option.getOrUndefined(prefix),
94
+ accessKeyId: Redacted.value(accessKeyId),
95
+ secretAccessKey: Redacted.value(secretAccessKey),
96
+ region: Option.getOrUndefined(region),
97
+ },
98
+ },
99
+ },
100
+ lake: lakeConfig,
101
+ }).pipe(Effect.tapError(() => Effect.sync(() => s.stop("Failed to create namespace"))));
102
+
103
+ s.stop("Namespace created successfully");
104
+ yield* Effect.sync(() => displayNamespace(namespace));
105
+ }),
106
+ ).pipe(
107
+ Command.withDescription("Create a namespace backed by AWS S3"),
108
+ Command.provide(({ host, port }) => makeClusterClientLayer(host, port)),
32
109
  );
33
110
 
34
- const dataLakeOption = Flag.string("data-lake").pipe(
35
- Flag.withDescription(
36
- "Data lake used by this namespace (format: tenants/{tenant}/data-lakes/{data-lake})",
37
- ),
111
+ // ─── Azure ────────────────────────────────────────────────────────────────────
112
+
113
+ const createNamespaceAzureCommand = Command.make(
114
+ "azure",
115
+ {
116
+ namespaceId: namespaceIdOption,
117
+ lake: lakeOption,
118
+ containerName: Flag.string("container-name").pipe(
119
+ Flag.withDescription("Azure Blob container name"),
120
+ ),
121
+ prefix: prefixOption,
122
+ storageAccountName: Flag.string("storage-account-name").pipe(
123
+ Flag.withDescription("Azure storage account name"),
124
+ ),
125
+ storageAccountKey: Flag.redacted("storage-account-key").pipe(
126
+ Flag.withDescription("Azure storage account key (or set AZURE_STORAGE_ACCOUNT_KEY env var)"),
127
+ Flag.withFallbackConfig(Config.redacted("AZURE_STORAGE_ACCOUNT_KEY")),
128
+ ),
129
+ host: hostOption,
130
+ port: portOption,
131
+ },
132
+ ({ namespaceId, lake, containerName, prefix, storageAccountName, storageAccountKey }) =>
133
+ Effect.gen(function* () {
134
+ p.intro("📁 Create Namespace (Azure Blob)");
135
+
136
+ const lakeConfig = yield* Effect.try({
137
+ try: () => makeLakeConfig(lake),
138
+ catch: (e) => (e instanceof Error ? e : new Error(String(e))),
139
+ });
140
+
141
+ const s = p.spinner();
142
+ s.start("Creating namespace...");
143
+
144
+ const namespace = yield* ClusterClient.createNamespace({
145
+ namespaceId,
146
+ objectStore: {
147
+ objectStoreConfig: {
148
+ _tag: "azure",
149
+ azure: {
150
+ containerName,
151
+ prefix: Option.getOrUndefined(prefix),
152
+ storageAccountName,
153
+ storageAccountKey: Redacted.value(storageAccountKey),
154
+ },
155
+ },
156
+ },
157
+ lake: lakeConfig,
158
+ }).pipe(Effect.tapError(() => Effect.sync(() => s.stop("Failed to create namespace"))));
159
+
160
+ s.stop("Namespace created successfully");
161
+ yield* Effect.sync(() => displayNamespace(namespace));
162
+ }),
163
+ ).pipe(
164
+ Command.withDescription("Create a namespace backed by Azure Blob Storage"),
165
+ Command.provide(({ host, port }) => makeClusterClientLayer(host, port)),
166
+ );
167
+
168
+ // ─── Google ───────────────────────────────────────────────────────────────────
169
+
170
+ const createNamespaceGoogleCommand = Command.make(
171
+ "google",
172
+ {
173
+ namespaceId: namespaceIdOption,
174
+ lake: lakeOption,
175
+ bucketName: Flag.string("bucket-name").pipe(Flag.withDescription("GCS bucket name")),
176
+ prefix: prefixOption,
177
+ serviceAccount: Flag.string("service-account").pipe(
178
+ Flag.withDescription("GCP service account email"),
179
+ ),
180
+ serviceAccountKey: Flag.redacted("service-account-key").pipe(
181
+ Flag.withDescription(
182
+ "GCP service account key JSON (or set GOOGLE_SERVICE_ACCOUNT_KEY env var)",
183
+ ),
184
+ Flag.withFallbackConfig(Config.redacted("GOOGLE_SERVICE_ACCOUNT_KEY")),
185
+ ),
186
+ host: hostOption,
187
+ port: portOption,
188
+ },
189
+ ({ namespaceId, lake, bucketName, prefix, serviceAccount, serviceAccountKey }) =>
190
+ Effect.gen(function* () {
191
+ p.intro("📁 Create Namespace (Google Cloud Storage)");
192
+
193
+ const lakeConfig = yield* Effect.try({
194
+ try: () => makeLakeConfig(lake),
195
+ catch: (e) => (e instanceof Error ? e : new Error(String(e))),
196
+ });
197
+
198
+ const s = p.spinner();
199
+ s.start("Creating namespace...");
200
+
201
+ const namespace = yield* ClusterClient.createNamespace({
202
+ namespaceId,
203
+ objectStore: {
204
+ objectStoreConfig: {
205
+ _tag: "google",
206
+ google: {
207
+ bucketName,
208
+ prefix: Option.getOrUndefined(prefix),
209
+ serviceAccount,
210
+ serviceAccountKey: Redacted.value(serviceAccountKey),
211
+ },
212
+ },
213
+ },
214
+ lake: lakeConfig,
215
+ }).pipe(Effect.tapError(() => Effect.sync(() => s.stop("Failed to create namespace"))));
216
+
217
+ s.stop("Namespace created successfully");
218
+ yield* Effect.sync(() => displayNamespace(namespace));
219
+ }),
220
+ ).pipe(
221
+ Command.withDescription("Create a namespace backed by Google Cloud Storage"),
222
+ Command.provide(({ host, port }) => makeClusterClientLayer(host, port)),
38
223
  );
39
224
 
40
- export const createNamespaceCommand = Command.make(
41
- "create-namespace",
225
+ // ─── S3-Compatible ────────────────────────────────────────────────────────────
226
+
227
+ const createNamespaceS3Command = Command.make(
228
+ "s3",
42
229
  {
43
- parent: parentOption,
44
230
  namespaceId: namespaceIdOption,
45
- flushSizeBytes: flushSizeBytesOption,
46
- flushIntervalMillis: flushIntervalMillisOption,
47
- objectStore: objectStoreOption,
48
- dataLake: dataLakeOption,
231
+ lake: lakeOption,
232
+ bucketName: Flag.string("bucket-name").pipe(Flag.withDescription("S3-compatible bucket name")),
233
+ prefix: prefixOption,
234
+ accessKeyId: Flag.redacted("access-key-id").pipe(
235
+ Flag.withDescription("Access key ID (or set S3_ACCESS_KEY_ID env var)"),
236
+ Flag.withFallbackConfig(Config.redacted("S3_ACCESS_KEY_ID")),
237
+ ),
238
+ secretAccessKey: Flag.redacted("secret-access-key").pipe(
239
+ Flag.withDescription("Secret access key (or set S3_SECRET_ACCESS_KEY env var)"),
240
+ Flag.withFallbackConfig(Config.redacted("S3_SECRET_ACCESS_KEY")),
241
+ ),
242
+ endpoint: Flag.string("endpoint").pipe(
243
+ Flag.withDescription("S3-compatible endpoint URL (e.g., http://minio:9000)"),
244
+ ),
245
+ allowHttp: Flag.boolean("allow-http").pipe(
246
+ Flag.withDescription("Allow plain HTTP connections"),
247
+ Flag.withDefault(false),
248
+ ),
249
+ region: Flag.string("region").pipe(Flag.withDescription("Region"), Flag.optional),
49
250
  host: hostOption,
50
251
  port: portOption,
51
252
  },
52
253
  ({
53
- parent,
54
254
  namespaceId,
55
- flushSizeBytes,
56
- flushIntervalMillis,
57
- objectStore,
58
- dataLake,
59
- host,
60
- port,
255
+ lake,
256
+ bucketName,
257
+ prefix,
258
+ accessKeyId,
259
+ secretAccessKey,
260
+ endpoint,
261
+ allowHttp,
262
+ region,
61
263
  }) =>
62
264
  Effect.gen(function* () {
63
- p.intro("📁 Create Namespace");
265
+ p.intro("📁 Create Namespace (S3-Compatible)");
64
266
 
65
- const layer = makeClusterMetadataLayer(host, port);
267
+ const lakeConfig = yield* Effect.try({
268
+ try: () => makeLakeConfig(lake),
269
+ catch: (e) => (e instanceof Error ? e : new Error(String(e))),
270
+ });
66
271
 
67
272
  const s = p.spinner();
68
273
  s.start("Creating namespace...");
69
274
 
70
- const namespace = yield* WingsClusterMetadata.createNamespace({
71
- parent,
275
+ const namespace = yield* ClusterClient.createNamespace({
72
276
  namespaceId,
73
- flushSizeBytes: BigInt(flushSizeBytes),
74
- flushIntervalMillis: BigInt(flushIntervalMillis),
75
- objectStore,
76
- dataLake,
77
- }).pipe(
78
- Effect.provide(layer),
79
- Effect.tapError(() => Effect.sync(() => s.stop("Failed to create namespace"))),
80
- );
277
+ objectStore: {
278
+ objectStoreConfig: {
279
+ _tag: "s3Compatible",
280
+ s3Compatible: {
281
+ bucketName,
282
+ prefix: Option.getOrUndefined(prefix),
283
+ accessKeyId: Redacted.value(accessKeyId),
284
+ secretAccessKey: Redacted.value(secretAccessKey),
285
+ endpoint,
286
+ allowHttp,
287
+ region: Option.getOrUndefined(region),
288
+ },
289
+ },
290
+ },
291
+ lake: lakeConfig,
292
+ }).pipe(Effect.tapError(() => Effect.sync(() => s.stop("Failed to create namespace"))));
81
293
 
82
294
  s.stop("Namespace created successfully");
83
-
84
- yield* Effect.sync(() => {
85
- printTable([
86
- {
87
- name: namespace.name,
88
- flush_size_bytes: namespace.flushSizeBytes.toString(),
89
- flush_interval_millis: namespace.flushIntervalMillis.toString(),
90
- object_store: namespace.objectStore || "-",
91
- data_lake: namespace.dataLake || "-",
92
- },
93
- ]);
94
- p.outro("✓ Done");
95
- });
295
+ yield* Effect.sync(() => displayNamespace(namespace));
96
296
  }),
97
- ).pipe(Command.withDescription("Create a new namespace belonging to a tenant"));
297
+ ).pipe(
298
+ Command.withDescription("Create a namespace backed by an S3-compatible object store"),
299
+ Command.provide(({ host, port }) => makeClusterClientLayer(host, port)),
300
+ );
301
+
302
+ // ─── Parent command ───────────────────────────────────────────────────────────
303
+
304
+ export const createNamespaceCommand = Command.make("create-namespace", {}, () => Effect.void).pipe(
305
+ Command.withDescription(
306
+ "Create a new namespace (choose a storage provider as subcommand: aws, azure, google, s3)",
307
+ ),
308
+ Command.withSubcommands([
309
+ createNamespaceAwsCommand,
310
+ createNamespaceAzureCommand,
311
+ createNamespaceGoogleCommand,
312
+ createNamespaceS3Command,
313
+ ]),
314
+ );
@@ -1,13 +1,13 @@
1
1
  import * as p from "@clack/prompts";
2
- import { WingsClusterMetadata } from "@useairfoil/wings";
2
+ import { ClusterClient } from "@useairfoil/wings";
3
3
  import { Effect } from "effect";
4
4
  import { Command, Flag } from "effect/unstable/cli";
5
5
 
6
- import { makeClusterMetadataLayer } from "../../../utils/client.js";
7
- import { forceOption, hostOption, portOption } from "../../../utils/options.js";
6
+ import { makeClusterClientLayer } from "../../../utils/client";
7
+ import { forceOption, hostOption, portOption } from "../../../utils/options";
8
8
 
9
9
  const nameOption = Flag.string("name").pipe(
10
- Flag.withDescription("Namespace name in format: tenants/{tenant}/namespaces/{namespace}"),
10
+ Flag.withDescription("Namespace name in format: namespaces/{namespace}"),
11
11
  );
12
12
 
13
13
  export const deleteNamespaceCommand = Command.make(
@@ -18,7 +18,7 @@ export const deleteNamespaceCommand = Command.make(
18
18
  host: hostOption,
19
19
  port: portOption,
20
20
  },
21
- ({ name, force, host, port }) =>
21
+ ({ name, force }) =>
22
22
  Effect.gen(function* () {
23
23
  p.intro("🗑️ Delete Namespace");
24
24
 
@@ -34,17 +34,14 @@ export const deleteNamespaceCommand = Command.make(
34
34
 
35
35
  if (p.isCancel(confirm) || !confirm) {
36
36
  p.cancel("Deletion cancelled");
37
- process.exit(0);
37
+ return;
38
38
  }
39
39
  }
40
40
 
41
- const layer = makeClusterMetadataLayer(host, port);
42
-
43
41
  const s = p.spinner();
44
42
  s.start("Deleting namespace...");
45
43
 
46
- yield* WingsClusterMetadata.deleteNamespace({ name }).pipe(
47
- Effect.provide(layer),
44
+ yield* ClusterClient.deleteNamespace({ name }).pipe(
48
45
  Effect.tapError(() => Effect.sync(() => s.stop("Failed to delete namespace"))),
49
46
  );
50
47
 
@@ -52,7 +49,6 @@ export const deleteNamespaceCommand = Command.make(
52
49
  p.outro("✓ Done");
53
50
  }),
54
51
  ).pipe(
55
- Command.withDescription(
56
- "Delete a namespace from the cluster (fails if namespace has any topics)",
57
- ),
52
+ Command.withDescription("Delete a namespace from the cluster"),
53
+ Command.provide(({ host, port }) => makeClusterClientLayer(host, port)),
58
54
  );
@@ -1,14 +1,14 @@
1
1
  import * as p from "@clack/prompts";
2
- import { WingsClusterMetadata } from "@useairfoil/wings";
2
+ import { ClusterClient } from "@useairfoil/wings";
3
3
  import { printTable } from "console-table-printer";
4
4
  import { Effect } from "effect";
5
5
  import { Command, Flag } from "effect/unstable/cli";
6
6
 
7
- import { makeClusterMetadataLayer } from "../../../utils/client.js";
8
- import { hostOption, portOption } from "../../../utils/options.js";
7
+ import { makeClusterClientLayer } from "../../../utils/client";
8
+ import { hostOption, portOption } from "../../../utils/options";
9
9
 
10
10
  const nameOption = Flag.string("name").pipe(
11
- Flag.withDescription("Namespace name in format: tenants/{tenant}/namespaces/{namespace}"),
11
+ Flag.withDescription("Namespace name in format: namespaces/{namespace}"),
12
12
  );
13
13
 
14
14
  export const getNamespaceCommand = Command.make(
@@ -18,15 +18,12 @@ export const getNamespaceCommand = Command.make(
18
18
  host: hostOption,
19
19
  port: portOption,
20
20
  },
21
- ({ name, host, port }) =>
21
+ ({ name }) =>
22
22
  Effect.gen(function* () {
23
- const layer = makeClusterMetadataLayer(host, port);
24
-
25
23
  const s = p.spinner();
26
24
  s.start("Fetching namespace...");
27
25
 
28
- const namespace = yield* WingsClusterMetadata.getNamespace({ name }).pipe(
29
- Effect.provide(layer),
26
+ const namespace = yield* ClusterClient.getNamespace({ name }).pipe(
30
27
  Effect.tapError(() => Effect.sync(() => s.stop("Failed to get namespace"))),
31
28
  );
32
29
 
@@ -36,13 +33,14 @@ export const getNamespaceCommand = Command.make(
36
33
  printTable([
37
34
  {
38
35
  name: namespace.name,
39
- flush_size_bytes: namespace.flushSizeBytes.toString(),
40
- flush_interval_millis: namespace.flushIntervalMillis.toString(),
41
- object_store: namespace.objectStore || "-",
42
- data_lake: namespace.dataLake || "-",
36
+ object_store: namespace.objectStore?.objectStoreConfig._tag ?? "-",
37
+ lake: namespace.lake?.lakeConfig._tag ?? "-",
43
38
  },
44
39
  ]);
45
40
  p.outro("✓ Done");
46
41
  });
47
42
  }),
48
- ).pipe(Command.withDescription("Get details of a specific namespace"));
43
+ ).pipe(
44
+ Command.withDescription("Get details of a specific namespace"),
45
+ Command.provide(({ host, port }) => makeClusterClientLayer(host, port)),
46
+ );
@@ -1,40 +1,29 @@
1
1
  import * as p from "@clack/prompts";
2
- import { WingsClusterMetadata } from "@useairfoil/wings";
2
+ import { ClusterClient } from "@useairfoil/wings";
3
3
  import { printTable } from "console-table-printer";
4
4
  import { Effect, Option } from "effect";
5
- import { Command, Flag } from "effect/unstable/cli";
5
+ import { Command } from "effect/unstable/cli";
6
6
 
7
- import { makeClusterMetadataLayer } from "../../../utils/client.js";
8
- import { hostOption, pageSizeOption, pageTokenOption, portOption } from "../../../utils/options.js";
9
-
10
- const parentOption = Flag.string("parent").pipe(
11
- Flag.withDescription("Parent tenant in format: tenants/{tenant} (e.g., 'tenants/default')"),
12
- );
7
+ import { makeClusterClientLayer } from "../../../utils/client";
8
+ import { hostOption, pageSizeOption, pageTokenOption, portOption } from "../../../utils/options";
13
9
 
14
10
  export const listNamespacesCommand = Command.make(
15
11
  "list-namespaces",
16
12
  {
17
- parent: parentOption,
18
13
  pageSize: pageSizeOption,
19
14
  pageToken: pageTokenOption,
20
15
  host: hostOption,
21
16
  port: portOption,
22
17
  },
23
- ({ parent, pageSize, pageToken, host, port }) =>
18
+ ({ pageSize, pageToken }) =>
24
19
  Effect.gen(function* () {
25
- const layer = makeClusterMetadataLayer(host, port);
26
-
27
20
  const s = p.spinner();
28
21
  s.start("Fetching namespaces...");
29
22
 
30
- const response = yield* WingsClusterMetadata.listNamespaces({
31
- parent,
23
+ const response = yield* ClusterClient.listNamespaces({
32
24
  pageSize,
33
25
  pageToken: Option.getOrUndefined(pageToken),
34
- }).pipe(
35
- Effect.provide(layer),
36
- Effect.tapError(() => Effect.sync(() => s.stop("Failed to list namespaces"))),
37
- );
26
+ }).pipe(Effect.tapError(() => Effect.sync(() => s.stop("Failed to list namespaces"))));
38
27
 
39
28
  s.stop(`Found ${response.namespaces.length} namespace(s)`);
40
29
 
@@ -43,21 +32,11 @@ export const listNamespacesCommand = Command.make(
43
32
  p.log.warn("No namespaces found");
44
33
  } else {
45
34
  printTable(
46
- response.namespaces.map(
47
- (namespace: {
48
- name: string;
49
- flushSizeBytes: bigint;
50
- flushIntervalMillis: bigint;
51
- objectStore?: string | null;
52
- dataLake?: string | null;
53
- }) => ({
54
- name: namespace.name,
55
- flush_size_bytes: namespace.flushSizeBytes.toString(),
56
- flush_interval_millis: namespace.flushIntervalMillis.toString(),
57
- object_store: namespace.objectStore || "-",
58
- data_lake: namespace.dataLake || "-",
59
- }),
60
- ),
35
+ response.namespaces.map((namespace) => ({
36
+ name: namespace.name,
37
+ object_store: namespace.objectStore?.objectStoreConfig._tag ?? "-",
38
+ lake: namespace.lake?.lakeConfig._tag ?? "-",
39
+ })),
61
40
  );
62
41
  }
63
42
 
@@ -68,4 +47,7 @@ export const listNamespacesCommand = Command.make(
68
47
  p.outro("✓ Done");
69
48
  });
70
49
  }),
71
- ).pipe(Command.withDescription("List all namespaces belonging to a tenant"));
50
+ ).pipe(
51
+ Command.withDescription("List all namespaces in the cluster"),
52
+ Command.provide(({ host, port }) => makeClusterClientLayer(host, port)),
53
+ );