Skip to content

Commit 6f1a555

Browse files
committed
Merge remote-tracking branch 'origin/master' into store-purchase-course
2 parents ffacd41 + 612d127 commit 6f1a555

25 files changed

+460
-241
lines changed

src/.claude/settings.local.json

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
{
2+
"permissions": {
3+
"allow": [
4+
"Bash(pnpm tsc:*)",
5+
"Bash(pnpm build:*)",
6+
"Bash(git add:*)",
7+
"Bash(git commit:*)"
8+
],
9+
"deny": []
10+
}
11+
}

src/CLAUDE.md

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
# CLAUDE.md
2+
3+
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
4+
5+
# CoCalc Source Repository
6+
7+
* This is the source code of CoCalc in a Git repository
8+
* It is a complex JavaScript/TypeScript SaaS application
9+
* CoCalc is organized as a monorepository (multi-packages) in the subdirectory "./packages"
10+
* The packages are managed as a pnpm workspace in "./packages/pnpm-workspace.yaml"
11+
12+
## Code Style
13+
14+
- Everything is written in TypeScript code
15+
- Indentation: 2-spaces
16+
- All .js and .ts files are formatted by the tool prettier
17+
- Add suitable types when you write code
18+
- Variable name styles are "camelCase" for local and "FOO_BAR" for global variables. If you edit older code not following these guidlines, adjust this rule to fit the files style.
19+
- Some older code is JavaScript or CoffeeScript, which will be translated to TypeScript
20+
- Use ES modules (import/export) syntax, not CommonJS (require)
21+
- Organize the list of imports in such a way: installed npm packages are on top, newline, then are imports from @cocalc's code base. Sorted alphabetically.
22+
23+
## Development Commands
24+
25+
### Essential Commands
26+
- `pnpm build-dev` - Build all packages for development
27+
- `pnpm clean` - Clean all node_modules and dist directories
28+
- `pnpm database` - Start PostgreSQL database server
29+
- `pnpm hub` - Start the main hub server
30+
- `pnpm psql` - Connect to the PostgreSQL database
31+
- `pnpm test` - Run full test suite
32+
- `pnpm test-parallel` - Run tests in parallel across packages
33+
- `pnpm depcheck` - Check for dependency issues
34+
35+
### Package-Specific Commands
36+
- `cd packages/[package] && pnpm tsc` - Watch TypeScript compilation for a specific package
37+
- `cd packages/[package] && pnpm test` - Run tests for a specific package
38+
- `cd packages/[package] && pnpm build` - Build a specific package
39+
40+
### Development Setup
41+
1. Start database: `pnpm database`
42+
2. Start hub: `pnpm hub`
43+
3. For TypeScript changes, run `pnpm tsc` in the relevant package directory
44+
45+
## Architecture Overview
46+
47+
### Package Structure
48+
CoCalc is organized as a monorepo with key packages:
49+
50+
- **frontend** - React/TypeScript frontend application using Redux-style stores and actions
51+
- **backend** - Node.js backend services and utilities
52+
- **hub** - Main server orchestrating the entire system
53+
- **database** - PostgreSQL database layer with queries and schema
54+
- **util** - Shared utilities and types used across packages
55+
- **comm** - Communication layer including WebSocket types
56+
- **conat** - CoCalc's container/compute orchestration system
57+
- **sync** - Real-time synchronization system for collaborative editing
58+
- **project** - Project-level services and management
59+
- **static** - Static assets and build configuration
60+
- **next** - Next.js server components
61+
62+
### Key Architectural Patterns
63+
64+
#### Frontend Architecture
65+
- **Redux-style State Management**: Uses custom stores and actions pattern (see `packages/frontend/app-framework/actions-and-stores.ts`)
66+
- **TypeScript React Components**: All frontend code is TypeScript with proper typing
67+
- **Modular Store System**: Each feature has its own store/actions (AccountStore, BillingStore, etc.)
68+
- **WebSocket Communication**: Real-time communication with backend via WebSocket messages
69+
70+
#### Backend Architecture
71+
- **PostgreSQL Database**: Primary data store with sophisticated querying system
72+
- **WebSocket Messaging**: Real-time communication between frontend and backend
73+
- **Conat System**: Container orchestration for compute servers
74+
- **Event-Driven Architecture**: Extensive use of EventEmitter patterns
75+
- **Microservice-like Packages**: Each package handles specific functionality
76+
77+
#### Communication Patterns
78+
- **WebSocket Messages**: Primary communication method (see `packages/comm/websocket/types.ts`)
79+
- **Database Queries**: Structured query system with typed interfaces
80+
- **Event Emitters**: Inter-service communication within backend
81+
- **REST-like APIs**: Some HTTP endpoints for specific operations
82+
83+
### Key Technologies
84+
- **TypeScript**: Primary language for all new code
85+
- **React**: Frontend framework
86+
- **PostgreSQL**: Database
87+
- **Node.js**: Backend runtime
88+
- **WebSockets**: Real-time communication
89+
- **pnpm**: Package manager and workspace management
90+
- **Jest**: Testing framework
91+
- **SASS**: CSS preprocessing
92+
93+
### Database Schema
94+
- Comprehensive schema in `packages/util/db-schema`
95+
- Query abstractions in `packages/database/postgres/`
96+
- Type-safe database operations with TypeScript interfaces
97+
98+
### Testing
99+
- **Jest**: Primary testing framework
100+
- **ts-jest**: TypeScript support for Jest
101+
- **jsdom**: Browser environment simulation for frontend tests
102+
- Test files use `.test.ts` or `.spec.ts` extensions
103+
- Each package has its own jest.config.js
104+
105+
### Import Patterns
106+
- Use absolute imports with `@cocalc/` prefix for cross-package imports
107+
- Example: `import { cmp } from "@cocalc/util/misc"`
108+
- Type imports: `import type { Foo } from "./bar"`
109+
- Destructure imports when possible
110+
111+
### Development Workflow
112+
1. Changes to TypeScript require compilation (`pnpm tsc` in relevant package)
113+
2. Database must be running before starting hub
114+
3. Hub coordinates all services and should be restarted after changes
115+
4. Use `pnpm clean && pnpm build-dev` when switching branches or after major changes
116+
117+
# Workflow
118+
- Be sure to typecheck when you're done making a series of code changes
119+
- Prefer running single tests, and not the whole test suite, for performance
120+
121+
## Git Workflow
122+
123+
- Prefix git commits with the package and general area. e.g. 'frontend/latex: ...' if it concerns latex editor changes in the packages/frontend/... code.
124+
- When pushing a new branch to Github, track it upstream. e.g. `git push --set-upstream origin feature-foo` for branch "feature-foo".
125+
126+
# important-instruction-reminders
127+
- Do what has been asked; nothing more, nothing less.
128+
- NEVER create files unless they're absolutely necessary for achieving your goal.
129+
- ALWAYS prefer editing an existing file to creating a new one.
130+
- NEVER proactively create documentation files (*.md) or README files. Only create documentation files if explicitly requested by the User.

src/packages/conat/core/server.ts

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ import { type SysConatServer, sysApiSubject, sysApi } from "./sys";
7474
import { forkedConatServer } from "./start-server";
7575
import { stickyChoice } from "./sticky";
7676
import { EventEmitter } from "events";
77+
import { Metrics } from "../types";
7778

7879
const logger = getLogger("conat:core:server");
7980

@@ -310,6 +311,10 @@ export class ConatServer extends EventEmitter {
310311
});
311312
};
312313

314+
public getUsage = (): Metrics => {
315+
return this.usage.getMetrics();
316+
};
317+
313318
// this is for the Kubernetes health check -- I haven't
314319
// thought at all about what to do here, really.
315320
// Hopefully experience can teach us.
@@ -602,7 +607,9 @@ export class ConatServer extends EventEmitter {
602607
return;
603608
}
604609
if (!(await this.isAllowed({ user, subject, type: "sub" }))) {
605-
const message = `permission denied subscribing to '${subject}' from ${JSON.stringify(user)}`;
610+
const message = `permission denied subscribing to '${subject}' from ${JSON.stringify(
611+
user,
612+
)}`;
606613
this.log(message);
607614
throw new ConatError(message, {
608615
code: 403,
@@ -706,7 +713,9 @@ export class ConatServer extends EventEmitter {
706713
}
707714

708715
if (!(await this.isAllowed({ user: from, subject, type: "pub" }))) {
709-
const message = `permission denied publishing to '${subject}' from ${JSON.stringify(from)}`;
716+
const message = `permission denied publishing to '${subject}' from ${JSON.stringify(
717+
from,
718+
)}`;
710719
this.log(message);
711720
throw new ConatError(message, {
712721
// this is the http code for permission denied, and having this
@@ -950,7 +959,9 @@ export class ConatServer extends EventEmitter {
950959
return;
951960
}
952961
if (!(await this.isAllowed({ user, subject, type: "pub" }))) {
953-
const message = `permission denied waiting for interest in '${subject}' from ${JSON.stringify(user)}`;
962+
const message = `permission denied waiting for interest in '${subject}' from ${JSON.stringify(
963+
user,
964+
)}`;
954965
this.log(message);
955966
respond({ error: message, code: 403 });
956967
}
@@ -1791,7 +1802,9 @@ export function updateSticky(update: StickyUpdate, sticky: Sticky): boolean {
17911802
function getServerAddress(options: Options) {
17921803
const port = options.port;
17931804
const path = options.path?.slice(0, -"/conat".length) ?? "";
1794-
return `http${options.ssl || port == 443 ? "s" : ""}://${options.clusterIpAddress ?? "localhost"}:${port}${path}`;
1805+
return `http${options.ssl || port == 443 ? "s" : ""}://${
1806+
options.clusterIpAddress ?? "localhost"
1807+
}:${port}${path}`;
17951808
}
17961809

17971810
/*

src/packages/conat/monitor/usage.ts

Lines changed: 45 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
1-
import json from "json-stable-stringify";
21
import { EventEmitter } from "events";
3-
import type { JSONValue } from "@cocalc/util/types";
4-
import { ConatError } from "@cocalc/conat/core/client";
2+
import json from "json-stable-stringify";
3+
54
import { getLogger } from "@cocalc/conat/client";
5+
import { ConatError } from "@cocalc/conat/core/client";
6+
import type { JSONValue } from "@cocalc/util/types";
7+
import { Metrics } from "../types";
68

79
const logger = getLogger("monitor:usage");
810

@@ -17,6 +19,9 @@ export class UsageMonitor extends EventEmitter {
1719
private options: Options;
1820
private total = 0;
1921
private perUser: { [user: string]: number } = {};
22+
// metrics will be picked up periodically and exposed via e.g. prometheus
23+
private countDeny = 0;
24+
private metrics: Metrics = {};
2025

2126
constructor(options: Options) {
2227
super();
@@ -38,27 +43,53 @@ export class UsageMonitor extends EventEmitter {
3843

3944
private initLogging = () => {
4045
const { log } = this.options;
41-
if (log == null) {
42-
return;
43-
}
46+
47+
// Record metrics for all events (even if logging is disabled)
4448
this.on("total", (total, limit) => {
45-
log("usage", this.options.resource, { total, limit });
49+
this.metrics["total:count"] = total;
50+
this.metrics["total:limit"] = limit;
51+
if (log) {
52+
log("usage", this.options.resource, { total, limit });
53+
}
4654
});
4755
this.on("add", (user, count, limit) => {
48-
log("usage", this.options.resource, "add", { user, count, limit });
56+
// this.metrics["add:count"] = count;
57+
// this.metrics["add:limit"] = limit;
58+
if (log) {
59+
log("usage", this.options.resource, "add", { user, count, limit });
60+
}
4961
});
5062
this.on("delete", (user, count, limit) => {
51-
log("usage", this.options.resource, "delete", { user, count, limit });
63+
// this.metrics["delete:count"] = count;
64+
// this.metrics["delete:limit"] = limit;
65+
if (log) {
66+
log("usage", this.options.resource, "delete", { user, count, limit });
67+
}
5268
});
5369
this.on("deny", (user, limit, type) => {
54-
log("usage", this.options.resource, "not allowed due to hitting limit", {
55-
type,
56-
user,
57-
limit,
58-
});
70+
this.countDeny += 1;
71+
this.metrics["deny:count"] = this.countDeny;
72+
this.metrics["deny:limit"] = limit;
73+
if (log) {
74+
log(
75+
"usage",
76+
this.options.resource,
77+
"not allowed due to hitting limit",
78+
{
79+
type,
80+
user,
81+
limit,
82+
},
83+
);
84+
}
5985
});
6086
};
6187

88+
// we return a copy
89+
getMetrics = () => {
90+
return { ...this.metrics };
91+
};
92+
6293
add = (user: JSONValue) => {
6394
const u = this.toJson(user);
6495
let count = this.perUser[u] ?? 0;

src/packages/conat/types.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,8 @@ export interface Location {
99

1010
path?: string;
1111
}
12+
13+
type EventType = "total" | "add" | "delete" | "deny";
14+
type ValueType = "count" | "limit";
15+
type MetricKey = `${EventType}:${ValueType}`;
16+
export type Metrics = { [K in MetricKey]?: number };

src/packages/frontend/_account-button.sass

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,14 @@
33
* License: MS-RSL – see LICENSE.md for details
44
*/
55

6-
@import colors
6+
@use 'colors'
77

88
.cocalc-account-button
99
&:hover
1010
text-decoration: none
11-
background-color: $COL_GRAY_LL
11+
background-color: colors.$COL_GRAY_LL
1212
outline: none
1313
&:focus
1414
text-decoration: none
15-
background-color: $COL_GRAY_LL
15+
background-color: colors.$COL_GRAY_LL
1616
outline: none

src/packages/frontend/_antd_fix.sass

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,20 +8,20 @@
88
// This is mainly meant to be temporary, so that we keep the
99
// overall global bootstrap style.
1010
11-
@import colors
11+
@use 'colors'
1212

1313
// make the color consistent with antd again, since bootstrap sets
1414
// it to something else. We want this to be consistent with our nextjs
1515
// landing pages.
1616
a
17-
color: $COL_ANTD_LINK_BLUE
17+
color: colors.$COL_ANTD_LINK_BLUE
1818

1919
html
2020
overflow: hidden
2121

2222
.ant-popover-content
2323
.ant-popover-arrow
24-
border-color: $COL_GRAY_L !important
24+
border-color: colors.$COL_GRAY_L !important
2525
z-index: -1 !important
2626

2727
.ant-popover-inner-content>.cocalc-account-button-dropdown-links

src/packages/frontend/_bootstrap.sass

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
* License: MS-RSL – see LICENSE.md for details
44
*/
55

6+
@use 'colors'
67

78
// There is a bootstrap css that is setting font-weight for label to 700. This
89
// sets the font-weight back to the normal default:
@@ -46,7 +47,7 @@ pre
4647
div
4748
color : white
4849
position : absolute
49-
background-color : $COL_BLUE_D
50+
background-color : colors.$COL_BLUE_D
5051
min-height : 100px
5152
width : 350px
5253
border-radius : 5px

0 commit comments

Comments
 (0)