import { jsonValueSchema } from "../../../../../../shared/src/bigint-json.js";
import { must } from "../../../../../../shared/src/must.js";
import { literalUnion } from "../../../../../../shared/src/valita.js";
import { tableSpec, columnSpec, indexSpec } from "../../../../db/specs.js";
import { jsonObjectSchema } from "./json.js";
import { object, boolean, literal, array, string, record, union } from "@badrap/valita";
const beginSchema = object({
  tag: literal("begin"),
  // The format of values of "json"-typed columns (e.g. "JSON" and "JSONB").
  // - 'p' is for parsed JSON, which may include JSON values or JSON objects.
  //   These values are parsed and stringified at every process boundary
  //   between the change-source and the replica.
  // - 's' is for stringified JSON. These values skip the parsing and
  //   stringification, and are directly ferried to the replica as a JSON
  //   string. For JSON values this improves performance by 20~25% in the
  //   change-streamer and 25~30% in the replicator.
  //
  // If absent, the format is assumed to be 'p' (parsed JSON objects/values).
  json: literalUnion("p", "s").optional(),
  // Directs the change-streamer to skip the ACK for the corresponding commit.
  skipAck: boolean().optional()
});
const commitSchema = object({
  tag: literal("commit")
});
const rollbackSchema = object({
  tag: literal("rollback")
});
const rowKeySchema = object({
  // The columns used to identify a row in insert, update, and delete changes.
  columns: array(string()),
  // An optional qualifier identifying how the key is chosen. Currently this
  // is postgres-specific, describing the REPLICA IDENTITY, for which replica
  // identity 'full' (FULL) is handled differently; the replicator handles
  // these tables by extracting a row key from the full row based on the
  // table's PRIMARY KEY or UNIQUE INDEX.
  type: literalUnion("default", "nothing", "full", "index").optional()
});
const relationSchema = object({
  schema: string(),
  name: string(),
  // This will become required.
  rowKey: rowKeySchema.optional(),
  /** Deprecated: set the rowKey.columns instead. */
  keyColumns: array(string()).optional(),
  /** Deprecated: set the rowKey.columns instead. */
  replicaIdentity: literalUnion("default", "nothing", "full", "index").optional()
}).map((rel) => {
  const { rowKey, ...rest } = rel;
  if (rowKey) {
    return { ...rest, rowKey };
  }
  return {
    ...rest,
    rowKey: {
      columns: must(rel.keyColumns),
      type: rel.replicaIdentity
    }
  };
});
const newRelationSchema = object({
  schema: string(),
  name: string(),
  rowKey: rowKeySchema
});
const tableMetadataSchema = object({ rowKey: record(jsonValueSchema) }).rest(jsonValueSchema);
const rowSchema = record(jsonValueSchema);
const insertSchema = object({
  tag: literal("insert"),
  relation: relationSchema,
  new: rowSchema
});
const updateSchema = object({
  tag: literal("update"),
  relation: relationSchema,
  // `key` is present if the update changed the key of the row, or if the
  // table's replicaIdentity === 'full'
  key: rowSchema.nullable(),
  // `new` is the full row (and not just the updated columns). This is
  // necessary for "catchup" replication scenarios such as adding tables
  // to a publication, or resharding.
  new: rowSchema
});
const deleteSchema = object({
  tag: literal("delete"),
  relation: relationSchema,
  // key is the full row if replicaIdentity === 'full'
  key: rowSchema
});
const truncateSchema = object({
  tag: literal("truncate"),
  relations: array(relationSchema)
});
const identifierSchema = object({
  schema: string(),
  name: string()
});
const backfillIDSchema = jsonObjectSchema;
const createTableSchema = object({
  tag: literal("create-table"),
  spec: tableSpec,
  // This must be set by change source implementations that support
  // table/column backfill.
  //
  // TODO: to simplify the protocol, see if we can make this required
  metadata: tableMetadataSchema.optional(),
  // Indicate that columns of the table require backfilling. These columns
  // should be created on the replica but not yet synced the clients.
  //
  // ## State Persistence
  //
  // To obviate the need for change-source implementations to persist state
  // related to backfill progress, the change-source only tracks backfills
  // **for the current session**. In the event that the session is interrupted
  // before columns have been fully backfilled, it is the responsibility of the
  // change-streamer to send {@link BackfillRequest}s when it reconnects.
  //
  // This means that the change-streamer must track and persist:
  // * the backfill IDs of the columns requiring backfilling
  // * the most current table metadata of the associated table(s)
  //
  // The change-streamer then uses this information to send backfill requests
  // when it reconnects.
  backfill: record(backfillIDSchema).optional()
});
const renameTableSchema = object({
  tag: literal("rename-table"),
  old: identifierSchema,
  new: identifierSchema
});
const updateTableMetadataSchema = object({
  tag: literal("update-table-metadata"),
  table: identifierSchema,
  old: tableMetadataSchema,
  new: tableMetadataSchema
});
const columnSchema = object({
  name: string(),
  spec: columnSpec
});
const addColumnSchema = object({
  tag: literal("add-column"),
  table: identifierSchema,
  column: columnSchema,
  // This must be set by change source implementations that support
  // table/column backfill.
  //
  // TODO: to simplify the protocol, see if we can make this required
  tableMetadata: tableMetadataSchema.optional(),
  // See documentation for the `backfill` field of the `create-table` change.
  backfill: backfillIDSchema.optional()
});
const updateColumnSchema = object({
  tag: literal("update-column"),
  table: identifierSchema,
  old: columnSchema,
  new: columnSchema
});
const dropColumnSchema = object({
  tag: literal("drop-column"),
  table: identifierSchema,
  column: string()
});
const dropTableSchema = object({
  tag: literal("drop-table"),
  id: identifierSchema
});
const createIndexSchema = object({
  tag: literal("create-index"),
  spec: indexSpec
});
const dropIndexSchema = object({
  tag: literal("drop-index"),
  id: identifierSchema
});
const backfillSchema = object({
  tag: literal("backfill"),
  relation: newRelationSchema,
  // The columns to be backfilled. `rowKey` columns are automatically excluded,
  // which means that this field may be empty.
  columns: array(string()),
  // The watermark at which the backfill data was queried. Note that this
  // generally will be different from the commit watermarks of the main change
  // stream, and in particular, the commit watermark of the backfill change's
  // enclosing transaction.
  watermark: string(),
  // A batch of row values, each row consisting of the `rowKey`
  // values, followed by the `column` values, in the same order in which
  // the column names appear in their respective fields, e.g.
  //
  // ```
  // [
  //   [...rowKeyValues, ...columnValues],  // row 1
  //   [...rowKeyValues, ...columnValues],  // row 2
  // ]
  // ```
  rowValues: array(array(jsonValueSchema))
});
const backfillCompletedSchema = object({
  tag: literal("backfill-completed"),
  relation: newRelationSchema,
  // The columns to be backfilled. `rowKey` columns are automatically excluded,
  // which means that this field may be empty.
  columns: array(string()),
  // The watermark at which the backfill data was queried. Note that this
  // generally will be different from the commit watermarks of the main change
  // stream, and in particular, the commit watermark of the backfill change's
  // enclosing transaction.
  watermark: string()
});
const dataChangeSchema = union(
  insertSchema,
  updateSchema,
  deleteSchema,
  truncateSchema,
  backfillSchema
);
const dataChangeTags = [
  "insert",
  "update",
  "delete",
  "truncate",
  "backfill"
];
literalUnion(...dataChangeTags);
const schemaChanges = [
  createTableSchema,
  renameTableSchema,
  updateTableMetadataSchema,
  addColumnSchema,
  updateColumnSchema,
  dropColumnSchema,
  dropTableSchema,
  createIndexSchema,
  dropIndexSchema,
  backfillCompletedSchema
];
const schemaChangeTags = [
  "create-table",
  "rename-table",
  "update-table-metadata",
  "add-column",
  "update-column",
  "drop-column",
  "drop-table",
  "create-index",
  "drop-index",
  "backfill-completed"
];
const schemaChangeSchema = union(...schemaChanges);
literalUnion(...schemaChangeTags);
const schemaChangeTagSet = new Set(schemaChangeTags);
function isSchemaChange(change) {
  return schemaChangeTagSet.has(change.tag);
}
const dataChangeTagSet = new Set(dataChangeTags);
function isDataChange(change) {
  return dataChangeTagSet.has(change.tag);
}
export {
  addColumnSchema,
  backfillCompletedSchema,
  backfillIDSchema,
  backfillSchema,
  beginSchema,
  commitSchema,
  createIndexSchema,
  createTableSchema,
  dataChangeSchema,
  deleteSchema,
  dropColumnSchema,
  dropIndexSchema,
  dropTableSchema,
  identifierSchema,
  insertSchema,
  isDataChange,
  isSchemaChange,
  newRelationSchema,
  relationSchema,
  renameTableSchema,
  rollbackSchema,
  rowSchema,
  schemaChangeSchema,
  tableMetadataSchema,
  truncateSchema,
  updateColumnSchema,
  updateSchema,
  updateTableMetadataSchema
};
//# sourceMappingURL=data.js.map
