diff --git a/.github/workflows/pre-release.yml b/.github/workflows/pre-release.yml index 9610413c..9fb67b55 100644 --- a/.github/workflows/pre-release.yml +++ b/.github/workflows/pre-release.yml @@ -49,6 +49,9 @@ jobs: with: node-version: "22.x" registry-url: "https://registry.npmjs.org" + - uses: pnpm/action-setup@v6 + with: + package_json_file: typescript/templates/node-gen/package.json - name: Bump sdk-typescript run: ./.tools/update_node_examples.sh ${{ inputs.sdkTypescriptVersion }} - name: Compute paths to commit diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 3cb4ec04..be92bcf1 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -57,6 +57,7 @@ jobs: kotlin-hello-world-lambda-cdk.zip typescript-hello-world.zip + typescript-hello-world-gen.zip typescript-hello-world-kubernetes.zip typescript-hello-world-bun.zip typescript-hello-world-cloudflare-worker.zip @@ -88,6 +89,13 @@ jobs: + copy-ts-gen-template: + name: Copy TypeScript gen template to typescript-gen-template repo + uses: ./.github/workflows/copy-to-repo.yml + secrets: inherit + with: + source-directory: typescript/templates/node-gen + destination-repo: typescript-gen-template copy-deno-template: name: Copy deno template to deno-template repo uses: ./.github/workflows/copy-to-repo.yml diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index c2d4bf8e..0d877657 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -12,7 +12,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - - run: echo '{"ignorePatterns":[{"pattern":"https://docs.restate.dev/_mintlify/feedback/restate-6d46e1dc/agent-feedback"},{"pattern":"https://slack.restate.dev"}]}' > mlc_config.json + - run: echo '{"ignorePatterns":[{"pattern":"https://docs.restate.dev/_mintlify/feedback/restate-6d46e1dc/agent-feedback"},{"pattern":"https://slack.restate.dev"},{"pattern":"^https://www.npmjs.com"}]}' > mlc_config.json - uses: gaurav-nelson/github-action-markdown-link-check@v1 with: use-verbose-mode: 'yes' @@ -68,7 +68,7 @@ jobs: RESTATE_DISABLE_TELEMETRY: "true" strategy: matrix: - node-version: [22.x] + node-version: [24.x] steps: - uses: actions/checkout@v3 @@ -80,6 +80,9 @@ jobs: with: node-version: ${{ matrix.node-version }} registry-url: "https://registry.npmjs.org" + - uses: pnpm/action-setup@v6 + with: + package_json_file: typescript/templates/node-gen/package.json - name: Run tests run: ./.tools/run_node_tests.sh diff --git a/.tools/prepare_release_zip.sh b/.tools/prepare_release_zip.sh index 48fb3101..b0bbfb4e 100755 --- a/.tools/prepare_release_zip.sh +++ b/.tools/prepare_release_zip.sh @@ -53,6 +53,7 @@ create_release_zip typescript/end-to-end-applications/chat-bot typescript-chat-b create_release_zip typescript/end-to-end-applications/ai-image-workflows typescript-ai-image-workflows create_release_zip typescript/patterns-use-cases typescript-patterns-use-cases create_release_zip typescript/templates/node typescript-hello-world +create_release_zip typescript/templates/node-gen typescript-hello-world-gen create_release_zip typescript/templates/node-kubernetes typescript-hello-world-kubernetes create_release_zip typescript/templates/lambda typescript-hello-world-lambda create_release_zip typescript/templates/bun typescript-hello-world-bun diff --git a/.tools/run_node_tests.sh b/.tools/run_node_tests.sh index a2b0fd6b..940dd328 100755 --- a/.tools/run_node_tests.sh +++ b/.tools/run_node_tests.sh @@ -9,9 +9,14 @@ function npm_install_check() { npm install --prefix $1 && npm --prefix $1 run build } +function pnpm_install_check() { + pnpm --dir $1 install && pnpm --dir $1 run check && pnpm --dir $1 run build +} + npm_install_check $PROJECT_ROOT/typescript/basics npm_install_check $PROJECT_ROOT/typescript/templates/node +pnpm_install_check $PROJECT_ROOT/typescript/templates/node-gen npm_install_check $PROJECT_ROOT/typescript/templates/node-kubernetes npm_install_check $PROJECT_ROOT/typescript/templates/lambda npm_install_check $PROJECT_ROOT/typescript/templates/cloudflare-worker diff --git a/.tools/update_node_examples.sh b/.tools/update_node_examples.sh index d6a323fe..642f023c 100755 --- a/.tools/update_node_examples.sh +++ b/.tools/update_node_examples.sh @@ -26,10 +26,21 @@ function bump_restate_sdk_deps() { console.log(restateDeps.join(' ')); ") + # Projects declaring pnpm as packageManager are bumped with pnpm, others with npm + local package_manager=$(node -e " + const fs = require('fs'); + const pkg = JSON.parse(fs.readFileSync('$package_json', 'utf8')); + console.log((pkg.packageManager || 'npm').split('@')[0]); + ") + # Install each dependency with the new version for dep in $deps; do echo "Installing $dep@^$NEW_VERSION in $project_dir" - npm install $dep@^$NEW_VERSION + if [ "$package_manager" == "pnpm" ]; then + pnpm add $dep@^$NEW_VERSION + else + npm install $dep@^$NEW_VERSION + fi done # If this is a template directory and has existing agents documentation, update it @@ -47,6 +58,7 @@ function bump_restate_sdk_deps() { # Update all projects with package.json bump_restate_sdk_deps $PROJECT_ROOT/typescript/basics bump_restate_sdk_deps $PROJECT_ROOT/typescript/templates/node +bump_restate_sdk_deps $PROJECT_ROOT/typescript/templates/node-gen bump_restate_sdk_deps $PROJECT_ROOT/typescript/templates/node-kubernetes bump_restate_sdk_deps $PROJECT_ROOT/typescript/templates/lambda bump_restate_sdk_deps $PROJECT_ROOT/typescript/templates/typescript-testing diff --git a/typescript/README.md b/typescript/README.md index 11a19f23..924031dc 100644 --- a/typescript/README.md +++ b/typescript/README.md @@ -68,6 +68,7 @@ Complete applications built with Restate: Starter templates for new projects: - **[Node Template](templates/node)** +- **[Node Generator SDK Template](templates/node-gen)**: Uses the generator-based SDK (`@restatedev/restate-sdk-gen`), with pnpm, TypeScript 7, oxlint and oxfmt. - **[Node.js Kubernetes Template](templates/node-kubernetes)**: Deploy to Kubernetes with an ingress tunnel and Kubernetes YAML files. - **[Bun Template](templates/bun)** - **[CloudFlare Workers Template](templates/cloudflare-worker)** diff --git a/typescript/templates/node-gen/.agents/plugins/marketplace.json b/typescript/templates/node-gen/.agents/plugins/marketplace.json new file mode 100644 index 00000000..b9c8574c --- /dev/null +++ b/typescript/templates/node-gen/.agents/plugins/marketplace.json @@ -0,0 +1,22 @@ +{ + "name": "restatedev-plugin", + "interface": { + "displayName": "Restate" + }, + "plugins": [ + { + "name": "restatedev", + "source": { + "source": "git-subdir", + "url": "https://github.com/restatedev/skills.git", + "path": "./plugins/restatedev", + "ref": "main" + }, + "policy": { + "installation": "AVAILABLE", + "authentication": "ON_INSTALL" + }, + "category": "Coding" + } + ] +} diff --git a/typescript/templates/node-gen/.claude/settings.json b/typescript/templates/node-gen/.claude/settings.json new file mode 100644 index 00000000..1c69c4f5 --- /dev/null +++ b/typescript/templates/node-gen/.claude/settings.json @@ -0,0 +1,13 @@ +{ + "extraKnownMarketplaces": { + "restatedev-plugin": { + "source": { + "source": "github", + "repo": "restatedev/skills" + } + } + }, + "enabledPlugins": { + "restatedev@restatedev-plugin": true + } +} diff --git a/typescript/templates/node-gen/.dockerignore b/typescript/templates/node-gen/.dockerignore new file mode 100644 index 00000000..6a9bc407 --- /dev/null +++ b/typescript/templates/node-gen/.dockerignore @@ -0,0 +1,4 @@ +.* +node_modules +dist +Dockerfile diff --git a/typescript/templates/node-gen/.gitignore b/typescript/templates/node-gen/.gitignore new file mode 100644 index 00000000..558866ca --- /dev/null +++ b/typescript/templates/node-gen/.gitignore @@ -0,0 +1,18 @@ +# Node +node_modules +dist + +# debug +npm-debug.log* +pnpm-debug.log* + +# env files (can opt-in for committing if needed) +.env* + +# typescript +*.tsbuildinfo + +# Restate +.restate +restate-data +pnpm-lock.yaml diff --git a/typescript/templates/node-gen/.oxfmtrc.json b/typescript/templates/node-gen/.oxfmtrc.json new file mode 100644 index 00000000..44374077 --- /dev/null +++ b/typescript/templates/node-gen/.oxfmtrc.json @@ -0,0 +1,5 @@ +{ + "$schema": "./node_modules/oxfmt/configuration_schema.json", + "printWidth": 100, + "ignorePatterns": ["dist", "pnpm-lock.yaml"] +} diff --git a/typescript/templates/node-gen/.oxlintrc.json b/typescript/templates/node-gen/.oxlintrc.json new file mode 100644 index 00000000..2ecac830 --- /dev/null +++ b/typescript/templates/node-gen/.oxlintrc.json @@ -0,0 +1,9 @@ +{ + "$schema": "./node_modules/oxlint/configuration_schema.json", + "plugins": ["typescript", "unicorn", "oxc", "import", "node"], + "categories": { + "correctness": "error", + "suspicious": "warn" + }, + "ignorePatterns": ["dist"] +} diff --git a/typescript/templates/node-gen/Dockerfile b/typescript/templates/node-gen/Dockerfile new file mode 100644 index 00000000..17cf36b0 --- /dev/null +++ b/typescript/templates/node-gen/Dockerfile @@ -0,0 +1,30 @@ +FROM node:24-alpine AS build + +RUN npm install -g pnpm@12.8.1 + +WORKDIR /usr/src/app + +# copy package.json separately to cache dependencies +COPY package.json pnpm-lock.yaml* ./ +RUN pnpm install + +COPY . . +RUN pnpm build + +RUN pnpm prune --prod + +FROM node:24-alpine + +# dumb-init helps handling SIGTERM and SIGINT correctly +RUN apk add dumb-init + +WORKDIR /usr/src/app +ENV NODE_ENV=production + +COPY --from=build --chown=node:node /usr/src/app/package.json ./ +COPY --from=build --chown=node:node /usr/src/app/node_modules ./node_modules +COPY --from=build --chown=node:node /usr/src/app/dist ./dist + +EXPOSE 9080 +USER node +CMD ["dumb-init", "node", "./dist/app.js"] diff --git a/typescript/templates/node-gen/README.md b/typescript/templates/node-gen/README.md new file mode 100644 index 00000000..5a105872 --- /dev/null +++ b/typescript/templates/node-gen/README.md @@ -0,0 +1,30 @@ +# Hello world - TypeScript generator SDK example + +Sample project configuration of a Restate service using the TypeScript SDK with the generator-based API ([`@restatedev/restate-sdk-gen`](https://www.npmjs.com/package/@restatedev/restate-sdk-gen)). + +Have a look at the [TypeScript Quickstart guide](https://docs.restate.dev/get_started/quickstart?sdk=ts) for more information on how to use Restate. + +## Requirements + +- Node.js 24+ +- [pnpm](https://pnpm.io/installation) + +## Running + +```shell +pnpm install +pnpm dev +``` + +## Docker + +```shell +docker build -t restate-ts-gen-template . +docker run -p 9080:9080 restate-ts-gen-template +``` + +## Using AI coding tools + +If you use Claude Code or Codex, then the Restate plugin will automatically be installed. For Cursor, consult the [skills repo README](https://github.com/restatedev/skills). + +Plugin repo: https://github.com/restatedev/skills/tree/main diff --git a/typescript/templates/node-gen/package.json b/typescript/templates/node-gen/package.json new file mode 100644 index 00000000..2c415061 --- /dev/null +++ b/typescript/templates/node-gen/package.json @@ -0,0 +1,31 @@ +{ + "name": "restate-ts-gen-template", + "version": "0.0.1", + "private": true, + "description": "Template for TypeScript services running with Restate (https://github.com/restatedev/), using the generator-based SDK", + "type": "module", + "scripts": { + "build": "tsc", + "start": "node ./dist/app.js", + "dev": "node --watch ./src/app.ts", + "typecheck": "tsc --noEmit", + "lint": "oxlint", + "fmt": "oxfmt", + "fmt:check": "oxfmt --check", + "check": "pnpm typecheck && pnpm lint && pnpm fmt:check" + }, + "dependencies": { + "@restatedev/restate-sdk": "^1.17.2", + "@restatedev/restate-sdk-gen": "^1.17.2" + }, + "devDependencies": { + "@types/node": "^24", + "oxfmt": "^0.71.0", + "oxlint": "^1.86.0", + "typescript": "^7.0.2" + }, + "engines": { + "node": ">=24" + }, + "packageManager": "pnpm@12.8.1" +} diff --git a/typescript/templates/node-gen/src/app.ts b/typescript/templates/node-gen/src/app.ts new file mode 100644 index 00000000..43d96048 --- /dev/null +++ b/typescript/templates/node-gen/src/app.ts @@ -0,0 +1,13 @@ +import * as restate from "@restatedev/restate-sdk"; +import { run, service, type Operation } from "@restatedev/restate-sdk-gen"; + +const greeter = service({ + name: "Greeter", + handlers: { + *greet(name: string): Operation { + return yield* run(async () => `Hello ${name}!`); + }, + }, +}); + +restate.serve({ services: [greeter] }); diff --git a/typescript/templates/node-gen/tsconfig.json b/typescript/templates/node-gen/tsconfig.json new file mode 100644 index 00000000..b7f00cdc --- /dev/null +++ b/typescript/templates/node-gen/tsconfig.json @@ -0,0 +1,27 @@ +{ + "compilerOptions": { + "target": "esnext", + "lib": ["esnext"], + "module": "nodenext", + "moduleResolution": "nodenext", + "types": ["node"], + + "rootDir": "./src", + "outDir": "./dist", + "sourceMap": true, + + // Run .ts files directly with Node's type stripping, emit .js on build + "allowImportingTsExtensions": true, + "rewriteRelativeImportExtensions": true, + "erasableSyntaxOnly": true, + "verbatimModuleSyntax": true, + "isolatedModules": true, + + "strict": true, + "noUncheckedIndexedAccess": true, + "noImplicitOverride": true, + "noEmitOnError": true, + "skipLibCheck": true + }, + "include": ["src"] +}