Skip to content
This repository was archived by the owner on Feb 6, 2026. It is now read-only.

Commit d9691f7

Browse files
committed
WIP
1 parent 6c50dba commit d9691f7

File tree

8 files changed

+315
-5
lines changed

8 files changed

+315
-5
lines changed

bin/si/deno.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
},
1515
"workspace": [],
1616
"tasks": {
17-
"dev": "deno run --allow-net --allow-env --allow-read --env-file=.env.local --allow-write --watch main.ts",
17+
"dev": "deno run --unstable-sloppy-imports --allow-net --allow-env --allow-read --env-file=.env.local --allow-write --watch main.ts",
1818
"build": "deno compile --allow-net --allow-env --allow-read --allow-write main.ts",
1919
"lint": "deno lint",
2020
"test": "deno test --allow-env --allow-read --allow-write"

bin/si/deno.lock

Lines changed: 27 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

bin/si/src/cli.ts

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ import { type ComponentGetOptions } from "./component/get.ts";
4040
import { type ComponentUpdateOptions } from "./component/update.ts";
4141
import { type ComponentDeleteOptions } from "./component/delete.ts";
4242
import { type ComponentSearchOptions } from "./component/search.ts";
43+
import { callTui } from "./command/tui.ts";
4344

4445
/** Current version of the SI CLI */
4546
const VERSION = "0.1.0";
@@ -175,7 +176,9 @@ function buildCommand() {
175176
// deno-lint-ignore no-explicit-any
176177
.command("template", buildTemplateCommand() as any)
177178
// deno-lint-ignore no-explicit-any
178-
.command("whoami", buildWhoamiCommand() as any);
179+
.command("whoami", buildWhoamiCommand() as any)
180+
// deno-lint-ignore no-explicit-any
181+
.command("tui", buildTuiCommand() as any);
179182
}
180183

181184
/**
@@ -267,7 +270,7 @@ function buildRemoteSchemaCommand() {
267270
"--builtins",
268271
"Include builtin schemas (schemas you don't own). By default, builtins are skipped.",
269272
)
270-
.action(async ({ root, apiBaseUrl, apiToken, builtins }, ...schemaNames) => {
273+
.action(async ({ root, apiBaseUrl, apiToken }, ...schemaNames) => {
271274
const project = createProject(root);
272275
const apiCtx = await createApiContext(apiBaseUrl, apiToken);
273276
let finalSchemaNames;
@@ -282,7 +285,6 @@ function buildRemoteSchemaCommand() {
282285
project,
283286
apiCtx,
284287
finalSchemaNames,
285-
builtins ?? false,
286288
);
287289
}),
288290
)
@@ -355,6 +357,22 @@ function buildWhoamiCommand() {
355357
});
356358
}
357359

360+
/**
361+
* Builds the tui command.
362+
*
363+
* @returns A SubCommand to start the TUI
364+
* @internal
365+
*/
366+
function buildTuiCommand() {
367+
return createSubCommand()
368+
.description("Starts the TUI")
369+
.action(async ({ apiBaseUrl, apiToken }) => {
370+
const apiCtx = await createApiContext(apiBaseUrl, apiToken);
371+
372+
await callTui(Context.instance(), apiCtx);
373+
});
374+
}
375+
358376
/**
359377
* Builds the project init subcommands.
360378
*

bin/si/src/command/tui.ts

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
import { Context } from "../context.ts";
2+
import { ApiContext } from "../api.ts";
3+
import * as p from 'npm:@clack/prompts';
4+
import color from 'npm:picocolors';
5+
import { apiConfig } from "../si_client.ts";
6+
// import { ChangeSetsApi, ChangeSetViewV1, MvApi } from "https://jsr.io/@systeminit/api-client/1.9.0/api.ts";
7+
import { ChangeSetsApi, ChangeSetViewV1, MvApi } from "../../../../generated-sdks/typescript/api.ts";
8+
9+
const EXIT = "-1";
10+
11+
export async function callTui(ctx: Context, _apiCtx: ApiContext) {
12+
const changeSetsApi = new ChangeSetsApi(apiConfig);
13+
const mvSetsApi = new MvApi(apiConfig);
14+
const workspaceId = ctx.workspaceId;
15+
if (!workspaceId) throw new Error("No Workspace");
16+
17+
p.updateSettings({
18+
aliases: {
19+
w: 'up',
20+
s: 'down',
21+
a: 'left',
22+
d: 'right',
23+
},
24+
});
25+
26+
p.intro("Welcome! Let's review propsed changes in your workspace")
27+
28+
const cs = p.spinner({
29+
onCancel: () => {
30+
process.exit(0);
31+
}
32+
});
33+
cs.start(`${color.bgBlack(color.greenBright("Retrieving change sets"))}`);
34+
const response = await changeSetsApi.listChangeSets({ workspaceId });
35+
const changeSets = response.data.changeSets as ChangeSetViewV1[];
36+
cs.stop();
37+
38+
const options = changeSets.filter((c) => !c.isHead).map((c) => {
39+
return {
40+
value: c.id,
41+
label: c.name,
42+
}
43+
})
44+
const changeSetId = await p.select({
45+
message: "Choose a change set:",
46+
options,
47+
});
48+
49+
if (p.isCancel(changeSetId)) {
50+
p.cancel("Cancelled, exiting...")
51+
process.exit(0);
52+
}
53+
54+
const c = p.spinner();
55+
56+
c.start(`${color.bgBlack(color.greenBright("Retrieving components"))}`);
57+
const componentList = await mvSetsApi.get({
58+
workspaceId,
59+
changeSetId,
60+
entityId: workspaceId,
61+
kind: "ComponentList"
62+
})
63+
64+
// N+1 requests are horribly in-efficient
65+
// if we wanted to invest in a TUI we could do the same
66+
// "sync all the MVs" on start, open a web socket, etc
67+
const componentDetails = await Promise.all(componentList.data.data.components.map((c) => {
68+
return mvSetsApi.get({
69+
workspaceId,
70+
changeSetId,
71+
entityId: c.id,
72+
kind: "ComponentInList"
73+
})
74+
}));
75+
c.stop();
76+
77+
const componentOptions = componentDetails.filter((req) => {
78+
const c = req.data.data;
79+
return c.diffStatus !== "None";
80+
}).map((req) => {
81+
const c = req.data.data;
82+
return {
83+
value: c.id,
84+
label: c.name,
85+
}
86+
});
87+
88+
if (componentOptions.length === 0) {
89+
p.outro(`${color.bgBlack(color.redBright("There are no modifications on this simulated change set."))}`);
90+
p.outro("Goodbye!");
91+
process.exit(0);
92+
}
93+
componentOptions.unshift({value: EXIT, label: "[quit]"})
94+
95+
while (true) {
96+
const componentId = await p.select({
97+
message: "Choose a modified component to review:",
98+
options: componentOptions,
99+
});
100+
if (p.isCancel(componentId)) {
101+
p.cancel("Cancelled, exiting...")
102+
process.exit(0);
103+
}
104+
if (componentId === EXIT) break;
105+
106+
const diff = await mvSetsApi.get({
107+
workspaceId,
108+
changeSetId,
109+
entityId: componentId,
110+
kind: "ComponentDiff"
111+
});
112+
113+
p.outro(`Here is what changed:`)
114+
console.log(JSON.stringify(diff.data.data, undefined, 2));
115+
}
116+
117+
p.outro("Goodbye");
118+
}

lib/luminork-server/src/service/v1.rs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ mod components;
1010
mod debug_funcs;
1111
mod funcs;
1212
mod management_funcs;
13+
mod mv;
1314
mod schemas;
1415
mod search;
1516
mod secrets;
@@ -132,6 +133,7 @@ pub use management_funcs::{
132133
ManagementFuncsResult,
133134
get_management_func_run_state::GetManagementFuncJobStateV1Response,
134135
};
136+
pub use mv::get::MvResponse;
135137
pub use schemas::{
136138
DetachFuncBindingV1Response,
137139
GetSchemaV1Response,
@@ -266,6 +268,7 @@ pub use crate::api_types::func_run::v1::{
266268
secrets::update_secret::update_secret,
267269
secrets::get_secrets::get_secrets,
268270
search::search,
271+
mv::get::get,
269272
),
270273
components(
271274
schemas(
@@ -364,6 +367,7 @@ pub use crate::api_types::func_run::v1::{
364367
ExecDebugFuncV1Request,
365368
ExecDebugFuncV1Response,
366369
GetDebugFuncJobStateV1Response,
370+
MvResponse,
367371
)
368372
),
369373
tags(
@@ -375,7 +379,8 @@ pub use crate::api_types::func_run::v1::{
375379
(name = "secrets", description = "Secret management endpoints"),
376380
(name = "funcs", description = "Functions management endpoints"),
377381
(name = "debug_funcs", description = "Debug function endpoints"),
378-
(name = "management_funcs", description = "Management functions endpoints")
382+
(name = "management_funcs", description = "Management functions endpoints"),
383+
(name = "mv", description = "Materialized view endpoints"),
379384
)
380385
)]
381386
pub struct V1ApiDoc;
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
use axum::{
2+
Json,
3+
extract::Query,
4+
};
5+
use sdf_extract::FriggStore;
6+
use serde::{
7+
Deserialize,
8+
Serialize,
9+
};
10+
use si_frontend_mv_types::object::FrontendObject;
11+
use utoipa::{
12+
IntoParams,
13+
ToSchema,
14+
};
15+
16+
use super::{
17+
MvError,
18+
MvResult,
19+
};
20+
use crate::extract::change_set::ChangeSetDalContext;
21+
22+
#[derive(Deserialize, Serialize, ToSchema, IntoParams, Debug, Clone)]
23+
#[serde(rename_all = "camelCase")]
24+
pub struct GetParams {
25+
#[schema(example = "01H9ZQD35JPMBGHH69BT0Q79VY", nullable = false, value_type = String)]
26+
pub entity_id: String,
27+
#[schema(example = "ComponentList", nullable = false, value_type = String)]
28+
pub kind: String,
29+
}
30+
31+
#[derive(Deserialize, Serialize, Debug, ToSchema, Clone)]
32+
#[serde(rename_all = "camelCase")]
33+
pub struct MvResponse {
34+
#[schema(example = "ComponentList")]
35+
pub kind: String,
36+
#[schema(example = "")]
37+
pub id: String,
38+
#[schema(example = "")]
39+
pub checksum: String,
40+
#[schema(example = "{}")]
41+
pub data: serde_json::Value,
42+
}
43+
44+
#[utoipa::path(
45+
get,
46+
path = "/v1/w/{workspace_id}/change-sets/{change_set_id}/mv",
47+
params(
48+
("workspace_id" = String, Path, description = "Workspace identifier"),
49+
("change_set_id" = String, Path, description = "Change Set identifier"),
50+
GetParams,
51+
),
52+
tag = "mv",
53+
summary = "Identifiers for a materialized view",
54+
responses(
55+
(status = 200, description = "Mv retrieved successfully", body = MvResponse),
56+
(status = 404, description = "Mv not found"),
57+
(status = 500, description = "Internal server error", body = crate::service::v1::common::ApiError)
58+
)
59+
)]
60+
pub async fn get(
61+
ChangeSetDalContext(ref ctx): ChangeSetDalContext,
62+
Query(params): Query<GetParams>,
63+
FriggStore(frigg): FriggStore,
64+
) -> MvResult<Json<MvResponse>> {
65+
let obj = frigg
66+
.get_current_workspace_object(
67+
ctx.workspace_pk()?,
68+
ctx.change_set_id(),
69+
&params.kind,
70+
&params.entity_id,
71+
)
72+
.await?;
73+
match obj {
74+
Some(FrontendObject {
75+
kind,
76+
id,
77+
checksum,
78+
data,
79+
}) => Ok(Json(MvResponse {
80+
kind,
81+
id,
82+
checksum,
83+
data,
84+
})),
85+
None => Err(MvError::NotFound(params.kind, params.entity_id)),
86+
}
87+
}

0 commit comments

Comments
 (0)