111 lines
3.1 KiB
Markdown
111 lines
3.1 KiB
Markdown
# 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<IMyInterface>({
|
|
_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 |