UNPKG

@firestore-emulator/server

Version:

This package is the implementation of the Firestore emulator. It is a Node.js

387 lines (381 loc) 12.2 kB
// src/server/FirestoreServiceV1Impl/index.ts import { BatchGetDocumentsResponse, BeginTransactionResponse, CommitResponse, ListenRequest, RunAggregationQueryResponse, RunQueryResponse, UnimplementedFirestoreService } from "@firestore-emulator/proto/dist/google/firestore/v1/firestore"; import { Empty } from "@firestore-emulator/proto/dist/google/protobuf/empty"; // src/FirestoreState/index.ts import { Document as v1Document } from "@firestore-emulator/proto/dist/google/firestore/v1/document"; import { ListenResponse as v1ListenResponse, TargetChangeTargetChangeType as v1TargetChangeTargetChangeType } from "@firestore-emulator/proto/dist/google/firestore/v1/firestore"; import { StructuredQueryCompositeFilterOperator as v1StructuredQueryCompositeFilterOperator, StructuredQueryDirection as v1StructuredQueryDirection, StructuredQueryFieldFilterOperator as v1StructuredQueryFieldFilterOperator } from "@firestore-emulator/proto/dist/google/firestore/v1/query"; import { DocumentTransformFieldTransformServerValue, WriteResult as v1WriteResult } from "@firestore-emulator/proto/dist/google/firestore/v1/write"; import { Timestamp } from "@firestore-emulator/proto/dist/google/protobuf/timestamp"; import { Status } from "@grpc/grpc-js/build/src/constants"; import { assertNever } from "assert-never"; import { produce as produce2 } from "immer"; // src/error/error.ts var FirestoreEmulatorError = class extends Error { constructor(code, message) { super(message); this.code = code; } }; // src/FirestoreState/field.ts import { NullValue } from "@firestore-emulator/proto/dist/google/protobuf/struct"; var FirestoreStateDocumentNullField = class { type = "null_value"; value = null; toJSON() { return { type: this.type, value: null }; } toV1ValueObject() { return { null_value: NullValue.NULL_VALUE }; } eq(other) { return other.type === this.type; } lt(_other) { return false; } lte(_other) { return false; } gt(_other) { return false; } gte(_other) { return false; } }; var FirestoreStateDocumentIntegerField = class _FirestoreStateDocumentIntegerField { constructor(value) { this.value = value; if (!Number.isInteger(value)) { throw new Error(`value must be integer. value=${value}`); } } type = "integer_value"; toJSON() { return { type: this.type, value: this.value }; } toV1ValueObject() { return { integer_value: this.value }; } eq(other) { return other instanceof _FirestoreStateDocumentIntegerField && this.value === other.value; } lt(other) { return other instanceof _FirestoreStateDocumentIntegerField && this.value < other.value; } lte(other) { return other instanceof _FirestoreStateDocumentIntegerField && this.value <= other.value; } gt(other) { return other instanceof _FirestoreStateDocumentIntegerField && this.value > other.value; } gte(other) { return other instanceof _FirestoreStateDocumentIntegerField && this.value >= other.value; } add(other) { if (other instanceof _FirestoreStateDocumentIntegerField) { return new _FirestoreStateDocumentIntegerField(this.value + other.value); } if (other instanceof FirestoreStateDocumentDoubleField) { return new FirestoreStateDocumentDoubleField(this.value + other.value); } throw new Error(`unsupported type. other=${other}`); } }; var FirestoreStateDocumentDoubleField = class _FirestoreStateDocumentDoubleField { constructor(value) { this.value = value; } type = "double_value"; toJSON() { return { type: this.type, value: this.value }; } toV1ValueObject() { return { double_value: this.value }; } eq(other) { return other instanceof _FirestoreStateDocumentDoubleField && this.value === other.value; } lt(other) { return other instanceof _FirestoreStateDocumentDoubleField && this.value < other.value; } lte(other) { return other instanceof _FirestoreStateDocumentDoubleField && this.value <= other.value; } gt(other) { return other instanceof _FirestoreStateDocumentDoubleField && this.value > other.value; } gte(other) { return other instanceof _FirestoreStateDocumentDoubleField && this.value >= other.value; } add(other) { if (other instanceof FirestoreStateDocumentIntegerField) { return new _FirestoreStateDocumentDoubleField(this.value + other.value); } if (other instanceof _FirestoreStateDocumentDoubleField) { return new _FirestoreStateDocumentDoubleField(this.value + other.value); } throw new Error(`unsupported type. other=${other}`); } }; // src/FirestoreState/mask.ts import { produce } from "immer"; // src/FirestoreState/index.ts var TimestampFromDate = (date) => Timestamp.fromObject({ nanos: date.getTime() % 1e3 * 1e3 * 1e3, seconds: Math.floor(date.getTime() / 1e3) }); var TimestampFromNow = () => TimestampFromDate(/* @__PURE__ */ new Date()); // src/server/FirestoreServiceV1Impl/index.ts var FirestoreServiceV1Impl = class extends UnimplementedFirestoreService { #state; constructor(state) { super(); this.#state = state; } GetDocument(_call, _callback) { console.error("Method<GetDocument> not implemented."); throw new Error("Method<GetDocument> not implemented."); } ListDocuments(_call, _callback) { console.error("Method<ListDocuments> not implemented."); throw new Error("Method<ListDocuments> not implemented."); } UpdateDocument(_call, _callback) { console.error("Method<UpdateDocument> not implemented."); throw new Error("Method<UpdateDocument> not implemented."); } DeleteDocument(_call, _callback) { console.error("Method<DeleteDocument> not implemented."); throw new Error("Method<DeleteDocument> not implemented."); } BatchGetDocuments(call) { const date = TimestampFromNow(); const tx = call.request.has_new_transaction ? Uint8Array.from([17, 2, 0, 0, 0, 0, 0, 0, 0]) : null; if (tx) { call.write( BatchGetDocumentsResponse.fromObject({ transaction: tx }) ); } for (const documentPath of call.request.documents) { const document = this.#state.getDocument(documentPath); call.write( BatchGetDocumentsResponse.fromObject({ found: document.metadata.hasExist ? document.toV1DocumentObject() : void 0, missing: document.metadata.hasExist ? void 0 : documentPath, read_time: date }) ); } call.end(); } BeginTransaction(_call, callback) { callback( null, BeginTransactionResponse.fromObject({ // dummy transaction id transaction: Uint8Array.from([17, 2, 0, 0, 0, 0, 0, 0, 0]) }) ); } Commit(call, callback) { const date = /* @__PURE__ */ new Date(); try { const results = call.request.writes.map((write) => { return this.#state.writeV1Document(date, write); }); callback( null, CommitResponse.fromObject({ commit_time: TimestampFromDate(date), write_results: results.map((result) => result.toObject()) }) ); } catch (err) { if (err instanceof FirestoreEmulatorError) { callback( { cause: err, code: err.code, message: err.message, name: "Error" }, null ); return; } console.error(err); callback( { cause: err, message: "Something went wrong", name: "Error" }, null ); } } Rollback(_call, callback) { callback(null, Empty.fromObject({})); } RunQuery(call) { const date = /* @__PURE__ */ new Date(); const results = this.#state.v1Query( call.request.parent, call.request.structured_query ); let transaction = call.request.transaction; if (call.request.consistency_selector === "new_transaction") { transaction = crypto.getRandomValues(new Uint8Array(12)); } if (results.length > 0) { results.forEach((result, i, arr) => { call.write( RunQueryResponse.fromObject({ document: result.toV1DocumentObject(), done: i === arr.length - 1, read_time: TimestampFromDate(date), skipped_results: 0, transaction }) ); }); } else { call.write( RunQueryResponse.fromObject({ done: true, read_time: TimestampFromDate(date), skipped_results: 0, transaction }) ); } call.end(); } RunAggregationQuery(call) { const date = TimestampFromNow(); const results = this.#state.v1Query( call.request.parent, call.request.structured_aggregation_query.structured_query ); let transaction = call.request.transaction; if (call.request.consistency_selector === "new_transaction") { transaction = crypto.getRandomValues(new Uint8Array(12)); } if (call.request.has_explain_options) { const error = new Error( "Explain for aggregation query is not supported." ); console.error(error); call.emit("error"); throw error; } const aggregateResult = Object.fromEntries( call.request.structured_aggregation_query.aggregations.map((agg) => { if (agg.has_count) return [agg.alias, { integer_value: results.length }]; if (agg.has_sum) { const sum = results.reduce((acc, cur) => { const field = cur.getField(agg.sum.field.field_path); if (!field) return acc; if (field.type === "integer_value") return acc.add(field); if (field.type === "double_value") return field.add(acc); return acc; }, new FirestoreStateDocumentIntegerField(0)); return [agg.alias, sum.toV1ValueObject()]; } if (agg.has_avg) { const sum = results.reduce((acc, cur) => { const field = cur.getField(agg.avg.field.field_path); if (!field) return acc; if (field.type === "integer_value") return acc.add(field); if (field.type === "double_value") return field.add(acc); return acc; }, new FirestoreStateDocumentIntegerField(0)); return [ agg.alias, new FirestoreStateDocumentDoubleField( sum.value / results.length ).toV1ValueObject() ]; } return [ agg.alias, new FirestoreStateDocumentNullField().toV1ValueObject() ]; }) ); const res = RunAggregationQueryResponse.fromObject({ result: { aggregate_fields: aggregateResult }, read_time: date, transaction }); call.write(res); call.end(); } PartitionQuery(_call, _callback) { console.error("Method<PartitionQuery> not implemented."); throw new Error("Method<PartitionQuery> not implemented."); } Write(_call) { console.error("Method<Write> not implemented."); throw new Error("Method<Write> not implemented."); } Listen(call) { call.once("data", (request) => { if (!(request instanceof ListenRequest)) { console.error("Invalid request type"); call.end(); throw new Error("Invalid request type"); } this.#state.v1Listen( request, (value) => { call.write(value); }, (handler) => { call.once("end", handler); } ); }); } ListCollectionIds(_call, _callback) { console.error("Method<ListCollectionIds> not implemented."); throw new Error("Method<ListCollectionIds> not implemented."); } BatchWrite(_call, _callback) { console.error("Method<BatchWrite> not implemented."); throw new Error("Method<BatchWrite> not implemented."); } CreateDocument(_call, _callback) { console.error("Method<CreateDocument> not implemented."); throw new Error("Method<CreateDocument> not implemented."); } }; export { FirestoreServiceV1Impl }; //# sourceMappingURL=index.mjs.map