Skip to content

Commit fb099e9

Browse files
authored
Update main with latest features (#31)
1 parent 1907ecf commit fb099e9

40 files changed

+653
-116
lines changed

cli/incloud.ts

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,9 @@ import { InLog } from "~/in-log/in-log.ts";
22
import convertString from "~/utils/convert-string.ts";
33
import { center } from "~/terminal/format-utils.ts";
44
import { RunManager } from "#cli/run-manager.ts";
5+
import { syncEntryInterface } from "#cli/sync-client/sync-entry-interface.ts";
56

6-
const inLog = new InLog({
7+
export const cliLog: any = new InLog({
78
consoleDefaultStyle: "full",
89
name: "InSpatial CLI",
910
traceOffset: 1,
@@ -48,26 +49,26 @@ createInCloud({
4849
}
4950

5051
function doRun(rootPath: string, file?: string) {
51-
inLog.info("Running InCloud project...");
52+
cliLog.info("Running InCloud project...");
5253
const runner = new RunManager(rootPath, file);
5354
runner.init();
5455
}
5556

5657
function doInit(_rootPath: string, projectName?: string) {
5758
if (!projectName) {
58-
inLog.warn("Project name is required for initialization.");
59+
cliLog.warn("Project name is required for initialization.");
5960
Deno.exit(1);
6061
}
6162
const folderName = convertString(projectName, "kebab", true);
6263
const name = convertString(folderName, "title", true);
63-
inLog.warn(`Initializing project: ${name}`);
64+
cliLog.warn(`Initializing project: ${name}`);
6465

6566
// Create the project directory
6667
try {
6768
Deno.mkdirSync(folderName);
6869
} catch (e) {
6970
if (e instanceof Deno.errors.AlreadyExists) {
70-
inLog.error(`Project directory ${folderName} already exists.`);
71+
cliLog.error(`Project directory ${folderName} already exists.`);
7172
Deno.exit(1);
7273
}
7374
}
@@ -105,31 +106,31 @@ function doInit(_rootPath: string, projectName?: string) {
105106
proc.status.then((status) => {
106107
Deno.writeTextFileSync("main.ts", makeMainFile(projectName));
107108
if (status.success) {
108-
inLog.info([
109+
cliLog.info([
109110
`Project ${name} initialized successfully!`,
110111
"",
111112
"You can now run your project:",
112113
`cd ${folderName}`,
113114
`incloud run main.ts`,
114115
]);
115116
} else {
116-
inLog.error(`Failed to initialize project ${name}.`);
117+
cliLog.error(`Failed to initialize project ${name}.`);
117118
Deno.exit(1);
118119
}
119120
}).catch((err) => {
120-
inLog.error(`Error initializing project: ${err.message}`);
121+
cliLog.error(`Error initializing project: ${err.message}`);
121122
Deno.exit(1);
122123
});
123124
}
124125

125-
function init() {
126-
inLog.setConfig({
126+
async function init() {
127+
cliLog.setConfig({
127128
logTrace: false,
128129
});
129130
const rootPath = Deno.cwd();
130131
const { command, file } = parseArgs();
131132
if (command === undefined) {
132-
inLog.warn(
133+
cliLog.warn(
133134
"No command provided. Please use 'incloud run <file>' or 'incloud init <project-name>'.",
134135
{
135136
compact: true,
@@ -143,11 +144,13 @@ function init() {
143144
case "init":
144145
doInit(rootPath, file);
145146
break;
147+
case "syncClient":
148+
await syncEntryInterface();
149+
break;
146150

147151
default:
148152
console.error(`Unknown command: ${command}`);
149153
Deno.exit(1);
150154
}
151155
}
152-
153156
init();
Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
import { InCloudClient, InLiveClient } from "@inspatial/cloud-client";
2+
import { cliLog } from "@inspatial/cloud/incloud";
3+
import { joinPath } from "~/utils/path-utils.ts";
4+
interface BaseOptions {
5+
host?: string;
6+
port?: number;
7+
onNotify?: (message: string) => void;
8+
}
9+
interface Credential {
10+
email: string;
11+
password: string;
12+
}
13+
14+
interface TokenCredentials {
15+
token: string;
16+
}
17+
18+
type Credentials = Credential | TokenCredentials;
19+
const argsMap = new Map<string, string>(Object.entries({
20+
"-t": "token",
21+
"--token": "token",
22+
"-h": "host",
23+
"--host": "host",
24+
"-P": "port",
25+
"--port": "port",
26+
"-f": "filePath",
27+
"--filePath": "filePath",
28+
}));
29+
30+
async function listenForEscapeKey(callback?: () => void) {
31+
Deno.stdin.setRaw(true);
32+
33+
const escapeKey = new Uint8Array([27]); // Escape key in ASCII
34+
while (true) {
35+
const buffer = new Uint8Array(1);
36+
const bytesRead = await Deno.stdin.read(buffer);
37+
if (bytesRead === null) {
38+
break; // EOF
39+
}
40+
if (buffer[0] === escapeKey[0]) {
41+
console.log("Escape key pressed. Exiting...");
42+
Deno.stdin.setRaw(false);
43+
if (callback) {
44+
callback();
45+
}
46+
47+
Deno.exit(0);
48+
}
49+
}
50+
Deno.stdin.setRaw(false); // Reset to normal mode
51+
}
52+
function parseArgs(): Map<string, string | boolean> {
53+
const argsRecord = new Map<string, string | boolean>();
54+
for (let i = 1; i < Deno.args.length; i++) {
55+
const arg = Deno.args[i];
56+
const nextArg = Deno.args[i + 1]?.trim();
57+
const parseArg = (trim: number) => {
58+
if (!nextArg.startsWith("-")) {
59+
argsRecord.set(arg.slice(trim), nextArg);
60+
i++; // Skip the next argument as it's the value for this flag
61+
return;
62+
}
63+
argsRecord.set(arg.slice(trim), true);
64+
};
65+
if (arg.startsWith("--")) {
66+
parseArg(2);
67+
continue;
68+
}
69+
if (arg.startsWith("-")) {
70+
parseArg(1);
71+
continue;
72+
}
73+
}
74+
return argsRecord;
75+
}
76+
export async function syncEntryInterface() {
77+
const args = parseArgs();
78+
const token = args.get("token") || args.get("t");
79+
let filePath = args.get("filePath") || args.get("f");
80+
if (typeof filePath === "boolean" || filePath === undefined) {
81+
filePath = Deno.cwd();
82+
}
83+
filePath = Deno.realPathSync(filePath);
84+
const file = joinPath(filePath, "cloudTypes.d.ts");
85+
let host = args.get("host") || args.get("h");
86+
if (typeof host === "boolean") {
87+
host = undefined; // If the host is set to true, we treat it as not provided
88+
}
89+
const port = args.get("port") || args.get("P");
90+
if (host && port) {
91+
host += `:${port}`;
92+
}
93+
if (!host) {
94+
host = `http://localhost:${port || "8000"}`;
95+
}
96+
if (!token || typeof token === "boolean") {
97+
cliLog.warn(
98+
"No credentials provided. Use --token <token>, or -t <token>",
99+
"MissingCredentialsError",
100+
);
101+
Deno.exit(1);
102+
}
103+
const inCloud = new InCloudClient(`${host}/api`);
104+
inCloud.headers.set("Authorization", `Bearer ${token}`);
105+
const writeClientFile = async () => {
106+
cliLog.warn("Syncing client interfaces", {
107+
compact: true,
108+
subject: "Sync",
109+
});
110+
const response = await fetch(
111+
`${host}/api?group=orm&action=getClientInterfaces
112+
`,
113+
{
114+
headers: {
115+
"Authorization": `Bearer ${token}`,
116+
},
117+
},
118+
);
119+
const text = await response.text();
120+
Deno.writeTextFileSync(file, text, {
121+
create: true,
122+
append: false,
123+
});
124+
cliLog.info(`Client interfaces written to ${file}`, {
125+
compact: true,
126+
subject: "Sync",
127+
});
128+
};
129+
const session = await inCloud.auth.authCheck();
130+
if (!session) {
131+
cliLog.warn(
132+
"Session check failed. Please ensure your credentials are correct.",
133+
);
134+
Deno.exit(1);
135+
}
136+
cliLog.info(
137+
[
138+
`Connected to InCloud at ${host}`,
139+
`as ${session.firstName} ${session.lastName} (${session.email})`,
140+
],
141+
"ConnectionSuccess",
142+
);
143+
const inLive = new InLiveClient(`${host}/ws`);
144+
writeClientFile();
145+
inLive.onConnectionStatus((status) => {
146+
switch (status) {
147+
case "reconnected":
148+
writeClientFile();
149+
break;
150+
}
151+
});
152+
inLive.start(token);
153+
154+
listenForEscapeKey(() => {
155+
inLive.client.disconnect();
156+
}).catch((err) => {
157+
cliLog.error("Error listening for escape key:", err);
158+
});
159+
}

deno.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
{
22
"name": "@inspatial/cloud",
3-
"version": "0.5.7",
3+
"version": "0.5.8",
44
"license": "Apache-2.0",
55
"exports": {
66
".": "./mod.ts",
77
"./extensions": "./extensions/mod.ts",
8+
"./utils": "./src/utils/mod.ts",
89
"./types": "./types.ts",
910
"./incloud": "./cli/incloud.ts"
1011
},
@@ -25,6 +26,7 @@
2526
"exclude": [".github/", ".vscode/", ".zed/", "examples/**/.inspatial/"]
2627
},
2728
"imports": {
29+
"@inspatial/cloud-client": "jsr:@inspatial/cloud-client@^0.1.22",
2830
"~/": "./src/",
2931
"#cli/": "./cli/src/",
3032
"#extensions/": "./extensions/",

extensions/flutter/actions/generate-models.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -416,6 +416,7 @@ const flutterTypeMap: Record<InFieldType, string> = {
416416
PhoneField: "String",
417417
FileField: "FileField",
418418
TimeField: "DateTime",
419+
CodeField: "String",
419420
};
420421

421422
async function writeModelFile(

mod.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
11
import MimeTypes from "~/files/mime-types/mime-types.ts";
2+
import { requestHandler } from "~/serve/request-handler.ts";
3+
export { PostgresPool } from "~/orm/db/postgres/pgPool.ts";
4+
export { InCloud } from "~/in-cloud.ts";
25
export {
36
CloudException,
47
raiseCloudException,
@@ -22,3 +25,7 @@ export { CloudExtension } from "~/extension/cloud-extension.ts";
2225
export { MimeTypes };
2326

2427
export * from "~/orm/mod.ts";
28+
29+
export const utils = {
30+
requestHandler,
31+
};

src/auth/auth-handler.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ export class AuthHandler {
8080
if (!authToken) {
8181
return null;
8282
}
83-
const sessionData: SessionData = this.#inCloud.inCache.getValue(
83+
let sessionData: SessionData = this.#inCloud.inCache.getValue(
8484
"authToken",
8585
authToken,
8686
);
@@ -90,8 +90,10 @@ export class AuthHandler {
9090
op: "=",
9191
value: authToken,
9292
}]);
93+
9394
if (user) {
94-
const sessionData = await makeSessiondata(user);
95+
sessionData = await makeSessiondata(user);
96+
9597
this.#inCloud.inCache.setValue("authToken", authToken, sessionData);
9698
}
9799
}

src/auth/auth-middleware.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,9 @@ export const authMiddleware: Middleware = {
1414
let sessionData = await authHandler.loadUserSession(sessionId);
1515
if (!sessionData) {
1616
const authToken = inRequest.context.get("authToken");
17-
sessionData = await authHandler.loadSessionFromToken(authToken);
17+
sessionData = await authHandler.loadSessionFromToken(
18+
authToken,
19+
);
1820
}
1921
if (sessionData) {
2022
inRequest.context.update("user", sessionData);

src/extension/core-extension.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ import { ServerException } from "../serve/server-exception.ts";
4040
import { onboardingStep } from "../onboarding/ob-step.ts";
4141
import { onboardingSettings } from "../onboarding/ob-settings.ts";
4242
import { Currencies } from "../orm/field/field-def-types.ts";
43+
import { inLiveLifecycle } from "../in-live/in-live-lifecycle.ts";
4344
export const coreExtension = new CloudExtension("core", {
4445
description: "InSpatial Cloud Core Extension",
4546
label: "Core",
@@ -83,7 +84,7 @@ export const coreExtension = new CloudExtension("core", {
8384
middleware: [corsMiddleware, authMiddleware, inLiveMiddleware],
8485
pathHandlers: [apiPathHandler],
8586
requestLifecycle: {
86-
setup: [authLifecycle],
87+
setup: [authLifecycle, inLiveLifecycle],
8788
},
8889
afterGlobalMigrate: {
8990
name: "initAdminAccount",

0 commit comments

Comments
 (0)