{"_id":"@codecompose/mono-ts","_rev":"4-78f39f5fa56b539b77a6147329af1c4c","name":"@codecompose/mono-ts","dist-tags":{"latest":"1.3.0"},"versions":{"1.0.0":{"name":"@codecompose/mono-ts","version":"1.0.0","keywords":["typescript","monorepo","firebase","isolate-package","esm","turbo","turborepo","nextjs"],"author":{"name":"Thijs Koerselman"},"license":"MIT","_id":"@codecompose/mono-ts@1.0.0","maintainers":[{"name":"thijskoerselman","email":"thijskoerselman@gmail.com"}],"homepage":"https://github.com/0x80/mono-ts#readme","bugs":{"url":"https://github.com/0x80/mono-ts/issues"},"dist":{"shasum":"53c7ff80ccd488461a6b7e1b6896f4cdc9dc3428","tarball":"https://registry.npmjs.org/@codecompose/mono-ts/-/mono-ts-1.0.0.tgz","fileCount":4,"integrity":"sha512-m08Hw0Mog1DbXx/wX8TKPPsapnJs27gLYdnjGaoayy13xXiYZvkar3HHpkDx3QI0PSDjVIYeWFQtgOaYpPDp2w==","signatures":[{"sig":"MEYCIQDpZn6dveXVdKy5SUbcZxvfURvhiKTCpI18/sy2Li1e/wIhAJvNo+5DztU7EexfJe40gP4KISBuL2v6EVZ0IznwhFns","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":17198},"main":"dist/index.js","_from":"file:codecompose-mono-ts-1.0.0.tgz","scripts":{"dev":"turbo run dev","lint":"turbo run lint --parallel","test":"turbo run test -- --watch false","build":"turbo run build","clean":"turbo run clean","watch":"turbo watch build --filter=@repo/core --filter=@repo/fns --filter=@repo/api","deploy":"firebase deploy","format":"prettier --write .","compile":"turbo run compile","emulate":"firebase emulators:start --project demo-mono-ts","db:get-indexes":"firebase firestore:indexes > firestore.indexes.json"},"_npmUser":{"name":"thijskoerselman","email":"thijskoerselman@gmail.com"},"_resolved":"/private/var/folders/tt/2t2tbx8d6f7_ph4c54m369jw0000gn/T/8db8d3e87470c632dda1f343bf2bb8ba/codecompose-mono-ts-1.0.0.tgz","_integrity":"sha512-m08Hw0Mog1DbXx/wX8TKPPsapnJs27gLYdnjGaoayy13xXiYZvkar3HHpkDx3QI0PSDjVIYeWFQtgOaYpPDp2w==","repository":{"url":"git+https://github.com/0x80/mono-ts.git","type":"git"},"_npmVersion":"10.9.0","description":"A quest for the ideal TS monorepo setup","directories":{},"_nodeVersion":"22.12.0","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"turbo":"^2.3.3","vercel":"^39.2.6","vitest":"^2.1.8","prettier":"^3.4.2","typescript":"^5.7.3","prettier-plugin-jsdoc":"^1.3.2","firebase-tools-with-isolate":"13.29.1-2"},"_npmOperationalInternal":{"tmp":"tmp/mono-ts_1.0.0_1736769076119_0.6417081201924961","host":"s3://npm-registry-packages-npm-production"}},"1.1.0":{"name":"@codecompose/mono-ts","version":"1.1.0","keywords":["typescript","monorepo","firebase","firestore","isolate-package","typed-firestore","esm","turborepo","nextjs"],"author":{"name":"Thijs Koerselman"},"license":"MIT","_id":"@codecompose/mono-ts@1.1.0","maintainers":[{"name":"thijskoerselman","email":"thijskoerselman@gmail.com"}],"homepage":"https://github.com/0x80/mono-ts#readme","bugs":{"url":"https://github.com/0x80/mono-ts/issues"},"dist":{"shasum":"4df3902c00c924a41434e9ac4c0747f727eeb8e9","tarball":"https://registry.npmjs.org/@codecompose/mono-ts/-/mono-ts-1.1.0.tgz","fileCount":3,"integrity":"sha512-iK9uJAl6EWR1bBY5pZ9wdtUdCe3YP6VvPOQRq97JAZ11ObquFIRfeIS0N9JsLTmmX//5P2oZImVuARiHj/usbA==","signatures":[{"sig":"MEUCIQCg1zhVDRwWH6vwgGFxum4quVIACHMyP0WUtQbusX2buAIgOWgZJL1VKC2mhQtuj3xq0qLEThUOIw4hv/GC6LuL9gQ=","keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA"}],"unpackedSize":11131},"main":"dist/index.js","_from":"file:codecompose-mono-ts-1.1.0.tgz","scripts":{"dev":"turbo run dev","lint":"turbo run lint --parallel","test":"turbo run test -- --watch false","build":"turbo run build","clean":"turbo run clean","watch":"turbo watch build --filter=@repo/core --filter=@repo/fns --filter=@repo/api","deploy":"firebase deploy","format":"prettier --write .","compile":"turbo run compile","emulate":"firebase emulators:start --project demo-mono-ts","build:api":"turbo run build --filter=@repo/api","build:fns":"turbo run build --filter=@repo/fns","db:get-indexes":"firebase firestore:indexes > firestore.indexes.json"},"_npmUser":{"name":"thijskoerselman","email":"thijskoerselman@gmail.com"},"_resolved":"/private/var/folders/tt/2t2tbx8d6f7_ph4c54m369jw0000gn/T/87960059d2a4a1787673a10dcf0d0e01/codecompose-mono-ts-1.1.0.tgz","_integrity":"sha512-iK9uJAl6EWR1bBY5pZ9wdtUdCe3YP6VvPOQRq97JAZ11ObquFIRfeIS0N9JsLTmmX//5P2oZImVuARiHj/usbA==","repository":{"url":"git+https://github.com/0x80/mono-ts.git","type":"git"},"_npmVersion":"10.9.0","description":"A quest for the ideal TS monorepo setup","directories":{},"_nodeVersion":"22.12.0","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"turbo":"^2.3.3","vercel":"^39.3.0","vitest":"^3.0.2","prettier":"^3.4.2","typescript":"^5.7.3","prettier-plugin-jsdoc":"^1.3.2","firebase-tools-with-isolate":"13.29.1-2"},"_npmOperationalInternal":{"tmp":"tmp/mono-ts_1.1.0_1737294711879_0.7592037029441927","host":"s3://npm-registry-packages-npm-production"}},"1.2.0":{"name":"@codecompose/mono-ts","version":"1.2.0","keywords":["typescript","monorepo","firebase","firestore","isolate-package","typed-firestore","esm","turborepo","nextjs"],"author":{"name":"Thijs Koerselman"},"license":"MIT","_id":"@codecompose/mono-ts@1.2.0","maintainers":[{"name":"thijskoerselman","email":"thijskoerselman@gmail.com"}],"homepage":"https://github.com/0x80/mono-ts#readme","bugs":{"url":"https://github.com/0x80/mono-ts/issues"},"dist":{"shasum":"218d945c34d3fc05b8938d0a0260cdd3353dd48b","tarball":"https://registry.npmjs.org/@codecompose/mono-ts/-/mono-ts-1.2.0.tgz","fileCount":3,"integrity":"sha512-TVM2/aX530ZDtAYzvZrv96umrRge90LfPMJlHFHGVogdFn766fXIA74RgdXdxXlcYBcegyVgwz4bKEF2ww53nA==","signatures":[{"sig":"MEUCIQD0J77Fnl3l7O76kIROqHdm4pGh1XGDP4Suyi5CwKqOPwIgPfezVTLhYbLuzW7PEJs6KTVMIDxuxtMCbISoC5GBRdM=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":12266},"main":"dist/index.js","_from":"file:codecompose-mono-ts-1.2.0.tgz","scripts":{"dev":"turbo dev","lint":"turbo lint","test":"turbo test -- --watch false","build":"turbo build","clean":"turbo clean","watch":"turbo watch build --filter=@repo/core --filter=@repo/fns --filter=@repo/api","deploy":"firebase deploy","format":"prettier --write .","emulate":"firebase emulators:start --project demo-mono-ts","build:api":"turbo build --filter=@repo/api","build:fns":"turbo build --filter=@repo/fns","check-types":"turbo check-types --continue","check-format":"prettier --check .","check-circular":"turbo check-circular --continue","db:get-indexes":"firebase firestore:indexes > firestore.indexes.json"},"_npmUser":{"name":"thijskoerselman","email":"thijskoerselman@gmail.com"},"_resolved":"/private/var/folders/tt/2t2tbx8d6f7_ph4c54m369jw0000gn/T/b97771f727bb57fb1671a33392ebfa07/codecompose-mono-ts-1.2.0.tgz","_integrity":"sha512-TVM2/aX530ZDtAYzvZrv96umrRge90LfPMJlHFHGVogdFn766fXIA74RgdXdxXlcYBcegyVgwz4bKEF2ww53nA==","repository":{"url":"git+https://github.com/0x80/mono-ts.git","type":"git"},"_npmVersion":"10.9.2","description":"A quest for the ideal TS monorepo setup","directories":{},"_nodeVersion":"22.15.1","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"turbo":"^2.3.4","vercel":"^44.7.3","vitest":"^3.0.4","prettier":"^3.4.2","typescript":"^5.9.2","prettier-plugin-jsdoc":"^1.3.2","firebase-tools-with-isolate":"14.10.1"},"_npmOperationalInternal":{"tmp":"tmp/mono-ts_1.2.0_1755000233741_0.9941405300289048","host":"s3://npm-registry-packages-npm-production"}},"1.3.0":{"name":"@codecompose/mono-ts","description":"A quest for the ideal TS monorepo setup","version":"1.3.0","keywords":["typescript","monorepo","firebase","firestore","isolate-package","typed-firestore","esm","turborepo","nextjs"],"main":"dist/index.js","author":{"name":"Thijs Koerselman"},"license":"MIT","publishConfig":{"access":"public"},"repository":{"type":"git","url":"git+https://github.com/0x80/mono-ts.git"},"devDependencies":{"firebase-tools-with-isolate":"14.10.1","prettier":"^3.4.2","prettier-plugin-jsdoc":"^1.3.2","turbo":"^2.3.4","typescript":"^5.9.2","vercel":"^44.7.3","vitest":"^3.0.4"},"scripts":{"check-types":"turbo check-types --continue","check-circular":"turbo check-circular --continue","build":"turbo build","build:fns":"turbo build --filter=@repo/fns","build:api":"turbo build --filter=@repo/api","clean":"turbo clean","dev":"turbo dev","deploy":"firebase deploy","watch":"turbo watch build --filter=@repo/core --filter=@repo/fns --filter=@repo/api","lint":"turbo lint","test":"turbo test -- --watch false","format":"prettier --write .","check-format":"prettier --check .","emulate":"firebase emulators:start --project demo-mono-ts","db:get-indexes":"firebase firestore:indexes > firestore.indexes.json"},"_id":"@codecompose/mono-ts@1.3.0","bugs":{"url":"https://github.com/0x80/mono-ts/issues"},"homepage":"https://github.com/0x80/mono-ts#readme","_integrity":"sha512-sK65swEffPh6koTUkxmf7Wm+cbl1ftVGqHyVBCL+fML8kxA45A/YtHdz2IKomXS00yYZ6I7MqJX1wpQjBUgdiQ==","_resolved":"/private/var/folders/tt/2t2tbx8d6f7_ph4c54m369jw0000gn/T/68a2e972d649ac140c0a437db79d5363/codecompose-mono-ts-1.3.0.tgz","_from":"file:codecompose-mono-ts-1.3.0.tgz","_nodeVersion":"22.15.1","_npmVersion":"10.9.2","dist":{"integrity":"sha512-sK65swEffPh6koTUkxmf7Wm+cbl1ftVGqHyVBCL+fML8kxA45A/YtHdz2IKomXS00yYZ6I7MqJX1wpQjBUgdiQ==","shasum":"26a5f6312397f37b7c3c5e887cbd1a9b7b06cb62","tarball":"https://registry.npmjs.org/@codecompose/mono-ts/-/mono-ts-1.3.0.tgz","fileCount":3,"unpackedSize":12266,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIQCYsj6jT6NbTxQjI/+ASOubgjAASGkYenl/oTxr/cQBTAIgU6DlTUPJqSBJrsePZhIYc3UXcip+IJoeYItgQCs7EC8="}]},"_npmUser":{"name":"thijskoerselman","email":"thijskoerselman@gmail.com"},"directories":{},"maintainers":[{"name":"thijskoerselman","email":"thijskoerselman@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/mono-ts_1.3.0_1755000536351_0.8788805060923726"},"_hasShrinkwrap":false}},"time":{"created":"2025-01-13T11:51:15.935Z","modified":"2025-08-12T12:08:56.760Z","1.0.0":"2025-01-13T11:51:16.313Z","1.1.0":"2025-01-19T13:51:52.072Z","1.2.0":"2025-08-12T12:03:53.912Z","1.3.0":"2025-08-12T12:08:56.550Z"},"bugs":{"url":"https://github.com/0x80/mono-ts/issues"},"author":{"name":"Thijs Koerselman"},"license":"MIT","homepage":"https://github.com/0x80/mono-ts#readme","keywords":["typescript","monorepo","firebase","firestore","isolate-package","typed-firestore","esm","turborepo","nextjs"],"repository":{"type":"git","url":"git+https://github.com/0x80/mono-ts.git"},"description":"A quest for the ideal TS monorepo setup","maintainers":[{"name":"thijskoerselman","email":"thijskoerselman@gmail.com"}],"readme":"# Mono TS\n\n<!-- TOC -->\n\n- [Introduction](#introduction)\n- [Features](#features)\n- [Install](#install)\n- [Usage](#usage)\n- [Monorepo Setup](#monorepo-setup)\n  - [Namespace](#namespace)\n  - [Packages](#packages)\n  - [Apps](#apps)\n  - [Services](#services)\n- [Firebase](#firebase)\n  - [Demo Project](#demo-project)\n  - [Deploying](#deploying)\n  - [Running Emulators](#running-emulators)\n    - [Secrets](#secrets)\n- [Typed Firestore](#typed-firestore)\n\n<!-- /TOC -->\n\n## Introduction\n\nThis is a personal quest for the perfect Typescript monorepo setup.\n\n> There is an accompanying article\n> [\"My quest for the perfect TS monorepo\"](https://thijs-koerselman.medium.com/my-quest-for-the-perfect-ts-monorepo-62653d3047eb)\n> that you might want to read for context.\n\nIt is the best I could come up with given the tooling that is available, so\nexpect this repository to change over time as the ecosystem around Typescript\nevolves.\n\nMy current projects are based on Node.js, Next.js, and Firebase, so that is what\nI am focussing on primarily. If you use different a different stack, I believe\nthis can still be a great reference, as the approach itself does not depend on\nit.\n\nContributions and suggestions are welcome within the scope of this example, but\nI doubt there ever will be a one-size-fits-all solution, so this code should be\nviewed as opinionated.\n\nI ended up basing a lot of things on the\n[Turborepo starter](https://turbo.build/repo/docs/getting-started/create-new),\nand I recommend reading\n[their monorepo handbook](https://turbo.build/repo/docs/handbook).\n\n## Features\n\n- [Turborepo](https://turbo.build/) to orchestrate the build process and\n  dependencies, including the v2 watch task.\n- Multiple isolated Firebase deployments, using\n  [firebase-tools-with-isolate](https://github.com/0x80/firebase-tools-with-isolate)\n- Firebase emulators with hot reloading\n- A web app based on Next.js with [ShadCN](https://ui.shadcn.com/) and\n  [Tailwind CSS](https://tailwindcss.com/)\n- Working IDE go-to-definition and go-to-type-definition using `.d.ts.map` files\n- ESM everything\n- Typescript path aliases (that get resolved in build output)\n- Shared configurations for ESLint v9 with strict type-aware rules\n- Shared standardized configuration for TypeScript\n- Vitest\n- Clean, typed Firestore abstractions using\n  [@typed-firestore/react](https://github.com/0x80/typed-firestore) and\n  [@typed-firestore/server](https://github.com/0x80/typed-firestore-server)\n\n## Install\n\nIn the main branch of this repo, packages are managed with PNPM.\n\nThere is also a branch for [NPM](https://github.com/0x80/mono-ts/tree/use-npm)\n\nOriginally, I included branches for\n[Yarn classic (v1)](https://github.com/0x80/mono-ts/tree/use-yarn-classic),\n[and modern (v4)](https://github.com/0x80/mono-ts/tree/use-yarn-modern), but I\nstopped updating them as Yarn is not that commonly used anymore.\n\nI recommend using `pnpm` over `npm` or `yarn`. Apart from being fast and\nefficient, PNPM has better support for monorepos.\n\nYou can install PNPM with `corepack` which is part of modern Node.js versions:\n\n- `corepack enable` (if you have not used it before)\n- `corepack prepare pnpm@latest --activate`\n\nThen run `pnpm install` from the repository root.\n\n## Usage\n\nTo get started, execute the following 3 scripts with `pnpm [script name]` from\nthe root of the monorepo:\n\n| Script    | Description                                                                                                        |\n| --------- | ------------------------------------------------------------------------------------------------------------------ |\n| `watch`   | Continuously builds everything using the Turborepo watch task, except for the web app which has its own dev server |\n| `emulate` | Starts the Firebase emulators.                                                                                     |\n| `dev`     | Starts the Next.js dev server to build the app on request.                                                         |\n\nThe web app should become available on http://localhost:3000 and the emulators\nUI on http://localhost:4000.\n\nYou should now have a working local setup, in which code changes to any package\nare picked up.\n\n## Monorepo Setup\n\n> There is an accompanying article\n> [\"My quest for the perfect TS monorepo\"](https://thijs-koerselman.medium.com/my-quest-for-the-perfect-ts-monorepo-62653d3047eb)\n> that you might want to read for context.\n\nThis monorepo used to showcase the \"internal packages approach\" for\n`@repo/common`, as described in the article, which lets you link to sources\ndirectly without a build step.\n\nI removed it because I ran into some issues, and I can not really recommend it.\nWith the right configuration of incremental builds, references, and build\norchestration, modern build tools seem to work fast and smoothly enough.\n\n### Namespace\n\nOften in a monorepo, you will never publish the shared packages to NPM or some\nother registry, and because of that, the namespace you use to prefix your\npackage names does not matter. You might as well pick a standard one that you\ncan use in every project.\n\nAt first I used `@mono`, and later I switched to `@repo` when I encountered that\nin the Turborepo examples. I like both, because they are equally short and\nclear, but I went with `@repo` because I expect it might become a standard\nsooner.\n\n### Packages\n\n- [common](./packages/common) Code that is shared across both front-end and\n  back-end environments simultaneously.\n- [core](./packages/core) Code that is only shared between server environments,\n  like cloud functions, containing mostly \"core\" business logic.\n\nA standard name for a package that is only shared between client-side apps is\n`ui`. Besides sharing UI components, I also use it to share other things that\nare solely relevant to the clients.\n\n### Apps\n\n- [web](./apps/web) A Next.js based web application configured to use Tailwind\n  CSS and ShadCN components.\n\n### Services\n\n- [fns](./services/fns) Various Firebase functions that execute on document\n  writes, pubsub events etc.\n- [api](./services/api) A 2nd gen Firebase function (based on Cloud Run) serving\n  as an API endpoint. This package also illustrates how to use secrets.\n\n## Firebase\n\nIn their\n[documentation for monorepos](https://firebase.google.com/docs/functions/organize-functions?gen=2nd#managing_multiple_source_packages_monorepo),\nFirebase recommends putting all configurations in the root of the monorepo. This\nmakes it possible to deploy all packages at once, and easily start the emulators\nshared between all packages.\n\n### Demo Project\n\nThroughout this repository, we use a Firebase demo project called `demo-mono-ts`\nA demo project allows you to run emulators for the different components like\ndatabase without creating a Firebase projects with resources. To make this work\nyou pass the `--project` flag when starting the emulator, and you need to use a\nname that starts with `demo-`.\n\nWhen passing configuration to initializeApp you can use any non-empty string for\nthe API keys as you can see in\n[apps/web/.env.development](apps/web/.env.development).\n\n### Deploying\n\nFirebase does not natively support monorepos where packages used shared code\nfrom other packages. The Firebase deploy pipeline wants to upload a\nself-contained package that can be treated similarly to an NPM package, so that\nit can run an install and execute the main entry from the manifest.\n\nTo support shared packages, this repo uses\n[firestore-tools-with-isolate](https://github.com/0x80/firebase-tools-with-isolate),\nwhich is a firebase-tools fork I created to integrate\n[isolate-package](https://github.com/0x80/isolate-package/). I wrote an\n[article](https://thijs-koerselman.medium.com/deploy-to-firebase-without-the-hacks-e685de39025e)\nexplaining what it does and why it is needed.\n\nThis demo can be run using only the emulators, but if you would like to see the\ndeployment to Firebase working you can simply execute\n`npx firebase deploy --project your-project-name` the root of the monorepo.\n\nYou might notice `@google-cloud/functions-framework` as a dependency in the\nservice package even though it is not being used in code imports. It is\ncurrently required for Firebase to be able to deploy a PNPM workspace. Without\nit you will get an error asking you to install the dependency. I don't quite\nunderstand how the two are related, but it works.\n\n### Running Emulators\n\nWith the firebase config in the root of the monorepo, you can configure and\nstart the emulators for all packages at once with `pnpm emulate`.\n\nI have stored these in `.env` files in the respective service packages. Normally\nyou would want to store them in a file that is not part of the repository like\n`.env.local` but by placing them in `.env` I prevent having to give instructions\nfor setting them up just for running the demo.\n\n#### Secrets\n\nThe api service uses a secret for DEMO_API_KEY. To make secrets work with the\nemulator you currently have to add the secret to `.secret.local` and also a\n`.env` or `.env.local` file. See\n[this issue](https://github.com/firebase/firebase-tools/issues/5520) for more\ninfo. I have placed it in `.env` which is part of the repo, so you don't have to\nset anything up, but .env.local is the proper location probably because that\nfile is not checked into git.\n\n## Typed Firestore\n\nThis repo uses [@typed-firestore/react](https://github.com/0x80/typed-firestore)\nand [@typed-firestore/server](https://github.com/0x80/typed-firestore-server) to\nprovide typed Firestore abstractions for both React and Node.js.\n\nIf you're interested here is an\n[in-depth article](https://dev.to/0x80/how-to-write-clean-typed-firestore-code-37j2)\nof how they came about.\n","readmeFilename":"README.md"}