@omen.foundation/node-microservice-runtime 0.1.122 → 0.1.123

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/AGENTS.md ADDED
@@ -0,0 +1,56 @@
1
+ # Agent instructions: @omen.foundation/node-microservice-runtime
2
+
3
+ Use this file when implementing or refactoring a **Beamable Node microservice** that depends on `@omen.foundation/node-microservice-runtime`. Prefer facts below over guesses; when unsure, read the installed package source under `dist/` or this repo’s `Microservice/src/`.
4
+
5
+ ## Hard rules
6
+
7
+ 1. **Single `@Microservice`** — Exactly one class per process with `@Microservice('Name')`. Service name must match `package.json` → `beamable.beamoId`.
8
+ 2. **`reflect-metadata` first** — `import 'reflect-metadata'` before any file that uses decorators on classes/methods.
9
+ 3. **Import order** — Import `@StorageObject` classes **before** services that use storage so registration runs.
10
+ 4. **Do not block publish/validate** — When OpenAPI generation or `beamo-node` loads the app without running the network stack, `BEAMABLE_SKIP_RUNTIME=true` is set; `runMicroservice()` becomes a no-op. Do not remove this guard or start long-lived work at module top level without checking that variable.
11
+ 5. **Required env for live runtime** — `CID`, `PID`, `HOST` are mandatory for `loadEnvironmentConfig()`. Missing values throw at startup.
12
+ 6. **`beam.env` semantics** — Loaded from `cwd` or `/beam/service/`. Injected keys **do not override** existing `process.env` entries.
13
+ 7. **No fake APIs** — Only use exports from the package’s public `dist/index` surface (`Microservice`, `Callable`, `ClientCallable`, `ServerCallable`, `AdminCallable`, `ConfigureServices`, `InitializeServices`, `runMicroservice`, `loadEnvironmentConfig`, `StorageObject`, federation types, DI tokens, etc.). Do not invent Beamable HTTP paths or env vars; verify in code or Beamable docs.
14
+
15
+ ## Callable access model
16
+
17
+ - **`@ClientCallable`** — Authenticated player/client flows (`userId` > 0 expected).
18
+ - **`@ServerCallable`** — Server/trusted routes; still use scopes appropriate to your game.
19
+ - **`@AdminCallable`** — Admin scope defaults.
20
+ - **`@Callable`** — Configure `access`, `route`, `requiredScopes`, `requireAuth` explicitly when you need fine control.
21
+
22
+ Always use **`RequestContext`** as the first parameter pattern consistent with existing handlers in the project.
23
+
24
+ ## Dependency injection
25
+
26
+ - Register dependencies in a **`@ConfigureServices` static** method on the microservice class: `builder.addSingletonClass`, `builder.addSingleton`, lifetimes as provided by `DependencyBuilder`.
27
+ - Resolve in handlers via **`ctx.provider.resolve(MyService)`** (or inject via factories registered on the builder).
28
+ - If `tsx` or build pipeline strips `emitDecoratorMetadata`, prefer **factory registration** that manually resolves dependencies (see Example microservice pattern).
29
+
30
+ ## Storage (MongoDB)
31
+
32
+ - Decorate with **`@StorageObject('StorageName')`**.
33
+ - Provide **`STORAGE_CONNSTR_<StorageName>`** when you need a direct connection string (name normalization matches runtime — use the same spelling as in code).
34
+ - **`MONGODB_MAX_POOL_SIZE`** optional; clamped by runtime.
35
+
36
+ ## Health and hosting
37
+
38
+ - Default **health HTTP port `6565`** when `HEALTH_PORT` is unset or invalid in container contexts.
39
+ - Production Docker should use **`WORKDIR /beam/service`** and place **`beam.env`** there (or supply equivalent env via the platform).
40
+
41
+ ## Logging / collector
42
+
43
+ - Runtime starts OpenTelemetry collector setup in the background; pre-baking collector binaries in the image under **`/opt/beam/collectors/`** avoids cold-start delay. See **`ai/RUNTIME_FOR_AI.md`** for Dockerfile snippets.
44
+
45
+ ## CLI
46
+
47
+ - **`beamo-node validate`** / **`beamo-node publish`** — use project `.env` or `--env-file` so `CID`/`PID`/`HOST`/tokens match the target realm.
48
+ - Binary name: **`beamo-node`** (`package.json` `bin`).
49
+
50
+ ## TypeScript config
51
+
52
+ - Enable **`experimentalDecorators`** and **`emitDecoratorMetadata`** unless the project standardizes on explicit factory-based DI only.
53
+
54
+ ## Longer reference
55
+
56
+ See **`ai/RUNTIME_FOR_AI.md`** in this package for Dockerfile examples, `beam.env` priority, Beamable Config keys, and troubleshooting.
package/CLAUDE.md ADDED
@@ -0,0 +1,11 @@
1
+ # AI agents (Claude, Codex, Cursor, etc.)
2
+
3
+ Authoritative usage rules for **`@omen.foundation/node-microservice-runtime`** ship in this package:
4
+
5
+ 1. **`AGENTS.md`** — concise rules (env, decorators, DI, Docker, publish).
6
+ 2. **`ai/RUNTIME_FOR_AI.md`** — full reference (startup order, env tables, Dockerfile fragment, troubleshooting).
7
+ 3. **`ai/cursor-rule-snippet.md`** — optional text to paste into Cursor `.cursor/rules`.
8
+
9
+ Human-oriented overview: **`README.md`**.
10
+
11
+ After `npm install`, these files are under `node_modules/@omen.foundation/node-microservice-runtime/`.
package/README.md ADDED
@@ -0,0 +1,174 @@
1
+ # @omen.foundation/node-microservice-runtime
2
+
3
+ TypeScript/Node.js runtime for **Beamable** microservices: WebSocket gateway integration, dependency injection, OpenAPI generation, MongoDB storage helpers, optional federation, logging (OpenTelemetry / collector), and a **`beamo-node`** CLI for validate/publish workflows.
4
+
5
+ ## Requirements
6
+
7
+ - **Node.js** `>= 22.14.0` (see `engines` in `package.json`).
8
+ - **ESM** projects: `"type": "module"` in your service `package.json`.
9
+ - **`reflect-metadata`** imported once before any decorated classes load (required for decorators / DI).
10
+
11
+ ## Install
12
+
13
+ ```bash
14
+ npm install @omen.foundation/node-microservice-runtime reflect-metadata
15
+ ```
16
+
17
+ The package ships:
18
+
19
+ - **Library** — decorators, `runMicroservice()`, DI tokens, storage/federation helpers.
20
+ - **CLI** — `beamo-node` (login, scaffold, validate, publish, …).
21
+
22
+ ## Minimal service shape
23
+
24
+ 1. **`package.json`** — declare the Beamable service id (must match `@Microservice` name):
25
+
26
+ ```json
27
+ {
28
+ "name": "my-game-service",
29
+ "type": "module",
30
+ "main": "dist/main.js",
31
+ "scripts": {
32
+ "dev": "tsx --env-file .env src/main.ts",
33
+ "build": "tsc -p tsconfig.json",
34
+ "validate": "npx beamo-node validate --env-file .env",
35
+ "publish": "npx beamo-node publish --env-file .env"
36
+ },
37
+ "beamable": {
38
+ "beamoId": "MyGameService",
39
+ "projectType": "service"
40
+ }
41
+ }
42
+ ```
43
+
44
+ 2. **Entry file** — load metadata, import the class that carries `@Microservice`, then start:
45
+
46
+ ```ts
47
+ import 'reflect-metadata';
48
+ import './MyGameService.js';
49
+ import { runMicroservice } from '@omen.foundation/node-microservice-runtime';
50
+
51
+ void runMicroservice();
52
+ ```
53
+
54
+ 3. **Service class** — exactly one class decorated with `@Microservice('Name')` (name must match `beamable.beamoId`).
55
+
56
+ ## Decorators and HTTP access
57
+
58
+ | Decorator | Typical use |
59
+ |-----------|-------------|
60
+ | `@Microservice('ServiceName')` | Registers the service (required, one per process). |
61
+ | `@Callable` | General callable; access via `access` option or use a specialized decorator below. |
62
+ | `@ClientCallable` | Game/client calls; expects authenticated user. |
63
+ | `@ServerCallable` | Trusted server-to-server style routes. |
64
+ | `@AdminCallable` | Admin-scoped routes. |
65
+ | `@ConfigureServices` | Static method receiving `DependencyBuilder` — register singletons, factories. |
66
+ | `@InitializeServices` | Static method receiving scope — run after container is built. |
67
+ | `@SwaggerCategory` / `@SwaggerTags` | OpenAPI grouping. |
68
+ | `@StorageObject('StorageName')` | Registers a named Beamable storage binding (MongoDB). |
69
+ | `@FederatedInventory` | Optional federation hook for inventory-style flows. |
70
+
71
+ Handlers receive **`RequestContext`** (`userId`, `cid`, `pid`, logger, `provider` for DI resolution, etc.).
72
+
73
+ ## Environment: Beamable vs `beam.env`
74
+
75
+ ### Required for a running service
76
+
77
+ The runtime calls `loadEnvironmentConfig()`, which **requires**:
78
+
79
+ - `CID` — customer id
80
+ - `PID` — project / realm id
81
+ - `HOST` — WebSocket host (e.g. `wss://api.beamable.com/socket`)
82
+ - `SECRET` — signing secret (when used by your deployment; local CLI may use tokens instead)
83
+
84
+ Optional common variables include `REFRESH_TOKEN`, `NAME_PREFIX` / `ROUTING_KEY`, `LOG_LEVEL`, `HEALTH_PORT` (defaults to **6565** when not set or invalid).
85
+
86
+ ### Developer file: `beam.env`
87
+
88
+ The runtime loads **`beam.env`** (or **`.beam.env`**) from, in order:
89
+
90
+ 1. `process.cwd()/beam.env` or `.beam.env`
91
+ 2. `/beam/service/beam.env` or `.beam.env` (typical container layout)
92
+
93
+ Rules:
94
+
95
+ - Format: `KEY=value`, `#` comments, optional quotes for values.
96
+ - **Does not override** variables already in `process.env`.
97
+
98
+ See the published **`beam.env.example`** in this package for a starting template.
99
+
100
+ ### Beamable Config API
101
+
102
+ Config from the portal can be merged in asynchronously (with a short timeout). Keys are exposed as `BEAM_CONFIG_*`. Details are summarized in **`AGENTS.md`** and **`ai/RUNTIME_FOR_AI.md`**.
103
+
104
+ ### OpenAPI / validate / publish without starting the server
105
+
106
+ Tools set:
107
+
108
+ ```bash
109
+ BEAMABLE_SKIP_RUNTIME=true
110
+ ```
111
+
112
+ so `runMicroservice()` returns immediately while your module graph still loads for schema generation.
113
+
114
+ ## Docker (production-oriented)
115
+
116
+ Recommended patterns used in production games:
117
+
118
+ 1. **Working directory** — use **`/beam/service`** so the default `beam.env` search path matches the runtime.
119
+ 2. **Copy artifacts** — `dist/`, `package.json`, lockfile, `beam_openApi.json` (if generated), and **`beam.env`** (or inject secrets via orchestrator and skip copying secrets into images where policy forbids it).
120
+ 3. **Health checks** — expose the HTTP health port (default **6565**, overridable with `HEALTH_PORT`).
121
+ 4. **Collector startup** — pre-installing the Beamable OpenTelemetry collector under **`/opt/beam/collectors/...`** avoids a multi-second download on cold start. See **`ai/RUNTIME_FOR_AI.md`** for an example multi-stage `Dockerfile` fragment.
122
+
123
+ Your game may use Node 20 images today; the **runtime’s declared engine is Node 22+** — align image version with `engines` before upgrading the dependency.
124
+
125
+ ## CLI: `beamo-node`
126
+
127
+ Installed as a binary with the package:
128
+
129
+ ```bash
130
+ npx beamo-node --help
131
+ ```
132
+
133
+ Typical project scripts:
134
+
135
+ ```bash
136
+ npx beamo-node validate --env-file .env
137
+ npx beamo-node publish --env-file .env
138
+ ```
139
+
140
+ Login and scaffolding commands create a local profile under `~/.beamo-node/`.
141
+
142
+ **`beamo-node scaffold <name>`** creates a new project that includes **`AGENTS.md`**, **`CLAUDE.md`**, **`ai/`** (reference docs), **`.cursor/rules/beamable-node-microservice.mdc`**, and a short **`README.md`** pointing at those files.
143
+
144
+ ## TypeScript
145
+
146
+ Enable decorator metadata in `tsconfig.json`:
147
+
148
+ ```json
149
+ {
150
+ "compilerOptions": {
151
+ "experimentalDecorators": true,
152
+ "emitDecoratorMetadata": true,
153
+ "module": "NodeNext",
154
+ "moduleResolution": "NodeNext",
155
+ "target": "ES2022"
156
+ }
157
+ }
158
+ ```
159
+
160
+ ## MongoDB / `@StorageObject`
161
+
162
+ - Decorate a class with `@StorageObject('MyStorage')`.
163
+ - Connection string env var: **`STORAGE_CONNSTR_<MyStorage>`** (see runtime `StorageService` — name is normalized).
164
+ - Optional pool sizing: **`MONGODB_MAX_POOL_SIZE`** (clamped).
165
+
166
+ Import storage classes **before** services that depend on them so decorators register.
167
+
168
+ ## AI assistants / agents
169
+
170
+ This package includes **`AGENTS.md`** (short rules), **`CLAUDE.md`** (index for AI tools), and **`ai/RUNTIME_FOR_AI.md`** (deep reference: Dockerfile, env, pitfalls). Point Cursor, Claude Code, Codex, or other tools at those files when generating or refactoring Beamable Node microservices. Optional Cursor rule text lives in **`ai/cursor-rule-snippet.md`**.
171
+
172
+ ## License
173
+
174
+ MIT
@@ -0,0 +1,145 @@
1
+ # Node Microservice Runtime — reference for AI tools
2
+
3
+ Companion to **`AGENTS.md`** (rules) and **`README.md`** (human overview). This document is optimized for accuracy and searchability by coding agents.
4
+
5
+ ## Package identity
6
+
7
+ - **npm**: `@omen.foundation/node-microservice-runtime`
8
+ - **CLI binary**: `beamo-node`
9
+ - **Main export**: ESM `dist/index.js`, CJS `dist/index.cjs` (dual package)
10
+ - **Node**: `engines.node` is `>= 22.14.0`
11
+
12
+ ## Startup sequence (conceptual)
13
+
14
+ 1. `loadDeveloperEnvVarsSync()` reads `beam.env` / `.beam.env` from cwd or `/beam/service/` and fills `process.env` only for keys **not** already set.
15
+ 2. `MicroserviceRuntime` constructor validates **at least one** `@Microservice` registration.
16
+ 3. Beamable Config fetch runs asynchronously (timeout ~2s); failures are non-fatal.
17
+ 4. WebSocket connection to `HOST`, gateway requester, auth, DI container build, health server, route dispatch.
18
+
19
+ If **`BEAMABLE_SKIP_RUNTIME=true`**, `runMicroservice()` exits before constructing the runtime — used when generating OpenAPI or running validate-only flows.
20
+
21
+ ## Environment variables
22
+
23
+ ### Required for `loadEnvironmentConfig()`
24
+
25
+ | Variable | Role |
26
+ |----------|------|
27
+ | `CID` | Customer ID |
28
+ | `PID` | Realm / project ID |
29
+ | `HOST` | WebSocket URL (e.g. `wss://api.beamable.com/socket`) |
30
+
31
+ ### Common optional
32
+
33
+ | Variable | Role |
34
+ |----------|------|
35
+ | `SECRET` | Request signing |
36
+ | `REFRESH_TOKEN` | Token refresh path |
37
+ | `NAME_PREFIX` / `ROUTING_KEY` | Local dev routing; cleared in container when appropriate |
38
+ | `LOG_LEVEL` | Pino level |
39
+ | `HEALTH_PORT` | HTTP health server (default **6565** when unset or ≤0 in practice for deployed images) |
40
+ | `USER_ACCOUNT_ID`, `USER_EMAIL` | Dev / tooling context |
41
+ | `BEAM_INSTANCE_COUNT` | Instance hint |
42
+ | `BEAM_CONFIG_API_PATH` | Override Beamable Config endpoint |
43
+
44
+ ### Beamable Config → env
45
+
46
+ API values are merged into `process.env` with prefix **`BEAM_CONFIG_`** (details in runtime `env-loader.ts`).
47
+
48
+ ### MongoDB storage
49
+
50
+ - **`STORAGE_CONNSTR_<StorageName>`** — direct connection string for that `@StorageObject` name.
51
+ - **`MONGODB_MAX_POOL_SIZE`** — pool size clamped between 1 and 100.
52
+
53
+ ## `beam.env` file
54
+
55
+ - Lines: `KEY=value`, `#` comments.
56
+ - Search paths (first match wins): explicit path (API), `./beam.env`, `./.beam.env`, `/beam/service/beam.env`, `/beam/service/.beam.env`.
57
+ - **Never overwrites** existing `process.env`.
58
+
59
+ **Production pattern (Omen Wars backend)**:
60
+
61
+ - Dockerfile **`WORKDIR /beam/service`**
62
+ - **`COPY ... beam.env ./beam.env`**
63
+ - Optionally also load `.env` in dev only via `dotenv` in entry file — keep secrets out of git; use `env.sample` for documentation.
64
+
65
+ ## Entrypoint patterns
66
+
67
+ ### Minimal (example / small services)
68
+
69
+ ```ts
70
+ import 'reflect-metadata';
71
+ import 'dotenv/config';
72
+ import './MyService.js';
73
+ import { runMicroservice } from '@omen.foundation/node-microservice-runtime';
74
+
75
+ void runMicroservice();
76
+ ```
77
+
78
+ ### Larger services (Omen Wars style)
79
+
80
+ - Guard heavy startup with `BEAMABLE_SKIP_RUNTIME === 'true'`.
81
+ - Explicitly `dotenv.config` for `beam.env` path relative to `dist/` if you need variables **before** custom loggers run (runtime still loads `beam.env` itself for its own logger path).
82
+ - Import **`@StorageObject`** module before the main `@Microservice` module.
83
+ - Call **`void runMicroservice()`** last.
84
+
85
+ ## Docker — recommended production fragment
86
+
87
+ Multi-stage build: install all deps → `npm run build` → slim runtime image with production `npm install` only.
88
+
89
+ ```dockerfile
90
+ FROM node:22-alpine AS builder
91
+ WORKDIR /build
92
+ COPY package*.json ./
93
+ RUN npm install
94
+ COPY . .
95
+ RUN npm run build
96
+
97
+ FROM node:22-alpine
98
+ WORKDIR /beam/service
99
+ COPY package*.json ./
100
+ RUN npm install --omit=dev && npm cache clean --force
101
+
102
+ # Optional but recommended: pre-cache OTel collector to avoid runtime download delay
103
+ RUN mkdir -p /opt/beam/collectors/1.0.1 && \
104
+ apk add --no-cache wget gzip && \
105
+ wget https://collectors.beamable.com/version/1.0.1/collector-linux-amd64.gz -O /tmp/collector.gz && \
106
+ gunzip /tmp/collector.gz && \
107
+ mv /tmp/collector /opt/beam/collectors/1.0.1/collector-linux-amd64 && \
108
+ chmod +x /opt/beam/collectors/1.0.1/collector-linux-amd64 && \
109
+ wget https://collectors.beamable.com/version/1.0.1/clickhouse-config.yaml.gz -O /tmp/config.gz && \
110
+ gunzip /tmp/config.gz && \
111
+ mv /tmp/config /opt/beam/collectors/1.0.1/clickhouse-config.yaml && \
112
+ rm -f /tmp/collector.gz /tmp/config.gz && \
113
+ apk del wget gzip
114
+
115
+ COPY --from=builder /build/dist ./dist
116
+ COPY --from=builder /build/beam_openApi.json ./beam_openApi.json
117
+ COPY --from=builder /build/beam.env ./beam.env
118
+
119
+ EXPOSE 6565
120
+ ENV NODE_ENV=production
121
+ CMD ["node", "dist/main.js"]
122
+ ```
123
+
124
+ Adjust Node major version to match your pinned runtime and `engines`. Older games may still be on Node 20 images — upgrading requires testing.
125
+
126
+ ## OpenAPI and publish
127
+
128
+ - `beamo-node validate` / `publish` invoke your build and OpenAPI pipeline; they rely on **`BEAMABLE_SKIP_RUNTIME`** so the process does not bind sockets or connect to Beamable as a full runtime.
129
+ - Ensure **`beam_openApi.json`** output path matches `publish-service.mjs` / CLI expectations for your repo (commonly project root).
130
+
131
+ ## Public exports (non-exhaustive)
132
+
133
+ From `src/index.ts`: decorators (`Microservice`, `Callable`, `ClientCallable`, `ServerCallable`, `AdminCallable`, `SwaggerCategory`, `SwaggerTags`, `ConfigureServices`, `InitializeServices`), types (`EnvironmentConfig`, `RequestContext`, `ServiceDefinition`, …), `BeamableServiceManager`, DI tokens (`LOGGER_TOKEN`, `ENVIRONMENT_CONFIG_TOKEN`, `REQUEST_CONTEXT_TOKEN`, `BEAMABLE_SERVICES_TOKEN`), `loadEnvironmentConfig`, `MicroserviceRuntime`, `runMicroservice`, inventory helpers, `StorageService` / `StorageObject`, federation symbols, collector helpers, `VERSION`.
134
+
135
+ ## Troubleshooting checklist for agents
136
+
137
+ 1. **“No microservices registered”** — Missing `@Microservice` import before `runMicroservice()` or wrong import order.
138
+ 2. **Missing CID/PID/HOST** — `.env` not passed to process; Docker missing env; wrong `WORKDIR` so `beam.env` not found **and** no env injected.
139
+ 3. **Duplicate route** — Two `@Callable` methods with same `route` string on one service class.
140
+ 4. **DI undefined at runtime** — Metadata not emitted; switch to factory `addSingleton` with explicit dependencies.
141
+ 5. **Publish hangs** — Side effects at import time (network, Redis, infinite loops). Guard with `BEAMABLE_SKIP_RUNTIME` or move work into `InitializeServices` / `runtime.start` path only.
142
+
143
+ ## Cursor rule file
144
+
145
+ Use **`beamable-node-microservice.mdc`** in this folder (or the copy under **`.cursor/rules/`** after **`beamo-node scaffold`**). See **`cursor-rule-snippet.md`** for notes.
@@ -0,0 +1,27 @@
1
+ ---
2
+ description: Beamable Node microservices using @omen.foundation/node-microservice-runtime
3
+ globs: "**/*.{ts,tsx,mjs,cjs,json}"
4
+ alwaysApply: false
5
+ ---
6
+
7
+ # Beamable Node microservice (Omen Foundation runtime)
8
+
9
+ When editing this microservice’s Beamable Node code:
10
+
11
+ 1. Use **`@omen.foundation/node-microservice-runtime`** public APIs only; verify exports in `node_modules/@omen.foundation/node-microservice-runtime/dist/index.d.ts` or the package **README.md** / **AGENTS.md** (root of this repo) / **`ai/RUNTIME_FOR_AI.md`**.
12
+
13
+ 2. **One** class with **`@Microservice('Name')`**; name matches **`package.json` → `beamable.beamoId`**.
14
+
15
+ 3. Start **`main.ts`** with **`import 'reflect-metadata'`**; import **`@StorageObject`** modules before the main service module.
16
+
17
+ 4. End with **`void runMicroservice()`** from the runtime. Respect **`BEAMABLE_SKIP_RUNTIME=true`** — no long-lived work at top level.
18
+
19
+ 5. **Env**: runtime requires **`CID`**, **`PID`**, **`HOST`**. **`beam.env`** in **`/beam/service/`** or cwd; does not override existing **`process.env`**.
20
+
21
+ 6. **Docker**: prefer **`WORKDIR /beam/service`**, copy **`beam.env`**, expose health port (**6565** default).
22
+
23
+ 7. **MongoDB**: **`STORAGE_CONNSTR_<StorageName>`** for **`@StorageObject('StorageName')`**.
24
+
25
+ 8. Use **`beamo-node validate`** / **`publish`** with **`--env-file`** matching the target realm.
26
+
27
+ For full detail, read **`AGENTS.md`** and **`ai/RUNTIME_FOR_AI.md`** in this project (scaffolded from the runtime package).
@@ -0,0 +1,8 @@
1
+ # Cursor rule for Beamable Node microservices
2
+
3
+ **Canonical file:** `ai/beamable-node-microservice.mdc` in this package (same content).
4
+
5
+ - **`beamo-node scaffold`** copies it to **`.cursor/rules/beamable-node-microservice.mdc`** in the new project.
6
+ - To add the rule manually, copy **`ai/beamable-node-microservice.mdc`** into your repo’s **`.cursor/rules/`**.
7
+
8
+ Adjust `globs` and `alwaysApply` in that file if the microservice lives inside a monorepo.
@@ -0,0 +1,24 @@
1
+ # Beamable Microservice Environment Variables
2
+ #
3
+ # This file defines developer-specific environment variables that will be
4
+ # loaded into the container at runtime.
5
+ #
6
+ # These variables take priority over Beamable Config values but will NOT
7
+ # overwrite existing process.env values.
8
+ #
9
+ # Format: KEY=value (one per line)
10
+ # Comments start with #
11
+ # Empty lines are ignored
12
+
13
+ # Example: BetterStack Logging Configuration
14
+ BEAM_BETTERSTACK_ENABLED=true
15
+ BEAM_BETTERSTACK_TOKEN=your-token-here
16
+
17
+ # Example: Custom configuration
18
+ MY_API_KEY=your-api-key-here
19
+ MY_FEATURE_FLAG=true
20
+
21
+ # Example: Service-specific settings
22
+ LOG_LEVEL=debug
23
+ MAX_CONNECTIONS=100
24
+
@@ -1 +1 @@
1
- {"version":3,"file":"scaffold.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/scaffold.ts"],"names":[],"mappings":"AAAA;;GAEG;AAeH;;GAEG;AACH,wBAAsB,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAkElE"}
1
+ {"version":3,"file":"scaffold.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/scaffold.ts"],"names":[],"mappings":"AAAA;;GAEG;AAgBH;;GAEG;AACH,wBAAsB,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAmElE"}
@@ -3,6 +3,7 @@
3
3
  */
4
4
  import fs from 'node:fs/promises';
5
5
  import path from 'node:path';
6
+ import { fileURLToPath } from 'node:url';
6
7
  import { toPascalCase, toKebabCase, validateServiceName } from '../utils/name-utils.js';
7
8
  import { fetchLatestRuntimeVersion } from '../utils/version-utils.js';
8
9
  import { promptInput } from '../utils/prompt-utils.js';
@@ -60,6 +61,7 @@ export async function handleScaffold(args) {
60
61
  console.log(` 3. Copy .env.sample to .env and configure your Beamable credentials`);
61
62
  console.log(` 4. (Optional) Copy beam.env.example to beam.env for custom environment variables`);
62
63
  console.log(` 5. npm run dev`);
64
+ console.log(`\nAI assistant docs were copied: AGENTS.md, CLAUDE.md, ai/, .cursor/rules/beamable-node-microservice.mdc`);
63
65
  }
64
66
  /**
65
67
  * Generates the complete project structure.
@@ -70,6 +72,8 @@ async function generateProjectStructure(targetDir, options) {
70
72
  await fs.mkdir(path.join(targetDir, 'src'), { recursive: true });
71
73
  await fs.mkdir(path.join(targetDir, 'src', 'services'), { recursive: true });
72
74
  await fs.mkdir(path.join(targetDir, 'dist'), { recursive: true });
75
+ // Copy AI assistant docs and Cursor rule from the installed runtime package
76
+ await copyAiAssistantArtifacts(getRuntimePackageRoot(), targetDir);
73
77
  // Generate files
74
78
  await fs.writeFile(path.join(targetDir, 'package.json'), generatePackageJson(options));
75
79
  await fs.writeFile(path.join(targetDir, 'tsconfig.json'), generateTsConfig());
@@ -79,6 +83,65 @@ async function generateProjectStructure(targetDir, options) {
79
83
  await fs.writeFile(path.join(targetDir, 'src', 'main.ts'), generateMainTs(options));
80
84
  await fs.writeFile(path.join(targetDir, 'src', `${toPascalCase(options.serviceName)}Service.ts`), generateServiceTs(options));
81
85
  await fs.writeFile(path.join(targetDir, 'src', 'services', 'example-domain-service.ts'), generateDomainServiceTs());
86
+ await fs.writeFile(path.join(targetDir, 'README.md'), generateProjectReadme(options));
87
+ }
88
+ /**
89
+ * Directory containing package.json for @omen.foundation/node-microservice-runtime
90
+ * (dist/cli/commands/*.js → ../../../).
91
+ */
92
+ function getRuntimePackageRoot() {
93
+ return path.join(path.dirname(fileURLToPath(import.meta.url)), '..', '..', '..');
94
+ }
95
+ /**
96
+ * Copies AGENTS.md, CLAUDE.md, ai/*.md, and the Cursor rule into the scaffolded project.
97
+ */
98
+ async function copyAiAssistantArtifacts(runtimeRoot, targetDir) {
99
+ const copies = [
100
+ { relFrom: 'AGENTS.md', relTo: 'AGENTS.md' },
101
+ { relFrom: 'CLAUDE.md', relTo: 'CLAUDE.md' },
102
+ { relFrom: path.join('ai', 'RUNTIME_FOR_AI.md'), relTo: path.join('ai', 'RUNTIME_FOR_AI.md') },
103
+ { relFrom: path.join('ai', 'cursor-rule-snippet.md'), relTo: path.join('ai', 'cursor-rule-snippet.md') },
104
+ {
105
+ relFrom: path.join('ai', 'beamable-node-microservice.mdc'),
106
+ relTo: path.join('.cursor', 'rules', 'beamable-node-microservice.mdc'),
107
+ },
108
+ ];
109
+ for (const { relFrom, relTo } of copies) {
110
+ const src = path.join(runtimeRoot, relFrom);
111
+ const dest = path.join(targetDir, relTo);
112
+ await fs.mkdir(path.dirname(dest), { recursive: true });
113
+ const content = await fs.readFile(src, 'utf-8');
114
+ await fs.writeFile(dest, content, 'utf-8');
115
+ }
116
+ }
117
+ /**
118
+ * Short project README; AI docs are copied alongside from the runtime package.
119
+ */
120
+ function generateProjectReadme(options) {
121
+ const title = options.displayName ?? options.serviceName;
122
+ return `# ${title}
123
+
124
+ Beamable Node microservice scaffolded with **\`beamo-node scaffold\`**.
125
+
126
+ ## Documentation in this project
127
+
128
+ | Path | Purpose |
129
+ |------|---------|
130
+ | **AGENTS.md** | Rules for AI coding agents (Cursor, Claude, Codex, …) |
131
+ | **CLAUDE.md** | Index pointing at the AI docs below |
132
+ | **ai/RUNTIME_FOR_AI.md** | Full runtime reference (env, Docker, troubleshooting) |
133
+ | **ai/cursor-rule-snippet.md** | Notes on the Cursor rule file |
134
+ | **.cursor/rules/beamable-node-microservice.mdc** | Cursor rule (tune \`globs\` if this service lives in a monorepo) |
135
+
136
+ After **\`npm install\`**, the same topics are also under **\`node_modules/@omen.foundation/node-microservice-runtime/\`**.
137
+
138
+ ## Scripts
139
+
140
+ - **\`npm run dev\`** — local development
141
+ - **\`npm run validate\`** / **\`npm run publish\`** — Beamable CLI (\`beamo-node\`)
142
+
143
+ Configure **\`.env\`** from **\`.env.sample\`** and optionally **\`beam.env\`** from **\`beam.env.example\`**.
144
+ `;
82
145
  }
83
146
  /**
84
147
  * Generates package.json content.
@@ -103,6 +166,7 @@ function generatePackageJson(options) {
103
166
  '@omen.foundation/node-microservice-runtime': `^${options.runtimeVersion}`,
104
167
  dotenv: '^16.4.7',
105
168
  mongodb: '^6.10.0',
169
+ 'reflect-metadata': '^0.2.2',
106
170
  },
107
171
  devDependencies: {
108
172
  '@types/node': '^20.11.30',
@@ -215,7 +279,8 @@ beam.env
215
279
  */
216
280
  function generateMainTs(options) {
217
281
  const serviceNamePascal = toPascalCase(options.serviceName);
218
- return `import 'dotenv/config';
282
+ return `import 'reflect-metadata';
283
+ import 'dotenv/config';
219
284
  import './${serviceNamePascal}Service.js';
220
285
  import { runMicroservice } from '@omen.foundation/node-microservice-runtime';
221
286
 
@@ -1 +1 @@
1
- {"version":3,"file":"scaffold.js","sourceRoot":"","sources":["../../../src/cli/commands/scaffold.ts"],"names":[],"mappings":"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","sourcesContent":["/**\r\n * Command handler for scaffolding new microservice projects.\r\n */\r\n\r\nimport fs from 'node:fs/promises';\r\nimport path from 'node:path';\r\nimport { toPascalCase, toKebabCase, validateServiceName } from '../utils/name-utils.js';\r\nimport { fetchLatestRuntimeVersion } from '../utils/version-utils.js';\r\nimport { promptInput } from '../utils/prompt-utils.js';\r\n\r\ninterface ScaffoldOptions {\r\n serviceName: string;\r\n displayName?: string;\r\n beamoId?: string;\r\n runtimeVersion: string;\r\n}\r\n\r\n/**\r\n * Handles the scaffold command.\r\n */\r\nexport async function handleScaffold(args: string[]): Promise<void> {\r\n let serviceName = args[0];\r\n\r\n // Interactive prompt for service name if not provided\r\n if (!serviceName) {\r\n serviceName = await promptInput('Enter microservice name (alphanumeric and underscores only)');\r\n }\r\n\r\n // Validate service name\r\n if (!serviceName || !validateServiceName(serviceName)) {\r\n throw new Error(\r\n `Invalid microservice name \"${serviceName}\". Only alphanumeric and underscore characters are allowed.`,\r\n );\r\n }\r\n\r\n // Fetch latest runtime version\r\n console.log('Fetching latest runtime version...');\r\n const runtimeVersion = await fetchLatestRuntimeVersion();\r\n console.log(`Using @omen.foundation/node-microservice-runtime version ${runtimeVersion}`);\r\n\r\n // Prompt for optional values\r\n const displayName = await promptInput('Enter display name for the service (optional)', serviceName);\r\n const beamoId = await promptInput('Enter Beamable Beamo ID (optional, defaults to service name)', serviceName);\r\n\r\n // Validate beamoId\r\n if (beamoId && !validateServiceName(beamoId)) {\r\n throw new Error(\r\n `Invalid Beamo ID \"${beamoId}\". Only alphanumeric and underscore characters are allowed.`,\r\n );\r\n }\r\n\r\n const options: ScaffoldOptions = {\r\n serviceName,\r\n displayName: displayName || serviceName,\r\n beamoId: beamoId || serviceName,\r\n runtimeVersion,\r\n };\r\n\r\n // Check if directory already exists\r\n const targetDir = path.resolve(process.cwd(), serviceName);\r\n try {\r\n await fs.access(targetDir);\r\n const overwrite = await promptInput(\r\n `Directory \"${serviceName}\" already exists. Overwrite? (y/n)`,\r\n 'n',\r\n );\r\n if (overwrite.toLowerCase() !== 'y' && overwrite.toLowerCase() !== 'yes') {\r\n console.log('Scaffold cancelled.');\r\n return;\r\n }\r\n // Remove existing directory\r\n await fs.rm(targetDir, { recursive: true, force: true });\r\n } catch {\r\n // Directory doesn't exist, that's fine\r\n }\r\n\r\n // Generate project structure\r\n await generateProjectStructure(targetDir, options);\r\n\r\n console.log(`\\n✅ Successfully scaffolded microservice \"${serviceName}\"!`);\r\n console.log(`\\nNext steps:`);\r\n console.log(` 1. cd ${serviceName}`);\r\n console.log(` 2. npm install`);\r\n console.log(` 3. Copy .env.sample to .env and configure your Beamable credentials`);\r\n console.log(` 4. (Optional) Copy beam.env.example to beam.env for custom environment variables`);\r\n console.log(` 5. npm run dev`);\r\n}\r\n\r\n/**\r\n * Generates the complete project structure.\r\n */\r\nasync function generateProjectStructure(targetDir: string, options: ScaffoldOptions): Promise<void> {\r\n // Create directories\r\n await fs.mkdir(targetDir, { recursive: true });\r\n await fs.mkdir(path.join(targetDir, 'src'), { recursive: true });\r\n await fs.mkdir(path.join(targetDir, 'src', 'services'), { recursive: true });\r\n await fs.mkdir(path.join(targetDir, 'dist'), { recursive: true });\r\n\r\n // Generate files\r\n await fs.writeFile(path.join(targetDir, 'package.json'), generatePackageJson(options));\r\n await fs.writeFile(path.join(targetDir, 'tsconfig.json'), generateTsConfig());\r\n await fs.writeFile(path.join(targetDir, '.env.sample'), generateEnvSample());\r\n await fs.writeFile(path.join(targetDir, 'beam.env.example'), generateBeamEnvExample());\r\n await fs.writeFile(path.join(targetDir, '.gitignore'), generateGitIgnore());\r\n await fs.writeFile(path.join(targetDir, 'src', 'main.ts'), generateMainTs(options));\r\n await fs.writeFile(\r\n path.join(targetDir, 'src', `${toPascalCase(options.serviceName)}Service.ts`),\r\n generateServiceTs(options),\r\n );\r\n await fs.writeFile(\r\n path.join(targetDir, 'src', 'services', 'example-domain-service.ts'),\r\n generateDomainServiceTs(),\r\n );\r\n}\r\n\r\n/**\r\n * Generates package.json content.\r\n */\r\nfunction generatePackageJson(options: ScaffoldOptions): string {\r\n const serviceNameKebab = toKebabCase(options.serviceName);\r\n const packageName = `@beamable/${serviceNameKebab}-node-microservice`;\r\n\r\n return JSON.stringify(\r\n {\r\n name: packageName,\r\n version: '0.1.0',\r\n private: true,\r\n type: 'module',\r\n main: 'dist/main.js',\r\n types: 'dist/main.d.ts',\r\n scripts: {\r\n dev: 'tsx --env-file .env src/main.ts',\r\n build: 'tsc -p tsconfig.json',\r\n validate: 'npx beamo-node validate --env-file .env',\r\n publish: 'npx beamo-node publish --env-file .env',\r\n },\r\n dependencies: {\r\n '@omen.foundation/node-microservice-runtime': `^${options.runtimeVersion}`,\r\n dotenv: '^16.4.7',\r\n mongodb: '^6.10.0',\r\n },\r\n devDependencies: {\r\n '@types/node': '^20.11.30',\r\n 'pino-pretty': '^13.0.0',\r\n tsx: '^4.19.2',\r\n typescript: '^5.4.5',\r\n },\r\n beamable: {\r\n beamoId: options.beamoId,\r\n projectType: 'service',\r\n },\r\n },\r\n null,\r\n 2,\r\n );\r\n}\r\n\r\n/**\r\n * Generates tsconfig.json content.\r\n * Includes all necessary compiler options directly to avoid dependency on base config.\r\n */\r\nfunction generateTsConfig(): string {\r\n return JSON.stringify(\r\n {\r\n compilerOptions: {\r\n target: 'ES2022',\r\n module: 'NodeNext',\r\n moduleResolution: 'NodeNext',\r\n esModuleInterop: true,\r\n forceConsistentCasingInFileNames: true,\r\n strict: true,\r\n skipLibCheck: false,\r\n noImplicitOverride: true,\r\n noUnusedLocals: true,\r\n noUnusedParameters: true,\r\n resolveJsonModule: true,\r\n sourceMap: true,\r\n inlineSources: true,\r\n experimentalDecorators: true,\r\n emitDecoratorMetadata: true,\r\n types: ['node'],\r\n outDir: 'dist',\r\n rootDir: 'src',\r\n declaration: true,\r\n declarationMap: true,\r\n },\r\n include: ['src/**/*.ts'],\r\n exclude: ['dist', 'node_modules'],\r\n },\r\n null,\r\n 2,\r\n );\r\n}\r\n\r\n/**\r\n * Generates .env.sample content.\r\n */\r\nfunction generateEnvSample(): string {\r\n return `# Beamable Authentication & Configuration\r\n# This file is for Beamable-specific credentials (CID, PID, SECRET, etc.)\r\n# For custom environment variables, use beam.env instead (see beam.env.example)\r\n\r\nCID=\r\nPID=\r\nHOST=wss://api.beamable.com/socket\r\nSECRET=\r\nNAME_PREFIX=\r\nLOG_LEVEL=info\r\nBEAMABLE_TOKEN=\r\nBEAMABLE_API_HOST=https://api.beamable.com\r\nBEAMABLE_GAME_PID=\r\n`;\r\n}\r\n\r\n/**\r\n * Generates beam.env.example content.\r\n */\r\nfunction generateBeamEnvExample(): string {\r\n return `# Beamable Microservice Environment Variables\r\n# \r\n# This file defines developer-specific environment variables that will be\r\n# loaded into the container at runtime.\r\n#\r\n# These variables take priority over Beamable Config values but will NOT\r\n# overwrite existing process.env values.\r\n#\r\n# Format: KEY=value (one per line)\r\n# Comments start with #\r\n# Empty lines are ignored\r\n\r\n# Example: BetterStack Logging Configuration\r\n# BEAM_BETTERSTACK_ENABLED=true\r\n# BEAM_BETTERSTACK_TOKEN=your-token-here\r\n\r\n# Example: Custom configuration\r\n# MY_API_KEY=your-api-key-here\r\n# MY_FEATURE_FLAG=true\r\n\r\n# Example: Service-specific settings\r\n# LOG_LEVEL=debug\r\n# MAX_CONNECTIONS=100\r\n`;\r\n}\r\n\r\n/**\r\n * Generates .gitignore content.\r\n */\r\nfunction generateGitIgnore(): string {\r\n return `node_modules/\r\ndist/\r\n.env\r\n.env.local\r\nbeam.env\r\n.beam.env\r\n*.log\r\n.DS_Store\r\n`;\r\n}\r\n\r\n/**\r\n * Generates main.ts content.\r\n */\r\nfunction generateMainTs(options: ScaffoldOptions): string {\r\n const serviceNamePascal = toPascalCase(options.serviceName);\r\n return `import 'dotenv/config';\r\nimport './${serviceNamePascal}Service.js';\r\nimport { runMicroservice } from '@omen.foundation/node-microservice-runtime';\r\n\r\nvoid runMicroservice();\r\n`;\r\n}\r\n\r\n/**\r\n * Generates the main service class file.\r\n */\r\nfunction generateServiceTs(options: ScaffoldOptions): string {\r\n const serviceNamePascal = toPascalCase(options.serviceName);\r\n return `import {\r\n Microservice,\r\n Callable,\r\n ClientCallable,\r\n ServerCallable,\r\n SwaggerCategory,\r\n ConfigureServices,\r\n type DependencyBuilder,\r\n type RequestContext,\r\n} from '@omen.foundation/node-microservice-runtime';\r\nimport { ExampleDomainService } from './services/example-domain-service.js';\r\n\r\n@Microservice('${options.serviceName}')\r\nexport class ${serviceNamePascal}Service {\r\n @ConfigureServices\r\n static register(builder: DependencyBuilder): void {\r\n builder.addSingletonClass(ExampleDomainService);\r\n }\r\n\r\n /**\r\n * Example of a public Callable endpoint.\r\n * No authentication required.\r\n */\r\n @Callable({ route: 'isTwo' })\r\n @SwaggerCategory('Examples')\r\n async isTwo(_ctx: RequestContext): Promise<boolean> {\r\n return 1 + 1 === 2;\r\n }\r\n\r\n /**\r\n * Example of a ClientCallable endpoint.\r\n * Requires user authentication (client scope).\r\n */\r\n @ClientCallable({ route: 'isThree' })\r\n @SwaggerCategory('Examples')\r\n async isThree(_ctx: RequestContext): Promise<boolean> {\r\n return 1 + 2 === 3;\r\n }\r\n\r\n /**\r\n * Example of a ServerCallable endpoint.\r\n * Requires server authentication (server scope).\r\n */\r\n @ServerCallable({ route: 'isFour' })\r\n @SwaggerCategory('Examples')\r\n async isFour(_ctx: RequestContext): Promise<boolean> {\r\n return 2 + 2 === 4;\r\n }\r\n}\r\n`;\r\n}\r\n\r\n/**\r\n * Generates example domain service content.\r\n */\r\nfunction generateDomainServiceTs(): string {\r\n return `import type { RequestContext } from '@omen.foundation/node-microservice-runtime';\r\n\r\nexport class ExampleDomainService {\r\n greet(userId: number): string {\r\n return \\`Hello from Node microservices, user \\${userId}!\\`;\r\n }\r\n\r\n async getServiceInfo(_ctx: RequestContext): Promise<{ service: string; version: string }> {\r\n return {\r\n service: 'ExampleDomainService',\r\n version: '1.0.0',\r\n };\r\n }\r\n}\r\n`;\r\n}\r\n\r\n"]}
1
+ {"version":3,"file":"scaffold.js","sourceRoot":"","sources":["../../../src/cli/commands/scaffold.ts"],"names":[],"mappings":"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","sourcesContent":["/**\r\n * Command handler for scaffolding new microservice projects.\r\n */\r\n\r\nimport fs from 'node:fs/promises';\r\nimport path from 'node:path';\r\nimport { fileURLToPath } from 'node:url';\r\nimport { toPascalCase, toKebabCase, validateServiceName } from '../utils/name-utils.js';\r\nimport { fetchLatestRuntimeVersion } from '../utils/version-utils.js';\r\nimport { promptInput } from '../utils/prompt-utils.js';\r\n\r\ninterface ScaffoldOptions {\r\n serviceName: string;\r\n displayName?: string;\r\n beamoId?: string;\r\n runtimeVersion: string;\r\n}\r\n\r\n/**\r\n * Handles the scaffold command.\r\n */\r\nexport async function handleScaffold(args: string[]): Promise<void> {\r\n let serviceName = args[0];\r\n\r\n // Interactive prompt for service name if not provided\r\n if (!serviceName) {\r\n serviceName = await promptInput('Enter microservice name (alphanumeric and underscores only)');\r\n }\r\n\r\n // Validate service name\r\n if (!serviceName || !validateServiceName(serviceName)) {\r\n throw new Error(\r\n `Invalid microservice name \"${serviceName}\". Only alphanumeric and underscore characters are allowed.`,\r\n );\r\n }\r\n\r\n // Fetch latest runtime version\r\n console.log('Fetching latest runtime version...');\r\n const runtimeVersion = await fetchLatestRuntimeVersion();\r\n console.log(`Using @omen.foundation/node-microservice-runtime version ${runtimeVersion}`);\r\n\r\n // Prompt for optional values\r\n const displayName = await promptInput('Enter display name for the service (optional)', serviceName);\r\n const beamoId = await promptInput('Enter Beamable Beamo ID (optional, defaults to service name)', serviceName);\r\n\r\n // Validate beamoId\r\n if (beamoId && !validateServiceName(beamoId)) {\r\n throw new Error(\r\n `Invalid Beamo ID \"${beamoId}\". Only alphanumeric and underscore characters are allowed.`,\r\n );\r\n }\r\n\r\n const options: ScaffoldOptions = {\r\n serviceName,\r\n displayName: displayName || serviceName,\r\n beamoId: beamoId || serviceName,\r\n runtimeVersion,\r\n };\r\n\r\n // Check if directory already exists\r\n const targetDir = path.resolve(process.cwd(), serviceName);\r\n try {\r\n await fs.access(targetDir);\r\n const overwrite = await promptInput(\r\n `Directory \"${serviceName}\" already exists. Overwrite? (y/n)`,\r\n 'n',\r\n );\r\n if (overwrite.toLowerCase() !== 'y' && overwrite.toLowerCase() !== 'yes') {\r\n console.log('Scaffold cancelled.');\r\n return;\r\n }\r\n // Remove existing directory\r\n await fs.rm(targetDir, { recursive: true, force: true });\r\n } catch {\r\n // Directory doesn't exist, that's fine\r\n }\r\n\r\n // Generate project structure\r\n await generateProjectStructure(targetDir, options);\r\n\r\n console.log(`\\n✅ Successfully scaffolded microservice \"${serviceName}\"!`);\r\n console.log(`\\nNext steps:`);\r\n console.log(` 1. cd ${serviceName}`);\r\n console.log(` 2. npm install`);\r\n console.log(` 3. Copy .env.sample to .env and configure your Beamable credentials`);\r\n console.log(` 4. (Optional) Copy beam.env.example to beam.env for custom environment variables`);\r\n console.log(` 5. npm run dev`);\r\n console.log(`\\nAI assistant docs were copied: AGENTS.md, CLAUDE.md, ai/, .cursor/rules/beamable-node-microservice.mdc`);\r\n}\r\n\r\n/**\r\n * Generates the complete project structure.\r\n */\r\nasync function generateProjectStructure(targetDir: string, options: ScaffoldOptions): Promise<void> {\r\n // Create directories\r\n await fs.mkdir(targetDir, { recursive: true });\r\n await fs.mkdir(path.join(targetDir, 'src'), { recursive: true });\r\n await fs.mkdir(path.join(targetDir, 'src', 'services'), { recursive: true });\r\n await fs.mkdir(path.join(targetDir, 'dist'), { recursive: true });\r\n\r\n // Copy AI assistant docs and Cursor rule from the installed runtime package\r\n await copyAiAssistantArtifacts(getRuntimePackageRoot(), targetDir);\r\n\r\n // Generate files\r\n await fs.writeFile(path.join(targetDir, 'package.json'), generatePackageJson(options));\r\n await fs.writeFile(path.join(targetDir, 'tsconfig.json'), generateTsConfig());\r\n await fs.writeFile(path.join(targetDir, '.env.sample'), generateEnvSample());\r\n await fs.writeFile(path.join(targetDir, 'beam.env.example'), generateBeamEnvExample());\r\n await fs.writeFile(path.join(targetDir, '.gitignore'), generateGitIgnore());\r\n await fs.writeFile(path.join(targetDir, 'src', 'main.ts'), generateMainTs(options));\r\n await fs.writeFile(\r\n path.join(targetDir, 'src', `${toPascalCase(options.serviceName)}Service.ts`),\r\n generateServiceTs(options),\r\n );\r\n await fs.writeFile(\r\n path.join(targetDir, 'src', 'services', 'example-domain-service.ts'),\r\n generateDomainServiceTs(),\r\n );\r\n await fs.writeFile(path.join(targetDir, 'README.md'), generateProjectReadme(options));\r\n}\r\n\r\n/**\r\n * Directory containing package.json for @omen.foundation/node-microservice-runtime\r\n * (dist/cli/commands/*.js → ../../../).\r\n */\r\nfunction getRuntimePackageRoot(): string {\r\n return path.join(path.dirname(fileURLToPath(import.meta.url)), '..', '..', '..');\r\n}\r\n\r\n/**\r\n * Copies AGENTS.md, CLAUDE.md, ai/*.md, and the Cursor rule into the scaffolded project.\r\n */\r\nasync function copyAiAssistantArtifacts(runtimeRoot: string, targetDir: string): Promise<void> {\r\n const copies: Array<{ relFrom: string; relTo: string }> = [\r\n { relFrom: 'AGENTS.md', relTo: 'AGENTS.md' },\r\n { relFrom: 'CLAUDE.md', relTo: 'CLAUDE.md' },\r\n { relFrom: path.join('ai', 'RUNTIME_FOR_AI.md'), relTo: path.join('ai', 'RUNTIME_FOR_AI.md') },\r\n { relFrom: path.join('ai', 'cursor-rule-snippet.md'), relTo: path.join('ai', 'cursor-rule-snippet.md') },\r\n {\r\n relFrom: path.join('ai', 'beamable-node-microservice.mdc'),\r\n relTo: path.join('.cursor', 'rules', 'beamable-node-microservice.mdc'),\r\n },\r\n ];\r\n\r\n for (const { relFrom, relTo } of copies) {\r\n const src = path.join(runtimeRoot, relFrom);\r\n const dest = path.join(targetDir, relTo);\r\n await fs.mkdir(path.dirname(dest), { recursive: true });\r\n const content = await fs.readFile(src, 'utf-8');\r\n await fs.writeFile(dest, content, 'utf-8');\r\n }\r\n}\r\n\r\n/**\r\n * Short project README; AI docs are copied alongside from the runtime package.\r\n */\r\nfunction generateProjectReadme(options: ScaffoldOptions): string {\r\n const title = options.displayName ?? options.serviceName;\r\n return `# ${title}\r\n\r\nBeamable Node microservice scaffolded with **\\`beamo-node scaffold\\`**.\r\n\r\n## Documentation in this project\r\n\r\n| Path | Purpose |\r\n|------|---------|\r\n| **AGENTS.md** | Rules for AI coding agents (Cursor, Claude, Codex, …) |\r\n| **CLAUDE.md** | Index pointing at the AI docs below |\r\n| **ai/RUNTIME_FOR_AI.md** | Full runtime reference (env, Docker, troubleshooting) |\r\n| **ai/cursor-rule-snippet.md** | Notes on the Cursor rule file |\r\n| **.cursor/rules/beamable-node-microservice.mdc** | Cursor rule (tune \\`globs\\` if this service lives in a monorepo) |\r\n\r\nAfter **\\`npm install\\`**, the same topics are also under **\\`node_modules/@omen.foundation/node-microservice-runtime/\\`**.\r\n\r\n## Scripts\r\n\r\n- **\\`npm run dev\\`** — local development\r\n- **\\`npm run validate\\`** / **\\`npm run publish\\`** — Beamable CLI (\\`beamo-node\\`)\r\n\r\nConfigure **\\`.env\\`** from **\\`.env.sample\\`** and optionally **\\`beam.env\\`** from **\\`beam.env.example\\`**.\r\n`;\r\n}\r\n\r\n/**\r\n * Generates package.json content.\r\n */\r\nfunction generatePackageJson(options: ScaffoldOptions): string {\r\n const serviceNameKebab = toKebabCase(options.serviceName);\r\n const packageName = `@beamable/${serviceNameKebab}-node-microservice`;\r\n\r\n return JSON.stringify(\r\n {\r\n name: packageName,\r\n version: '0.1.0',\r\n private: true,\r\n type: 'module',\r\n main: 'dist/main.js',\r\n types: 'dist/main.d.ts',\r\n scripts: {\r\n dev: 'tsx --env-file .env src/main.ts',\r\n build: 'tsc -p tsconfig.json',\r\n validate: 'npx beamo-node validate --env-file .env',\r\n publish: 'npx beamo-node publish --env-file .env',\r\n },\r\n dependencies: {\r\n '@omen.foundation/node-microservice-runtime': `^${options.runtimeVersion}`,\r\n dotenv: '^16.4.7',\r\n mongodb: '^6.10.0',\r\n 'reflect-metadata': '^0.2.2',\r\n },\r\n devDependencies: {\r\n '@types/node': '^20.11.30',\r\n 'pino-pretty': '^13.0.0',\r\n tsx: '^4.19.2',\r\n typescript: '^5.4.5',\r\n },\r\n beamable: {\r\n beamoId: options.beamoId,\r\n projectType: 'service',\r\n },\r\n },\r\n null,\r\n 2,\r\n );\r\n}\r\n\r\n/**\r\n * Generates tsconfig.json content.\r\n * Includes all necessary compiler options directly to avoid dependency on base config.\r\n */\r\nfunction generateTsConfig(): string {\r\n return JSON.stringify(\r\n {\r\n compilerOptions: {\r\n target: 'ES2022',\r\n module: 'NodeNext',\r\n moduleResolution: 'NodeNext',\r\n esModuleInterop: true,\r\n forceConsistentCasingInFileNames: true,\r\n strict: true,\r\n skipLibCheck: false,\r\n noImplicitOverride: true,\r\n noUnusedLocals: true,\r\n noUnusedParameters: true,\r\n resolveJsonModule: true,\r\n sourceMap: true,\r\n inlineSources: true,\r\n experimentalDecorators: true,\r\n emitDecoratorMetadata: true,\r\n types: ['node'],\r\n outDir: 'dist',\r\n rootDir: 'src',\r\n declaration: true,\r\n declarationMap: true,\r\n },\r\n include: ['src/**/*.ts'],\r\n exclude: ['dist', 'node_modules'],\r\n },\r\n null,\r\n 2,\r\n );\r\n}\r\n\r\n/**\r\n * Generates .env.sample content.\r\n */\r\nfunction generateEnvSample(): string {\r\n return `# Beamable Authentication & Configuration\r\n# This file is for Beamable-specific credentials (CID, PID, SECRET, etc.)\r\n# For custom environment variables, use beam.env instead (see beam.env.example)\r\n\r\nCID=\r\nPID=\r\nHOST=wss://api.beamable.com/socket\r\nSECRET=\r\nNAME_PREFIX=\r\nLOG_LEVEL=info\r\nBEAMABLE_TOKEN=\r\nBEAMABLE_API_HOST=https://api.beamable.com\r\nBEAMABLE_GAME_PID=\r\n`;\r\n}\r\n\r\n/**\r\n * Generates beam.env.example content.\r\n */\r\nfunction generateBeamEnvExample(): string {\r\n return `# Beamable Microservice Environment Variables\r\n# \r\n# This file defines developer-specific environment variables that will be\r\n# loaded into the container at runtime.\r\n#\r\n# These variables take priority over Beamable Config values but will NOT\r\n# overwrite existing process.env values.\r\n#\r\n# Format: KEY=value (one per line)\r\n# Comments start with #\r\n# Empty lines are ignored\r\n\r\n# Example: BetterStack Logging Configuration\r\n# BEAM_BETTERSTACK_ENABLED=true\r\n# BEAM_BETTERSTACK_TOKEN=your-token-here\r\n\r\n# Example: Custom configuration\r\n# MY_API_KEY=your-api-key-here\r\n# MY_FEATURE_FLAG=true\r\n\r\n# Example: Service-specific settings\r\n# LOG_LEVEL=debug\r\n# MAX_CONNECTIONS=100\r\n`;\r\n}\r\n\r\n/**\r\n * Generates .gitignore content.\r\n */\r\nfunction generateGitIgnore(): string {\r\n return `node_modules/\r\ndist/\r\n.env\r\n.env.local\r\nbeam.env\r\n.beam.env\r\n*.log\r\n.DS_Store\r\n`;\r\n}\r\n\r\n/**\r\n * Generates main.ts content.\r\n */\r\nfunction generateMainTs(options: ScaffoldOptions): string {\r\n const serviceNamePascal = toPascalCase(options.serviceName);\r\n return `import 'reflect-metadata';\r\nimport 'dotenv/config';\r\nimport './${serviceNamePascal}Service.js';\r\nimport { runMicroservice } from '@omen.foundation/node-microservice-runtime';\r\n\r\nvoid runMicroservice();\r\n`;\r\n}\r\n\r\n/**\r\n * Generates the main service class file.\r\n */\r\nfunction generateServiceTs(options: ScaffoldOptions): string {\r\n const serviceNamePascal = toPascalCase(options.serviceName);\r\n return `import {\r\n Microservice,\r\n Callable,\r\n ClientCallable,\r\n ServerCallable,\r\n SwaggerCategory,\r\n ConfigureServices,\r\n type DependencyBuilder,\r\n type RequestContext,\r\n} from '@omen.foundation/node-microservice-runtime';\r\nimport { ExampleDomainService } from './services/example-domain-service.js';\r\n\r\n@Microservice('${options.serviceName}')\r\nexport class ${serviceNamePascal}Service {\r\n @ConfigureServices\r\n static register(builder: DependencyBuilder): void {\r\n builder.addSingletonClass(ExampleDomainService);\r\n }\r\n\r\n /**\r\n * Example of a public Callable endpoint.\r\n * No authentication required.\r\n */\r\n @Callable({ route: 'isTwo' })\r\n @SwaggerCategory('Examples')\r\n async isTwo(_ctx: RequestContext): Promise<boolean> {\r\n return 1 + 1 === 2;\r\n }\r\n\r\n /**\r\n * Example of a ClientCallable endpoint.\r\n * Requires user authentication (client scope).\r\n */\r\n @ClientCallable({ route: 'isThree' })\r\n @SwaggerCategory('Examples')\r\n async isThree(_ctx: RequestContext): Promise<boolean> {\r\n return 1 + 2 === 3;\r\n }\r\n\r\n /**\r\n * Example of a ServerCallable endpoint.\r\n * Requires server authentication (server scope).\r\n */\r\n @ServerCallable({ route: 'isFour' })\r\n @SwaggerCategory('Examples')\r\n async isFour(_ctx: RequestContext): Promise<boolean> {\r\n return 2 + 2 === 4;\r\n }\r\n}\r\n`;\r\n}\r\n\r\n/**\r\n * Generates example domain service content.\r\n */\r\nfunction generateDomainServiceTs(): string {\r\n return `import type { RequestContext } from '@omen.foundation/node-microservice-runtime';\r\n\r\nexport class ExampleDomainService {\r\n greet(userId: number): string {\r\n return \\`Hello from Node microservices, user \\${userId}!\\`;\r\n }\r\n\r\n async getServiceInfo(_ctx: RequestContext): Promise<{ service: string; version: string }> {\r\n return {\r\n service: 'ExampleDomainService',\r\n version: '1.0.0',\r\n };\r\n }\r\n}\r\n`;\r\n}\r\n\r\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@omen.foundation/node-microservice-runtime",
3
- "version": "0.1.122",
3
+ "version": "0.1.123",
4
4
  "description": "Beamable microservice runtime for Node.js/TypeScript services.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -16,7 +16,12 @@
16
16
  },
17
17
  "files": [
18
18
  "dist",
19
- "scripts"
19
+ "scripts",
20
+ "README.md",
21
+ "CLAUDE.md",
22
+ "AGENTS.md",
23
+ "beam.env.example",
24
+ "ai"
20
25
  ],
21
26
  "scripts": {
22
27
  "clean": "rimraf dist dist-cjs",