railcode 0.1.0 → 0.1.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.
- package/README.md +29 -1
- package/dist/index.js +1 -1
- package/package.json +1 -1
- package/railcode-templates/railcode-react/src/App.tsx +4 -4
- package/railcode-templates/railcode-react/src/lib/railcode.ts +18 -3
- package/railcode-templates/railcode-react/src/store/app-store.ts +14 -9
- package/static/sdk.js +5 -3
package/README.md
CHANGED
|
@@ -2,6 +2,33 @@
|
|
|
2
2
|
|
|
3
3
|
TypeScript CLI for app builders working on static Railcode apps.
|
|
4
4
|
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
Install the CLI globally from npm:
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install -g railcode
|
|
11
|
+
railcode --version
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
On first install, log in from a terminal:
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
railcode login
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
If the CLI prompts for `Railcode auth/API domain`, enter your Railcode domain.
|
|
21
|
+
Bare domains are treated as HTTPS, so `railcode.example.com` becomes
|
|
22
|
+
`https://railcode.example.com`. Full `https://...` URLs and trailing slashes are
|
|
23
|
+
also accepted. To skip the prompt, pass the domain up front:
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
railcode login --api-url railcode.example.com
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
The login flow opens a browser authorization link and saves the resulting API
|
|
30
|
+
token under `~/.railcode/config.json`.
|
|
31
|
+
|
|
5
32
|
## Build
|
|
6
33
|
|
|
7
34
|
```bash
|
|
@@ -13,11 +40,12 @@ npm link
|
|
|
13
40
|
## Common Flow
|
|
14
41
|
|
|
15
42
|
```bash
|
|
43
|
+
npm install -g railcode
|
|
44
|
+
railcode login
|
|
16
45
|
mkdir my-app
|
|
17
46
|
cd my-app
|
|
18
47
|
railcode init my-app
|
|
19
48
|
railcode dev
|
|
20
|
-
railcode login
|
|
21
49
|
railcode design-system pull design-system.md
|
|
22
50
|
railcode deploy
|
|
23
51
|
```
|
package/dist/index.js
CHANGED
|
@@ -8,7 +8,7 @@ import { basename, dirname, join, relative, resolve, sep } from "node:path";
|
|
|
8
8
|
import process from "node:process";
|
|
9
9
|
import { createInterface } from "node:readline/promises";
|
|
10
10
|
import { fileURLToPath } from "node:url";
|
|
11
|
-
const VERSION = "0.1.
|
|
11
|
+
const VERSION = "0.1.1";
|
|
12
12
|
const CLI_PACKAGE_NAME = "railcode";
|
|
13
13
|
const SKILL_NAME = "create-railcode-app";
|
|
14
14
|
const RAILCODE_HOME = process.env.RAILCODE_HOME || join(homedir(), ".railcode");
|
package/package.json
CHANGED
|
@@ -378,20 +378,20 @@ function SqlPanel() {
|
|
|
378
378
|
return (
|
|
379
379
|
<Panel
|
|
380
380
|
title="Read-only SQL"
|
|
381
|
-
code="await postgres(name).runSQL(query, params)"
|
|
381
|
+
code="await dataConnectors(); await postgres(name).runSQL(query, params)"
|
|
382
382
|
icon={<Database className="size-4" />}
|
|
383
383
|
actions={
|
|
384
384
|
<Button
|
|
385
385
|
icon={<RefreshCcw className="size-4" />}
|
|
386
386
|
onClick={() => void refreshConnections()}
|
|
387
387
|
>
|
|
388
|
-
|
|
388
|
+
Connectors
|
|
389
389
|
</Button>
|
|
390
390
|
}
|
|
391
391
|
>
|
|
392
392
|
<form className="grid gap-3" onSubmit={submit}>
|
|
393
393
|
<label className="grid gap-1.5 text-sm">
|
|
394
|
-
<span className="font-medium">
|
|
394
|
+
<span className="font-medium">Data connector</span>
|
|
395
395
|
<select
|
|
396
396
|
className="h-9 rounded-md border border-input bg-canvas px-3 outline-none focus:border-rail focus:ring-2 focus:ring-rail/20"
|
|
397
397
|
disabled={!hasConnections}
|
|
@@ -408,7 +408,7 @@ function SqlPanel() {
|
|
|
408
408
|
</label>
|
|
409
409
|
{!hasConnections ? (
|
|
410
410
|
<div className="rounded-md border border-line bg-muted px-3 py-2 text-sm text-dim">
|
|
411
|
-
No
|
|
411
|
+
No data connectors are configured.
|
|
412
412
|
</div>
|
|
413
413
|
) : null}
|
|
414
414
|
<label className="grid gap-1.5 text-sm">
|
|
@@ -100,11 +100,13 @@ export type SqlRows = Array<Record<string, unknown>> & {
|
|
|
100
100
|
truncated?: boolean;
|
|
101
101
|
};
|
|
102
102
|
|
|
103
|
-
export type
|
|
103
|
+
export type DataConnectorInfo = {
|
|
104
104
|
engine: "postgres" | "mysql" | string;
|
|
105
105
|
name: string;
|
|
106
106
|
};
|
|
107
107
|
|
|
108
|
+
export type DatabaseConnectorInfo = DataConnectorInfo;
|
|
109
|
+
|
|
108
110
|
export type DatabaseHandle = {
|
|
109
111
|
runSQL(query: string, params?: unknown[]): Promise<SqlRows>;
|
|
110
112
|
};
|
|
@@ -182,6 +184,7 @@ declare global {
|
|
|
182
184
|
};
|
|
183
185
|
postgres?: DatabaseNamespace;
|
|
184
186
|
mysql?: DatabaseNamespace;
|
|
187
|
+
dataConnectors?: () => Promise<DataConnectorInfo[]>;
|
|
185
188
|
databaseConnectors?: () => Promise<DatabaseConnectorInfo[]>;
|
|
186
189
|
llm?: {
|
|
187
190
|
generate(
|
|
@@ -233,11 +236,23 @@ export const fileStore = {
|
|
|
233
236
|
},
|
|
234
237
|
};
|
|
235
238
|
|
|
239
|
+
function requireDataConnectors(): () => Promise<DataConnectorInfo[]> {
|
|
240
|
+
const value = window.dataConnectors ?? window.databaseConnectors;
|
|
241
|
+
if (!value) {
|
|
242
|
+
throw new Error(
|
|
243
|
+
"Railcode SDK global 'dataConnectors' is not available. Make sure /_api/sdk.js loads before the app bundle.",
|
|
244
|
+
);
|
|
245
|
+
}
|
|
246
|
+
return value;
|
|
247
|
+
}
|
|
248
|
+
|
|
236
249
|
// -> [{ engine, name }, ...] every configured connection this app can query.
|
|
237
|
-
export function
|
|
238
|
-
return
|
|
250
|
+
export function dataConnectors(): Promise<DataConnectorInfo[]> {
|
|
251
|
+
return requireDataConnectors()();
|
|
239
252
|
}
|
|
240
253
|
|
|
254
|
+
export const databaseConnectors = dataConnectors;
|
|
255
|
+
|
|
241
256
|
// Per-engine namespaces: `postgres('orders').runSQL(q, p)` targets a named
|
|
242
257
|
// connection; `postgres.runSQL(q, p)` uses the connection named "default".
|
|
243
258
|
function databaseNamespace(engine: "postgres" | "mysql"): DatabaseNamespace {
|
|
@@ -2,7 +2,7 @@ import { create } from "zustand";
|
|
|
2
2
|
|
|
3
3
|
import { cleanError } from "@/lib/format";
|
|
4
4
|
import {
|
|
5
|
-
|
|
5
|
+
DataConnectorInfo,
|
|
6
6
|
FileEntry,
|
|
7
7
|
Identity,
|
|
8
8
|
KvRow,
|
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
SqlRows,
|
|
11
11
|
AppUsers,
|
|
12
12
|
collection,
|
|
13
|
-
|
|
13
|
+
dataConnectors,
|
|
14
14
|
fileStore,
|
|
15
15
|
getIdentity,
|
|
16
16
|
getAppUsers,
|
|
@@ -34,7 +34,7 @@ type AppState = {
|
|
|
34
34
|
appUsers?: AppUsers;
|
|
35
35
|
kvRows: KvRow<StarterDoc>[];
|
|
36
36
|
files: FileEntry[];
|
|
37
|
-
connections:
|
|
37
|
+
connections: DataConnectorInfo[];
|
|
38
38
|
sqlRows?: SqlRows;
|
|
39
39
|
llmResult?: LlmResult;
|
|
40
40
|
streamText: string;
|
|
@@ -51,7 +51,7 @@ type AppState = {
|
|
|
51
51
|
querySql: (
|
|
52
52
|
query: string,
|
|
53
53
|
params: unknown[],
|
|
54
|
-
connector?:
|
|
54
|
+
connector?: DataConnectorInfo,
|
|
55
55
|
) => Promise<void>;
|
|
56
56
|
generateText: (prompt: string) => Promise<void>;
|
|
57
57
|
streamTextResponse: (prompt: string) => Promise<void>;
|
|
@@ -81,6 +81,13 @@ function withError(error: unknown, area: ApiArea) {
|
|
|
81
81
|
});
|
|
82
82
|
}
|
|
83
83
|
|
|
84
|
+
function namespaceFor(connector?: DataConnectorInfo) {
|
|
85
|
+
const engine = connector?.engine ?? "postgres";
|
|
86
|
+
if (engine === "postgres") return postgres;
|
|
87
|
+
if (engine === "mysql") return mysql;
|
|
88
|
+
throw new Error(`Unsupported data connector engine: ${engine}`);
|
|
89
|
+
}
|
|
90
|
+
|
|
84
91
|
export const useAppStore = create<AppState>((set, get) => ({
|
|
85
92
|
kvRows: [],
|
|
86
93
|
files: [],
|
|
@@ -106,7 +113,7 @@ export const useAppStore = create<AppState>((set, get) => ({
|
|
|
106
113
|
getAppUsers(),
|
|
107
114
|
starterCollection().list(),
|
|
108
115
|
fileStore.list(),
|
|
109
|
-
|
|
116
|
+
dataConnectors(),
|
|
110
117
|
]);
|
|
111
118
|
set((state) => ({
|
|
112
119
|
identity,
|
|
@@ -210,7 +217,7 @@ export const useAppStore = create<AppState>((set, get) => ({
|
|
|
210
217
|
async refreshConnections() {
|
|
211
218
|
set(patchStatus("sql", "loading"));
|
|
212
219
|
try {
|
|
213
|
-
const connections = await
|
|
220
|
+
const connections = await dataConnectors();
|
|
214
221
|
set((state) => ({
|
|
215
222
|
connections,
|
|
216
223
|
status: { ...state.status, sql: "ready" },
|
|
@@ -223,10 +230,8 @@ export const useAppStore = create<AppState>((set, get) => ({
|
|
|
223
230
|
async querySql(query, params, connector) {
|
|
224
231
|
set(patchStatus("sql", "loading"));
|
|
225
232
|
try {
|
|
226
|
-
const engine = connector?.engine ?? "postgres";
|
|
227
233
|
const name = connector?.name ?? "default";
|
|
228
|
-
const
|
|
229
|
-
const sqlRows = await ns(name).runSQL(query, params);
|
|
234
|
+
const sqlRows = await namespaceFor(connector)(name).runSQL(query, params);
|
|
230
235
|
set((state) => ({
|
|
231
236
|
sqlRows,
|
|
232
237
|
status: { ...state.status, sql: "ready" },
|
package/static/sdk.js
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
* await files.upload('logo.png', blob, 'image/png');
|
|
13
13
|
*
|
|
14
14
|
* // query an external database an admin configured for this app (read-only):
|
|
15
|
-
* const rows = await
|
|
15
|
+
* const rows = await postgres('analytics').runSQL('select * from orders where id = $1', [orderId]);
|
|
16
16
|
* </script>
|
|
17
17
|
*
|
|
18
18
|
* Everything is scoped to THIS app automatically (derived from the hostname
|
|
@@ -75,7 +75,7 @@
|
|
|
75
75
|
<button id="collapse">Hide \u203A</button>
|
|
76
76
|
</header>
|
|
77
77
|
<div class="log" id="log"></div>
|
|
78
|
-
<footer>Calls made via <code>
|
|
78
|
+
<footer>Calls made via <code>postgres()</code>, <code>mysql()</code>, <code>llm</code>, <code>db</code>, <code>files</code>, <code>me()</code>, <code>appUsers()</code></footer>
|
|
79
79
|
</div>`;
|
|
80
80
|
function mount() {
|
|
81
81
|
(document.body || document.documentElement).appendChild(host);
|
|
@@ -368,7 +368,8 @@
|
|
|
368
368
|
};
|
|
369
369
|
var postgres = namespace("postgres");
|
|
370
370
|
var mysql = namespace("mysql");
|
|
371
|
-
var
|
|
371
|
+
var dataConnectors = () => track("connections", "dataConnectors()", () => call("GET", "/connections"));
|
|
372
|
+
var databaseConnectors = dataConnectors;
|
|
372
373
|
|
|
373
374
|
// src/design-system.ts
|
|
374
375
|
var designSystem = () => track("design-system", "designSystem()", () => call("GET", "/config/design-system"));
|
|
@@ -425,6 +426,7 @@
|
|
|
425
426
|
window.llm = llm;
|
|
426
427
|
window.postgres = postgres;
|
|
427
428
|
window.mysql = mysql;
|
|
429
|
+
window.dataConnectors = dataConnectors;
|
|
428
430
|
window.databaseConnectors = databaseConnectors;
|
|
429
431
|
window.designSystem = designSystem;
|
|
430
432
|
window.me = me;
|