# GadgetId ## Overview GadgetId is a string-based identifier type used throughout the Gadget monorepo for all entity IDs. It replaces MongoDB's native `ObjectId` type with a simple, portable string that works consistently across all packages without requiring Mongoose or MongoDB-specific conversions. ## Type Definition ```ts // packages/api/src/lib/gadget-id.ts export type GadgetId = string; ``` ## Why GadgetId? - **Consistency**: All packages use the same ID type regardless of whether they use Mongoose - **Simplicity**: No `createFromHexString()` or `toHexString()` conversions needed - **Portability**: Works seamlessly from backend to frontend to drone workers - **Type safety**: Compile-time checking ensures IDs are always strings ## Usage ### Interfaces Always use `GadgetId` for `_id` fields in interfaces: ```ts import { GadgetId } from "@gadget/api"; interface IUser { _id: GadgetId; email: string; displayName: string; } ``` ### Mongoose Schemas Use the following pattern for all schemas: ```ts import { Schema, model } from "mongoose"; import { GadgetId } from "@gadget/api"; import { nanoid } from "nanoid"; const MySchema = new Schema({ _id: { type: String, default: () => nanoid() }, // ... other fields }); ``` ### Important Schema Rules **Do NOT add `unique: true` or `required: true` to the `_id` field:** - MongoDB automatically creates a unique index on `_id`; specifying `unique: true` causes duplicate index errors at startup - MongoDB handles `_id` validation automatically; specifying `required: true` causes save failures because the default is assigned AFTER validation Correct: ```ts _id: { type: String, default: () => nanoid() } ``` Incorrect: ```ts // DON'T do this - breaks everything _id: { type: String, default: () => nanoid(), unique: true, required: true } ``` ### Creating Documents ```ts const thing = new Thing({ /* other fields */ }); await thing.save(); // Works - MongoDB handles _id automatically ``` ### Passing IDs to Functions IDs are strings - just pass them directly: ```ts const userId: GadgetId = user._id; await userService.getById(userId); // No conversion needed ``` ## GadgetId vs ObjectId | Operation | ObjectId | GadgetId | |-----------|----------|----------| | Type | `ObjectId` | `string` | | Creation | `new ObjectId()` | `nanoid()` (via schema default) | | String conversion | `id.toHexString()` | Not needed | | Parsing from string | `createFromHexString(str)` | Not needed | | Mongoose casting | Automatic with `Schema.Types.ObjectId` | Automatic with `type: String` | ## Importing GadgetId ```ts // From @gadget/api (recommended for most code) import { GadgetId } from "@gadget/api"; // From packages/api directly (for @gadget/api consumers) import { GadgetId } from "../../../packages/api/dist/lib/gadget-id.js"; ``` ## Rules 1. **Never use `ObjectId` directly** in application code 2. **Never import `ObjectId` from Mongoose** in service/controller code 3. **Always use `GadgetId`** for `_id` fields in interfaces and function parameters 4. **Never add `unique` or `required`** to `_id` in Mongoose schemas