Skip to content

Commit 996900e

Browse files
committed
chore: rename to firesmelt for npm
1 parent 0406179 commit 996900e

10 files changed

Lines changed: 52 additions & 52 deletions

File tree

README.md

Lines changed: 33 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,20 @@
11
<p align="center">
2-
<img src="assets/firecast.png" alt="firecast" width="192">
2+
<img src="assets/kilncast.png" alt="kilncast" width="192">
33
</p>
44

5-
<h1 align="center">firecast</h1>
5+
<h1 align="center">kilncast</h1>
66

77
<p align="center">Both Firestore SDKs behind one thin, schema-typed TypeScript interface.</p>
88

99
<p align="center">
10-
<a href="https://www.npmjs.com/package/firecast"><img src="https://img.shields.io/npm/v/firecast" alt="npm version"></a>
11-
<a href="https://github.com/mwpryer/firecast/stargazers"><img src="https://img.shields.io/github/stars/mwpryer/firecast" alt="GitHub stars"></a>
10+
<a href="https://www.npmjs.com/package/kilncast"><img src="https://img.shields.io/npm/v/kilncast" alt="npm version"></a>
11+
<a href="https://github.com/mwpryer/kilncast/stargazers"><img src="https://img.shields.io/github/stars/mwpryer/kilncast" alt="GitHub stars"></a>
1212
</p>
1313

1414
> [!IMPORTANT]
1515
> This project is under active development. Expect breaking changes before v1.0.
1616
17-
firecast is a thin wrapper over Firestore's SDKs, giving both one schema-typed interface: `firebase-admin` on the server and `firebase` on the web. Define a collection's schema once, in a validator you already use. Reads come back coerced and typed, and queries are typed to its fields. Underneath it's still plain Firestore, so you can reach for the SDK whenever you like.
17+
kilncast is a thin wrapper over Firestore's SDKs, giving both one schema-typed interface: `firebase-admin` on the server and `firebase` on the web. Define a collection's schema once, in a validator you already use. Reads come back coerced and typed, and queries are typed to its fields. Underneath it's still plain Firestore, so you can reach for the SDK whenever you like.
1818

1919
## Why
2020

@@ -35,7 +35,7 @@ await setDoc(doc(db, "posts", "hello-world"), {
3535
likes: "10", // wrong type, stored anyway
3636
});
3737

38-
// firecast
38+
// kilncast
3939
const post = await db.collection(posts).get("hello-world");
4040
// { id: string; title: string; likes: number; createdAt: Date } | null
4141
await db.collection(posts).set("hello-world", {
@@ -45,20 +45,20 @@ await db.collection(posts).set("hello-world", {
4545
});
4646
```
4747

48-
firecast moves all of this onto the collection definition, built from a schema you already have.
48+
kilncast moves all of this onto the collection definition, built from a schema you already have.
4949

50-
The schema is a [Standard Schema](https://standardschema.dev) validator, so Zod, Valibot, ArkType or any other compliant library supplies the types. firecast depends only on the spec's type definitions and never runs your schema; there is no runtime validation unless you want some, and then you run the same schema yourself.
50+
The schema is a [Standard Schema](https://standardschema.dev) validator, so Zod, Valibot, ArkType or any other compliant library supplies the types. kilncast depends only on the spec's type definitions and never runs your schema; there is no runtime validation unless you want some, and then you run the same schema yourself.
5151

5252
Timestamps round-trip. Write a `Date`, read a `Date`, however deeply it sits in maps and arrays. And one definition covers both SDKs: the same typed surface works with `firebase-admin` on the server and `firebase` on the web, so the model code you used to duplicate lives in one module, and that module imports no Firebase.
5353

5454
The whole surface is typed against the schema: reads and writes, the query builder and its aggregations, listeners, transactions, subcollections and collection-group queries. A misspelt field is a compile error rather than an empty result.
5555

56-
Underneath, it is still plain Firestore. firecast is a thin wrapper, not an ORM, and every handle exposes `.ref`, so you can always drop to the raw SDK.
56+
Underneath, it is still plain Firestore. kilncast is a thin wrapper, not an ORM, and every handle exposes `.ref`, so you can always drop to the raw SDK.
5757

5858
## Install
5959

6060
```sh
61-
npm install firecast
61+
npm install kilncast
6262
# plus whichever SDK you use
6363
npm install firebase-admin # server
6464
npm install firebase # web
@@ -69,7 +69,7 @@ npm install firebase # web
6969
Define a collection, connect a database, then read and write typed documents.
7070

7171
```ts
72-
import { collection } from "firecast";
72+
import { collection } from "kilncast";
7373
import { z } from "zod";
7474

7575
const posts = collection(
@@ -81,7 +81,7 @@ const posts = collection(
8181
}),
8282
);
8383

84-
import { createDatabase } from "firecast/admin";
84+
import { createDatabase } from "kilncast/admin";
8585
import { getFirestore } from "firebase-admin/firestore";
8686

8787
const db = createDatabase(getFirestore());
@@ -98,12 +98,12 @@ const post = await db.collection(posts).get("hello-world");
9898

9999
## Define a schema
100100

101-
A collection definition is a plain value (`name` + `schema`) with no database binding, so it's reusable at any path, including subcollections. The schema module imports only `firecast`, so Firebase never reaches a frontend bundle.
101+
A collection definition is a plain value (`name` + `schema`) with no database binding, so it's reusable at any path, including subcollections. The schema module imports only `kilncast`, so Firebase never reaches a frontend bundle.
102102

103-
The schema should describe a document Firestore can store: an object of fields, with no directly nested arrays (an array of arrays). firecast does not police this, so a non-storable shape surfaces as an SDK error at write time rather than a firecast one.
103+
The schema should describe a document Firestore can store: an object of fields, with no directly nested arrays (an array of arrays). kilncast does not police this, so a non-storable shape surfaces as an SDK error at write time rather than a kilncast one.
104104

105105
```ts
106-
import { collection } from "firecast";
106+
import { collection } from "kilncast";
107107
import { z } from "zod";
108108

109109
export const posts = collection(
@@ -123,7 +123,7 @@ Pass a Firestore instance to `createDatabase`. The server entrypoint uses `fireb
123123

124124
```ts
125125
// server.ts
126-
import { createDatabase } from "firecast/admin";
126+
import { createDatabase } from "kilncast/admin";
127127
import { getFirestore } from "firebase-admin/firestore";
128128

129129
const db = createDatabase(getFirestore());
@@ -132,7 +132,7 @@ const db = createDatabase(getFirestore());
132132
The web entrypoint is the same, but uses the modular `firebase` SDK.
133133

134134
```ts
135-
import { createDatabase } from "firecast/web";
135+
import { createDatabase } from "kilncast/web";
136136
import { getFirestore } from "firebase/firestore";
137137

138138
const db = createDatabase(getFirestore(app));
@@ -183,7 +183,7 @@ await db.collection(posts).delete("hello-world");
183183

184184
Every write also works on a document handle, like `db.collection(posts).doc("hello-world").set(...) / .update(...) / .delete()`.
185185

186-
`increment`, `serverTimestamp`, `arrayUnion`, `arrayRemove` and `deleteField` import from `firecast`, see [Sentinels](#sentinels).
186+
`increment`, `serverTimestamp`, `arrayUnion`, `arrayRemove` and `deleteField` import from `kilncast`, see [Sentinels](#sentinels).
187187

188188
## Query
189189

@@ -215,10 +215,10 @@ const meanLikes = await db.collection(posts).average("likes");
215215

216216
Dotted paths reach into nested maps, typed end to end. `where("customer.address.city", "==", "London")` requires that path to exist and its value to be a string. Paths stop at arrays and timestamps (those are queried as whole values), so `where("tags", "array-contains", "vip")` is valid but `where("tags.0", ...)` is not.
217217

218-
The document id is not a schema field, so target it with `documentId()` (imported from `firecast`). Use it to filter by id, or as an ordering tiebreak. Id values are plain strings, not the schema's field types.
218+
The document id is not a schema field, so target it with `documentId()` (imported from `kilncast`). Use it to filter by id, or as an ordering tiebreak. Id values are plain strings, not the schema's field types.
219219

220220
```ts
221-
import { documentId } from "firecast";
221+
import { documentId } from "kilncast";
222222

223223
const some = await db
224224
.collection(posts)
@@ -312,15 +312,15 @@ It is the right tool for blind atomic writes. A transaction would cover these to
312312

313313
## What's guaranteed
314314

315-
firecast types and coerces. It does not validate.
315+
kilncast types and coerces. It does not validate.
316316

317-
Reads are coerced and typed. A read coerces stored Firestore values to neutral types (every `Timestamp` becomes a `Date`), then merges the document id in flat as `(T & { id }) | null`. firecast never runs your schema, so a document that has drifted from it still comes back, typed as valid. Validate on read yourself where that matters.
317+
Reads are coerced and typed. A read coerces stored Firestore values to neutral types (every `Timestamp` becomes a `Date`), then merges the document id in flat as `(T & { id }) | null`. kilncast never runs your schema, so a document that has drifted from it still comes back, typed as valid. Validate on read yourself where that matters.
318318

319319
Writes are coerced. `set`, `add`, `update` and merge `set` coerce `Date` to `Timestamp` and translate sentinels, then write. The typed surface constrains every field at compile time, but nothing is checked at runtime.
320320

321321
### Sentinels
322322

323-
firecast provides its own sentinels (`serverTimestamp`, `increment`, `arrayUnion`, `arrayRemove`, `deleteField`) because a neutral schema can't reference the admin or web `FieldValue` class. Each driver translates them to its own SDK at write time.
323+
kilncast provides its own sentinels (`serverTimestamp`, `increment`, `arrayUnion`, `arrayRemove`, `deleteField`) because a neutral schema can't reference the admin or web `FieldValue` class. Each driver translates them to its own SDK at write time.
324324

325325
In `update` and merge `set`, each sentinel is constrained to the field types it fits. `increment` works only on a number field, `arrayUnion` / `arrayRemove` only on a matching array, `serverTimestamp` only on a `Date` or `Timestamp` field, and `deleteField` only on an optional field. A mismatch is a compile error.
326326

@@ -354,7 +354,7 @@ Schemas speak `Date`. The boundary coerces between `Date` and `Timestamp` deeply
354354
If a field needs full nanosecond precision, keep it raw with the `raw` option (a list of dotted field paths). Those paths return the raw SDK value uncoerced on read, whatever the type, so your schema types them as the SDK type (`Timestamp` here) rather than a `Date`. The same option keeps any other field raw too, for example an SDK `Bytes` instead of a coerced `Uint8Array`.
355355

356356
```ts
357-
import { collection, isTimestampLike, type Timestamp } from "firecast";
357+
import { collection, isTimestampLike, type Timestamp } from "kilncast";
358358
import { z } from "zod";
359359

360360
const events = collection(
@@ -373,7 +373,7 @@ const events = collection(
373373
Bytes are the binary analogue of timestamps. Schemas speak `Uint8Array`, the JS-native binary type, with no SDK import. The boundary coerces it to the SDK bytes type on write (the web `Bytes` class, an admin `Buffer`) and back to a plain `Uint8Array` on read, deeply, the same as `Date` and `Timestamp`. Lossless, so no precision caveat.
374374

375375
```ts
376-
import { collection } from "firecast";
376+
import { collection } from "kilncast";
377377
import { z } from "zod";
378378

379379
const files = collection("files", z.object({ blob: z.custom<Uint8Array>() }));
@@ -386,10 +386,10 @@ file?.blob;
386386

387387
## Neutral value types
388388

389-
A schema can name Firestore's other value types without importing either SDK. firecast ships structural `GeoPoint`, `DocumentReference` and `VectorValue` interfaces that mirror neutral `Timestamp`. They are types only: firecast does not coerce them. They round-trip uncoerced as the SDK class instance you read and write.
389+
A schema can name Firestore's other value types without importing either SDK. kilncast ships structural `GeoPoint`, `DocumentReference` and `VectorValue` interfaces that mirror neutral `Timestamp`. They are types only: kilncast does not coerce them. They round-trip uncoerced as the SDK class instance you read and write.
390390

391391
```ts
392-
import { collection, type GeoPoint } from "firecast";
392+
import { collection, type GeoPoint } from "kilncast";
393393
import { z } from "zod";
394394

395395
const places = collection(
@@ -404,22 +404,22 @@ const places = collection(
404404

405405
## Schema drift
406406

407-
firecast does not validate, so it does not catch drift. Stored data can diverge from the current schema: legacy docs, partial migrations, edits from other services or the console. A drifted document comes back coerced and typed as valid rather than throwing. Where that matters, run your schema on the result yourself, or drop to the SDK via `.ref`.
407+
kilncast does not validate, so it does not catch drift. Stored data can diverge from the current schema: legacy docs, partial migrations, edits from other services or the console. A drifted document comes back coerced and typed as valid rather than throwing. Where that matters, run your schema on the result yourself, or drop to the SDK via `.ref`.
408408

409409
## Escape hatch
410410

411-
Every handle exposes a `.ref` with the converter attached, so raw Firestore calls that firecast doesn't wrap still run through it where the SDK invokes it. The converter coerces both directions: a read coerces stored values to neutral types and merges the id in, a write coerces `Date` to `Timestamp` and translates sentinels.
411+
Every handle exposes a `.ref` with the converter attached, so raw Firestore calls that kilncast doesn't wrap still run through it where the SDK invokes it. The converter coerces both directions: a read coerces stored values to neutral types and merges the id in, a write coerces `Date` to `Timestamp` and translates sentinels.
412412

413413
`.ref` is typed `unknown` so the neutral core imports no SDK. Each entrypoint ships typed helpers, `docRef` / `collectionRef` / `queryRef`, that hand back the SDK ref typed to your schema, so you don't cast by hand.
414414

415415
```ts
416-
// or "firecast/web"
417-
import { docRef } from "firecast/admin";
416+
// or "kilncast/web"
417+
import { docRef } from "kilncast/admin";
418418

419419
// DocumentReference<Doc<typeof posts.schema>>
420420
const ref = docRef(db.collection(posts).doc("hello-world"));
421421

422-
// raw SDK call firecast does not wrap, data() is still coerced
422+
// raw SDK call kilncast does not wrap, data() is still coerced
423423
const unsub = ref.onSnapshot((snap) => {
424424
// post: Doc<typeof posts.schema> | undefined
425425
const post = snap.data();
@@ -433,5 +433,5 @@ If you need to drop fully to the raw SDK type yourself, `.ref` is still there to
433433
434434
## Errors
435435

436-
- `FirecastError` is the base class for the few errors firecast raises itself (such as an unknown sentinel). firecast polices neither ids nor write/query shapes, so those surface as the SDK's own error.
436+
- `KilncastError` is the base class for the few errors kilncast raises itself (such as an unknown sentinel). kilncast polices neither ids nor write/query shapes, so those surface as the SDK's own error.
437437
- Firestore, network, and permission errors propagate untouched.
File renamed without changes.

bun.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
{
2-
"name": "firecast",
2+
"name": "kilncast",
33
"version": "0.1.0",
44
"description": "Thin typed wrapper over both Firestore SDKs",
55
"license": "MIT",
66
"author": "mwpryer",
77
"repository": {
88
"type": "git",
9-
"url": "git+https://github.com/mwpryer/firecast.git"
9+
"url": "git+https://github.com/mwpryer/kilncast.git"
1010
},
1111
"files": [
1212
"dist"
@@ -29,7 +29,7 @@
2929
"scripts": {
3030
"build": "tsup",
3131
"typecheck": "tsc --noEmit",
32-
"test": "firebase emulators:exec --only firestore --project firecast 'bun test'",
32+
"test": "firebase emulators:exec --only firestore --project kilncast 'bun test'",
3333
"lint": "oxlint",
3434
"lint:fix": "oxlint --fix",
3535
"fmt": "oxfmt",

src/core/errors.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Base for firecast's own failures, SDK and network errors propagate untouched
2-
export class FirecastError extends Error {
3-
override readonly name: string = "FirecastError";
1+
// Base for kilncast's own failures, SDK and network errors propagate untouched
2+
export class KilncastError extends Error {
3+
override readonly name: string = "KilncastError";
44
}

src/drivers/admin.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ import type {
3232
Unsubscribe,
3333
WriteOptions,
3434
} from "@/core/driver";
35-
import { FirecastError } from "@/core/errors";
35+
import { KilncastError } from "@/core/errors";
3636
import {
3737
ArrayRemoveSentinel,
3838
ArrayUnionSentinel,
@@ -73,7 +73,7 @@ export class AdminDriver implements Driver {
7373
if (sentinel instanceof DeleteFieldSentinel) {
7474
return AdminFieldValue.delete();
7575
}
76-
throw new FirecastError("Unknown sentinel");
76+
throw new KilncastError("Unknown sentinel");
7777
}
7878

7979
#values(values: readonly unknown[]): unknown[] {

src/drivers/web.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ import type {
6565
Unsubscribe,
6666
WriteOptions,
6767
} from "@/core/driver";
68-
import { FirecastError } from "@/core/errors";
68+
import { KilncastError } from "@/core/errors";
6969
import {
7070
ArrayRemoveSentinel,
7171
ArrayUnionSentinel,
@@ -108,7 +108,7 @@ export class WebDriver implements Driver {
108108
if (sentinel instanceof DeleteFieldSentinel) {
109109
return webDeleteField();
110110
}
111-
throw new FirecastError("Unknown sentinel");
111+
throw new KilncastError("Unknown sentinel");
112112
}
113113

114114
#values(values: readonly unknown[]): unknown[] {

src/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ export type {
2121
Timestamp,
2222
VectorValue,
2323
} from "@/core/firestore";
24-
export { FirecastError } from "@/core/errors";
24+
export { KilncastError } from "@/core/errors";
2525

2626
export { Batch, Database, Transaction } from "@/core/database";
2727
export type { TransactionOptions, Unsubscribe } from "@/core/driver";

test/emulator.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -39,14 +39,14 @@ async function clearProject(projectId: string): Promise<void> {
3939
}
4040

4141
// Admin finds the emulator via FIRESTORE_EMULATOR_HOST, no query cache so a REST wipe is enough
42-
const adminApp = initAdminApp({ projectId: "firecast-admin" }, "admin");
42+
const adminApp = initAdminApp({ projectId: "kilncast-admin" }, "admin");
4343
const adminFs = getAdminFirestore(adminApp);
4444

4545
export const adminKit: DriverKit = {
4646
name: "admin",
4747
db: createAdminDatabase(adminFs),
4848
rawGet: async (path) => (await adminFs.doc(path).get()).data(),
49-
clear: () => clearProject("firecast-admin"),
49+
clear: () => clearProject("kilncast-admin"),
5050
timestamp: (seconds, nanoseconds) => new AdminTimestamp(seconds, nanoseconds),
5151
isSdkTimestamp: (value) => value instanceof AdminTimestamp,
5252
geoPoint: (latitude, longitude) => new AdminGeoPoint(latitude, longitude),
@@ -57,7 +57,7 @@ export const adminKit: DriverKit = {
5757

5858
// Web SDK caches a listener's first delivery, so rebuild the client each clear() for a cold cache
5959
let webSeq = 0;
60-
let webApp = initWebApp({ projectId: "firecast-web" }, "web-0");
60+
let webApp = initWebApp({ projectId: "kilncast-web" }, "web-0");
6161
// Long polling is the reliable off-browser transport
6262
let webFs = initializeFirestore(webApp, { experimentalForceLongPolling: true });
6363
connectFirestoreEmulator(webFs, HOST_NAME!, Number(HOST_PORT));
@@ -67,7 +67,7 @@ async function rebuildWeb(): Promise<void> {
6767
await terminate(webFs).catch(() => {});
6868
await deleteApp(webApp).catch(() => {});
6969
webSeq += 1;
70-
webApp = initWebApp({ projectId: "firecast-web" }, `web-${webSeq}`);
70+
webApp = initWebApp({ projectId: "kilncast-web" }, `web-${webSeq}`);
7171
webFs = initializeFirestore(webApp, { experimentalForceLongPolling: true });
7272
connectFirestoreEmulator(webFs, HOST_NAME!, Number(HOST_PORT));
7373
webDb = createWebDatabase(webFs);
@@ -80,7 +80,7 @@ export const webKit: DriverKit = {
8080
},
8181
rawGet: async (path) => (await webGetDoc(webDoc(webFs, path))).data(),
8282
clear: async () => {
83-
await clearProject("firecast-web");
83+
await clearProject("kilncast-web");
8484
await rebuildWeb();
8585
},
8686
timestamp: (seconds, nanoseconds) => new WebTimestamp(seconds, nanoseconds),

0 commit comments

Comments
 (0)