Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/generate-firestore-id.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'fireworkers': minor
---

Add `generateFirestoreId()`, a utility that returns a random 20-character `[A-Za-z0-9]` ID in the same format Firestore uses for auto-generated document IDs. Useful when you need to know a document's ID before writing it (e.g. to reference it from sibling writes in a `batch`). Ported from `@firebase/firestore`'s `AutoId.newId()` — uses `crypto.getRandomValues` with rejection sampling to avoid modulo bias.
17 changes: 17 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -339,6 +339,23 @@ const response = await b.commit();

---

### generateFirestoreId()

Generates a random ID matching Firestore's auto-generated document ID format: 20 characters from `[A-Za-z0-9]`, produced with rejection sampling via `crypto.getRandomValues` to avoid modulo bias. Ported from [`@firebase/firestore`'s `AutoId.newId()`](https://github.com/firebase/firebase-js-sdk/blob/main/packages/firestore/src/util/misc.ts).

Useful when you need to know a document's ID before writing it — for example, to reference it from other documents in the same [`batch`](#batchdb).

```typescript
const id = Firestore.generateFirestoreId();

const b = Firestore.batch(db);
b.set(['todos', id], { title: 'Win the lottery', completed: false });
b.set(['todo-index', id], { createdAt: Date.now() });
await b.commit();
```

---

## Error handling

All operations reject with a `FirestoreError` when Firestore returns an error response or the network request fails. `FirestoreError` extends the built-in `Error`, so existing `try/catch` and `.message` checks keep working — but you can now branch on a stable string `code` instead of parsing the message.
Expand Down
24 changes: 24 additions & 0 deletions src/id.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { describe, expect, it } from 'vitest';

import { generateFirestoreId } from './id';

describe('generateFirestoreId', () => {
it('returns a 20-character string', () => {
const id = generateFirestoreId();
expect(id).toHaveLength(20);
});

it('only uses characters from [A-Za-z0-9]', () => {
for (let i = 0; i < 100; i++) {
expect(generateFirestoreId()).toMatch(/^[A-Za-z0-9]{20}$/);
}
});

it('produces unique IDs across many invocations', () => {
const ids = new Set<string>();
for (let i = 0; i < 10_000; i++) {
ids.add(generateFirestoreId());
}
expect(ids.size).toBe(10_000);
Comment thread
besart-finsweet marked this conversation as resolved.
Outdated
});
});
28 changes: 28 additions & 0 deletions src/id.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
const AUTO_ID_CHARS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
const AUTO_ID_LENGTH = 20;
// Largest multiple of AUTO_ID_CHARS.length that fits in a byte.
// Bytes at or above this value are discarded so the modulo below is unbiased.
const MAX_MULTIPLE = Math.floor(256 / AUTO_ID_CHARS.length) * AUTO_ID_CHARS.length;

/**
* Generates a random ID matching Firestore's auto-generated document ID format.
* 20 characters from [A-Za-z0-9], with rejection sampling to avoid modulo bias.
* Ported from `@firebase/firestore`'s `AutoId.newId()`.
*/
export const generateFirestoreId = (): string => {
let id = '';
Comment thread
besart-finsweet marked this conversation as resolved.

while (id.length < AUTO_ID_LENGTH) {
const bytes = new Uint8Array(40);
crypto.getRandomValues(bytes);
Comment thread
besart-finsweet marked this conversation as resolved.

for (let i = 0; i < bytes.length && id.length < AUTO_ID_LENGTH; i++) {
const byte = bytes[i]!;
if (byte < MAX_MULTIPLE) {
id += AUTO_ID_CHARS.charAt(byte % AUTO_ID_CHARS.length);
}
}
}

return id;
};
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ export * from './batch';
export * from './create';
export { FirestoreError, type FirestoreErrorCode } from './error';
export * from './get';
export * from './id';
export * from './init';
export * from './query';
export * from './remove';
Expand Down