airfoil 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 (37) hide show
  1. package/dist/index.js +1182 -99651
  2. package/package.json +19 -23
  3. package/src/commands/cluster/data-lake/create-iceberg.ts +3 -5
  4. package/src/commands/cluster/data-lake/create-parquet.ts +3 -5
  5. package/src/commands/cluster/data-lake/create.ts +3 -9
  6. package/src/commands/cluster/data-lake/delete.ts +4 -8
  7. package/src/commands/cluster/data-lake/get.ts +5 -11
  8. package/src/commands/cluster/data-lake/list.ts +5 -15
  9. package/src/commands/cluster/index.ts +2 -5
  10. package/src/commands/cluster/namespace/create.ts +7 -17
  11. package/src/commands/cluster/namespace/delete.ts +4 -8
  12. package/src/commands/cluster/namespace/get.ts +4 -8
  13. package/src/commands/cluster/namespace/list.ts +5 -14
  14. package/src/commands/cluster/object-store/create-aws.ts +3 -5
  15. package/src/commands/cluster/object-store/create-azure.ts +3 -7
  16. package/src/commands/cluster/object-store/create-google.ts +5 -20
  17. package/src/commands/cluster/object-store/create-s3.ts +6 -18
  18. package/src/commands/cluster/object-store/create.ts +1 -0
  19. package/src/commands/cluster/object-store/delete.ts +3 -5
  20. package/src/commands/cluster/object-store/get.ts +3 -5
  21. package/src/commands/cluster/object-store/list.ts +5 -15
  22. package/src/commands/cluster/tenant/create.ts +3 -5
  23. package/src/commands/cluster/tenant/delete.ts +7 -13
  24. package/src/commands/cluster/tenant/get.ts +4 -8
  25. package/src/commands/cluster/tenant/list.ts +4 -11
  26. package/src/commands/cluster/topic/create.ts +19 -40
  27. package/src/commands/cluster/topic/delete.ts +3 -5
  28. package/src/commands/cluster/topic/get.ts +2 -2
  29. package/src/commands/cluster/topic/list.ts +5 -14
  30. package/src/commands/dev.ts +70 -173
  31. package/src/commands/sql.ts +23 -44
  32. package/src/index.ts +12 -5
  33. package/src/utils/docker.ts +43 -0
  34. package/src/utils/options.ts +1 -3
  35. package/src/utils/wings.ts +68 -64
  36. package/src/scripts/compile-all.ts +0 -21
  37. package/src/utils/effect.ts +0 -11
@@ -3,8 +3,8 @@ import { WingsClusterMetadata } 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";
7
- import { handleCliError } from "../../../utils/effect.js";
8
8
  import { hostOption, portOption } from "../../../utils/options.js";
9
9
 
10
10
  const nameOption = Flag.string("name").pipe(
@@ -31,9 +31,7 @@ export const getObjectStoreCommand = Command.make(
31
31
  name,
32
32
  }).pipe(
33
33
  Effect.provide(layer),
34
- Effect.tapError(() =>
35
- Effect.sync(() => s.stop("Failed to get object store")),
36
- ),
34
+ Effect.tapError(() => Effect.sync(() => s.stop("Failed to get object store"))),
37
35
  );
38
36
 
39
37
  s.stop("Object store retrieved");
@@ -47,5 +45,5 @@ export const getObjectStoreCommand = Command.make(
47
45
  ]);
48
46
  p.outro("✓ Done");
49
47
  });
50
- }).pipe(Effect.catch(handleCliError("Failed to get object store"))),
48
+ }),
51
49
  ).pipe(Command.withDescription("Get details of a specific object store"));
@@ -3,14 +3,9 @@ import { WingsClusterMetadata } from "@useairfoil/wings";
3
3
  import { printTable } from "console-table-printer";
4
4
  import { Effect, Option } from "effect";
5
5
  import { Command, Flag } from "effect/unstable/cli";
6
+
6
7
  import { makeClusterMetadataLayer } from "../../../utils/client.js";
7
- import { handleCliError } from "../../../utils/effect.js";
8
- import {
9
- hostOption,
10
- pageSizeOption,
11
- pageTokenOption,
12
- portOption,
13
- } from "../../../utils/options.js";
8
+ import { hostOption, pageSizeOption, pageTokenOption, portOption } from "../../../utils/options.js";
14
9
 
15
10
  const parentOption = Flag.string("parent").pipe(
16
11
  Flag.withDescription("Parent tenant in format: tenants/{tenant}"),
@@ -38,9 +33,7 @@ export const listObjectStoresCommand = Command.make(
38
33
  pageToken: Option.getOrUndefined(pageToken),
39
34
  }).pipe(
40
35
  Effect.provide(layer),
41
- Effect.tapError(() =>
42
- Effect.sync(() => s.stop("Failed to list object stores")),
43
- ),
36
+ Effect.tapError(() => Effect.sync(() => s.stop("Failed to list object stores"))),
44
37
  );
45
38
 
46
39
  s.stop(`Found ${response.objectStores.length} object store(s)`);
@@ -51,10 +44,7 @@ export const listObjectStoresCommand = Command.make(
51
44
  } else {
52
45
  printTable(
53
46
  response.objectStores.map(
54
- (objectStore: {
55
- name: string;
56
- objectStoreConfig: { _tag?: string | null };
57
- }) => ({
47
+ (objectStore: { name: string; objectStoreConfig: { _tag?: string | null } }) => ({
58
48
  name: objectStore.name,
59
49
  type: objectStore.objectStoreConfig._tag || "-",
60
50
  }),
@@ -68,5 +58,5 @@ export const listObjectStoresCommand = Command.make(
68
58
 
69
59
  p.outro("✓ Done");
70
60
  });
71
- }).pipe(Effect.catch(handleCliError("Failed to list object stores"))),
61
+ }),
72
62
  ).pipe(Command.withDescription("List all object stores belonging to a tenant"));
@@ -3,8 +3,8 @@ import { WingsClusterMetadata } 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";
7
- import { handleCliError } from "../../../utils/effect.js";
8
8
  import { hostOption, portOption } from "../../../utils/options.js";
9
9
 
10
10
  const tenantIdOption = Flag.string("tenant-id").pipe(
@@ -31,9 +31,7 @@ export const createTenantCommand = Command.make(
31
31
  tenantId,
32
32
  }).pipe(
33
33
  Effect.provide(layer),
34
- Effect.tapError(() =>
35
- Effect.sync(() => s.stop("Failed to create tenant")),
36
- ),
34
+ Effect.tapError(() => Effect.sync(() => s.stop("Failed to create tenant"))),
37
35
  );
38
36
 
39
37
  s.stop("Tenant created successfully");
@@ -42,5 +40,5 @@ export const createTenantCommand = Command.make(
42
40
  printTable([{ name: tenant.name }]);
43
41
  p.outro("✓ Done");
44
42
  });
45
- }).pipe(Effect.catch(handleCliError("Failed to create tenant"))),
43
+ }),
46
44
  ).pipe(Command.withDescription("Create a new tenant in the cluster"));
@@ -2,14 +2,12 @@ import * as p from "@clack/prompts";
2
2
  import { WingsClusterMetadata } from "@useairfoil/wings";
3
3
  import { Effect } from "effect";
4
4
  import { Command, Flag } from "effect/unstable/cli";
5
- import { makeClusterMetadataLayer } from "../../../utils/client.js";
6
- import { handleCliError } from "../../../utils/effect.js";
7
- import { forceOption, hostOption, portOption } from "../../../utils/options.js";
5
+
6
+ import { makeClusterMetadataLayer } from "../../../utils/client";
7
+ import { forceOption, hostOption, portOption } from "../../../utils/options";
8
8
 
9
9
  const nameOption = Flag.string("name").pipe(
10
- Flag.withDescription(
11
- "Tenant name in format: tenants/{tenant} (e.g., 'tenants/acme-corp')",
12
- ),
10
+ Flag.withDescription("Tenant name in format: tenants/{tenant} (e.g., 'tenants/acme-corp')"),
13
11
  );
14
12
 
15
13
  export const deleteTenantCommand = Command.make(
@@ -47,16 +45,12 @@ export const deleteTenantCommand = Command.make(
47
45
 
48
46
  yield* WingsClusterMetadata.deleteTenant({ name }).pipe(
49
47
  Effect.provide(layer),
50
- Effect.tapError(() =>
51
- Effect.sync(() => s.stop("Failed to delete tenant")),
52
- ),
48
+ Effect.tapError(() => Effect.sync(() => s.stop("Failed to delete tenant"))),
53
49
  );
54
50
 
55
51
  s.stop("Tenant deleted successfully");
56
52
  p.outro("✓ Done");
57
- }).pipe(Effect.catch(handleCliError("Failed to delete tenant"))),
53
+ }),
58
54
  ).pipe(
59
- Command.withDescription(
60
- "Delete a tenant from the cluster (fails if tenant has any namespaces)",
61
- ),
55
+ Command.withDescription("Delete a tenant from the cluster (fails if tenant has any namespaces)"),
62
56
  );
@@ -3,14 +3,12 @@ import { WingsClusterMetadata } 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";
7
- import { handleCliError } from "../../../utils/effect.js";
8
8
  import { hostOption, portOption } from "../../../utils/options.js";
9
9
 
10
10
  const nameOption = Flag.string("name").pipe(
11
- Flag.withDescription(
12
- "Tenant name in format: tenants/{tenant} (e.g., 'tenants/acme-corp')",
13
- ),
11
+ Flag.withDescription("Tenant name in format: tenants/{tenant} (e.g., 'tenants/acme-corp')"),
14
12
  );
15
13
 
16
14
  export const getTenantCommand = Command.make(
@@ -29,9 +27,7 @@ export const getTenantCommand = Command.make(
29
27
 
30
28
  const tenant = yield* WingsClusterMetadata.getTenant({ name }).pipe(
31
29
  Effect.provide(layer),
32
- Effect.tapError(() =>
33
- Effect.sync(() => s.stop("Failed to get tenant")),
34
- ),
30
+ Effect.tapError(() => Effect.sync(() => s.stop("Failed to get tenant"))),
35
31
  );
36
32
 
37
33
  s.stop("Tenant retrieved");
@@ -40,5 +36,5 @@ export const getTenantCommand = Command.make(
40
36
  printTable([{ name: tenant.name }]);
41
37
  p.outro("✓ Done");
42
38
  });
43
- }).pipe(Effect.catch(handleCliError("Failed to get tenant"))),
39
+ }),
44
40
  ).pipe(Command.withDescription("Get details of a specific tenant"));
@@ -3,14 +3,9 @@ import { WingsClusterMetadata } from "@useairfoil/wings";
3
3
  import { printTable } from "console-table-printer";
4
4
  import { Effect, Option } from "effect";
5
5
  import { Command } from "effect/unstable/cli";
6
+
6
7
  import { makeClusterMetadataLayer } from "../../../utils/client.js";
7
- import { handleCliError } from "../../../utils/effect.js";
8
- import {
9
- hostOption,
10
- pageSizeOption,
11
- pageTokenOption,
12
- portOption,
13
- } from "../../../utils/options.js";
8
+ import { hostOption, pageSizeOption, pageTokenOption, portOption } from "../../../utils/options.js";
14
9
 
15
10
  export const listTenantsCommand = Command.make(
16
11
  "list-tenants",
@@ -32,9 +27,7 @@ export const listTenantsCommand = Command.make(
32
27
  pageToken: Option.getOrUndefined(pageToken),
33
28
  }).pipe(
34
29
  Effect.provide(layer),
35
- Effect.tapError(() =>
36
- Effect.sync(() => s.stop("Failed to list tenants")),
37
- ),
30
+ Effect.tapError(() => Effect.sync(() => s.stop("Failed to list tenants"))),
38
31
  );
39
32
 
40
33
  s.stop(`Found ${response.tenants.length} tenant(s)`);
@@ -56,5 +49,5 @@ export const listTenantsCommand = Command.make(
56
49
 
57
50
  p.outro("✓ Done");
58
51
  });
59
- }).pipe(Effect.catch(handleCliError("Failed to list tenants"))),
52
+ }),
60
53
  ).pipe(Command.withDescription("List all tenants in the cluster"));
@@ -1,12 +1,13 @@
1
- import * as fs from "node:fs";
2
- import * as p from "@clack/prompts";
3
1
  import type { FieldConfig } from "@useairfoil/wings";
2
+
3
+ import * as p from "@clack/prompts";
4
4
  import { WingsClusterMetadata } from "@useairfoil/wings";
5
5
  import { printTable } from "console-table-printer";
6
6
  import { Effect, Option } from "effect";
7
7
  import { Command, Flag } from "effect/unstable/cli";
8
+ import * as fs from "node:fs";
9
+
8
10
  import { makeClusterMetadataLayer } from "../../../utils/client.js";
9
- import { handleCliError } from "../../../utils/effect.js";
10
11
  import { hostOption, portOption } from "../../../utils/options.js";
11
12
 
12
13
  // Supported simple types (no config required)
@@ -41,9 +42,7 @@ const SUPPORTED_INLINE_TYPES = [
41
42
  type SupportedInlineType = (typeof SUPPORTED_INLINE_TYPES)[number];
42
43
 
43
44
  const parentOption = Flag.string("parent").pipe(
44
- Flag.withDescription(
45
- "Parent namespace in format: tenants/{tenant}/namespaces/{namespace}",
46
- ),
45
+ Flag.withDescription("Parent namespace in format: tenants/{tenant}/namespaces/{namespace}"),
47
46
  );
48
47
 
49
48
  const topicIdOption = Flag.string("topic-id").pipe(
@@ -56,16 +55,12 @@ const descriptionOption = Flag.string("description").pipe(
56
55
  );
57
56
 
58
57
  const fieldsOption = Flag.string("fields").pipe(
59
- Flag.withDescription(
60
- 'Field definitions in format "name:Type" or "name:Type?" for nullable',
61
- ),
58
+ Flag.withDescription('Field definitions in format "name:Type" or "name:Type?" for nullable'),
62
59
  Flag.atLeast(0),
63
60
  );
64
61
 
65
62
  const schemaFileOption = Flag.string("schema-file").pipe(
66
- Flag.withDescription(
67
- "Path to JSON file containing FieldConfig[] (for complex types)",
68
- ),
63
+ Flag.withDescription("Path to JSON file containing FieldConfig[] (for complex types)"),
69
64
  Flag.optional,
70
65
  );
71
66
 
@@ -151,15 +146,11 @@ function loadFieldsFromFile(filePath: string): FieldConfig[] {
151
146
  }
152
147
 
153
148
  if (!Array.isArray(parsed)) {
154
- throw new Error(
155
- `Schema file must contain an array of fields. Got: ${typeof parsed}`,
156
- );
149
+ throw new Error(`Schema file must contain an array of fields. Got: ${typeof parsed}`);
157
150
  }
158
151
 
159
152
  if (parsed.length === 0) {
160
- throw new Error(
161
- "Schema file contains an empty array. At least one field is required.",
162
- );
153
+ throw new Error("Schema file contains an empty array. At least one field is required.");
163
154
  }
164
155
 
165
156
  return (parsed as FieldConfig[]).map((field, index) => {
@@ -206,9 +197,7 @@ export const createTopicCommand = Command.make(
206
197
  const hasFields = fields.length > 0;
207
198
 
208
199
  if (!hasFields && !schemaFilePath) {
209
- return yield* Effect.fail(
210
- new Error("Either --fields or --schema-file must be provided"),
211
- );
200
+ return yield* Effect.fail(new Error("Either --fields or --schema-file must be provided"));
212
201
  }
213
202
 
214
203
  if (hasFields && schemaFilePath) {
@@ -219,11 +208,8 @@ export const createTopicCommand = Command.make(
219
208
 
220
209
  const topicFields = yield* Effect.try({
221
210
  try: () =>
222
- schemaFilePath
223
- ? loadFieldsFromFile(schemaFilePath)
224
- : parseFieldsFromArgs(fields),
225
- catch: (error) =>
226
- error instanceof Error ? error : new Error("Failed to parse fields"),
211
+ schemaFilePath ? loadFieldsFromFile(schemaFilePath) : parseFieldsFromArgs(fields),
212
+ catch: (error) => (error instanceof Error ? error : new Error("Failed to parse fields")),
227
213
  });
228
214
 
229
215
  if (topicFields.length === 0) {
@@ -233,9 +219,7 @@ export const createTopicCommand = Command.make(
233
219
  const partitionKeyName = Option.getOrUndefined(partitionKey);
234
220
  let partitionKeyId: bigint | undefined;
235
221
  if (partitionKeyName) {
236
- const index = topicFields.findIndex(
237
- (field) => field.name === partitionKeyName,
238
- );
222
+ const index = topicFields.findIndex((field) => field.name === partitionKeyName);
239
223
  if (index === -1) {
240
224
  return yield* Effect.fail(
241
225
  new Error(
@@ -267,16 +251,12 @@ export const createTopicCommand = Command.make(
267
251
  partitionKey: partitionKeyId,
268
252
  compaction: {
269
253
  freshnessSeconds: BigInt(freshnessSeconds),
270
- ttlSeconds: Option.getOrUndefined(
271
- Option.map(ttlSeconds, (ttl) => BigInt(ttl)),
272
- ),
254
+ ttlSeconds: Option.getOrUndefined(Option.map(ttlSeconds, (ttl) => BigInt(ttl))),
273
255
  targetFileSizeBytes: BigInt(targetFileSizeBytes),
274
256
  },
275
257
  }).pipe(
276
258
  Effect.provide(layer),
277
- Effect.tapError(() =>
278
- Effect.sync(() => s.stop("Failed to create topic")),
279
- ),
259
+ Effect.tapError(() => Effect.sync(() => s.stop("Failed to create topic"))),
280
260
  );
281
261
 
282
262
  s.stop("Topic created successfully");
@@ -288,13 +268,12 @@ export const createTopicCommand = Command.make(
288
268
  description: topic.description || "-",
289
269
  partition_key:
290
270
  topic.partitionKey !== undefined
291
- ? topicFields.find((field) => field.id === topic.partitionKey)
292
- ?.name || topic.partitionKey.toString()
271
+ ? topicFields.find((field) => field.id === topic.partitionKey)?.name ||
272
+ topic.partitionKey.toString()
293
273
  : "-",
294
274
  freshness_seconds: topic.compaction.freshnessSeconds.toString(),
295
275
  ttl_seconds: topic.compaction.ttlSeconds?.toString() || "-",
296
- target_file_size_bytes:
297
- topic.compaction.targetFileSizeBytes.toString(),
276
+ target_file_size_bytes: topic.compaction.targetFileSizeBytes.toString(),
298
277
  fields_count: topic.schema.fields.length.toString(),
299
278
  },
300
279
  ]);
@@ -313,5 +292,5 @@ export const createTopicCommand = Command.make(
313
292
 
314
293
  p.outro("✓ Done");
315
294
  });
316
- }).pipe(Effect.catch(handleCliError("Failed to create topic"))),
295
+ }),
317
296
  ).pipe(Command.withDescription("Create a new topic belonging to a namespace"));
@@ -2,8 +2,8 @@ import * as p from "@clack/prompts";
2
2
  import { WingsClusterMetadata } 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";
6
- import { handleCliError } from "../../../utils/effect.js";
7
7
  import { forceOption, hostOption, portOption } from "../../../utils/options.js";
8
8
 
9
9
  const nameOption = Flag.string("name").pipe(
@@ -56,12 +56,10 @@ export const deleteTopicCommand = Command.make(
56
56
  force: forceDelete,
57
57
  }).pipe(
58
58
  Effect.provide(layer),
59
- Effect.tapError(() =>
60
- Effect.sync(() => s.stop("Failed to delete topic")),
61
- ),
59
+ Effect.tapError(() => Effect.sync(() => s.stop("Failed to delete topic"))),
62
60
  );
63
61
 
64
62
  s.stop("Topic deleted successfully");
65
63
  p.outro("✓ Done");
66
- }).pipe(Effect.catch(handleCliError("Failed to delete topic"))),
64
+ }),
67
65
  ).pipe(Command.withDescription("Delete a topic from the cluster"));
@@ -3,8 +3,8 @@ import { WingsClusterMetadata } 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";
7
- import { handleCliError } from "../../../utils/effect.js";
8
8
  import { hostOption, portOption } from "../../../utils/options.js";
9
9
 
10
10
  const nameOption = Flag.string("name").pipe(
@@ -47,5 +47,5 @@ export const getTopicCommand = Command.make(
47
47
 
48
48
  p.outro("✓ Done");
49
49
  });
50
- }).pipe(Effect.catch(handleCliError("Failed to get topic"))),
50
+ }),
51
51
  ).pipe(Command.withDescription("Get details of a specific topic"));
@@ -3,19 +3,12 @@ import { WingsClusterMetadata } from "@useairfoil/wings";
3
3
  import { printTable } from "console-table-printer";
4
4
  import { Effect, Option } from "effect";
5
5
  import { Command, Flag } from "effect/unstable/cli";
6
+
6
7
  import { makeClusterMetadataLayer } from "../../../utils/client.js";
7
- import { handleCliError } from "../../../utils/effect.js";
8
- import {
9
- hostOption,
10
- pageSizeOption,
11
- pageTokenOption,
12
- portOption,
13
- } from "../../../utils/options.js";
8
+ import { hostOption, pageSizeOption, pageTokenOption, portOption } from "../../../utils/options.js";
14
9
 
15
10
  const parentOption = Flag.string("parent").pipe(
16
- Flag.withDescription(
17
- "Parent namespace in format: tenants/{tenant}/namespaces/{namespace}",
18
- ),
11
+ Flag.withDescription("Parent namespace in format: tenants/{tenant}/namespaces/{namespace}"),
19
12
  );
20
13
 
21
14
  export const listTopicsCommand = Command.make(
@@ -40,9 +33,7 @@ export const listTopicsCommand = Command.make(
40
33
  pageToken: Option.getOrUndefined(pageToken),
41
34
  }).pipe(
42
35
  Effect.provide(layer),
43
- Effect.tapError(() =>
44
- Effect.sync(() => s.stop("Failed to list topics")),
45
- ),
36
+ Effect.tapError(() => Effect.sync(() => s.stop("Failed to list topics"))),
46
37
  );
47
38
 
48
39
  s.stop(`Found ${response.topics.length} topic(s)`);
@@ -66,5 +57,5 @@ export const listTopicsCommand = Command.make(
66
57
 
67
58
  p.outro("✓ Done");
68
59
  });
69
- }).pipe(Effect.catch(handleCliError("Failed to list topics"))),
60
+ }),
70
61
  ).pipe(Command.withDescription("List all topics belonging to a namespace"));