firestore-proto-codec 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js ADDED
@@ -0,0 +1,3 @@
1
+ export { FirestoreProtoCodec, MAX_NESTING_DEPTH } from "./codec.js";
2
+ export { CodecError } from "./errors.js";
3
+ export { DefaultFirestoreTypes, FsBlob, FsGeoPoint, FsTimestamp, } from "./values.js";
@@ -0,0 +1,52 @@
1
+ /**
2
+ * Firestore has three value types protobuf cannot express as plain data.
3
+ * Implement this to bind them to a particular SDK's classes -- `Timestamp`,
4
+ * `Bytes`, and `GeoPoint` in `firebase-admin`, for example. Everything else in
5
+ * the encoding is a plain string, number, bigint, boolean, array, or object.
6
+ */
7
+ export interface FirestoreTypes {
8
+ timestamp(seconds: bigint, nanos: number): unknown;
9
+ blob(bytes: Uint8Array): unknown;
10
+ geoPoint(latitude: number, longitude: number): unknown;
11
+ /** Return undefined when the value is not of this type, so decoding can
12
+ * report a useful error rather than throwing on a bad cast. */
13
+ readTimestamp(value: unknown): {
14
+ seconds: bigint;
15
+ nanos: number;
16
+ } | undefined;
17
+ readBlob(value: unknown): Uint8Array | undefined;
18
+ readGeoPoint(value: unknown): {
19
+ latitude: number;
20
+ longitude: number;
21
+ } | undefined;
22
+ }
23
+ export declare class FsTimestamp {
24
+ readonly seconds: bigint;
25
+ readonly nanos: number;
26
+ constructor(seconds: bigint, nanos: number);
27
+ }
28
+ export declare class FsBlob {
29
+ readonly bytes: Uint8Array;
30
+ constructor(bytes: Uint8Array);
31
+ }
32
+ export declare class FsGeoPoint {
33
+ readonly latitude: number;
34
+ readonly longitude: number;
35
+ constructor(latitude: number, longitude: number);
36
+ }
37
+ /** Dependency-free default. Swap in an SDK-specific implementation to write
38
+ * directly to Firestore. */
39
+ export declare class DefaultFirestoreTypes implements FirestoreTypes {
40
+ timestamp(seconds: bigint, nanos: number): unknown;
41
+ blob(bytes: Uint8Array): unknown;
42
+ geoPoint(latitude: number, longitude: number): unknown;
43
+ readTimestamp(value: unknown): {
44
+ seconds: bigint;
45
+ nanos: number;
46
+ } | undefined;
47
+ readBlob(value: unknown): Uint8Array | undefined;
48
+ readGeoPoint(value: unknown): {
49
+ latitude: number;
50
+ longitude: number;
51
+ } | undefined;
52
+ }
package/dist/values.js ADDED
@@ -0,0 +1,48 @@
1
+ export class FsTimestamp {
2
+ seconds;
3
+ nanos;
4
+ constructor(seconds, nanos) {
5
+ this.seconds = seconds;
6
+ this.nanos = nanos;
7
+ }
8
+ }
9
+ export class FsBlob {
10
+ bytes;
11
+ constructor(bytes) {
12
+ this.bytes = bytes;
13
+ }
14
+ }
15
+ export class FsGeoPoint {
16
+ latitude;
17
+ longitude;
18
+ constructor(latitude, longitude) {
19
+ this.latitude = latitude;
20
+ this.longitude = longitude;
21
+ }
22
+ }
23
+ /** Dependency-free default. Swap in an SDK-specific implementation to write
24
+ * directly to Firestore. */
25
+ export class DefaultFirestoreTypes {
26
+ timestamp(seconds, nanos) {
27
+ return new FsTimestamp(seconds, nanos);
28
+ }
29
+ blob(bytes) {
30
+ return new FsBlob(bytes);
31
+ }
32
+ geoPoint(latitude, longitude) {
33
+ return new FsGeoPoint(latitude, longitude);
34
+ }
35
+ readTimestamp(value) {
36
+ return value instanceof FsTimestamp
37
+ ? { seconds: value.seconds, nanos: value.nanos }
38
+ : undefined;
39
+ }
40
+ readBlob(value) {
41
+ return value instanceof FsBlob ? value.bytes : undefined;
42
+ }
43
+ readGeoPoint(value) {
44
+ return value instanceof FsGeoPoint
45
+ ? { latitude: value.latitude, longitude: value.longitude }
46
+ : undefined;
47
+ }
48
+ }
package/package.json ADDED
@@ -0,0 +1,44 @@
1
+ {
2
+ "name": "firestore-proto-codec",
3
+ "version": "0.1.0",
4
+ "description": "Encode a protobuf message as a Firestore value, and decode it back.",
5
+ "license": "Apache-2.0",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "git+https://github.com/willbinge/firestore-proto-codec.git",
9
+ "directory": "ts"
10
+ },
11
+ "type": "module",
12
+ "main": "./dist/index.js",
13
+ "types": "./dist/index.d.ts",
14
+ "exports": {
15
+ ".": {
16
+ "types": "./dist/index.d.ts",
17
+ "default": "./dist/index.js"
18
+ },
19
+ "./proto/*": "./proto/*"
20
+ },
21
+ "files": [
22
+ "dist",
23
+ "proto",
24
+ "NOTICE"
25
+ ],
26
+ "scripts": {
27
+ "build": "tsc -p tsconfig.build.json",
28
+ "generate": "./tool/generate.sh",
29
+ "test": "tsx --test test/*.test.ts",
30
+ "test:emulator": "npx --yes firebase-tools@15.27.0 emulators:exec --only firestore --project demo-codec 'tsx --test test/emulator.test.ts'",
31
+ "typecheck": "tsc --noEmit",
32
+ "prepublishOnly": "npm run build"
33
+ },
34
+ "dependencies": {
35
+ "@bufbuild/protobuf": "^2.2.0"
36
+ },
37
+ "devDependencies": {
38
+ "@bufbuild/protoc-gen-es": "^2.2.0",
39
+ "@google-cloud/firestore": "^9.0.1",
40
+ "@types/node": "^22.10.0",
41
+ "tsx": "^4.19.0",
42
+ "typescript": "^5.7.0"
43
+ }
44
+ }
@@ -0,0 +1,66 @@
1
+ // Copyright 2026 Code Binge LLC
2
+
3
+ syntax = "proto3";
4
+
5
+ package codebinge.firestore.codec.v1;
6
+
7
+ import "google/protobuf/descriptor.proto";
8
+
9
+ option java_package = "com.codebinge.firestore.codec.v1";
10
+ option java_multiple_files = true;
11
+
12
+ // Per-field encoding options. See docs/encoding.md §7.
13
+ //
14
+ // NOTE: extension number 50000 is provisional. 50000-99999 is protobuf's
15
+ // organization-internal range -- shared scratch space every organization is told
16
+ // to use freely -- so a collision there is documented behavior, not bad luck,
17
+ // and it surfaces as soon as two published schemas meet in one descriptor pool.
18
+ // protocolbuffers/protobuf#29013 reports exactly that failure. Replace this with
19
+ // a Global Extension Registry number before any public release: changing it
20
+ // afterwards is breaking, and it is baked into generated identifiers too.
21
+ extend google.protobuf.FieldOptions {
22
+ Field field = 50000;
23
+ }
24
+
25
+ message Field {
26
+ // Never encoded.
27
+ bool skip = 1;
28
+
29
+ // Stored-name override. Exists only for adopting an existing collection;
30
+ // the default is the proto field name verbatim (§1).
31
+ string name = 2;
32
+
33
+ // Default: encode enum values as their name string (§4).
34
+ EnumEncoding enum_as = 3;
35
+
36
+ // Default true: omit singular scalar fields holding their default value (§6).
37
+ // Set false where an index or security rule depends on the field existing.
38
+ //
39
+ // Explicit presence is required. The intended default is true, and a proto3
40
+ // implicit-presence bool would not serialize `false`, leaving "set to false"
41
+ // indistinguishable from "not set".
42
+ optional bool omit_when_default = 4;
43
+
44
+ // Overrides the encoding inferred from the proto type.
45
+ Kind kind = 5;
46
+ }
47
+
48
+ enum Kind {
49
+ // Infer from the proto type. The default for every field.
50
+ KIND_UNSPECIFIED = 0;
51
+
52
+ // A project's own lat/lng message -> GeoPoint (§3.3). Not needed for
53
+ // google.type.LatLng, which is recognized automatically. The annotated
54
+ // message must have exactly two double fields named `latitude` and
55
+ // `longitude`.
56
+ KIND_GEO_POINT = 1;
57
+
58
+ // uint64/fixed64 -> Integer rather than String, keeping the field ordered
59
+ // and queryable. Throws at encode time above 2^63-1 (§2.1).
60
+ KIND_UNSIGNED_AS_INTEGER = 2;
61
+ }
62
+
63
+ enum EnumEncoding {
64
+ ENUM_ENCODING_NAME = 0;
65
+ ENUM_ENCODING_NUMBER = 1;
66
+ }