# @localsecurity/name-id

A tiny TypeScript utility to create and parse lexicographically sortable IDs with embedded model names and creation timestamps.

> **Use case:**  
Durable, globally unique IDs for NoSQL or single-table designs that sort newest-first. 

## ✨ Features  

✅ Generates stable IDs in the format:

`model` _ `reversedTimestamp` `random`

✅ Converts legacy UUIDs to sortable IDs 

✅ Extracts model name and timestamp 

✅ Zero dependencies (besides `@localsecurity/types`) 

✅ TypeScript-native with branded types for safety 


## 🚀 Install  

```bash
npm install @localsecurity/name-id
```

## 🔧 Usage 

### Create a NameID

```ts
import { NameID } from "@localsecurity/name-id";

// From a model name
const id1 = new NameID("User");
console.log(id1.value);
// => user_k9z8a1abcdef234567...

// From an existing object with __typename and createdAt
const id2 = new NameID({
  __typename: "CameraEvent",
  createdAt: "2024-07-01T00:00:00.000Z"
});
console.log(id2.value);
// => cameraevent_k9z8a1abcdef234567...
```

### Parse a NameID 

```ts
const parsed = NameID.parse(id1.value);
console.log(parsed.name);      // "User"
console.log(parsed.timestamp); // Date
```

### Type Guards 
```ts
import { isNameId, isUUID } from "@localsecurity/name-id";

isNameId("user_abc..."); // true
isUUID("xxxxxxx-xxxx..."); // true
```

### MutableModel

You can extend MutableModel to get a base class with automatic nameId and timestamps:
```ts
import { MutableModel } from "@localsecurity/name-id";

class MyModel extends MutableModel {
  data: string;
  constructor(init: Partial<MyModel>) {
    super("User");
    Object.assign(this, init);
  }
}
```

### Example Worker
```ts
import { Model } from '@localsecurity/types';
import { ModelName, NameID, NameIdString, isModelName } from "@localsecurity/name-id";

const ErrorResponse = (e: any) => Response.json({ error: e }, { status: 500 });

/**
 * Handle a request with a Model Name in the body
 */
async function handleName(request: Request) {

    const __typename = await request.text();

    const execute = () => {
      if (isModelName(__typename)) {
        const nameId = new NameID(__typename).value;
        const createdAt = NameID.parse(nameId).timestamp.toISOString();
        return Response.json({ nameId, createdAt, __typename });
      } else { return ErrorResponse(`Not a valid Model Name ('${__typename}')`) }
    }

    try { return execute() }
    catch (e: any) { return ErrorResponse(e) }
};

/**
 * Handle a request with a Model Object in the body
 */
async function handleObject(request: Request) {

    const object: Model[ModelName] = await request.json();

    const __typename = object['__typename'];

    const execute = () => {
      if (isModelName(__typename)) {
        const nameId: NameIdString = new NameID(object).value;
        return Response.json({ nameId, ...object });
      } else { return ErrorResponse(`Not a valid Model Name ('${__typename}')`) }
    }

    try { return execute() }
    catch (e: any) { return ErrorResponse(e) }
};

/**
 * Worker entrypoint
 */
export default {
  async fetch(request: Request, env: any, ctx: any) {
    const contentType = request.headers.get("content-type") ?? '';

    const execute = () => {
      if (contentType.includes("json")) return handleObject(request)
      else return handleName(request)
    }

    try { return execute() }
    catch (e: any) { return ErrorResponse(e) }
  }
};
```

## 🧑‍💻 Development 

Build: 
```bash
npm run build
```

Lint:
```bash
npm run lint
```

Format: 
```bash
npm run format
```

Bundle ESM:
```bash
npm run bundle
```

## 📄 License

MIT © LocalSecurity