import * as Array from "effect/Array"; import * as Effect from "effect/Effect"; import * as QueryStreamKey from "./QueryStreamKey"; import * as SchemaIssue from "effect/SchemaIssue"; import * as Match from "effect/Match"; import * as Schema from "effect/Schema"; import * as SchemaGetter from "effect/SchemaGetter"; import * as QueryStreamKeyLayout from "./QueryStreamKeyLayout"; import * as QueryStreamKeyValues from "./QueryStreamKeyValues"; // Serialized labels are a cursor boundary representation. They include every // runtime position but do not preserve implicitness. const RuntimeLabels = Schema.Array(Schema.String); const RuntimeLabelsEquivalence = Schema.toEquivalence(RuntimeLabels); const positionRuntimeLabel = Match.type().pipe( Match.tagsExhaustive({ Visible: ({ label }) => label, ImplicitId: () => "_id", }), ); /** * @experimental */ export const QueryStreamCursor = Schema.Struct({ runtimeLabels: RuntimeLabels, keyValues: QueryStreamKeyValues.QueryStreamKeyValues, }).check( Schema.makeFilter( (cursor) => cursor.keyValues.length === cursor.runtimeLabels.length, { message: "key values and runtime labels must have the same length" }, ), ); /** * @experimental */ export interface QueryStreamCursor extends Schema.Schema.Type< typeof QueryStreamCursor > {} /** * @experimental */ export const END_CURSOR = "[]"; /** * Bind cursor string encoding and decoding to a layout. * * @experimental */ export const fromKeyLayout = ( keyLayout: QueryStreamKeyLayout.QueryStreamKeyLayout, ) => { const runtimeLabels = Array.map( QueryStreamKeyLayout.positions(keyLayout), positionRuntimeLabel, ); return Schema.fromJsonString(QueryStreamCursor) .check( Schema.makeFilter( (cursor) => RuntimeLabelsEquivalence(cursor.runtimeLabels, runtimeLabels), { message: "Cursor runtime labels do not match the stream" }, ), ) .pipe( Schema.decodeTo( Schema.declare(QueryStreamKey.isComplete).check( Schema.makeFilter( (key) => QueryStreamKeyLayout.Equivalence(key.layout, keyLayout), { message: "Cursor key does not belong to the stream layout" }, ), ), { decode: SchemaGetter.transformEffect((cursor, options) => Effect.fromResult( QueryStreamKey.complete(keyLayout, cursor.keyValues), ).pipe( Effect.mapError( (error) => new SchemaIssue.InvalidValue( { message: error.message }, cursor.keyValues, options, ), ), ), ), encode: SchemaGetter.transformEffect((key) => QueryStreamCursor.makeEffect({ runtimeLabels, keyValues: key.values, }), ), }, ), ); };