@korajs/store 1.0.0-beta.13 → 1.0.0-beta.14
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/adapters/better-sqlite3.cjs.map +1 -1
- package/dist/adapters/better-sqlite3.js +1 -1
- package/dist/adapters/indexeddb.cjs.map +1 -1
- package/dist/adapters/indexeddb.js +4 -4
- package/dist/adapters/sqlite-wasm-worker.cjs +67 -15
- package/dist/adapters/sqlite-wasm-worker.cjs.map +1 -1
- package/dist/adapters/sqlite-wasm-worker.js +65 -17
- package/dist/adapters/sqlite-wasm-worker.js.map +1 -1
- package/dist/adapters/sqlite-wasm.cjs.map +1 -1
- package/dist/adapters/sqlite-wasm.js +5 -5
- package/dist/{backup-4NUPDEMB.js → backup-IHKVMUAM.js} +5 -5
- package/dist/{chunk-Y5LRH7KI.js → chunk-5AQSGMJV.js} +151 -21
- package/dist/chunk-5AQSGMJV.js.map +1 -0
- package/dist/{chunk-W5F2LWG6.js → chunk-5JMGMPUE.js} +2 -2
- package/dist/{chunk-HZTDBLDB.js → chunk-BILSA6XZ.js} +4 -3
- package/dist/{chunk-HZTDBLDB.js.map → chunk-BILSA6XZ.js.map} +1 -1
- package/dist/{chunk-QDUSJMFV.js → chunk-BQSIL6TT.js} +5 -5
- package/dist/{chunk-MCQKEHOD.js → chunk-KK3GBONL.js} +2 -2
- package/dist/{chunk-32EDKWMI.js → chunk-KTVNANUH.js} +2 -2
- package/dist/{chunk-SJP2HTYZ.js → chunk-QW4UU7NG.js} +2 -2
- package/dist/{chunk-DUKTWVZW.js → chunk-SRHDJF5B.js} +13 -2
- package/dist/{chunk-DUKTWVZW.js.map → chunk-SRHDJF5B.js.map} +1 -1
- package/dist/{chunk-P4V375FY.js → chunk-UC2LGFPV.js} +4 -4
- package/dist/index.cjs +203 -47
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +96 -6
- package/dist/index.d.ts +96 -6
- package/dist/index.js +55 -39
- package/dist/index.js.map +1 -1
- package/dist/internal.cjs +1 -0
- package/dist/internal.cjs.map +1 -1
- package/dist/internal.d.cts +2 -2
- package/dist/internal.d.ts +2 -2
- package/dist/internal.js +3 -3
- package/dist/{restore-FR7XIKT2.js → restore-MNDPR557.js} +4 -4
- package/dist/{sql-builder-C4u55jKW.d.cts → sql-builder-D0tC3sv9.d.cts} +8 -1
- package/dist/{sql-builder-CGckdBDL.d.ts → sql-builder-D5UMzHEo.d.ts} +8 -1
- package/dist/sqlite-wasm-channel-QCCNBYCU.js +12 -0
- package/dist/store-HNGX4KTT.js +16 -0
- package/package.json +2 -2
- package/dist/chunk-Y5LRH7KI.js.map +0 -1
- package/dist/sqlite-wasm-channel-MLHLKEED.js +0 -12
- package/dist/store-IMWKZLWB.js +0 -16
- /package/dist/{backup-4NUPDEMB.js.map → backup-IHKVMUAM.js.map} +0 -0
- /package/dist/{chunk-W5F2LWG6.js.map → chunk-5JMGMPUE.js.map} +0 -0
- /package/dist/{chunk-QDUSJMFV.js.map → chunk-BQSIL6TT.js.map} +0 -0
- /package/dist/{chunk-MCQKEHOD.js.map → chunk-KK3GBONL.js.map} +0 -0
- /package/dist/{chunk-32EDKWMI.js.map → chunk-KTVNANUH.js.map} +0 -0
- /package/dist/{chunk-SJP2HTYZ.js.map → chunk-QW4UU7NG.js.map} +0 -0
- /package/dist/{chunk-P4V375FY.js.map → chunk-UC2LGFPV.js.map} +0 -0
- /package/dist/{restore-FR7XIKT2.js.map → restore-MNDPR557.js.map} +0 -0
- /package/dist/{sqlite-wasm-channel-MLHLKEED.js.map → sqlite-wasm-channel-QCCNBYCU.js.map} +0 -0
- /package/dist/{store-IMWKZLWB.js.map → store-HNGX4KTT.js.map} +0 -0
|
@@ -4,7 +4,7 @@ import {
|
|
|
4
4
|
NoLeaderError,
|
|
5
5
|
RequestAbortedError,
|
|
6
6
|
WorkerTimeoutError
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-SRHDJF5B.js";
|
|
8
8
|
|
|
9
9
|
// src/adapters/opfs-names.ts
|
|
10
10
|
var LEGACY_OPFS_POOL_NAME = "kora-opfs";
|
|
@@ -588,4 +588,4 @@ export {
|
|
|
588
588
|
FollowerBroadcastBridge,
|
|
589
589
|
TransactionSerializingWorkerBridge
|
|
590
590
|
};
|
|
591
|
-
//# sourceMappingURL=chunk-
|
|
591
|
+
//# sourceMappingURL=chunk-5JMGMPUE.js.map
|
|
@@ -4,7 +4,7 @@ import {
|
|
|
4
4
|
buildSoftDeleteQuery,
|
|
5
5
|
buildUpdateQuery,
|
|
6
6
|
serializeRowVersion
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-QW4UU7NG.js";
|
|
8
8
|
import {
|
|
9
9
|
deserializeRecord,
|
|
10
10
|
encodeRichtextFieldsForOpData,
|
|
@@ -14,7 +14,7 @@ import {
|
|
|
14
14
|
} from "./chunk-J5AGXF3D.js";
|
|
15
15
|
import {
|
|
16
16
|
RecordNotFoundError
|
|
17
|
-
} from "./chunk-
|
|
17
|
+
} from "./chunk-SRHDJF5B.js";
|
|
18
18
|
|
|
19
19
|
// src/mutations/write-context.ts
|
|
20
20
|
import { KoraError } from "@korajs/core";
|
|
@@ -497,6 +497,7 @@ async function writeDeleteInTx(env, scope, collection, id, options = {}) {
|
|
|
497
497
|
};
|
|
498
498
|
}
|
|
499
499
|
async function buildLocalOperation(env, scope, input) {
|
|
500
|
+
env.assertLocalWriteAllowed?.();
|
|
500
501
|
const sequenceNumber = await scope.sequence.take();
|
|
501
502
|
const causalDeps = scope.causal.depsFor(input.collection, input.extraCausalDeps);
|
|
502
503
|
const operation = await createOperation(
|
|
@@ -604,4 +605,4 @@ export {
|
|
|
604
605
|
executeInsert,
|
|
605
606
|
executeUpdate
|
|
606
607
|
};
|
|
607
|
-
//# sourceMappingURL=chunk-
|
|
608
|
+
//# sourceMappingURL=chunk-BILSA6XZ.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/mutations/write-context.ts","../src/lww/field-versions.ts","../src/state-machine/state-validator.ts","../src/mutations/run-local-write.ts","../src/mutations/write-ops.ts","../src/mutations/secret-write.ts","../src/mutations/execute-delete.ts","../src/mutations/execute-insert.ts","../src/mutations/execute-update.ts"],"sourcesContent":["import type {\n\tCausalTracker,\n\tHybridLogicalClock,\n\tOperation,\n\tSchemaDefinition,\n\tSecretKeyProvider,\n} from '@korajs/core'\nimport { KoraError } from '@korajs/core'\nimport type { RecordFolder } from '../fold/record-folder'\nimport type { RelationEnforcer } from '../relations/relation-enforcer'\nimport type { Transaction } from '../types'\n\n/**\n * Called inside the write transaction for every local delete, after the delete\n * operation is built and before referential side effects run. Lets the unified\n * apply pipeline run its referential checks (and emit their traces) against the\n * rows the transaction sees. Throwing aborts the whole write.\n */\nexport type LocalDeleteHook = (operation: Operation, tx: Transaction) => Promise<void>\n\n/**\n * Everything the single local write path needs to build and persist an\n * operation inside a storage transaction. One environment serves single-record\n * writes, `app.transaction` commits and referential cascades, so every local\n * operation is built the same way (W6).\n */\nexport interface WriteEnv {\n\treadonly schema: SchemaDefinition\n\treadonly clock: HybridLogicalClock\n\treadonly nodeId: string\n\treadonly relationEnforcer: RelationEnforcer | null\n\treadonly secretKeyProvider?: SecretKeyProvider\n\t/** Shared by every operation of an `app.transaction` / `app.mutation` commit. */\n\treadonly transactionId?: string\n\t/** Human-readable mutation name propagated to every operation (DevTools). */\n\treadonly mutationName?: string\n\t/** See {@link LocalDeleteHook}. */\n\treadonly beforeLocalDelete?: LocalDeleteHook\n\t/**\n\t * The W7 record fold. When set, a local write appends its operation and then\n\t * merges it into the record's fold state, which re-materializes the row (the\n\t * same path remote operations take). Absent only under\n\t * `experimental.legacyMerge` (and while a pre-W7 database is migrated, before its\n\t * re-materialization): the row is then written directly, as in beta.12.\n\t */\n\treadonly fold?: RecordFolder\n\t/**\n\t * Largest operation a local write may produce (RT-86), the server's\n\t * `maxOperationBytes`. Default `DEFAULT_MAX_OPERATION_BYTES` (256 KiB).\n\t */\n\treadonly maxOperationBytes?: number\n}\n\n/**\n * Causal bookkeeping for the operations built inside ONE storage transaction.\n *\n * Dependencies are computed from the committed causal heads (the shared\n * {@link CausalTracker}) overlaid with the operations already built in this\n * transaction. Heads are published to the shared tracker only after the\n * transaction commits, so a rolled-back write never leaves a dependency on an\n * operation that does not exist.\n */\nexport class CausalScope {\n\tprivate readonly lastByCollection = new Map<string, string>()\n\tprivate lastInScope: string | null = null\n\tprivate readonly recorded: Array<{ collection: string; id: string }> = []\n\n\tconstructor(\n\t\tprivate readonly tracker: CausalTracker | null,\n\t\t/** When true, every operation also depends on the previous one in this scope. */\n\t\tprivate readonly chainOperations: boolean,\n\t) {}\n\n\t/**\n\t * Direct causal parents for the next operation in `collection`.\n\t *\n\t * @param collection - Collection of the operation being built\n\t * @param extra - Explicit parents (e.g. the delete that caused a cascade)\n\t */\n\tdepsFor(collection: string, extra: readonly string[] = []): string[] {\n\t\tconst deps: string[] = []\n\t\tconst add = (id: string | undefined | null): void => {\n\t\t\tif (id && !deps.includes(id)) deps.push(id)\n\t\t}\n\t\tfor (const id of extra) add(id)\n\t\tconst local = this.lastByCollection.get(collection)\n\t\tif (local !== undefined) {\n\t\t\tadd(local)\n\t\t} else {\n\t\t\tfor (const id of this.tracker?.nextCausalDeps(collection, false) ?? []) add(id)\n\t\t}\n\t\tif (this.chainOperations) add(this.lastInScope)\n\t\treturn deps\n\t}\n\n\t/** Record an operation built in this scope. */\n\trecord(collection: string, operationId: string): void {\n\t\tthis.lastByCollection.set(collection, operationId)\n\t\tthis.lastInScope = operationId\n\t\tthis.recorded.push({ collection, id: operationId })\n\t}\n\n\t/** Publish this scope's heads to the shared tracker. Call only after commit. */\n\tpublish(): void {\n\t\tfor (const { collection, id } of this.recorded) {\n\t\t\tthis.tracker?.afterOperation(collection, id, false)\n\t\t}\n\t}\n}\n\n/**\n * The sequence-number block of ONE write transaction.\n *\n * The first number is reserved with one `UPSERT ... RETURNING` on\n * `_kora_version_vector`, inside the transaction; later numbers continue the block\n * in memory, and {@link SequenceBlock.close} persists the block's end with one\n * write before the transaction commits. Write transactions are serialized and no\n * code path allocates numbers outside one, so the block is contiguous and never\n * shared; the UNIQUE `(node_id, sequence_number)` index on every op table is the\n * backstop. A rollback discards the reservation with everything else.\n */\nexport class SequenceBlock {\n\tprivate last: number | null = null\n\tprivate persisted = 0\n\n\tconstructor(\n\t\tprivate readonly tx: Transaction,\n\t\tprivate readonly nodeId: string,\n\t) {}\n\n\t/** The next number of the block. */\n\tasync take(): Promise<number> {\n\t\tif (this.last === null) {\n\t\t\tconst rows = await this.tx.query<{ sequence_number: number }>(\n\t\t\t\t`INSERT INTO _kora_version_vector (node_id, sequence_number) VALUES (?, 1)\n ON CONFLICT(node_id) DO UPDATE SET sequence_number = sequence_number + 1\n RETURNING sequence_number`,\n\t\t\t\t[this.nodeId],\n\t\t\t)\n\t\t\tconst first = rows[0]?.sequence_number\n\t\t\tif (first === undefined) {\n\t\t\t\tthrow new KoraError(\n\t\t\t\t\t`Failed to reserve a sequence number for node \"${this.nodeId}\"`,\n\t\t\t\t\t'SEQUENCE_RESERVATION_FAILED',\n\t\t\t\t\t{ nodeId: this.nodeId },\n\t\t\t\t)\n\t\t\t}\n\t\t\tthis.last = first\n\t\t\tthis.persisted = first\n\t\t\treturn first\n\t\t}\n\t\tthis.last += 1\n\t\treturn this.last\n\t}\n\n\t/** Persist the end of the block. Must run inside the transaction, before commit. */\n\tasync close(): Promise<void> {\n\t\tif (this.last === null || this.last === this.persisted) return\n\t\tawait this.tx.execute(\n\t\t\t'UPDATE _kora_version_vector SET sequence_number = MAX(sequence_number, ?) WHERE node_id = ?',\n\t\t\t[this.last, this.nodeId],\n\t\t)\n\t\tthis.persisted = this.last\n\t}\n}\n\n/**\n * The per-transaction state of a local write: the transaction handle, its\n * sequence block and its causal scope. Every operation built in one transaction\n * takes the next number of the same contiguous block.\n */\nexport interface WriteScope {\n\treadonly tx: Transaction\n\treadonly sequence: SequenceBlock\n\treadonly causal: CausalScope\n}\n\n/**\n * Run `write` with a fresh {@link WriteScope} on `tx`, then close the sequence\n * block (still inside the transaction).\n */\nexport async function withWriteScope<T>(\n\ttx: Transaction,\n\tnodeId: string,\n\tcausal: CausalScope,\n\twrite: (scope: WriteScope) => Promise<T>,\n): Promise<T> {\n\tconst scope: WriteScope = { tx, sequence: new SequenceBlock(tx, nodeId), causal }\n\tconst result = await write(scope)\n\tawait scope.sequence.close()\n\treturn result\n}\n\n/**\n * Whether a storage error means the device ran out of space: SQLite's\n * `SQLITE_FULL` (\"database or disk is full\") or a browser quota error from OPFS /\n * IndexedDB. The store maps these to a `store:quota-exceeded` event (STORE-15).\n */\nexport function isStorageFullError(error: unknown): boolean {\n\tif (error === null || typeof error !== 'object') return false\n\tconst candidate = error as { name?: unknown; code?: unknown; message?: unknown; cause?: unknown }\n\tif (candidate.name === 'QuotaExceededError') return true\n\tif (candidate.code === 'SQLITE_FULL' || candidate.code === 'QUOTA_EXCEEDED') return true\n\tconst message = typeof candidate.message === 'string' ? candidate.message : ''\n\tif (/SQLITE_FULL|database or disk is full|quota ?exceeded/i.test(message)) return true\n\treturn candidate.cause !== undefined && candidate.cause !== error\n\t\t? isStorageFullError(candidate.cause)\n\t\t: false\n}\n","import { EMPTY_ROW_VERSION } from './row-version'\n\n/**\n * Per-field last-writer versions for a materialized row.\n *\n * Each entry maps a field name to a serialized HLC timestamp (the same\n * lexicographically-sortable form used by the row `_version` column). Because\n * the serialized form sorts identically to {@link HybridLogicalClock.compare},\n * per-field last-write-wins is a plain string comparison: the greater string is\n * the newer writer. This makes field-level convergence deterministic and\n * independent of the order operations happen to arrive in.\n */\nexport type FieldVersions = Record<string, string>\n\n/**\n * Parse the `_field_versions` JSON column into a map. Tolerates null, empty,\n * and malformed values by returning an empty map — a row that predates\n * per-field tracking simply has no field versions and falls back to `_version`.\n */\nexport function parseFieldVersions(raw: unknown): FieldVersions {\n\tif (typeof raw !== 'string' || raw.length === 0) {\n\t\treturn {}\n\t}\n\ttry {\n\t\tconst parsed = JSON.parse(raw) as unknown\n\t\tif (parsed === null || typeof parsed !== 'object' || Array.isArray(parsed)) {\n\t\t\treturn {}\n\t\t}\n\t\tconst result: FieldVersions = {}\n\t\tfor (const [key, value] of Object.entries(parsed as Record<string, unknown>)) {\n\t\t\tif (typeof value === 'string') {\n\t\t\t\tresult[key] = value\n\t\t\t}\n\t\t}\n\t\treturn result\n\t} catch {\n\t\treturn {}\n\t}\n}\n\n/**\n * Serialize a field-version map for storage in the `_field_versions` column.\n */\nexport function serializeFieldVersions(versions: FieldVersions): string {\n\treturn JSON.stringify(versions)\n}\n\n/**\n * Build the initial field-version map for an insert: every supplied field is\n * stamped with the operation's version.\n */\nexport function fieldVersionsForFields(\n\tfieldNames: Iterable<string>,\n\tversion: string,\n): FieldVersions {\n\tconst versions: FieldVersions = {}\n\tfor (const field of fieldNames) {\n\t\tversions[field] = version\n\t}\n\treturn versions\n}\n\n/**\n * The effective stored version for a field: its own tracked version if present,\n * otherwise the row-level `_version` (so rows written before per-field tracking\n * still compare correctly), otherwise the empty sentinel (older than anything).\n */\nexport function effectiveFieldVersion(\n\tcurrent: FieldVersions,\n\tfield: string,\n\trowVersion: string | undefined,\n): string {\n\tconst own = current[field]\n\tif (own !== undefined) {\n\t\treturn own\n\t}\n\treturn rowVersion ?? EMPTY_ROW_VERSION\n}\n\n/**\n * Result of resolving an incoming operation against the current per-field\n * versions of a row.\n */\nexport interface PerFieldLwwResult {\n\t/** Fields the incoming operation wins (strictly newer than the stored version). */\n\twinners: string[]\n\t/** The merged field-version map to persist (winners advanced to `incomingVersion`). */\n\tmerged: FieldVersions\n}\n\n/**\n * Resolve per-field last-write-wins for an incoming operation.\n *\n * For each incoming field, the operation wins only when its version is strictly\n * greater than the field's effective stored version. Ties (identical version,\n * i.e. the same operation re-applied) are losses, keeping the resolution\n * idempotent. The comparison is a total order, so every node resolves the same\n * winner regardless of the order operations arrive.\n *\n * @param current - Parsed `_field_versions` of the target row\n * @param incomingFields - Field names carried by the incoming operation\n * @param incomingVersion - Serialized HLC version of the incoming operation\n * @param rowVersion - The row's `_version` (fallback for untracked fields)\n */\nexport function resolvePerFieldLww(\n\tcurrent: FieldVersions,\n\tincomingFields: Iterable<string>,\n\tincomingVersion: string,\n\trowVersion: string | undefined,\n): PerFieldLwwResult {\n\tconst winners: string[] = []\n\tconst merged: FieldVersions = { ...current }\n\tfor (const field of incomingFields) {\n\t\tconst stored = effectiveFieldVersion(current, field, rowVersion)\n\t\tif (incomingVersion > stored) {\n\t\t\twinners.push(field)\n\t\t\tmerged[field] = incomingVersion\n\t\t}\n\t\t// Losing fields are left untouched: their existing tracked version (or the\n\t\t// row-level `_version` fallback) already reflects the newer writer.\n\t}\n\treturn { winners, merged }\n}\n\n/**\n * Stamp `fields` with `version` on top of a stored `_field_versions` value and\n * return the serialized column. This is the one per-field stamp every local\n * write uses (insert, update, transaction entries, cascades), so every field a\n * device writes carries the HLC of the operation that wrote it.\n *\n * Stamps only move forward: a field already holding a newer version keeps it.\n * The map is keyed by field name with one HLC register per field, the shape the\n * per-field fold (W7) builds on.\n *\n * @param currentRaw - The stored `_field_versions` column (or null for a new row)\n * @param fields - Field names this write sets\n * @param version - The writing operation's serialized HLC version\n */\nexport function stampFieldVersions(\n\tcurrentRaw: unknown,\n\tfields: Iterable<string>,\n\tversion: string,\n): string {\n\tconst versions = parseFieldVersions(currentRaw)\n\tfor (const field of fields) {\n\t\tconst existing = versions[field]\n\t\tif (existing === undefined || existing < version) {\n\t\t\tversions[field] = version\n\t\t}\n\t}\n\treturn serializeFieldVersions(versions)\n}\n","import { KoraError, validateTransition } from '@korajs/core'\nimport type {\n\tCollectionDefinition,\n\tStateMachineConstraint,\n\tStateMachineDefinition,\n} from '@korajs/core'\n\n/**\n * Error thrown when a local mutation attempts an invalid state transition.\n *\n * Contains enough context to debug without reproduction:\n * the collection, record, field, current state, attempted state, and allowed transitions.\n */\nexport class InvalidStateTransitionError extends KoraError {\n\tconstructor(\n\t\tpublic readonly collection: string,\n\t\tpublic readonly recordId: string,\n\t\tpublic readonly field: string,\n\t\tpublic readonly fromState: string,\n\t\tpublic readonly toState: string,\n\t\tpublic readonly allowedStates: string[],\n\t) {\n\t\tsuper(\n\t\t\t`Invalid state transition in collection \"${collection}\": ` +\n\t\t\t\t`cannot transition field \"${field}\" from \"${fromState}\" to \"${toState}\". ` +\n\t\t\t\t`Allowed transitions from \"${fromState}\": ${allowedStates.length > 0 ? allowedStates.join(', ') : '(none -- terminal state)'}`,\n\t\t\t'INVALID_STATE_TRANSITION',\n\t\t\t{ collection, recordId, field, fromState, toState, allowedStates },\n\t\t)\n\t\tthis.name = 'InvalidStateTransitionError'\n\t}\n}\n\n/**\n * Validates a state machine transition for a local mutation (insert or update).\n *\n * For inserts: validates that the initial state (from the data or the default value)\n * is a known state in the state machine.\n *\n * For updates: looks up the current state field value and checks whether\n * the transition to the new value is allowed.\n *\n * @param collectionName - Name of the collection\n * @param recordId - The record being mutated\n * @param stateMachine - The state machine definition\n * @param currentState - The current value of the state field (null for inserts)\n * @param newState - The new value being set for the state field\n * @returns An object indicating whether the transition is valid, and if not, the allowed states.\n * When `onInvalidTransition` is 'last-valid-state', callers should suppress the field update\n * rather than throwing.\n */\nexport function validateStateTransition(\n\tcollectionName: string,\n\trecordId: string,\n\tstateMachine: StateMachineDefinition,\n\tcurrentState: string | null,\n\tnewState: string,\n): { valid: boolean; allowedStates: string[] } {\n\t// For inserts, any valid enum value is acceptable as the initial state\n\t// (schema validation already ensures the value is a valid enum value)\n\tif (currentState === null) {\n\t\treturn { valid: true, allowedStates: [] }\n\t}\n\n\t// Same-state transitions are always valid (idempotent updates)\n\tif (currentState === newState) {\n\t\treturn { valid: true, allowedStates: stateMachine.transitions[currentState] ?? [] }\n\t}\n\n\tconst constraint: StateMachineConstraint = {\n\t\tfield: stateMachine.field,\n\t\tcollection: collectionName,\n\t\ttransitions: stateMachine.transitions,\n\t}\n\tconst transitionResult = validateTransition(constraint, currentState, newState)\n\tif (transitionResult.valid) {\n\t\treturn { valid: true, allowedStates: transitionResult.allowedTargets }\n\t}\n\n\tconst allowedStates = transitionResult.allowedTargets\n\n\tif (stateMachine.onInvalidTransition === 'reject') {\n\t\tthrow new InvalidStateTransitionError(\n\t\t\tcollectionName,\n\t\t\trecordId,\n\t\t\tstateMachine.field,\n\t\t\tcurrentState,\n\t\t\tnewState,\n\t\t\tallowedStates,\n\t\t)\n\t}\n\n\t// 'last-valid-state': return invalid so the caller can suppress the field update\n\treturn { valid: false, allowedStates }\n}\n\n/**\n * Checks whether a collection update data object contains a change to the state machine field,\n * and if so, validates the transition.\n *\n * If the state field is not in the update data, returns the data unchanged.\n * If the transition is invalid and mode is 'last-valid-state', removes the field from the data.\n * If the transition is invalid and mode is 'reject', throws InvalidStateTransitionError.\n *\n * @param collectionName - Name of the collection\n * @param recordId - The record being updated\n * @param collectionDef - The collection definition from the schema\n * @param currentRecord - The current record data (must include the state field)\n * @param updateData - The partial update data\n * @returns The update data, potentially with the state field removed if invalid and mode is 'last-valid-state'\n */\nexport function validateUpdateStateMachine(\n\tcollectionName: string,\n\trecordId: string,\n\tcollectionDef: CollectionDefinition,\n\tcurrentRecord: Record<string, unknown>,\n\tupdateData: Record<string, unknown>,\n): Record<string, unknown> {\n\tlet result = updateData\n\tconst stateMachine = collectionDef.stateMachine\n\tif (stateMachine !== undefined) {\n\t\tresult = applyStateMachine(collectionName, recordId, stateMachine, currentRecord, result)\n\t}\n\t// Field-level `t.enum(...).transitions()` maps are state machines too, in the\n\t// default 'reject' mode (NEW-STORE-3). A collection-level machine on the same\n\t// field takes precedence.\n\tfor (const [field, descriptor] of Object.entries(collectionDef.fields)) {\n\t\tif (descriptor.kind !== 'enum' || !descriptor.transitions) continue\n\t\tif (stateMachine?.field === field) continue\n\t\tresult = applyStateMachine(\n\t\t\tcollectionName,\n\t\t\trecordId,\n\t\t\t{ field, transitions: descriptor.transitions, onInvalidTransition: 'reject' },\n\t\t\tcurrentRecord,\n\t\t\tresult,\n\t\t)\n\t}\n\treturn result\n}\n\nfunction applyStateMachine(\n\tcollectionName: string,\n\trecordId: string,\n\tstateMachine: StateMachineDefinition,\n\tcurrentRecord: Record<string, unknown>,\n\tupdateData: Record<string, unknown>,\n): Record<string, unknown> {\n\tconst stateField = stateMachine.field\n\tif (!(stateField in updateData)) {\n\t\t// State field not being changed -- no validation needed\n\t\treturn updateData\n\t}\n\n\tconst currentState = currentRecord[stateField]\n\tconst newState = updateData[stateField]\n\n\t// Both values must be strings for state machine validation\n\tif (typeof currentState !== 'string' || typeof newState !== 'string') {\n\t\treturn updateData\n\t}\n\n\tconst result = validateStateTransition(\n\t\tcollectionName,\n\t\trecordId,\n\t\tstateMachine,\n\t\tcurrentState,\n\t\tnewState,\n\t)\n\n\tif (result.valid) {\n\t\treturn updateData\n\t}\n\n\t// Mode is 'last-valid-state': silently remove the state field from the update\n\tconst filtered = { ...updateData }\n\tdelete filtered[stateField]\n\n\t// If no fields remain after removing the state field, the update becomes a no-op.\n\t// Return the empty object -- the caller can decide whether to proceed.\n\treturn filtered\n}\n","import type { Operation } from '@korajs/core'\nimport { KoraError } from '@korajs/core'\nimport type { LocalMutationContext } from './types'\nimport { CausalScope, type WriteScope, withWriteScope } from './write-context'\nimport type { WriteResult } from './write-ops'\n\n/**\n * Run one local write in its own storage transaction, then publish it.\n *\n * Post-commit work (causal heads, in-memory version vector, subscriptions,\n * `operation:created`) runs only after the transaction commits, so nothing\n * observable ever points at an operation that was rolled back (STORE-15).\n */\nexport async function runLocalWrite(\n\tctx: LocalMutationContext,\n\twrite: (scope: WriteScope) => Promise<WriteResult>,\n): Promise<WriteResult> {\n\tconst causal = new CausalScope(ctx.causalTracker, false)\n\tlet result: WriteResult | undefined\n\ttry {\n\t\tawait ctx.adapter.transaction(async (tx) => {\n\t\t\tresult = await withWriteScope(tx, ctx.nodeId, causal, write)\n\t\t})\n\t} catch (error) {\n\t\tctx.onStorageError?.(error)\n\t\tthrow error\n\t}\n\tif (!result) {\n\t\tthrow new KoraError('Local write transaction completed without a result', 'WRITE_INCOMPLETE', {\n\t\t\tcollection: ctx.collection,\n\t\t})\n\t}\n\tcausal.publish()\n\tpublishOperations(ctx, result)\n\treturn result\n}\n\n/** Notify the store about every operation of a committed write, in creation order. */\nfunction publishOperations(ctx: LocalMutationContext, result: WriteResult): void {\n\tconst ops: Operation[] = []\n\tif (result.operation) ops.push(result.operation)\n\tops.push(...result.sideEffects)\n\tfor (const op of ops) {\n\t\tctx.onMutation(op.collection, op)\n\t}\n}\n","import type { AtomicOp, CollectionDefinition, Operation } from '@korajs/core'\nimport {\n\tDEFAULT_MAX_OPERATION_BYTES,\n\tKoraError,\n\tOperationTooLargeError,\n\tassertResolvedFieldValue,\n\tcreateOperation,\n\tgenerateUUIDv7,\n\tisAtomicOp,\n\tmeasureOperationBytes,\n\tquoteIdent,\n\tresolveAtomicOp,\n\ttoAtomicOp,\n\tvalidateRecord,\n} from '@korajs/core'\nimport { RecordNotFoundError } from '../errors'\nimport { stampFieldVersions } from '../lww/field-versions'\nimport { serializeRowVersion } from '../lww/row-version'\nimport { buildInsertQuery, buildSoftDeleteQuery, buildUpdateQuery } from '../query/sql-builder'\nimport { encodeRichtextFieldsForOpData } from '../serialization/op-data-encoding'\nimport {\n\tdeserializeRecord,\n\tserializeOperation,\n\tserializeRecord,\n\ttoRichtextReadShape,\n} from '../serialization/serializer'\nimport { validateUpdateStateMachine } from '../state-machine/state-validator'\nimport type { CollectionRecord, RawCollectionRow } from '../types'\nimport { toAtRestWriteData } from './secret-write'\nimport type { WriteEnv, WriteScope } from './write-context'\n\n/*\n * THE local write path (W6). Every local operation — a single-record write, each\n * entry of an `app.transaction` / `app.mutation` commit, and every referential\n * cascade — is built and persisted by the functions in this file, inside the\n * storage transaction that commits it:\n *\n * 1. the current row is read through the transaction handle, so validation\n * (existence, state machine) and atomic ops resolve against committed state;\n * 2. the sequence number comes from the transaction's SequenceBlock (reserved\n * with UPSERT ... RETURNING on `_kora_version_vector` inside the transaction,\n * end persisted before commit); transactions are serialized, so the numbers of\n * one commit form one contiguous block and two commits can never share one;\n * 3. the operation is built only AFTER its sequence number is reserved, and is\n * never re-stamped (protocol v2 hashes the sequence number into the id);\n * 4. the row is written with per-field version stamps and the operation row is\n * appended, in the same transaction.\n */\n\n/** An insert whose values are validated and in their at-rest form. */\nexport interface PreparedInsert {\n\treadonly collection: string\n\treadonly recordId: string\n\t/** Record-shaped, at-rest values (secret fields hashed / encrypted). */\n\treadonly data: Record<string, unknown>\n}\n\n/**\n * Validate insert data, fill auto fields and transform secrets. Pure with\n * respect to storage, so `app.transaction` can do it when the developer calls\n * `insert` (returning the new id immediately) and write it at commit.\n */\nexport async function prepareInsert(\n\tenv: WriteEnv,\n\tcollection: string,\n\tdata: Record<string, unknown>,\n): Promise<PreparedInsert> {\n\tconst definition = requireDefinition(env, collection)\n\tconst validated = validateRecord(collection, definition, data, 'insert')\n\tconst now = Date.now()\n\tfor (const [fieldName, descriptor] of Object.entries(definition.fields)) {\n\t\tif (descriptor.auto && descriptor.kind === 'timestamp') {\n\t\t\tvalidated[fieldName] = now\n\t\t}\n\t}\n\t// Secret fields reach their at-rest form BEFORE the operation is built, so\n\t// plaintext never enters the op log, the row, or the wire (STORE-4).\n\tconst writeData = await toAtRestWriteData(validated, definition, env.secretKeyProvider)\n\treturn { collection, recordId: generateUUIDv7(), data: writeData }\n}\n\n/** Result of writing one local operation. */\nexport interface WriteResult {\n\t/** The operation, or null when the write turned out to be a no-op. */\n\treadonly operation: Operation | null\n\t/** The record as stored after the write. */\n\treadonly record: CollectionRecord\n\t/** Operations created as side effects (referential cascades), in creation order. */\n\treadonly sideEffects: Operation[]\n}\n\n/**\n * Write a prepared insert inside `scope.tx`.\n */\nexport async function writeInsertInTx(\n\tenv: WriteEnv,\n\tscope: WriteScope,\n\tinsert: PreparedInsert,\n\textraCausalDeps: readonly string[] = [],\n): Promise<WriteResult> {\n\tconst definition = requireDefinition(env, insert.collection)\n\tconst operation = await buildLocalOperation(env, scope, {\n\t\ttype: 'insert',\n\t\tcollection: insert.collection,\n\t\trecordId: insert.recordId,\n\t\t// Binary richtext values are tagged as canonical JSON BEFORE hashing, so the\n\t\t// hash input, persisted JSON and wire payload are the identical value.\n\t\tdata: encodeRichtextFieldsForOpData(insert.data, definition.fields),\n\t\tpreviousData: null,\n\t\textraCausalDeps,\n\t})\n\n\tif (env.fold) {\n\t\t// Append, then merge: the row is the materialization of the record's state.\n\t\tawait appendOperationRow(scope, operation)\n\t\tawait env.fold.applyInTx(scope.tx, operation, 'local')\n\t} else {\n\t\tconst serialized = serializeRecord(insert.data, definition.fields)\n\t\tconst version = serializeRowVersion(operation.timestamp)\n\t\tconst row: Record<string, unknown> = {\n\t\t\tid: insert.recordId,\n\t\t\t...serialized,\n\t\t\t_created_at: operation.timestamp.wallTime,\n\t\t\t_updated_at: operation.timestamp.wallTime,\n\t\t\t_version: version,\n\t\t\t// Every inserted field is stamped with this operation's version, so later\n\t\t\t// per-field LWW compares against a real writer, not the row fallback.\n\t\t\t_field_versions: stampFieldVersions(null, Object.keys(serialized), version),\n\t\t}\n\t\tconst rowInsert = buildInsertQuery(insert.collection, row)\n\t\tawait scope.tx.execute(rowInsert.sql, rowInsert.params)\n\t\tawait appendOperationRow(scope, operation)\n\t}\n\n\treturn {\n\t\toperation,\n\t\trecord: {\n\t\t\tid: insert.recordId,\n\t\t\t// A richtext value written as a string returns as the bytes reads return.\n\t\t\t...toRichtextReadShape(insert.data, definition.fields),\n\t\t\tcreatedAt: operation.timestamp.wallTime,\n\t\t\tupdatedAt: operation.timestamp.wallTime,\n\t\t},\n\t\tsideEffects: [],\n\t}\n}\n\n/**\n * Write a local update inside `scope.tx`.\n *\n * `data` must already be schema-validated (it may contain atomic op descriptors).\n * The state machine, atomic ops and `previousData` are all resolved against the\n * row read through the transaction, so concurrent writers serialize correctly\n * (STORE-9, NEW-STORE-2).\n */\nexport async function writeUpdateInTx(\n\tenv: WriteEnv,\n\tscope: WriteScope,\n\tcollection: string,\n\tid: string,\n\tdata: Record<string, unknown>,\n\textraCausalDeps: readonly string[] = [],\n): Promise<WriteResult> {\n\tconst definition = requireDefinition(env, collection)\n\tconst currentRow = await readLiveRow(scope, collection, id)\n\tif (!currentRow) {\n\t\tthrow new RecordNotFoundError(collection, id)\n\t}\n\tconst currentRecord = deserializeRecord(currentRow, definition.fields)\n\tconst allowed = validateUpdateStateMachine(collection, id, definition, currentRecord, data)\n\tif (Object.keys(allowed).length === 0) {\n\t\treturn { operation: null, record: currentRecord, sideEffects: [] }\n\t}\n\n\tconst previousData: Record<string, unknown> = {}\n\tconst resolved: Record<string, unknown> = {}\n\tconst atomicOps: Record<string, AtomicOp> = {}\n\tfor (const [key, value] of Object.entries(allowed)) {\n\t\tpreviousData[key] = currentRecord[key]\n\t\tif (isAtomicOp(value)) {\n\t\t\tresolved[key] = resolveAtomicOp(currentRecord[key], value)\n\t\t\t// The resolved value must be in the field's domain like any written value\n\t\t\t// (RT-87): `op.increment(0.5)` on a timestamp, an increment past the largest\n\t\t\t// finite number.\n\t\t\tconst descriptor = definition.fields[key]\n\t\t\tif (descriptor) assertResolvedFieldValue(collection, key, descriptor, resolved[key])\n\t\t\tatomicOps[key] = toAtomicOp(value)\n\t\t} else {\n\t\t\tresolved[key] = value\n\t\t}\n\t}\n\t// previousData already holds the stored (at-rest) values, so only the new\n\t// values need the secret transform.\n\tconst writeData = await toAtRestWriteData(resolved, definition, env.secretKeyProvider)\n\n\tconst operation = await buildLocalOperation(env, scope, {\n\t\ttype: 'update',\n\t\tcollection,\n\t\trecordId: id,\n\t\tdata: encodeRichtextFieldsForOpData(writeData, definition.fields),\n\t\tpreviousData: encodeRichtextFieldsForOpData(previousData, definition.fields),\n\t\textraCausalDeps,\n\t\t...(Object.keys(atomicOps).length > 0 ? { atomicOps } : {}),\n\t})\n\n\tif (env.fold) {\n\t\tawait appendOperationRow(scope, operation)\n\t\tawait env.fold.applyInTx(scope.tx, operation, 'local')\n\t} else {\n\t\tconst serializedChanges = serializeRecord(writeData, definition.fields)\n\t\tconst version = serializeRowVersion(operation.timestamp)\n\t\t// A local edit is the newest writer of every field it touches (the HLC is past\n\t\t// every timestamp this device has seen), so each changed field gets its stamp.\n\t\tconst rowUpdate = buildUpdateQuery(collection, id, {\n\t\t\t...serializedChanges,\n\t\t\t_updated_at: operation.timestamp.wallTime,\n\t\t\t_version: version,\n\t\t\t_field_versions: stampFieldVersions(\n\t\t\t\tcurrentRow._field_versions,\n\t\t\t\tObject.keys(serializedChanges),\n\t\t\t\tversion,\n\t\t\t),\n\t\t})\n\t\tawait scope.tx.execute(rowUpdate.sql, rowUpdate.params)\n\t\tawait appendOperationRow(scope, operation)\n\t}\n\n\tconst updatedRow = await readLiveRow(scope, collection, id)\n\tif (!updatedRow) {\n\t\tthrow new RecordNotFoundError(collection, id)\n\t}\n\treturn {\n\t\toperation,\n\t\trecord: deserializeRecord(updatedRow, definition.fields),\n\t\tsideEffects: [],\n\t}\n}\n\n/** Options for {@link writeDeleteInTx}. */\nexport interface WriteDeleteOptions {\n\t/** Skip referential checks and cascades (the caller already applied them). */\n\treadonly skipReferentialEnforcement?: boolean\n\t/** Explicit causal parents (e.g. the delete that caused this cascade). */\n\treadonly extraCausalDeps?: readonly string[]\n}\n\n/**\n * Soft-delete a record inside `scope.tx`, then apply the schema's referential\n * policies (cascade, set-null, restrict) in the same transaction. Cascaded\n * operations are built through this same write path, after the parent, so their\n * sequence numbers follow the parent's in the commit's block (STORE-2).\n */\nexport async function writeDeleteInTx(\n\tenv: WriteEnv,\n\tscope: WriteScope,\n\tcollection: string,\n\tid: string,\n\toptions: WriteDeleteOptions = {},\n): Promise<WriteResult> {\n\tconst definition = requireDefinition(env, collection)\n\tconst currentRow = await readLiveRow(scope, collection, id)\n\tif (!currentRow) {\n\t\tthrow new RecordNotFoundError(collection, id)\n\t}\n\tconst operation = await buildLocalOperation(env, scope, {\n\t\ttype: 'delete',\n\t\tcollection,\n\t\trecordId: id,\n\t\tdata: null,\n\t\tpreviousData: null,\n\t\textraCausalDeps: options.extraCausalDeps ?? [],\n\t})\n\n\tif (!options.skipReferentialEnforcement && env.beforeLocalDelete) {\n\t\tawait env.beforeLocalDelete(operation, scope.tx)\n\t}\n\n\t// The row is tombstoned BEFORE side effects run, so a reference cycle\n\t// (self-referencing or mutually-referencing records) cannot cascade back\n\t// into this record.\n\tif (env.fold) {\n\t\tawait appendOperationRow(scope, operation)\n\t\tawait env.fold.applyInTx(scope.tx, operation, 'local')\n\t} else {\n\t\tconst version = serializeRowVersion(operation.timestamp)\n\t\tconst softDelete = buildSoftDeleteQuery(collection, id, operation.timestamp.wallTime, version)\n\t\tawait scope.tx.execute(softDelete.sql, softDelete.params)\n\t\tawait appendOperationRow(scope, operation)\n\t}\n\n\tconst sideEffects: Operation[] = []\n\tif (!options.skipReferentialEnforcement && env.relationEnforcer) {\n\t\tconst result = await env.relationEnforcer.enforceDelete(\n\t\t\tcollection,\n\t\t\tid,\n\t\t\tscope,\n\t\t\tenv,\n\t\t\toperation.id,\n\t\t)\n\t\tsideEffects.push(...result.operations)\n\t}\n\n\treturn {\n\t\toperation,\n\t\trecord: deserializeRecord(currentRow, definition.fields),\n\t\tsideEffects,\n\t}\n}\n\ninterface LocalOperationInput {\n\ttype: Operation['type']\n\tcollection: string\n\trecordId: string\n\tdata: Record<string, unknown> | null\n\tpreviousData: Record<string, unknown> | null\n\textraCausalDeps: readonly string[]\n\tatomicOps?: Record<string, AtomicOp>\n}\n\n/**\n * Reserve the next sequence number through the transaction, THEN build the\n * operation. The order matters: the operation is final the moment it exists.\n */\nasync function buildLocalOperation(\n\tenv: WriteEnv,\n\tscope: WriteScope,\n\tinput: LocalOperationInput,\n): Promise<Operation> {\n\tconst sequenceNumber = await scope.sequence.take()\n\tconst causalDeps = scope.causal.depsFor(input.collection, input.extraCausalDeps)\n\tconst operation = await createOperation(\n\t\t{\n\t\t\tnodeId: env.nodeId,\n\t\t\ttype: input.type,\n\t\t\tcollection: input.collection,\n\t\t\trecordId: input.recordId,\n\t\t\tdata: input.data,\n\t\t\tpreviousData: input.previousData,\n\t\t\tsequenceNumber,\n\t\t\tcausalDeps,\n\t\t\tschemaVersion: env.schema.version,\n\t\t\t...(input.atomicOps !== undefined ? { atomicOps: input.atomicOps } : {}),\n\t\t\t...(env.transactionId !== undefined ? { transactionId: env.transactionId } : {}),\n\t\t\t...(env.mutationName !== undefined ? { mutationName: env.mutationName } : {}),\n\t\t},\n\t\tenv.clock,\n\t)\n\t// Every operation must fit the server's maxOperationBytes (RT-86): refused here, in\n\t// the write's transaction (nothing is written), rather than accepted and then refused\n\t// by the server.\n\tconst maxBytes = env.maxOperationBytes ?? DEFAULT_MAX_OPERATION_BYTES\n\tconst bytes = measureOperationBytes(operation)\n\tif (bytes > maxBytes) {\n\t\tthrow new OperationTooLargeError(input.collection, input.recordId, bytes, maxBytes)\n\t}\n\tscope.causal.record(input.collection, operation.id)\n\treturn operation\n}\n\nasync function appendOperationRow(scope: WriteScope, operation: Operation): Promise<void> {\n\tconst opInsert = buildInsertQuery(\n\t\t`_kora_ops_${operation.collection}`,\n\t\tserializeOperation(operation) as unknown as Record<string, unknown>,\n\t)\n\tawait scope.tx.execute(opInsert.sql, opInsert.params)\n}\n\nasync function readLiveRow(\n\tscope: WriteScope,\n\tcollection: string,\n\tid: string,\n): Promise<RawCollectionRow | undefined> {\n\tconst rows = await scope.tx.query<RawCollectionRow>(\n\t\t`SELECT * FROM ${quoteIdent(collection)} WHERE id = ? AND _deleted = 0`,\n\t\t[id],\n\t)\n\treturn rows[0]\n}\n\nfunction requireDefinition(env: WriteEnv, collection: string): CollectionDefinition {\n\tconst definition = env.schema.collections[collection]\n\tif (!definition) {\n\t\tthrow new KoraError(\n\t\t\t`Unknown collection \"${collection}\". Available: ${Object.keys(env.schema.collections).join(', ')}`,\n\t\t\t'UNKNOWN_COLLECTION',\n\t\t\t{ collection },\n\t\t)\n\t}\n\treturn definition\n}\n","import type { CollectionDefinition, SecretKeyProvider } from '@korajs/core'\nimport { transformSecretFieldsForWrite } from '@korajs/core'\n\n/**\n * Convert the values a local write is about to persist into their at-rest form.\n *\n * Every local write path (single-record insert/update and the buffered\n * `TransactionContext` used by `app.transaction` / `app.mutation`) MUST route\n * its data through this helper BEFORE the operation is built, so plaintext\n * secrets never reach the operation log, the materialized row, or the wire.\n * Keeping one helper means a new write path cannot forget the transform\n * without also bypassing this function (STORE-4).\n *\n * @param data - Validated (and atomic-op-resolved) field values for the write\n * @param definition - The collection definition, used to find secret fields\n * @param secretKeyProvider - Key provider for `t.secret()` encrypted fields\n * @returns A copy of `data` with secret fields hashed or encrypted\n */\nexport function toAtRestWriteData(\n\tdata: Record<string, unknown>,\n\tdefinition: CollectionDefinition,\n\tsecretKeyProvider: SecretKeyProvider | undefined,\n): Promise<Record<string, unknown>> {\n\treturn transformSecretFieldsForWrite(data, definition, secretKeyProvider)\n}\n","import type { Operation } from '@korajs/core'\nimport { runLocalWrite } from './run-local-write'\nimport type { LocalMutationContext } from './types'\nimport { writeDeleteInTx } from './write-ops'\n\nexport interface ExecuteDeleteOptions {\n\t/** When true, skip referential checks and cascades (caller already enforced them). */\n\tskipReferentialEnforcement?: boolean\n}\n\n/**\n * Soft-delete a record and persist the operation log entry atomically, together\n * with any referential side effects (cascades / set-null), in one transaction.\n * Returns the primary delete operation followed by the side-effect operations.\n */\nexport async function executeDelete(\n\tctx: LocalMutationContext,\n\tid: string,\n\toptions?: ExecuteDeleteOptions,\n): Promise<Operation[]> {\n\tconst result = await runLocalWrite(ctx, (scope) =>\n\t\twriteDeleteInTx(ctx, scope, ctx.collection, id, {\n\t\t\tskipReferentialEnforcement: options?.skipReferentialEnforcement ?? false,\n\t\t\textraCausalDeps: ctx.extraCausalDeps ?? [],\n\t\t}),\n\t)\n\treturn result.operation ? [result.operation, ...result.sideEffects] : result.sideEffects\n}\n","import type { CollectionRecord } from '../types'\nimport { runLocalWrite } from './run-local-write'\nimport type { LocalMutationContext } from './types'\nimport { prepareInsert, writeInsertInTx } from './write-ops'\n\n/**\n * Insert a record and persist its operation, atomically, through the single\n * local write path (sequence reserved and operation built inside the write\n * transaction).\n */\nexport async function executeInsert(\n\tctx: LocalMutationContext,\n\tdata: Record<string, unknown>,\n): Promise<CollectionRecord> {\n\tconst prepared = await prepareInsert(ctx, ctx.collection, data)\n\tconst result = await runLocalWrite(ctx, (scope) =>\n\t\twriteInsertInTx(ctx, scope, prepared, ctx.extraCausalDeps ?? []),\n\t)\n\treturn result.record\n}\n","import { validateRecord } from '@korajs/core'\nimport type { CollectionRecord } from '../types'\nimport { runLocalWrite } from './run-local-write'\nimport type { LocalMutationContext } from './types'\nimport { writeUpdateInTx } from './write-ops'\n\n/**\n * Update a record and persist its operation, atomically. The row read, the\n * state-machine check and atomic-op resolution all happen inside the write\n * transaction, so concurrent increments compose (STORE-9).\n */\nexport async function executeUpdate(\n\tctx: LocalMutationContext,\n\tid: string,\n\tdata: Record<string, unknown>,\n): Promise<CollectionRecord> {\n\tconst validated = validateRecord(ctx.collection, ctx.definition, data, 'update')\n\tconst result = await runLocalWrite(ctx, (scope) =>\n\t\twriteUpdateInTx(ctx, scope, ctx.collection, id, validated, ctx.extraCausalDeps ?? []),\n\t)\n\treturn result.record\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;AAOA,SAAS,iBAAiB;AAuDnB,IAAM,cAAN,MAAkB;AAAA,EAKxB,YACkB,SAEA,iBAChB;AAHgB;AAEA;AAAA,EACf;AAAA,EAHe;AAAA,EAEA;AAAA,EAPD,mBAAmB,oBAAI,IAAoB;AAAA,EACpD,cAA6B;AAAA,EACpB,WAAsD,CAAC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAcxE,QAAQ,YAAoB,QAA2B,CAAC,GAAa;AACpE,UAAM,OAAiB,CAAC;AACxB,UAAM,MAAM,CAAC,OAAwC;AACpD,UAAI,MAAM,CAAC,KAAK,SAAS,EAAE,EAAG,MAAK,KAAK,EAAE;AAAA,IAC3C;AACA,eAAW,MAAM,MAAO,KAAI,EAAE;AAC9B,UAAM,QAAQ,KAAK,iBAAiB,IAAI,UAAU;AAClD,QAAI,UAAU,QAAW;AACxB,UAAI,KAAK;AAAA,IACV,OAAO;AACN,iBAAW,MAAM,KAAK,SAAS,eAAe,YAAY,KAAK,KAAK,CAAC,EAAG,KAAI,EAAE;AAAA,IAC/E;AACA,QAAI,KAAK,gBAAiB,KAAI,KAAK,WAAW;AAC9C,WAAO;AAAA,EACR;AAAA;AAAA,EAGA,OAAO,YAAoB,aAA2B;AACrD,SAAK,iBAAiB,IAAI,YAAY,WAAW;AACjD,SAAK,cAAc;AACnB,SAAK,SAAS,KAAK,EAAE,YAAY,IAAI,YAAY,CAAC;AAAA,EACnD;AAAA;AAAA,EAGA,UAAgB;AACf,eAAW,EAAE,YAAY,GAAG,KAAK,KAAK,UAAU;AAC/C,WAAK,SAAS,eAAe,YAAY,IAAI,KAAK;AAAA,IACnD;AAAA,EACD;AACD;AAaO,IAAM,gBAAN,MAAoB;AAAA,EAI1B,YACkB,IACA,QAChB;AAFgB;AACA;AAAA,EACf;AAAA,EAFe;AAAA,EACA;AAAA,EALV,OAAsB;AAAA,EACtB,YAAY;AAAA;AAAA,EAQpB,MAAM,OAAwB;AAC7B,QAAI,KAAK,SAAS,MAAM;AACvB,YAAM,OAAO,MAAM,KAAK,GAAG;AAAA,QAC1B;AAAA;AAAA;AAAA,QAGA,CAAC,KAAK,MAAM;AAAA,MACb;AACA,YAAM,QAAQ,KAAK,CAAC,GAAG;AACvB,UAAI,UAAU,QAAW;AACxB,cAAM,IAAI;AAAA,UACT,iDAAiD,KAAK,MAAM;AAAA,UAC5D;AAAA,UACA,EAAE,QAAQ,KAAK,OAAO;AAAA,QACvB;AAAA,MACD;AACA,WAAK,OAAO;AACZ,WAAK,YAAY;AACjB,aAAO;AAAA,IACR;AACA,SAAK,QAAQ;AACb,WAAO,KAAK;AAAA,EACb;AAAA;AAAA,EAGA,MAAM,QAAuB;AAC5B,QAAI,KAAK,SAAS,QAAQ,KAAK,SAAS,KAAK,UAAW;AACxD,UAAM,KAAK,GAAG;AAAA,MACb;AAAA,MACA,CAAC,KAAK,MAAM,KAAK,MAAM;AAAA,IACxB;AACA,SAAK,YAAY,KAAK;AAAA,EACvB;AACD;AAiBA,eAAsB,eACrB,IACA,QACA,QACA,OACa;AACb,QAAM,QAAoB,EAAE,IAAI,UAAU,IAAI,cAAc,IAAI,MAAM,GAAG,OAAO;AAChF,QAAM,SAAS,MAAM,MAAM,KAAK;AAChC,QAAM,MAAM,SAAS,MAAM;AAC3B,SAAO;AACR;AAOO,SAAS,mBAAmB,OAAyB;AAC3D,MAAI,UAAU,QAAQ,OAAO,UAAU,SAAU,QAAO;AACxD,QAAM,YAAY;AAClB,MAAI,UAAU,SAAS,qBAAsB,QAAO;AACpD,MAAI,UAAU,SAAS,iBAAiB,UAAU,SAAS,iBAAkB,QAAO;AACpF,QAAM,UAAU,OAAO,UAAU,YAAY,WAAW,UAAU,UAAU;AAC5E,MAAI,wDAAwD,KAAK,OAAO,EAAG,QAAO;AAClF,SAAO,UAAU,UAAU,UAAa,UAAU,UAAU,QACzD,mBAAmB,UAAU,KAAK,IAClC;AACJ;;;AC7LO,SAAS,mBAAmB,KAA6B;AAC/D,MAAI,OAAO,QAAQ,YAAY,IAAI,WAAW,GAAG;AAChD,WAAO,CAAC;AAAA,EACT;AACA,MAAI;AACH,UAAM,SAAS,KAAK,MAAM,GAAG;AAC7B,QAAI,WAAW,QAAQ,OAAO,WAAW,YAAY,MAAM,QAAQ,MAAM,GAAG;AAC3E,aAAO,CAAC;AAAA,IACT;AACA,UAAM,SAAwB,CAAC;AAC/B,eAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,MAAiC,GAAG;AAC7E,UAAI,OAAO,UAAU,UAAU;AAC9B,eAAO,GAAG,IAAI;AAAA,MACf;AAAA,IACD;AACA,WAAO;AAAA,EACR,QAAQ;AACP,WAAO,CAAC;AAAA,EACT;AACD;AAKO,SAAS,uBAAuB,UAAiC;AACvE,SAAO,KAAK,UAAU,QAAQ;AAC/B;AAMO,SAAS,uBACf,YACA,SACgB;AAChB,QAAM,WAA0B,CAAC;AACjC,aAAW,SAAS,YAAY;AAC/B,aAAS,KAAK,IAAI;AAAA,EACnB;AACA,SAAO;AACR;AAOO,SAAS,sBACf,SACA,OACA,YACS;AACT,QAAM,MAAM,QAAQ,KAAK;AACzB,MAAI,QAAQ,QAAW;AACtB,WAAO;AAAA,EACR;AACA,SAAO,cAAc;AACtB;AA2BO,SAAS,mBACf,SACA,gBACA,iBACA,YACoB;AACpB,QAAM,UAAoB,CAAC;AAC3B,QAAM,SAAwB,EAAE,GAAG,QAAQ;AAC3C,aAAW,SAAS,gBAAgB;AACnC,UAAM,SAAS,sBAAsB,SAAS,OAAO,UAAU;AAC/D,QAAI,kBAAkB,QAAQ;AAC7B,cAAQ,KAAK,KAAK;AAClB,aAAO,KAAK,IAAI;AAAA,IACjB;AAAA,EAGD;AACA,SAAO,EAAE,SAAS,OAAO;AAC1B;AAgBO,SAAS,mBACf,YACA,QACA,SACS;AACT,QAAM,WAAW,mBAAmB,UAAU;AAC9C,aAAW,SAAS,QAAQ;AAC3B,UAAM,WAAW,SAAS,KAAK;AAC/B,QAAI,aAAa,UAAa,WAAW,SAAS;AACjD,eAAS,KAAK,IAAI;AAAA,IACnB;AAAA,EACD;AACA,SAAO,uBAAuB,QAAQ;AACvC;;;ACvJA,SAAS,aAAAA,YAAW,0BAA0B;AAavC,IAAM,8BAAN,cAA0CA,WAAU;AAAA,EAC1D,YACiB,YACA,UACA,OACA,WACA,SACA,eACf;AACD;AAAA,MACC,2CAA2C,UAAU,+BACxB,KAAK,WAAW,SAAS,SAAS,OAAO,gCACxC,SAAS,MAAM,cAAc,SAAS,IAAI,cAAc,KAAK,IAAI,IAAI,0BAA0B;AAAA,MAC7H;AAAA,MACA,EAAE,YAAY,UAAU,OAAO,WAAW,SAAS,cAAc;AAAA,IAClE;AAbgB;AACA;AACA;AACA;AACA;AACA;AAShB,SAAK,OAAO;AAAA,EACb;AAAA,EAfiB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAWlB;AAoBO,SAAS,wBACf,gBACA,UACA,cACA,cACA,UAC8C;AAG9C,MAAI,iBAAiB,MAAM;AAC1B,WAAO,EAAE,OAAO,MAAM,eAAe,CAAC,EAAE;AAAA,EACzC;AAGA,MAAI,iBAAiB,UAAU;AAC9B,WAAO,EAAE,OAAO,MAAM,eAAe,aAAa,YAAY,YAAY,KAAK,CAAC,EAAE;AAAA,EACnF;AAEA,QAAM,aAAqC;AAAA,IAC1C,OAAO,aAAa;AAAA,IACpB,YAAY;AAAA,IACZ,aAAa,aAAa;AAAA,EAC3B;AACA,QAAM,mBAAmB,mBAAmB,YAAY,cAAc,QAAQ;AAC9E,MAAI,iBAAiB,OAAO;AAC3B,WAAO,EAAE,OAAO,MAAM,eAAe,iBAAiB,eAAe;AAAA,EACtE;AAEA,QAAM,gBAAgB,iBAAiB;AAEvC,MAAI,aAAa,wBAAwB,UAAU;AAClD,UAAM,IAAI;AAAA,MACT;AAAA,MACA;AAAA,MACA,aAAa;AAAA,MACb;AAAA,MACA;AAAA,MACA;AAAA,IACD;AAAA,EACD;AAGA,SAAO,EAAE,OAAO,OAAO,cAAc;AACtC;AAiBO,SAAS,2BACf,gBACA,UACA,eACA,eACA,YAC0B;AAC1B,MAAI,SAAS;AACb,QAAM,eAAe,cAAc;AACnC,MAAI,iBAAiB,QAAW;AAC/B,aAAS,kBAAkB,gBAAgB,UAAU,cAAc,eAAe,MAAM;AAAA,EACzF;AAIA,aAAW,CAAC,OAAO,UAAU,KAAK,OAAO,QAAQ,cAAc,MAAM,GAAG;AACvE,QAAI,WAAW,SAAS,UAAU,CAAC,WAAW,YAAa;AAC3D,QAAI,cAAc,UAAU,MAAO;AACnC,aAAS;AAAA,MACR;AAAA,MACA;AAAA,MACA,EAAE,OAAO,aAAa,WAAW,aAAa,qBAAqB,SAAS;AAAA,MAC5E;AAAA,MACA;AAAA,IACD;AAAA,EACD;AACA,SAAO;AACR;AAEA,SAAS,kBACR,gBACA,UACA,cACA,eACA,YAC0B;AAC1B,QAAM,aAAa,aAAa;AAChC,MAAI,EAAE,cAAc,aAAa;AAEhC,WAAO;AAAA,EACR;AAEA,QAAM,eAAe,cAAc,UAAU;AAC7C,QAAM,WAAW,WAAW,UAAU;AAGtC,MAAI,OAAO,iBAAiB,YAAY,OAAO,aAAa,UAAU;AACrE,WAAO;AAAA,EACR;AAEA,QAAM,SAAS;AAAA,IACd;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACD;AAEA,MAAI,OAAO,OAAO;AACjB,WAAO;AAAA,EACR;AAGA,QAAM,WAAW,EAAE,GAAG,WAAW;AACjC,SAAO,SAAS,UAAU;AAI1B,SAAO;AACR;;;ACnLA,SAAS,aAAAC,kBAAiB;AAY1B,eAAsB,cACrB,KACA,OACuB;AACvB,QAAM,SAAS,IAAI,YAAY,IAAI,eAAe,KAAK;AACvD,MAAI;AACJ,MAAI;AACH,UAAM,IAAI,QAAQ,YAAY,OAAO,OAAO;AAC3C,eAAS,MAAM,eAAe,IAAI,IAAI,QAAQ,QAAQ,KAAK;AAAA,IAC5D,CAAC;AAAA,EACF,SAAS,OAAO;AACf,QAAI,iBAAiB,KAAK;AAC1B,UAAM;AAAA,EACP;AACA,MAAI,CAAC,QAAQ;AACZ,UAAM,IAAIC,WAAU,sDAAsD,oBAAoB;AAAA,MAC7F,YAAY,IAAI;AAAA,IACjB,CAAC;AAAA,EACF;AACA,SAAO,QAAQ;AACf,oBAAkB,KAAK,MAAM;AAC7B,SAAO;AACR;AAGA,SAAS,kBAAkB,KAA2B,QAA2B;AAChF,QAAM,MAAmB,CAAC;AAC1B,MAAI,OAAO,UAAW,KAAI,KAAK,OAAO,SAAS;AAC/C,MAAI,KAAK,GAAG,OAAO,WAAW;AAC9B,aAAW,MAAM,KAAK;AACrB,QAAI,WAAW,GAAG,YAAY,EAAE;AAAA,EACjC;AACD;;;AC5CA;AAAA,EACC;AAAA,EACA,aAAAC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACM;;;ACbP,SAAS,qCAAqC;AAiBvC,SAAS,kBACf,MACA,YACA,mBACmC;AACnC,SAAO,8BAA8B,MAAM,YAAY,iBAAiB;AACzE;;;ADsCA,eAAsB,cACrB,KACA,YACA,MAC0B;AAC1B,QAAM,aAAa,kBAAkB,KAAK,UAAU;AACpD,QAAM,YAAY,eAAe,YAAY,YAAY,MAAM,QAAQ;AACvE,QAAM,MAAM,KAAK,IAAI;AACrB,aAAW,CAAC,WAAW,UAAU,KAAK,OAAO,QAAQ,WAAW,MAAM,GAAG;AACxE,QAAI,WAAW,QAAQ,WAAW,SAAS,aAAa;AACvD,gBAAU,SAAS,IAAI;AAAA,IACxB;AAAA,EACD;AAGA,QAAM,YAAY,MAAM,kBAAkB,WAAW,YAAY,IAAI,iBAAiB;AACtF,SAAO,EAAE,YAAY,UAAU,eAAe,GAAG,MAAM,UAAU;AAClE;AAeA,eAAsB,gBACrB,KACA,OACA,QACA,kBAAqC,CAAC,GACf;AACvB,QAAM,aAAa,kBAAkB,KAAK,OAAO,UAAU;AAC3D,QAAM,YAAY,MAAM,oBAAoB,KAAK,OAAO;AAAA,IACvD,MAAM;AAAA,IACN,YAAY,OAAO;AAAA,IACnB,UAAU,OAAO;AAAA;AAAA;AAAA,IAGjB,MAAM,8BAA8B,OAAO,MAAM,WAAW,MAAM;AAAA,IAClE,cAAc;AAAA,IACd;AAAA,EACD,CAAC;AAED,MAAI,IAAI,MAAM;AAEb,UAAM,mBAAmB,OAAO,SAAS;AACzC,UAAM,IAAI,KAAK,UAAU,MAAM,IAAI,WAAW,OAAO;AAAA,EACtD,OAAO;AACN,UAAM,aAAa,gBAAgB,OAAO,MAAM,WAAW,MAAM;AACjE,UAAM,UAAU,oBAAoB,UAAU,SAAS;AACvD,UAAM,MAA+B;AAAA,MACpC,IAAI,OAAO;AAAA,MACX,GAAG;AAAA,MACH,aAAa,UAAU,UAAU;AAAA,MACjC,aAAa,UAAU,UAAU;AAAA,MACjC,UAAU;AAAA;AAAA;AAAA,MAGV,iBAAiB,mBAAmB,MAAM,OAAO,KAAK,UAAU,GAAG,OAAO;AAAA,IAC3E;AACA,UAAM,YAAY,iBAAiB,OAAO,YAAY,GAAG;AACzD,UAAM,MAAM,GAAG,QAAQ,UAAU,KAAK,UAAU,MAAM;AACtD,UAAM,mBAAmB,OAAO,SAAS;AAAA,EAC1C;AAEA,SAAO;AAAA,IACN;AAAA,IACA,QAAQ;AAAA,MACP,IAAI,OAAO;AAAA;AAAA,MAEX,GAAG,oBAAoB,OAAO,MAAM,WAAW,MAAM;AAAA,MACrD,WAAW,UAAU,UAAU;AAAA,MAC/B,WAAW,UAAU,UAAU;AAAA,IAChC;AAAA,IACA,aAAa,CAAC;AAAA,EACf;AACD;AAUA,eAAsB,gBACrB,KACA,OACA,YACA,IACA,MACA,kBAAqC,CAAC,GACf;AACvB,QAAM,aAAa,kBAAkB,KAAK,UAAU;AACpD,QAAM,aAAa,MAAM,YAAY,OAAO,YAAY,EAAE;AAC1D,MAAI,CAAC,YAAY;AAChB,UAAM,IAAI,oBAAoB,YAAY,EAAE;AAAA,EAC7C;AACA,QAAM,gBAAgB,kBAAkB,YAAY,WAAW,MAAM;AACrE,QAAM,UAAU,2BAA2B,YAAY,IAAI,YAAY,eAAe,IAAI;AAC1F,MAAI,OAAO,KAAK,OAAO,EAAE,WAAW,GAAG;AACtC,WAAO,EAAE,WAAW,MAAM,QAAQ,eAAe,aAAa,CAAC,EAAE;AAAA,EAClE;AAEA,QAAM,eAAwC,CAAC;AAC/C,QAAM,WAAoC,CAAC;AAC3C,QAAM,YAAsC,CAAC;AAC7C,aAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,OAAO,GAAG;AACnD,iBAAa,GAAG,IAAI,cAAc,GAAG;AACrC,QAAI,WAAW,KAAK,GAAG;AACtB,eAAS,GAAG,IAAI,gBAAgB,cAAc,GAAG,GAAG,KAAK;AAIzD,YAAM,aAAa,WAAW,OAAO,GAAG;AACxC,UAAI,WAAY,0BAAyB,YAAY,KAAK,YAAY,SAAS,GAAG,CAAC;AACnF,gBAAU,GAAG,IAAI,WAAW,KAAK;AAAA,IAClC,OAAO;AACN,eAAS,GAAG,IAAI;AAAA,IACjB;AAAA,EACD;AAGA,QAAM,YAAY,MAAM,kBAAkB,UAAU,YAAY,IAAI,iBAAiB;AAErF,QAAM,YAAY,MAAM,oBAAoB,KAAK,OAAO;AAAA,IACvD,MAAM;AAAA,IACN;AAAA,IACA,UAAU;AAAA,IACV,MAAM,8BAA8B,WAAW,WAAW,MAAM;AAAA,IAChE,cAAc,8BAA8B,cAAc,WAAW,MAAM;AAAA,IAC3E;AAAA,IACA,GAAI,OAAO,KAAK,SAAS,EAAE,SAAS,IAAI,EAAE,UAAU,IAAI,CAAC;AAAA,EAC1D,CAAC;AAED,MAAI,IAAI,MAAM;AACb,UAAM,mBAAmB,OAAO,SAAS;AACzC,UAAM,IAAI,KAAK,UAAU,MAAM,IAAI,WAAW,OAAO;AAAA,EACtD,OAAO;AACN,UAAM,oBAAoB,gBAAgB,WAAW,WAAW,MAAM;AACtE,UAAM,UAAU,oBAAoB,UAAU,SAAS;AAGvD,UAAM,YAAY,iBAAiB,YAAY,IAAI;AAAA,MAClD,GAAG;AAAA,MACH,aAAa,UAAU,UAAU;AAAA,MACjC,UAAU;AAAA,MACV,iBAAiB;AAAA,QAChB,WAAW;AAAA,QACX,OAAO,KAAK,iBAAiB;AAAA,QAC7B;AAAA,MACD;AAAA,IACD,CAAC;AACD,UAAM,MAAM,GAAG,QAAQ,UAAU,KAAK,UAAU,MAAM;AACtD,UAAM,mBAAmB,OAAO,SAAS;AAAA,EAC1C;AAEA,QAAM,aAAa,MAAM,YAAY,OAAO,YAAY,EAAE;AAC1D,MAAI,CAAC,YAAY;AAChB,UAAM,IAAI,oBAAoB,YAAY,EAAE;AAAA,EAC7C;AACA,SAAO;AAAA,IACN;AAAA,IACA,QAAQ,kBAAkB,YAAY,WAAW,MAAM;AAAA,IACvD,aAAa,CAAC;AAAA,EACf;AACD;AAgBA,eAAsB,gBACrB,KACA,OACA,YACA,IACA,UAA8B,CAAC,GACR;AACvB,QAAM,aAAa,kBAAkB,KAAK,UAAU;AACpD,QAAM,aAAa,MAAM,YAAY,OAAO,YAAY,EAAE;AAC1D,MAAI,CAAC,YAAY;AAChB,UAAM,IAAI,oBAAoB,YAAY,EAAE;AAAA,EAC7C;AACA,QAAM,YAAY,MAAM,oBAAoB,KAAK,OAAO;AAAA,IACvD,MAAM;AAAA,IACN;AAAA,IACA,UAAU;AAAA,IACV,MAAM;AAAA,IACN,cAAc;AAAA,IACd,iBAAiB,QAAQ,mBAAmB,CAAC;AAAA,EAC9C,CAAC;AAED,MAAI,CAAC,QAAQ,8BAA8B,IAAI,mBAAmB;AACjE,UAAM,IAAI,kBAAkB,WAAW,MAAM,EAAE;AAAA,EAChD;AAKA,MAAI,IAAI,MAAM;AACb,UAAM,mBAAmB,OAAO,SAAS;AACzC,UAAM,IAAI,KAAK,UAAU,MAAM,IAAI,WAAW,OAAO;AAAA,EACtD,OAAO;AACN,UAAM,UAAU,oBAAoB,UAAU,SAAS;AACvD,UAAM,aAAa,qBAAqB,YAAY,IAAI,UAAU,UAAU,UAAU,OAAO;AAC7F,UAAM,MAAM,GAAG,QAAQ,WAAW,KAAK,WAAW,MAAM;AACxD,UAAM,mBAAmB,OAAO,SAAS;AAAA,EAC1C;AAEA,QAAM,cAA2B,CAAC;AAClC,MAAI,CAAC,QAAQ,8BAA8B,IAAI,kBAAkB;AAChE,UAAM,SAAS,MAAM,IAAI,iBAAiB;AAAA,MACzC;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA,UAAU;AAAA,IACX;AACA,gBAAY,KAAK,GAAG,OAAO,UAAU;AAAA,EACtC;AAEA,SAAO;AAAA,IACN;AAAA,IACA,QAAQ,kBAAkB,YAAY,WAAW,MAAM;AAAA,IACvD;AAAA,EACD;AACD;AAgBA,eAAe,oBACd,KACA,OACA,OACqB;AACrB,QAAM,iBAAiB,MAAM,MAAM,SAAS,KAAK;AACjD,QAAM,aAAa,MAAM,OAAO,QAAQ,MAAM,YAAY,MAAM,eAAe;AAC/E,QAAM,YAAY,MAAM;AAAA,IACvB;AAAA,MACC,QAAQ,IAAI;AAAA,MACZ,MAAM,MAAM;AAAA,MACZ,YAAY,MAAM;AAAA,MAClB,UAAU,MAAM;AAAA,MAChB,MAAM,MAAM;AAAA,MACZ,cAAc,MAAM;AAAA,MACpB;AAAA,MACA;AAAA,MACA,eAAe,IAAI,OAAO;AAAA,MAC1B,GAAI,MAAM,cAAc,SAAY,EAAE,WAAW,MAAM,UAAU,IAAI,CAAC;AAAA,MACtE,GAAI,IAAI,kBAAkB,SAAY,EAAE,eAAe,IAAI,cAAc,IAAI,CAAC;AAAA,MAC9E,GAAI,IAAI,iBAAiB,SAAY,EAAE,cAAc,IAAI,aAAa,IAAI,CAAC;AAAA,IAC5E;AAAA,IACA,IAAI;AAAA,EACL;AAIA,QAAM,WAAW,IAAI,qBAAqB;AAC1C,QAAM,QAAQ,sBAAsB,SAAS;AAC7C,MAAI,QAAQ,UAAU;AACrB,UAAM,IAAI,uBAAuB,MAAM,YAAY,MAAM,UAAU,OAAO,QAAQ;AAAA,EACnF;AACA,QAAM,OAAO,OAAO,MAAM,YAAY,UAAU,EAAE;AAClD,SAAO;AACR;AAEA,eAAe,mBAAmB,OAAmB,WAAqC;AACzF,QAAM,WAAW;AAAA,IAChB,aAAa,UAAU,UAAU;AAAA,IACjC,mBAAmB,SAAS;AAAA,EAC7B;AACA,QAAM,MAAM,GAAG,QAAQ,SAAS,KAAK,SAAS,MAAM;AACrD;AAEA,eAAe,YACd,OACA,YACA,IACwC;AACxC,QAAM,OAAO,MAAM,MAAM,GAAG;AAAA,IAC3B,iBAAiB,WAAW,UAAU,CAAC;AAAA,IACvC,CAAC,EAAE;AAAA,EACJ;AACA,SAAO,KAAK,CAAC;AACd;AAEA,SAAS,kBAAkB,KAAe,YAA0C;AACnF,QAAM,aAAa,IAAI,OAAO,YAAY,UAAU;AACpD,MAAI,CAAC,YAAY;AAChB,UAAM,IAAIC;AAAA,MACT,uBAAuB,UAAU,iBAAiB,OAAO,KAAK,IAAI,OAAO,WAAW,EAAE,KAAK,IAAI,CAAC;AAAA,MAChG;AAAA,MACA,EAAE,WAAW;AAAA,IACd;AAAA,EACD;AACA,SAAO;AACR;;;AEtXA,eAAsB,cACrB,KACA,IACA,SACuB;AACvB,QAAM,SAAS,MAAM;AAAA,IAAc;AAAA,IAAK,CAAC,UACxC,gBAAgB,KAAK,OAAO,IAAI,YAAY,IAAI;AAAA,MAC/C,4BAA4B,SAAS,8BAA8B;AAAA,MACnE,iBAAiB,IAAI,mBAAmB,CAAC;AAAA,IAC1C,CAAC;AAAA,EACF;AACA,SAAO,OAAO,YAAY,CAAC,OAAO,WAAW,GAAG,OAAO,WAAW,IAAI,OAAO;AAC9E;;;ACjBA,eAAsB,cACrB,KACA,MAC4B;AAC5B,QAAM,WAAW,MAAM,cAAc,KAAK,IAAI,YAAY,IAAI;AAC9D,QAAM,SAAS,MAAM;AAAA,IAAc;AAAA,IAAK,CAAC,UACxC,gBAAgB,KAAK,OAAO,UAAU,IAAI,mBAAmB,CAAC,CAAC;AAAA,EAChE;AACA,SAAO,OAAO;AACf;;;ACnBA,SAAS,kBAAAC,uBAAsB;AAW/B,eAAsB,cACrB,KACA,IACA,MAC4B;AAC5B,QAAM,YAAYC,gBAAe,IAAI,YAAY,IAAI,YAAY,MAAM,QAAQ;AAC/E,QAAM,SAAS,MAAM;AAAA,IAAc;AAAA,IAAK,CAAC,UACxC,gBAAgB,KAAK,OAAO,IAAI,YAAY,IAAI,WAAW,IAAI,mBAAmB,CAAC,CAAC;AAAA,EACrF;AACA,SAAO,OAAO;AACf;","names":["KoraError","KoraError","KoraError","KoraError","KoraError","validateRecord","validateRecord"]}
|
|
1
|
+
{"version":3,"sources":["../src/mutations/write-context.ts","../src/lww/field-versions.ts","../src/state-machine/state-validator.ts","../src/mutations/run-local-write.ts","../src/mutations/write-ops.ts","../src/mutations/secret-write.ts","../src/mutations/execute-delete.ts","../src/mutations/execute-insert.ts","../src/mutations/execute-update.ts"],"sourcesContent":["import type {\n\tCausalTracker,\n\tHybridLogicalClock,\n\tOperation,\n\tSchemaDefinition,\n\tSecretKeyProvider,\n} from '@korajs/core'\nimport { KoraError } from '@korajs/core'\nimport type { RecordFolder } from '../fold/record-folder'\nimport type { RelationEnforcer } from '../relations/relation-enforcer'\nimport type { Transaction } from '../types'\n\n/**\n * Called inside the write transaction for every local delete, after the delete\n * operation is built and before referential side effects run. Lets the unified\n * apply pipeline run its referential checks (and emit their traces) against the\n * rows the transaction sees. Throwing aborts the whole write.\n */\nexport type LocalDeleteHook = (operation: Operation, tx: Transaction) => Promise<void>\n\n/**\n * Everything the single local write path needs to build and persist an\n * operation inside a storage transaction. One environment serves single-record\n * writes, `app.transaction` commits and referential cascades, so every local\n * operation is built the same way (W6).\n */\nexport interface WriteEnv {\n\treadonly schema: SchemaDefinition\n\treadonly clock: HybridLogicalClock\n\treadonly nodeId: string\n\treadonly relationEnforcer: RelationEnforcer | null\n\treadonly secretKeyProvider?: SecretKeyProvider\n\t/** Shared by every operation of an `app.transaction` / `app.mutation` commit. */\n\treadonly transactionId?: string\n\t/** Human-readable mutation name propagated to every operation (DevTools). */\n\treadonly mutationName?: string\n\t/** See {@link LocalDeleteHook}. */\n\treadonly beforeLocalDelete?: LocalDeleteHook\n\t/**\n\t * The W7 record fold. When set, a local write appends its operation and then\n\t * merges it into the record's fold state, which re-materializes the row (the\n\t * same path remote operations take). Absent only under\n\t * `experimental.legacyMerge` (and while a pre-W7 database is migrated, before its\n\t * re-materialization): the row is then written directly, as in beta.12.\n\t */\n\treadonly fold?: RecordFolder\n\t/**\n\t * Largest operation a local write may produce (RT-86), the server's\n\t * `maxOperationBytes`. Default `DEFAULT_MAX_OPERATION_BYTES` (256 KiB).\n\t */\n\treadonly maxOperationBytes?: number\n\t/**\n\t * Throws when local writes are not allowed right now: the store's pinned node\n\t * belongs to another user than the one signed in (F9). Called before every local\n\t * operation is built.\n\t */\n\treadonly assertLocalWriteAllowed?: () => void\n}\n\n/**\n * Causal bookkeeping for the operations built inside ONE storage transaction.\n *\n * Dependencies are computed from the committed causal heads (the shared\n * {@link CausalTracker}) overlaid with the operations already built in this\n * transaction. Heads are published to the shared tracker only after the\n * transaction commits, so a rolled-back write never leaves a dependency on an\n * operation that does not exist.\n */\nexport class CausalScope {\n\tprivate readonly lastByCollection = new Map<string, string>()\n\tprivate lastInScope: string | null = null\n\tprivate readonly recorded: Array<{ collection: string; id: string }> = []\n\n\tconstructor(\n\t\tprivate readonly tracker: CausalTracker | null,\n\t\t/** When true, every operation also depends on the previous one in this scope. */\n\t\tprivate readonly chainOperations: boolean,\n\t) {}\n\n\t/**\n\t * Direct causal parents for the next operation in `collection`.\n\t *\n\t * @param collection - Collection of the operation being built\n\t * @param extra - Explicit parents (e.g. the delete that caused a cascade)\n\t */\n\tdepsFor(collection: string, extra: readonly string[] = []): string[] {\n\t\tconst deps: string[] = []\n\t\tconst add = (id: string | undefined | null): void => {\n\t\t\tif (id && !deps.includes(id)) deps.push(id)\n\t\t}\n\t\tfor (const id of extra) add(id)\n\t\tconst local = this.lastByCollection.get(collection)\n\t\tif (local !== undefined) {\n\t\t\tadd(local)\n\t\t} else {\n\t\t\tfor (const id of this.tracker?.nextCausalDeps(collection, false) ?? []) add(id)\n\t\t}\n\t\tif (this.chainOperations) add(this.lastInScope)\n\t\treturn deps\n\t}\n\n\t/** Record an operation built in this scope. */\n\trecord(collection: string, operationId: string): void {\n\t\tthis.lastByCollection.set(collection, operationId)\n\t\tthis.lastInScope = operationId\n\t\tthis.recorded.push({ collection, id: operationId })\n\t}\n\n\t/** Publish this scope's heads to the shared tracker. Call only after commit. */\n\tpublish(): void {\n\t\tfor (const { collection, id } of this.recorded) {\n\t\t\tthis.tracker?.afterOperation(collection, id, false)\n\t\t}\n\t}\n}\n\n/**\n * The sequence-number block of ONE write transaction.\n *\n * The first number is reserved with one `UPSERT ... RETURNING` on\n * `_kora_version_vector`, inside the transaction; later numbers continue the block\n * in memory, and {@link SequenceBlock.close} persists the block's end with one\n * write before the transaction commits. Write transactions are serialized and no\n * code path allocates numbers outside one, so the block is contiguous and never\n * shared; the UNIQUE `(node_id, sequence_number)` index on every op table is the\n * backstop. A rollback discards the reservation with everything else.\n */\nexport class SequenceBlock {\n\tprivate last: number | null = null\n\tprivate persisted = 0\n\n\tconstructor(\n\t\tprivate readonly tx: Transaction,\n\t\tprivate readonly nodeId: string,\n\t) {}\n\n\t/** The next number of the block. */\n\tasync take(): Promise<number> {\n\t\tif (this.last === null) {\n\t\t\tconst rows = await this.tx.query<{ sequence_number: number }>(\n\t\t\t\t`INSERT INTO _kora_version_vector (node_id, sequence_number) VALUES (?, 1)\n ON CONFLICT(node_id) DO UPDATE SET sequence_number = sequence_number + 1\n RETURNING sequence_number`,\n\t\t\t\t[this.nodeId],\n\t\t\t)\n\t\t\tconst first = rows[0]?.sequence_number\n\t\t\tif (first === undefined) {\n\t\t\t\tthrow new KoraError(\n\t\t\t\t\t`Failed to reserve a sequence number for node \"${this.nodeId}\"`,\n\t\t\t\t\t'SEQUENCE_RESERVATION_FAILED',\n\t\t\t\t\t{ nodeId: this.nodeId },\n\t\t\t\t)\n\t\t\t}\n\t\t\tthis.last = first\n\t\t\tthis.persisted = first\n\t\t\treturn first\n\t\t}\n\t\tthis.last += 1\n\t\treturn this.last\n\t}\n\n\t/** Persist the end of the block. Must run inside the transaction, before commit. */\n\tasync close(): Promise<void> {\n\t\tif (this.last === null || this.last === this.persisted) return\n\t\tawait this.tx.execute(\n\t\t\t'UPDATE _kora_version_vector SET sequence_number = MAX(sequence_number, ?) WHERE node_id = ?',\n\t\t\t[this.last, this.nodeId],\n\t\t)\n\t\tthis.persisted = this.last\n\t}\n}\n\n/**\n * The per-transaction state of a local write: the transaction handle, its\n * sequence block and its causal scope. Every operation built in one transaction\n * takes the next number of the same contiguous block.\n */\nexport interface WriteScope {\n\treadonly tx: Transaction\n\treadonly sequence: SequenceBlock\n\treadonly causal: CausalScope\n}\n\n/**\n * Run `write` with a fresh {@link WriteScope} on `tx`, then close the sequence\n * block (still inside the transaction).\n */\nexport async function withWriteScope<T>(\n\ttx: Transaction,\n\tnodeId: string,\n\tcausal: CausalScope,\n\twrite: (scope: WriteScope) => Promise<T>,\n): Promise<T> {\n\tconst scope: WriteScope = { tx, sequence: new SequenceBlock(tx, nodeId), causal }\n\tconst result = await write(scope)\n\tawait scope.sequence.close()\n\treturn result\n}\n\n/**\n * Whether a storage error means the device ran out of space: SQLite's\n * `SQLITE_FULL` (\"database or disk is full\") or a browser quota error from OPFS /\n * IndexedDB. The store maps these to a `store:quota-exceeded` event (STORE-15).\n */\nexport function isStorageFullError(error: unknown): boolean {\n\tif (error === null || typeof error !== 'object') return false\n\tconst candidate = error as { name?: unknown; code?: unknown; message?: unknown; cause?: unknown }\n\tif (candidate.name === 'QuotaExceededError') return true\n\tif (candidate.code === 'SQLITE_FULL' || candidate.code === 'QUOTA_EXCEEDED') return true\n\tconst message = typeof candidate.message === 'string' ? candidate.message : ''\n\tif (/SQLITE_FULL|database or disk is full|quota ?exceeded/i.test(message)) return true\n\treturn candidate.cause !== undefined && candidate.cause !== error\n\t\t? isStorageFullError(candidate.cause)\n\t\t: false\n}\n","import { EMPTY_ROW_VERSION } from './row-version'\n\n/**\n * Per-field last-writer versions for a materialized row.\n *\n * Each entry maps a field name to a serialized HLC timestamp (the same\n * lexicographically-sortable form used by the row `_version` column). Because\n * the serialized form sorts identically to {@link HybridLogicalClock.compare},\n * per-field last-write-wins is a plain string comparison: the greater string is\n * the newer writer. This makes field-level convergence deterministic and\n * independent of the order operations happen to arrive in.\n */\nexport type FieldVersions = Record<string, string>\n\n/**\n * Parse the `_field_versions` JSON column into a map. Tolerates null, empty,\n * and malformed values by returning an empty map — a row that predates\n * per-field tracking simply has no field versions and falls back to `_version`.\n */\nexport function parseFieldVersions(raw: unknown): FieldVersions {\n\tif (typeof raw !== 'string' || raw.length === 0) {\n\t\treturn {}\n\t}\n\ttry {\n\t\tconst parsed = JSON.parse(raw) as unknown\n\t\tif (parsed === null || typeof parsed !== 'object' || Array.isArray(parsed)) {\n\t\t\treturn {}\n\t\t}\n\t\tconst result: FieldVersions = {}\n\t\tfor (const [key, value] of Object.entries(parsed as Record<string, unknown>)) {\n\t\t\tif (typeof value === 'string') {\n\t\t\t\tresult[key] = value\n\t\t\t}\n\t\t}\n\t\treturn result\n\t} catch {\n\t\treturn {}\n\t}\n}\n\n/**\n * Serialize a field-version map for storage in the `_field_versions` column.\n */\nexport function serializeFieldVersions(versions: FieldVersions): string {\n\treturn JSON.stringify(versions)\n}\n\n/**\n * Build the initial field-version map for an insert: every supplied field is\n * stamped with the operation's version.\n */\nexport function fieldVersionsForFields(\n\tfieldNames: Iterable<string>,\n\tversion: string,\n): FieldVersions {\n\tconst versions: FieldVersions = {}\n\tfor (const field of fieldNames) {\n\t\tversions[field] = version\n\t}\n\treturn versions\n}\n\n/**\n * The effective stored version for a field: its own tracked version if present,\n * otherwise the row-level `_version` (so rows written before per-field tracking\n * still compare correctly), otherwise the empty sentinel (older than anything).\n */\nexport function effectiveFieldVersion(\n\tcurrent: FieldVersions,\n\tfield: string,\n\trowVersion: string | undefined,\n): string {\n\tconst own = current[field]\n\tif (own !== undefined) {\n\t\treturn own\n\t}\n\treturn rowVersion ?? EMPTY_ROW_VERSION\n}\n\n/**\n * Result of resolving an incoming operation against the current per-field\n * versions of a row.\n */\nexport interface PerFieldLwwResult {\n\t/** Fields the incoming operation wins (strictly newer than the stored version). */\n\twinners: string[]\n\t/** The merged field-version map to persist (winners advanced to `incomingVersion`). */\n\tmerged: FieldVersions\n}\n\n/**\n * Resolve per-field last-write-wins for an incoming operation.\n *\n * For each incoming field, the operation wins only when its version is strictly\n * greater than the field's effective stored version. Ties (identical version,\n * i.e. the same operation re-applied) are losses, keeping the resolution\n * idempotent. The comparison is a total order, so every node resolves the same\n * winner regardless of the order operations arrive.\n *\n * @param current - Parsed `_field_versions` of the target row\n * @param incomingFields - Field names carried by the incoming operation\n * @param incomingVersion - Serialized HLC version of the incoming operation\n * @param rowVersion - The row's `_version` (fallback for untracked fields)\n */\nexport function resolvePerFieldLww(\n\tcurrent: FieldVersions,\n\tincomingFields: Iterable<string>,\n\tincomingVersion: string,\n\trowVersion: string | undefined,\n): PerFieldLwwResult {\n\tconst winners: string[] = []\n\tconst merged: FieldVersions = { ...current }\n\tfor (const field of incomingFields) {\n\t\tconst stored = effectiveFieldVersion(current, field, rowVersion)\n\t\tif (incomingVersion > stored) {\n\t\t\twinners.push(field)\n\t\t\tmerged[field] = incomingVersion\n\t\t}\n\t\t// Losing fields are left untouched: their existing tracked version (or the\n\t\t// row-level `_version` fallback) already reflects the newer writer.\n\t}\n\treturn { winners, merged }\n}\n\n/**\n * Stamp `fields` with `version` on top of a stored `_field_versions` value and\n * return the serialized column. This is the one per-field stamp every local\n * write uses (insert, update, transaction entries, cascades), so every field a\n * device writes carries the HLC of the operation that wrote it.\n *\n * Stamps only move forward: a field already holding a newer version keeps it.\n * The map is keyed by field name with one HLC register per field, the shape the\n * per-field fold (W7) builds on.\n *\n * @param currentRaw - The stored `_field_versions` column (or null for a new row)\n * @param fields - Field names this write sets\n * @param version - The writing operation's serialized HLC version\n */\nexport function stampFieldVersions(\n\tcurrentRaw: unknown,\n\tfields: Iterable<string>,\n\tversion: string,\n): string {\n\tconst versions = parseFieldVersions(currentRaw)\n\tfor (const field of fields) {\n\t\tconst existing = versions[field]\n\t\tif (existing === undefined || existing < version) {\n\t\t\tversions[field] = version\n\t\t}\n\t}\n\treturn serializeFieldVersions(versions)\n}\n","import { KoraError, validateTransition } from '@korajs/core'\nimport type {\n\tCollectionDefinition,\n\tStateMachineConstraint,\n\tStateMachineDefinition,\n} from '@korajs/core'\n\n/**\n * Error thrown when a local mutation attempts an invalid state transition.\n *\n * Contains enough context to debug without reproduction:\n * the collection, record, field, current state, attempted state, and allowed transitions.\n */\nexport class InvalidStateTransitionError extends KoraError {\n\tconstructor(\n\t\tpublic readonly collection: string,\n\t\tpublic readonly recordId: string,\n\t\tpublic readonly field: string,\n\t\tpublic readonly fromState: string,\n\t\tpublic readonly toState: string,\n\t\tpublic readonly allowedStates: string[],\n\t) {\n\t\tsuper(\n\t\t\t`Invalid state transition in collection \"${collection}\": ` +\n\t\t\t\t`cannot transition field \"${field}\" from \"${fromState}\" to \"${toState}\". ` +\n\t\t\t\t`Allowed transitions from \"${fromState}\": ${allowedStates.length > 0 ? allowedStates.join(', ') : '(none -- terminal state)'}`,\n\t\t\t'INVALID_STATE_TRANSITION',\n\t\t\t{ collection, recordId, field, fromState, toState, allowedStates },\n\t\t)\n\t\tthis.name = 'InvalidStateTransitionError'\n\t}\n}\n\n/**\n * Validates a state machine transition for a local mutation (insert or update).\n *\n * For inserts: validates that the initial state (from the data or the default value)\n * is a known state in the state machine.\n *\n * For updates: looks up the current state field value and checks whether\n * the transition to the new value is allowed.\n *\n * @param collectionName - Name of the collection\n * @param recordId - The record being mutated\n * @param stateMachine - The state machine definition\n * @param currentState - The current value of the state field (null for inserts)\n * @param newState - The new value being set for the state field\n * @returns An object indicating whether the transition is valid, and if not, the allowed states.\n * When `onInvalidTransition` is 'last-valid-state', callers should suppress the field update\n * rather than throwing.\n */\nexport function validateStateTransition(\n\tcollectionName: string,\n\trecordId: string,\n\tstateMachine: StateMachineDefinition,\n\tcurrentState: string | null,\n\tnewState: string,\n): { valid: boolean; allowedStates: string[] } {\n\t// For inserts, any valid enum value is acceptable as the initial state\n\t// (schema validation already ensures the value is a valid enum value)\n\tif (currentState === null) {\n\t\treturn { valid: true, allowedStates: [] }\n\t}\n\n\t// Same-state transitions are always valid (idempotent updates)\n\tif (currentState === newState) {\n\t\treturn { valid: true, allowedStates: stateMachine.transitions[currentState] ?? [] }\n\t}\n\n\tconst constraint: StateMachineConstraint = {\n\t\tfield: stateMachine.field,\n\t\tcollection: collectionName,\n\t\ttransitions: stateMachine.transitions,\n\t}\n\tconst transitionResult = validateTransition(constraint, currentState, newState)\n\tif (transitionResult.valid) {\n\t\treturn { valid: true, allowedStates: transitionResult.allowedTargets }\n\t}\n\n\tconst allowedStates = transitionResult.allowedTargets\n\n\tif (stateMachine.onInvalidTransition === 'reject') {\n\t\tthrow new InvalidStateTransitionError(\n\t\t\tcollectionName,\n\t\t\trecordId,\n\t\t\tstateMachine.field,\n\t\t\tcurrentState,\n\t\t\tnewState,\n\t\t\tallowedStates,\n\t\t)\n\t}\n\n\t// 'last-valid-state': return invalid so the caller can suppress the field update\n\treturn { valid: false, allowedStates }\n}\n\n/**\n * Checks whether a collection update data object contains a change to the state machine field,\n * and if so, validates the transition.\n *\n * If the state field is not in the update data, returns the data unchanged.\n * If the transition is invalid and mode is 'last-valid-state', removes the field from the data.\n * If the transition is invalid and mode is 'reject', throws InvalidStateTransitionError.\n *\n * @param collectionName - Name of the collection\n * @param recordId - The record being updated\n * @param collectionDef - The collection definition from the schema\n * @param currentRecord - The current record data (must include the state field)\n * @param updateData - The partial update data\n * @returns The update data, potentially with the state field removed if invalid and mode is 'last-valid-state'\n */\nexport function validateUpdateStateMachine(\n\tcollectionName: string,\n\trecordId: string,\n\tcollectionDef: CollectionDefinition,\n\tcurrentRecord: Record<string, unknown>,\n\tupdateData: Record<string, unknown>,\n): Record<string, unknown> {\n\tlet result = updateData\n\tconst stateMachine = collectionDef.stateMachine\n\tif (stateMachine !== undefined) {\n\t\tresult = applyStateMachine(collectionName, recordId, stateMachine, currentRecord, result)\n\t}\n\t// Field-level `t.enum(...).transitions()` maps are state machines too, in the\n\t// default 'reject' mode (NEW-STORE-3). A collection-level machine on the same\n\t// field takes precedence.\n\tfor (const [field, descriptor] of Object.entries(collectionDef.fields)) {\n\t\tif (descriptor.kind !== 'enum' || !descriptor.transitions) continue\n\t\tif (stateMachine?.field === field) continue\n\t\tresult = applyStateMachine(\n\t\t\tcollectionName,\n\t\t\trecordId,\n\t\t\t{ field, transitions: descriptor.transitions, onInvalidTransition: 'reject' },\n\t\t\tcurrentRecord,\n\t\t\tresult,\n\t\t)\n\t}\n\treturn result\n}\n\nfunction applyStateMachine(\n\tcollectionName: string,\n\trecordId: string,\n\tstateMachine: StateMachineDefinition,\n\tcurrentRecord: Record<string, unknown>,\n\tupdateData: Record<string, unknown>,\n): Record<string, unknown> {\n\tconst stateField = stateMachine.field\n\tif (!(stateField in updateData)) {\n\t\t// State field not being changed -- no validation needed\n\t\treturn updateData\n\t}\n\n\tconst currentState = currentRecord[stateField]\n\tconst newState = updateData[stateField]\n\n\t// Both values must be strings for state machine validation\n\tif (typeof currentState !== 'string' || typeof newState !== 'string') {\n\t\treturn updateData\n\t}\n\n\tconst result = validateStateTransition(\n\t\tcollectionName,\n\t\trecordId,\n\t\tstateMachine,\n\t\tcurrentState,\n\t\tnewState,\n\t)\n\n\tif (result.valid) {\n\t\treturn updateData\n\t}\n\n\t// Mode is 'last-valid-state': silently remove the state field from the update\n\tconst filtered = { ...updateData }\n\tdelete filtered[stateField]\n\n\t// If no fields remain after removing the state field, the update becomes a no-op.\n\t// Return the empty object -- the caller can decide whether to proceed.\n\treturn filtered\n}\n","import type { Operation } from '@korajs/core'\nimport { KoraError } from '@korajs/core'\nimport type { LocalMutationContext } from './types'\nimport { CausalScope, type WriteScope, withWriteScope } from './write-context'\nimport type { WriteResult } from './write-ops'\n\n/**\n * Run one local write in its own storage transaction, then publish it.\n *\n * Post-commit work (causal heads, in-memory version vector, subscriptions,\n * `operation:created`) runs only after the transaction commits, so nothing\n * observable ever points at an operation that was rolled back (STORE-15).\n */\nexport async function runLocalWrite(\n\tctx: LocalMutationContext,\n\twrite: (scope: WriteScope) => Promise<WriteResult>,\n): Promise<WriteResult> {\n\tconst causal = new CausalScope(ctx.causalTracker, false)\n\tlet result: WriteResult | undefined\n\ttry {\n\t\tawait ctx.adapter.transaction(async (tx) => {\n\t\t\tresult = await withWriteScope(tx, ctx.nodeId, causal, write)\n\t\t})\n\t} catch (error) {\n\t\tctx.onStorageError?.(error)\n\t\tthrow error\n\t}\n\tif (!result) {\n\t\tthrow new KoraError('Local write transaction completed without a result', 'WRITE_INCOMPLETE', {\n\t\t\tcollection: ctx.collection,\n\t\t})\n\t}\n\tcausal.publish()\n\tpublishOperations(ctx, result)\n\treturn result\n}\n\n/** Notify the store about every operation of a committed write, in creation order. */\nfunction publishOperations(ctx: LocalMutationContext, result: WriteResult): void {\n\tconst ops: Operation[] = []\n\tif (result.operation) ops.push(result.operation)\n\tops.push(...result.sideEffects)\n\tfor (const op of ops) {\n\t\tctx.onMutation(op.collection, op)\n\t}\n}\n","import type { AtomicOp, CollectionDefinition, Operation } from '@korajs/core'\nimport {\n\tDEFAULT_MAX_OPERATION_BYTES,\n\tKoraError,\n\tOperationTooLargeError,\n\tassertResolvedFieldValue,\n\tcreateOperation,\n\tgenerateUUIDv7,\n\tisAtomicOp,\n\tmeasureOperationBytes,\n\tquoteIdent,\n\tresolveAtomicOp,\n\ttoAtomicOp,\n\tvalidateRecord,\n} from '@korajs/core'\nimport { RecordNotFoundError } from '../errors'\nimport { stampFieldVersions } from '../lww/field-versions'\nimport { serializeRowVersion } from '../lww/row-version'\nimport { buildInsertQuery, buildSoftDeleteQuery, buildUpdateQuery } from '../query/sql-builder'\nimport { encodeRichtextFieldsForOpData } from '../serialization/op-data-encoding'\nimport {\n\tdeserializeRecord,\n\tserializeOperation,\n\tserializeRecord,\n\ttoRichtextReadShape,\n} from '../serialization/serializer'\nimport { validateUpdateStateMachine } from '../state-machine/state-validator'\nimport type { CollectionRecord, RawCollectionRow } from '../types'\nimport { toAtRestWriteData } from './secret-write'\nimport type { WriteEnv, WriteScope } from './write-context'\n\n/*\n * THE local write path (W6). Every local operation — a single-record write, each\n * entry of an `app.transaction` / `app.mutation` commit, and every referential\n * cascade — is built and persisted by the functions in this file, inside the\n * storage transaction that commits it:\n *\n * 1. the current row is read through the transaction handle, so validation\n * (existence, state machine) and atomic ops resolve against committed state;\n * 2. the sequence number comes from the transaction's SequenceBlock (reserved\n * with UPSERT ... RETURNING on `_kora_version_vector` inside the transaction,\n * end persisted before commit); transactions are serialized, so the numbers of\n * one commit form one contiguous block and two commits can never share one;\n * 3. the operation is built only AFTER its sequence number is reserved, and is\n * never re-stamped (protocol v2 hashes the sequence number into the id);\n * 4. the row is written with per-field version stamps and the operation row is\n * appended, in the same transaction.\n */\n\n/** An insert whose values are validated and in their at-rest form. */\nexport interface PreparedInsert {\n\treadonly collection: string\n\treadonly recordId: string\n\t/** Record-shaped, at-rest values (secret fields hashed / encrypted). */\n\treadonly data: Record<string, unknown>\n}\n\n/**\n * Validate insert data, fill auto fields and transform secrets. Pure with\n * respect to storage, so `app.transaction` can do it when the developer calls\n * `insert` (returning the new id immediately) and write it at commit.\n */\nexport async function prepareInsert(\n\tenv: WriteEnv,\n\tcollection: string,\n\tdata: Record<string, unknown>,\n): Promise<PreparedInsert> {\n\tconst definition = requireDefinition(env, collection)\n\tconst validated = validateRecord(collection, definition, data, 'insert')\n\tconst now = Date.now()\n\tfor (const [fieldName, descriptor] of Object.entries(definition.fields)) {\n\t\tif (descriptor.auto && descriptor.kind === 'timestamp') {\n\t\t\tvalidated[fieldName] = now\n\t\t}\n\t}\n\t// Secret fields reach their at-rest form BEFORE the operation is built, so\n\t// plaintext never enters the op log, the row, or the wire (STORE-4).\n\tconst writeData = await toAtRestWriteData(validated, definition, env.secretKeyProvider)\n\treturn { collection, recordId: generateUUIDv7(), data: writeData }\n}\n\n/** Result of writing one local operation. */\nexport interface WriteResult {\n\t/** The operation, or null when the write turned out to be a no-op. */\n\treadonly operation: Operation | null\n\t/** The record as stored after the write. */\n\treadonly record: CollectionRecord\n\t/** Operations created as side effects (referential cascades), in creation order. */\n\treadonly sideEffects: Operation[]\n}\n\n/**\n * Write a prepared insert inside `scope.tx`.\n */\nexport async function writeInsertInTx(\n\tenv: WriteEnv,\n\tscope: WriteScope,\n\tinsert: PreparedInsert,\n\textraCausalDeps: readonly string[] = [],\n): Promise<WriteResult> {\n\tconst definition = requireDefinition(env, insert.collection)\n\tconst operation = await buildLocalOperation(env, scope, {\n\t\ttype: 'insert',\n\t\tcollection: insert.collection,\n\t\trecordId: insert.recordId,\n\t\t// Binary richtext values are tagged as canonical JSON BEFORE hashing, so the\n\t\t// hash input, persisted JSON and wire payload are the identical value.\n\t\tdata: encodeRichtextFieldsForOpData(insert.data, definition.fields),\n\t\tpreviousData: null,\n\t\textraCausalDeps,\n\t})\n\n\tif (env.fold) {\n\t\t// Append, then merge: the row is the materialization of the record's state.\n\t\tawait appendOperationRow(scope, operation)\n\t\tawait env.fold.applyInTx(scope.tx, operation, 'local')\n\t} else {\n\t\tconst serialized = serializeRecord(insert.data, definition.fields)\n\t\tconst version = serializeRowVersion(operation.timestamp)\n\t\tconst row: Record<string, unknown> = {\n\t\t\tid: insert.recordId,\n\t\t\t...serialized,\n\t\t\t_created_at: operation.timestamp.wallTime,\n\t\t\t_updated_at: operation.timestamp.wallTime,\n\t\t\t_version: version,\n\t\t\t// Every inserted field is stamped with this operation's version, so later\n\t\t\t// per-field LWW compares against a real writer, not the row fallback.\n\t\t\t_field_versions: stampFieldVersions(null, Object.keys(serialized), version),\n\t\t}\n\t\tconst rowInsert = buildInsertQuery(insert.collection, row)\n\t\tawait scope.tx.execute(rowInsert.sql, rowInsert.params)\n\t\tawait appendOperationRow(scope, operation)\n\t}\n\n\treturn {\n\t\toperation,\n\t\trecord: {\n\t\t\tid: insert.recordId,\n\t\t\t// A richtext value written as a string returns as the bytes reads return.\n\t\t\t...toRichtextReadShape(insert.data, definition.fields),\n\t\t\tcreatedAt: operation.timestamp.wallTime,\n\t\t\tupdatedAt: operation.timestamp.wallTime,\n\t\t},\n\t\tsideEffects: [],\n\t}\n}\n\n/**\n * Write a local update inside `scope.tx`.\n *\n * `data` must already be schema-validated (it may contain atomic op descriptors).\n * The state machine, atomic ops and `previousData` are all resolved against the\n * row read through the transaction, so concurrent writers serialize correctly\n * (STORE-9, NEW-STORE-2).\n */\nexport async function writeUpdateInTx(\n\tenv: WriteEnv,\n\tscope: WriteScope,\n\tcollection: string,\n\tid: string,\n\tdata: Record<string, unknown>,\n\textraCausalDeps: readonly string[] = [],\n): Promise<WriteResult> {\n\tconst definition = requireDefinition(env, collection)\n\tconst currentRow = await readLiveRow(scope, collection, id)\n\tif (!currentRow) {\n\t\tthrow new RecordNotFoundError(collection, id)\n\t}\n\tconst currentRecord = deserializeRecord(currentRow, definition.fields)\n\tconst allowed = validateUpdateStateMachine(collection, id, definition, currentRecord, data)\n\tif (Object.keys(allowed).length === 0) {\n\t\treturn { operation: null, record: currentRecord, sideEffects: [] }\n\t}\n\n\tconst previousData: Record<string, unknown> = {}\n\tconst resolved: Record<string, unknown> = {}\n\tconst atomicOps: Record<string, AtomicOp> = {}\n\tfor (const [key, value] of Object.entries(allowed)) {\n\t\tpreviousData[key] = currentRecord[key]\n\t\tif (isAtomicOp(value)) {\n\t\t\tresolved[key] = resolveAtomicOp(currentRecord[key], value)\n\t\t\t// The resolved value must be in the field's domain like any written value\n\t\t\t// (RT-87): `op.increment(0.5)` on a timestamp, an increment past the largest\n\t\t\t// finite number.\n\t\t\tconst descriptor = definition.fields[key]\n\t\t\tif (descriptor) assertResolvedFieldValue(collection, key, descriptor, resolved[key])\n\t\t\tatomicOps[key] = toAtomicOp(value)\n\t\t} else {\n\t\t\tresolved[key] = value\n\t\t}\n\t}\n\t// previousData already holds the stored (at-rest) values, so only the new\n\t// values need the secret transform.\n\tconst writeData = await toAtRestWriteData(resolved, definition, env.secretKeyProvider)\n\n\tconst operation = await buildLocalOperation(env, scope, {\n\t\ttype: 'update',\n\t\tcollection,\n\t\trecordId: id,\n\t\tdata: encodeRichtextFieldsForOpData(writeData, definition.fields),\n\t\tpreviousData: encodeRichtextFieldsForOpData(previousData, definition.fields),\n\t\textraCausalDeps,\n\t\t...(Object.keys(atomicOps).length > 0 ? { atomicOps } : {}),\n\t})\n\n\tif (env.fold) {\n\t\tawait appendOperationRow(scope, operation)\n\t\tawait env.fold.applyInTx(scope.tx, operation, 'local')\n\t} else {\n\t\tconst serializedChanges = serializeRecord(writeData, definition.fields)\n\t\tconst version = serializeRowVersion(operation.timestamp)\n\t\t// A local edit is the newest writer of every field it touches (the HLC is past\n\t\t// every timestamp this device has seen), so each changed field gets its stamp.\n\t\tconst rowUpdate = buildUpdateQuery(collection, id, {\n\t\t\t...serializedChanges,\n\t\t\t_updated_at: operation.timestamp.wallTime,\n\t\t\t_version: version,\n\t\t\t_field_versions: stampFieldVersions(\n\t\t\t\tcurrentRow._field_versions,\n\t\t\t\tObject.keys(serializedChanges),\n\t\t\t\tversion,\n\t\t\t),\n\t\t})\n\t\tawait scope.tx.execute(rowUpdate.sql, rowUpdate.params)\n\t\tawait appendOperationRow(scope, operation)\n\t}\n\n\tconst updatedRow = await readLiveRow(scope, collection, id)\n\tif (!updatedRow) {\n\t\tthrow new RecordNotFoundError(collection, id)\n\t}\n\treturn {\n\t\toperation,\n\t\trecord: deserializeRecord(updatedRow, definition.fields),\n\t\tsideEffects: [],\n\t}\n}\n\n/** Options for {@link writeDeleteInTx}. */\nexport interface WriteDeleteOptions {\n\t/** Skip referential checks and cascades (the caller already applied them). */\n\treadonly skipReferentialEnforcement?: boolean\n\t/** Explicit causal parents (e.g. the delete that caused this cascade). */\n\treadonly extraCausalDeps?: readonly string[]\n}\n\n/**\n * Soft-delete a record inside `scope.tx`, then apply the schema's referential\n * policies (cascade, set-null, restrict) in the same transaction. Cascaded\n * operations are built through this same write path, after the parent, so their\n * sequence numbers follow the parent's in the commit's block (STORE-2).\n */\nexport async function writeDeleteInTx(\n\tenv: WriteEnv,\n\tscope: WriteScope,\n\tcollection: string,\n\tid: string,\n\toptions: WriteDeleteOptions = {},\n): Promise<WriteResult> {\n\tconst definition = requireDefinition(env, collection)\n\tconst currentRow = await readLiveRow(scope, collection, id)\n\tif (!currentRow) {\n\t\tthrow new RecordNotFoundError(collection, id)\n\t}\n\tconst operation = await buildLocalOperation(env, scope, {\n\t\ttype: 'delete',\n\t\tcollection,\n\t\trecordId: id,\n\t\tdata: null,\n\t\tpreviousData: null,\n\t\textraCausalDeps: options.extraCausalDeps ?? [],\n\t})\n\n\tif (!options.skipReferentialEnforcement && env.beforeLocalDelete) {\n\t\tawait env.beforeLocalDelete(operation, scope.tx)\n\t}\n\n\t// The row is tombstoned BEFORE side effects run, so a reference cycle\n\t// (self-referencing or mutually-referencing records) cannot cascade back\n\t// into this record.\n\tif (env.fold) {\n\t\tawait appendOperationRow(scope, operation)\n\t\tawait env.fold.applyInTx(scope.tx, operation, 'local')\n\t} else {\n\t\tconst version = serializeRowVersion(operation.timestamp)\n\t\tconst softDelete = buildSoftDeleteQuery(collection, id, operation.timestamp.wallTime, version)\n\t\tawait scope.tx.execute(softDelete.sql, softDelete.params)\n\t\tawait appendOperationRow(scope, operation)\n\t}\n\n\tconst sideEffects: Operation[] = []\n\tif (!options.skipReferentialEnforcement && env.relationEnforcer) {\n\t\tconst result = await env.relationEnforcer.enforceDelete(\n\t\t\tcollection,\n\t\t\tid,\n\t\t\tscope,\n\t\t\tenv,\n\t\t\toperation.id,\n\t\t)\n\t\tsideEffects.push(...result.operations)\n\t}\n\n\treturn {\n\t\toperation,\n\t\trecord: deserializeRecord(currentRow, definition.fields),\n\t\tsideEffects,\n\t}\n}\n\ninterface LocalOperationInput {\n\ttype: Operation['type']\n\tcollection: string\n\trecordId: string\n\tdata: Record<string, unknown> | null\n\tpreviousData: Record<string, unknown> | null\n\textraCausalDeps: readonly string[]\n\tatomicOps?: Record<string, AtomicOp>\n}\n\n/**\n * Reserve the next sequence number through the transaction, THEN build the\n * operation. The order matters: the operation is final the moment it exists.\n */\nasync function buildLocalOperation(\n\tenv: WriteEnv,\n\tscope: WriteScope,\n\tinput: LocalOperationInput,\n): Promise<Operation> {\n\tenv.assertLocalWriteAllowed?.()\n\tconst sequenceNumber = await scope.sequence.take()\n\tconst causalDeps = scope.causal.depsFor(input.collection, input.extraCausalDeps)\n\tconst operation = await createOperation(\n\t\t{\n\t\t\tnodeId: env.nodeId,\n\t\t\ttype: input.type,\n\t\t\tcollection: input.collection,\n\t\t\trecordId: input.recordId,\n\t\t\tdata: input.data,\n\t\t\tpreviousData: input.previousData,\n\t\t\tsequenceNumber,\n\t\t\tcausalDeps,\n\t\t\tschemaVersion: env.schema.version,\n\t\t\t...(input.atomicOps !== undefined ? { atomicOps: input.atomicOps } : {}),\n\t\t\t...(env.transactionId !== undefined ? { transactionId: env.transactionId } : {}),\n\t\t\t...(env.mutationName !== undefined ? { mutationName: env.mutationName } : {}),\n\t\t},\n\t\tenv.clock,\n\t)\n\t// Every operation must fit the server's maxOperationBytes (RT-86): refused here, in\n\t// the write's transaction (nothing is written), rather than accepted and then refused\n\t// by the server.\n\tconst maxBytes = env.maxOperationBytes ?? DEFAULT_MAX_OPERATION_BYTES\n\tconst bytes = measureOperationBytes(operation)\n\tif (bytes > maxBytes) {\n\t\tthrow new OperationTooLargeError(input.collection, input.recordId, bytes, maxBytes)\n\t}\n\tscope.causal.record(input.collection, operation.id)\n\treturn operation\n}\n\nasync function appendOperationRow(scope: WriteScope, operation: Operation): Promise<void> {\n\tconst opInsert = buildInsertQuery(\n\t\t`_kora_ops_${operation.collection}`,\n\t\tserializeOperation(operation) as unknown as Record<string, unknown>,\n\t)\n\tawait scope.tx.execute(opInsert.sql, opInsert.params)\n}\n\nasync function readLiveRow(\n\tscope: WriteScope,\n\tcollection: string,\n\tid: string,\n): Promise<RawCollectionRow | undefined> {\n\tconst rows = await scope.tx.query<RawCollectionRow>(\n\t\t`SELECT * FROM ${quoteIdent(collection)} WHERE id = ? AND _deleted = 0`,\n\t\t[id],\n\t)\n\treturn rows[0]\n}\n\nfunction requireDefinition(env: WriteEnv, collection: string): CollectionDefinition {\n\tconst definition = env.schema.collections[collection]\n\tif (!definition) {\n\t\tthrow new KoraError(\n\t\t\t`Unknown collection \"${collection}\". Available: ${Object.keys(env.schema.collections).join(', ')}`,\n\t\t\t'UNKNOWN_COLLECTION',\n\t\t\t{ collection },\n\t\t)\n\t}\n\treturn definition\n}\n","import type { CollectionDefinition, SecretKeyProvider } from '@korajs/core'\nimport { transformSecretFieldsForWrite } from '@korajs/core'\n\n/**\n * Convert the values a local write is about to persist into their at-rest form.\n *\n * Every local write path (single-record insert/update and the buffered\n * `TransactionContext` used by `app.transaction` / `app.mutation`) MUST route\n * its data through this helper BEFORE the operation is built, so plaintext\n * secrets never reach the operation log, the materialized row, or the wire.\n * Keeping one helper means a new write path cannot forget the transform\n * without also bypassing this function (STORE-4).\n *\n * @param data - Validated (and atomic-op-resolved) field values for the write\n * @param definition - The collection definition, used to find secret fields\n * @param secretKeyProvider - Key provider for `t.secret()` encrypted fields\n * @returns A copy of `data` with secret fields hashed or encrypted\n */\nexport function toAtRestWriteData(\n\tdata: Record<string, unknown>,\n\tdefinition: CollectionDefinition,\n\tsecretKeyProvider: SecretKeyProvider | undefined,\n): Promise<Record<string, unknown>> {\n\treturn transformSecretFieldsForWrite(data, definition, secretKeyProvider)\n}\n","import type { Operation } from '@korajs/core'\nimport { runLocalWrite } from './run-local-write'\nimport type { LocalMutationContext } from './types'\nimport { writeDeleteInTx } from './write-ops'\n\nexport interface ExecuteDeleteOptions {\n\t/** When true, skip referential checks and cascades (caller already enforced them). */\n\tskipReferentialEnforcement?: boolean\n}\n\n/**\n * Soft-delete a record and persist the operation log entry atomically, together\n * with any referential side effects (cascades / set-null), in one transaction.\n * Returns the primary delete operation followed by the side-effect operations.\n */\nexport async function executeDelete(\n\tctx: LocalMutationContext,\n\tid: string,\n\toptions?: ExecuteDeleteOptions,\n): Promise<Operation[]> {\n\tconst result = await runLocalWrite(ctx, (scope) =>\n\t\twriteDeleteInTx(ctx, scope, ctx.collection, id, {\n\t\t\tskipReferentialEnforcement: options?.skipReferentialEnforcement ?? false,\n\t\t\textraCausalDeps: ctx.extraCausalDeps ?? [],\n\t\t}),\n\t)\n\treturn result.operation ? [result.operation, ...result.sideEffects] : result.sideEffects\n}\n","import type { CollectionRecord } from '../types'\nimport { runLocalWrite } from './run-local-write'\nimport type { LocalMutationContext } from './types'\nimport { prepareInsert, writeInsertInTx } from './write-ops'\n\n/**\n * Insert a record and persist its operation, atomically, through the single\n * local write path (sequence reserved and operation built inside the write\n * transaction).\n */\nexport async function executeInsert(\n\tctx: LocalMutationContext,\n\tdata: Record<string, unknown>,\n): Promise<CollectionRecord> {\n\tconst prepared = await prepareInsert(ctx, ctx.collection, data)\n\tconst result = await runLocalWrite(ctx, (scope) =>\n\t\twriteInsertInTx(ctx, scope, prepared, ctx.extraCausalDeps ?? []),\n\t)\n\treturn result.record\n}\n","import { validateRecord } from '@korajs/core'\nimport type { CollectionRecord } from '../types'\nimport { runLocalWrite } from './run-local-write'\nimport type { LocalMutationContext } from './types'\nimport { writeUpdateInTx } from './write-ops'\n\n/**\n * Update a record and persist its operation, atomically. The row read, the\n * state-machine check and atomic-op resolution all happen inside the write\n * transaction, so concurrent increments compose (STORE-9).\n */\nexport async function executeUpdate(\n\tctx: LocalMutationContext,\n\tid: string,\n\tdata: Record<string, unknown>,\n): Promise<CollectionRecord> {\n\tconst validated = validateRecord(ctx.collection, ctx.definition, data, 'update')\n\tconst result = await runLocalWrite(ctx, (scope) =>\n\t\twriteUpdateInTx(ctx, scope, ctx.collection, id, validated, ctx.extraCausalDeps ?? []),\n\t)\n\treturn result.record\n}\n"],"mappings":"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","names":["KoraError","KoraError","KoraError","KoraError","KoraError","validateRecord","validateRecord"]}
|
|
@@ -4,14 +4,14 @@ import {
|
|
|
4
4
|
import {
|
|
5
5
|
FollowerBroadcastBridge,
|
|
6
6
|
acquireTabStorageSession
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-5JMGMPUE.js";
|
|
8
8
|
import {
|
|
9
9
|
AdapterError,
|
|
10
10
|
BridgeTerminatedError,
|
|
11
11
|
PersistenceError,
|
|
12
12
|
StorageDurabilityError,
|
|
13
13
|
StoreNotOpenError
|
|
14
|
-
} from "./chunk-
|
|
14
|
+
} from "./chunk-SRHDJF5B.js";
|
|
15
15
|
|
|
16
16
|
// src/adapters/sqlite-wasm-adapter.ts
|
|
17
17
|
import { generateFullDDL } from "@korajs/core";
|
|
@@ -425,7 +425,7 @@ var SqliteWasmAdapter = class {
|
|
|
425
425
|
});
|
|
426
426
|
this.tabSession = session;
|
|
427
427
|
if (session.role === "leader") {
|
|
428
|
-
const { WebWorkerBridge } = await import("./sqlite-wasm-channel-
|
|
428
|
+
const { WebWorkerBridge } = await import("./sqlite-wasm-channel-QCCNBYCU.js");
|
|
429
429
|
const worker = new WebWorkerBridge(workerUrl, this.workerResponseTimeoutMs, {
|
|
430
430
|
onEvent: (event) => this.onWorkerEvent(event)
|
|
431
431
|
});
|
|
@@ -730,7 +730,7 @@ var SqliteWasmAdapter = class {
|
|
|
730
730
|
const gate = createGate();
|
|
731
731
|
this.gate = gate;
|
|
732
732
|
try {
|
|
733
|
-
const { WebWorkerBridge } = await import("./sqlite-wasm-channel-
|
|
733
|
+
const { WebWorkerBridge } = await import("./sqlite-wasm-channel-QCCNBYCU.js");
|
|
734
734
|
if (this.closing || !this.opened) return;
|
|
735
735
|
const worker = new WebWorkerBridge(this.workerUrl, this.workerResponseTimeoutMs, {
|
|
736
736
|
onEvent: (event) => this.onWorkerEvent(event)
|
|
@@ -1092,4 +1092,4 @@ export {
|
|
|
1092
1092
|
deleteManifestRecord,
|
|
1093
1093
|
SqliteWasmAdapter
|
|
1094
1094
|
};
|
|
1095
|
-
//# sourceMappingURL=chunk-
|
|
1095
|
+
//# sourceMappingURL=chunk-BQSIL6TT.js.map
|
|
@@ -2,7 +2,7 @@ import {
|
|
|
2
2
|
BridgeTerminatedError,
|
|
3
3
|
RequestAbortedError,
|
|
4
4
|
WorkerTimeoutError
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-SRHDJF5B.js";
|
|
6
6
|
|
|
7
7
|
// src/adapters/sqlite-wasm-channel.ts
|
|
8
8
|
var WebWorkerBridge = class {
|
|
@@ -109,4 +109,4 @@ var WebWorkerBridge = class {
|
|
|
109
109
|
export {
|
|
110
110
|
WebWorkerBridge
|
|
111
111
|
};
|
|
112
|
-
//# sourceMappingURL=chunk-
|
|
112
|
+
//# sourceMappingURL=chunk-KK3GBONL.js.map
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
TERMINAL_REJECTIONS_TABLE,
|
|
3
3
|
buildInsertQuery
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-QW4UU7NG.js";
|
|
5
5
|
import {
|
|
6
6
|
deserializeOperationWithCollection,
|
|
7
7
|
serializeOperation,
|
|
@@ -873,4 +873,4 @@ export {
|
|
|
873
873
|
isCompacted,
|
|
874
874
|
RecordFolder
|
|
875
875
|
};
|
|
876
|
-
//# sourceMappingURL=chunk-
|
|
876
|
+
//# sourceMappingURL=chunk-KTVNANUH.js.map
|
|
@@ -3,7 +3,7 @@ import {
|
|
|
3
3
|
} from "./chunk-J5AGXF3D.js";
|
|
4
4
|
import {
|
|
5
5
|
QueryError
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-SRHDJF5B.js";
|
|
7
7
|
|
|
8
8
|
// src/lww/row-version.ts
|
|
9
9
|
import { HybridLogicalClock } from "@korajs/core";
|
|
@@ -1000,4 +1000,4 @@ export {
|
|
|
1000
1000
|
loadAcceptedCycle,
|
|
1001
1001
|
hasUnsyncedOwnOperations
|
|
1002
1002
|
};
|
|
1003
|
-
//# sourceMappingURL=chunk-
|
|
1003
|
+
//# sourceMappingURL=chunk-QW4UU7NG.js.map
|
|
@@ -152,6 +152,16 @@ var PersistenceError = class extends KoraError {
|
|
|
152
152
|
this.name = "PersistenceError";
|
|
153
153
|
}
|
|
154
154
|
};
|
|
155
|
+
var NodeOwnedByAnotherUserError = class extends KoraError {
|
|
156
|
+
constructor(nodeId, principal) {
|
|
157
|
+
super(
|
|
158
|
+
"This device belongs to another account, so changes made while this account is signed in here cannot be saved: they would be uploaded as the device owner's. Sign in as the device's owner, or use this account on its own device.",
|
|
159
|
+
"NODE_OWNED_BY_ANOTHER_USER",
|
|
160
|
+
{ nodeId, principal }
|
|
161
|
+
);
|
|
162
|
+
this.name = "NodeOwnedByAnotherUserError";
|
|
163
|
+
}
|
|
164
|
+
};
|
|
155
165
|
|
|
156
166
|
export {
|
|
157
167
|
QueryError,
|
|
@@ -170,6 +180,7 @@ export {
|
|
|
170
180
|
StorageInUseError,
|
|
171
181
|
UnsyncedDataError,
|
|
172
182
|
StorageBackendMismatchError,
|
|
173
|
-
PersistenceError
|
|
183
|
+
PersistenceError,
|
|
184
|
+
NodeOwnedByAnotherUserError
|
|
174
185
|
};
|
|
175
|
-
//# sourceMappingURL=chunk-
|
|
186
|
+
//# sourceMappingURL=chunk-SRHDJF5B.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/errors.ts"],"sourcesContent":["import { KoraError } from '@korajs/core'\n\n/**\n * Thrown when a query is invalid (bad field names, invalid operators, etc.).\n */\nexport class QueryError extends KoraError {\n\tconstructor(message: string, context?: Record<string, unknown>) {\n\t\tsuper(message, 'QUERY_ERROR', context)\n\t\tthis.name = 'QueryError'\n\t}\n}\n\n/**\n * Thrown when a record is not found by ID (findById, update, delete on missing record).\n */\nexport class RecordNotFoundError extends KoraError {\n\tconstructor(collection: string, recordId: string) {\n\t\tsuper(`Record \"${recordId}\" not found in collection \"${collection}\"`, 'RECORD_NOT_FOUND', {\n\t\t\tcollection,\n\t\t\trecordId,\n\t\t})\n\t\tthis.name = 'RecordNotFoundError'\n\t}\n}\n\n/**\n * Thrown inside `applyRemoteOperation` when the row's version state changed\n * between the caller's snapshot read and the guarded write (a concurrent local\n * mutation landed in the window). The transaction is rolled back with nothing\n * written; the caller re-reads fresh state, recomputes its merge, and retries.\n * This is the optimistic-concurrency guard that keeps merge-engine results\n * (richtext, add-wins arrays, constraint resolutions) from clobbering newer\n * local edits they never saw.\n */\nexport class OptimisticLockError extends KoraError {\n\tconstructor(collection: string, recordId: string) {\n\t\tsuper(\n\t\t\t`Row version changed while merging record \"${recordId}\" in \"${collection}\"; retry with fresh state`,\n\t\t\t'OPTIMISTIC_LOCK',\n\t\t\t{ collection, recordId },\n\t\t)\n\t\tthis.name = 'OptimisticLockError'\n\t}\n}\n\n/**\n * Thrown when a storage adapter operation fails.\n */\nexport class AdapterError extends KoraError {\n\tconstructor(message: string, context?: Record<string, unknown>) {\n\t\tsuper(message, 'ADAPTER_ERROR', context)\n\t\tthis.name = 'AdapterError'\n\t}\n}\n\n/**\n * Thrown when a write reaches a storage adapter that could not obtain durable\n * storage (it opened, or was promoted to leader, on a non-persistent in-memory\n * database). Kora refuses such writes instead of accepting data that would be\n * lost on reload. Reads still work.\n *\n * Fix: close other tabs or apps using the same origin's OPFS storage and reopen,\n * or opt in explicitly with `allowNonDurable: true` if in-memory storage is\n * acceptable for this app.\n */\nexport class StorageDurabilityError extends KoraError {\n\tconstructor(dbName: string, phase: 'open' | 'promotion', reason: string) {\n\t\tsuper(\n\t\t\t`Database \"${dbName}\" has no durable storage (${reason} during ${phase}); writes are refused so they are not silently lost on reload. Close other tabs or apps using this origin's storage and reload, or set allowNonDurable: true to accept in-memory storage.`,\n\t\t\t'STORAGE_DURABILITY_LOST',\n\t\t\t{ dbName, phase, reason },\n\t\t)\n\t\tthis.name = 'StorageDurabilityError'\n\t}\n}\n\n/**\n * Thrown when the store opens a database a NEWER build of the app already migrated: its\n * stored schema version is above this code's `schema.version` (RT-109). Running older\n * code against it would write operations of the old schema into the new one, or fail\n * part-way, so the store refuses before changing anything and emits `store:schema-ahead`.\n *\n * Fix: run the newer build (reload while online, or accept the app's update prompt).\n * Never lower `schema.version` in a deployed app.\n */\nexport class SchemaVersionAheadError extends KoraError {\n\tconstructor(\n\t\tpublic readonly dbName: string,\n\t\tpublic readonly storedVersion: number,\n\t\tpublic readonly codeVersion: number,\n\t) {\n\t\tsuper(\n\t\t\t`Database \"${dbName}\" is at schema version ${storedVersion}, newer than this build's schema version ${codeVersion}: a newer build of the app already migrated it. It was not opened and nothing in it changed. Run the newer build (reload while online, or accept the update); never lower schema.version in a deployed app.`,\n\t\t\t'SCHEMA_VERSION_AHEAD',\n\t\t\t{ dbName, storedVersion, codeVersion },\n\t\t)\n\t\tthis.name = 'SchemaVersionAheadError'\n\t}\n}\n\n/**\n * Thrown when an operation is attempted on a store that has not been opened.\n */\nexport class StoreNotOpenError extends KoraError {\n\tconstructor() {\n\t\tsuper('Store is not open. Call store.open() before performing operations.', 'STORE_NOT_OPEN')\n\t\tthis.name = 'StoreNotOpenError'\n\t}\n}\n\n/**\n * Thrown when the Web Worker fails to initialize (WASM load failure, OPFS unavailable, etc.).\n */\nexport class WorkerInitError extends KoraError {\n\tconstructor(message: string, context?: Record<string, unknown>) {\n\t\tsuper(`Worker initialization failed: ${message}`, 'WORKER_INIT_ERROR', context)\n\t\tthis.name = 'WorkerInitError'\n\t}\n}\n\n/**\n * Thrown when the Web Worker does not respond within the configured timeout.\n */\nexport class WorkerTimeoutError extends KoraError {\n\tconstructor(operation: string, timeoutMs: number) {\n\t\tsuper(\n\t\t\t`Worker did not respond within ${timeoutMs}ms for operation \"${operation}\"`,\n\t\t\t'WORKER_TIMEOUT',\n\t\t\t{ operation, timeoutMs },\n\t\t)\n\t\tthis.name = 'WorkerTimeoutError'\n\t}\n}\n\n/**\n * Thrown when a follower tab cannot reach a live leader tab over the multi-tab\n * broadcast channel (the leader tab was closed, crashed, or is unresponsive).\n * Distinct from {@link WorkerTimeoutError}: this fails fast on a confirmed-absent\n * leader instead of waiting out the full RPC timeout.\n */\nexport class NoLeaderError extends KoraError {\n\tconstructor(\n\t\toperation: string,\n\t\tmessage = `No live leader tab is answering multi-tab storage RPC for operation \"${operation}\"`,\n\t\tcode = 'NO_LEADER',\n\t\tcontext: Record<string, unknown> = {},\n\t) {\n\t\tsuper(message, code, { operation, ...context })\n\t\tthis.name = 'NoLeaderError'\n\t}\n}\n\n/**\n * Thrown to a follower tab's pending storage requests when the leader tab that\n * was answering stopped sending heartbeats (hung, frozen, or suspended). The\n * request may or may not have been applied: retry it with the same `requestId`\n * (in the error context) and the leader de-duplicates it. Retriable.\n */\nexport class LeaderUnresponsiveError extends NoLeaderError {\n\tconstructor(operation: string, silentMs: number, requestId: string) {\n\t\tsuper(\n\t\t\toperation,\n\t\t\t`The leader tab stopped responding (no heartbeat for ${silentMs}ms) during \"${operation}\". The request may or may not have been applied; retry it with the same requestId and the leader de-duplicates it.`,\n\t\t\t'LEADER_UNRESPONSIVE',\n\t\t\t{ silentMs, requestId, retriable: true },\n\t\t)\n\t\tthis.name = 'LeaderUnresponsiveError'\n\t}\n}\n\n/**\n * Thrown to pending storage requests when their bridge was torn down, typically\n * because this tab was just promoted to leader or the leader handed over.\n * Retriable: the next attempt goes to the new leader.\n */\nexport class BridgeTerminatedError extends KoraError {\n\tconstructor(operation: string, reason: string) {\n\t\tsuper(\n\t\t\t`Storage request \"${operation}\" was interrupted (${reason}); retry it against the new storage leader.`,\n\t\t\t'BRIDGE_TERMINATED',\n\t\t\t{ operation, reason, retriable: true },\n\t\t)\n\t\tthis.name = 'BridgeTerminatedError'\n\t}\n}\n\n/** Thrown when a storage request is cancelled through its `AbortSignal`. */\nexport class RequestAbortedError extends KoraError {\n\tconstructor(operation: string, requestId: string) {\n\t\tsuper(\n\t\t\t`Storage request \"${operation}\" was aborted by the caller. A write may already have reached the leader; retry with the same requestId to stay idempotent.`,\n\t\t\t'REQUEST_ABORTED',\n\t\t\t{ operation, requestId },\n\t\t)\n\t\tthis.name = 'RequestAbortedError'\n\t}\n}\n\n/**\n * Thrown by `deleteDatabase()` when the database is open in some tab or worker\n * on this origin. Close it everywhere (or call `app.close()`) and retry.\n */\nexport class StorageInUseError extends KoraError {\n\tconstructor(dbName: string) {\n\t\tsuper(\n\t\t\t`Database \"${dbName}\" is open in a tab on this origin; close it before deleting it.`,\n\t\t\t'STORAGE_IN_USE',\n\t\t\t{ dbName },\n\t\t)\n\t\tthis.name = 'StorageInUseError'\n\t}\n}\n\n/**\n * Thrown by `deleteDatabase()` when the database still holds operations that\n * never reached the server. Sync first, or pass `{ force: true }` to discard them.\n */\nexport class UnsyncedDataError extends KoraError {\n\tconstructor(dbName: string) {\n\t\tsuper(\n\t\t\t`Database \"${dbName}\" has unsynced operations; deleting it would lose them. Sync first, or pass { force: true } to discard them deliberately.`,\n\t\t\t'UNSYNCED_DATA',\n\t\t\t{ dbName },\n\t\t)\n\t\tthis.name = 'UnsyncedDataError'\n\t}\n}\n\n/**\n * Thrown when a database's data lives in a storage backend this runtime cannot\n * read (for example it was written to OPFS, and OPFS is unavailable now). Kora\n * refuses to start an empty, disjoint copy rather than hide the existing data.\n */\nexport class StorageBackendMismatchError extends KoraError {\n\tconstructor(dbName: string, recorded: string, requested: string, reason: string) {\n\t\tsuper(\n\t\t\t`Database \"${dbName}\" is stored in ${recorded}, but this session can only use ${requested} (${reason}). Kora will not start a separate empty copy; reload in a browser profile where ${recorded} is available.`,\n\t\t\t'STORAGE_BACKEND_MISMATCH',\n\t\t\t{ dbName, recorded, requested, reason },\n\t\t)\n\t\tthis.name = 'StorageBackendMismatchError'\n\t}\n}\n\n/**\n * Thrown when IndexedDB persistence operations fail (serialize/deserialize).\n */\nexport class PersistenceError extends KoraError {\n\tconstructor(message: string, context?: Record<string, unknown>) {\n\t\tsuper(`Persistence error: ${message}`, 'PERSISTENCE_ERROR', context)\n\t\tthis.name = 'PersistenceError'\n\t}\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/errors.ts"],"sourcesContent":["import { KoraError } from '@korajs/core'\n\n/**\n * Thrown when a query is invalid (bad field names, invalid operators, etc.).\n */\nexport class QueryError extends KoraError {\n\tconstructor(message: string, context?: Record<string, unknown>) {\n\t\tsuper(message, 'QUERY_ERROR', context)\n\t\tthis.name = 'QueryError'\n\t}\n}\n\n/**\n * Thrown when a record is not found by ID (findById, update, delete on missing record).\n */\nexport class RecordNotFoundError extends KoraError {\n\tconstructor(collection: string, recordId: string) {\n\t\tsuper(`Record \"${recordId}\" not found in collection \"${collection}\"`, 'RECORD_NOT_FOUND', {\n\t\t\tcollection,\n\t\t\trecordId,\n\t\t})\n\t\tthis.name = 'RecordNotFoundError'\n\t}\n}\n\n/**\n * Thrown inside `applyRemoteOperation` when the row's version state changed\n * between the caller's snapshot read and the guarded write (a concurrent local\n * mutation landed in the window). The transaction is rolled back with nothing\n * written; the caller re-reads fresh state, recomputes its merge, and retries.\n * This is the optimistic-concurrency guard that keeps merge-engine results\n * (richtext, add-wins arrays, constraint resolutions) from clobbering newer\n * local edits they never saw.\n */\nexport class OptimisticLockError extends KoraError {\n\tconstructor(collection: string, recordId: string) {\n\t\tsuper(\n\t\t\t`Row version changed while merging record \"${recordId}\" in \"${collection}\"; retry with fresh state`,\n\t\t\t'OPTIMISTIC_LOCK',\n\t\t\t{ collection, recordId },\n\t\t)\n\t\tthis.name = 'OptimisticLockError'\n\t}\n}\n\n/**\n * Thrown when a storage adapter operation fails.\n */\nexport class AdapterError extends KoraError {\n\tconstructor(message: string, context?: Record<string, unknown>) {\n\t\tsuper(message, 'ADAPTER_ERROR', context)\n\t\tthis.name = 'AdapterError'\n\t}\n}\n\n/**\n * Thrown when a write reaches a storage adapter that could not obtain durable\n * storage (it opened, or was promoted to leader, on a non-persistent in-memory\n * database). Kora refuses such writes instead of accepting data that would be\n * lost on reload. Reads still work.\n *\n * Fix: close other tabs or apps using the same origin's OPFS storage and reopen,\n * or opt in explicitly with `allowNonDurable: true` if in-memory storage is\n * acceptable for this app.\n */\nexport class StorageDurabilityError extends KoraError {\n\tconstructor(dbName: string, phase: 'open' | 'promotion', reason: string) {\n\t\tsuper(\n\t\t\t`Database \"${dbName}\" has no durable storage (${reason} during ${phase}); writes are refused so they are not silently lost on reload. Close other tabs or apps using this origin's storage and reload, or set allowNonDurable: true to accept in-memory storage.`,\n\t\t\t'STORAGE_DURABILITY_LOST',\n\t\t\t{ dbName, phase, reason },\n\t\t)\n\t\tthis.name = 'StorageDurabilityError'\n\t}\n}\n\n/**\n * Thrown when the store opens a database a NEWER build of the app already migrated: its\n * stored schema version is above this code's `schema.version` (RT-109). Running older\n * code against it would write operations of the old schema into the new one, or fail\n * part-way, so the store refuses before changing anything and emits `store:schema-ahead`.\n *\n * Fix: run the newer build (reload while online, or accept the app's update prompt).\n * Never lower `schema.version` in a deployed app.\n */\nexport class SchemaVersionAheadError extends KoraError {\n\tconstructor(\n\t\tpublic readonly dbName: string,\n\t\tpublic readonly storedVersion: number,\n\t\tpublic readonly codeVersion: number,\n\t) {\n\t\tsuper(\n\t\t\t`Database \"${dbName}\" is at schema version ${storedVersion}, newer than this build's schema version ${codeVersion}: a newer build of the app already migrated it. It was not opened and nothing in it changed. Run the newer build (reload while online, or accept the update); never lower schema.version in a deployed app.`,\n\t\t\t'SCHEMA_VERSION_AHEAD',\n\t\t\t{ dbName, storedVersion, codeVersion },\n\t\t)\n\t\tthis.name = 'SchemaVersionAheadError'\n\t}\n}\n\n/**\n * Thrown when an operation is attempted on a store that has not been opened.\n */\nexport class StoreNotOpenError extends KoraError {\n\tconstructor() {\n\t\tsuper('Store is not open. Call store.open() before performing operations.', 'STORE_NOT_OPEN')\n\t\tthis.name = 'StoreNotOpenError'\n\t}\n}\n\n/**\n * Thrown when the Web Worker fails to initialize (WASM load failure, OPFS unavailable, etc.).\n */\nexport class WorkerInitError extends KoraError {\n\tconstructor(message: string, context?: Record<string, unknown>) {\n\t\tsuper(`Worker initialization failed: ${message}`, 'WORKER_INIT_ERROR', context)\n\t\tthis.name = 'WorkerInitError'\n\t}\n}\n\n/**\n * Thrown when the Web Worker does not respond within the configured timeout.\n */\nexport class WorkerTimeoutError extends KoraError {\n\tconstructor(operation: string, timeoutMs: number) {\n\t\tsuper(\n\t\t\t`Worker did not respond within ${timeoutMs}ms for operation \"${operation}\"`,\n\t\t\t'WORKER_TIMEOUT',\n\t\t\t{ operation, timeoutMs },\n\t\t)\n\t\tthis.name = 'WorkerTimeoutError'\n\t}\n}\n\n/**\n * Thrown when a follower tab cannot reach a live leader tab over the multi-tab\n * broadcast channel (the leader tab was closed, crashed, or is unresponsive).\n * Distinct from {@link WorkerTimeoutError}: this fails fast on a confirmed-absent\n * leader instead of waiting out the full RPC timeout.\n */\nexport class NoLeaderError extends KoraError {\n\tconstructor(\n\t\toperation: string,\n\t\tmessage = `No live leader tab is answering multi-tab storage RPC for operation \"${operation}\"`,\n\t\tcode = 'NO_LEADER',\n\t\tcontext: Record<string, unknown> = {},\n\t) {\n\t\tsuper(message, code, { operation, ...context })\n\t\tthis.name = 'NoLeaderError'\n\t}\n}\n\n/**\n * Thrown to a follower tab's pending storage requests when the leader tab that\n * was answering stopped sending heartbeats (hung, frozen, or suspended). The\n * request may or may not have been applied: retry it with the same `requestId`\n * (in the error context) and the leader de-duplicates it. Retriable.\n */\nexport class LeaderUnresponsiveError extends NoLeaderError {\n\tconstructor(operation: string, silentMs: number, requestId: string) {\n\t\tsuper(\n\t\t\toperation,\n\t\t\t`The leader tab stopped responding (no heartbeat for ${silentMs}ms) during \"${operation}\". The request may or may not have been applied; retry it with the same requestId and the leader de-duplicates it.`,\n\t\t\t'LEADER_UNRESPONSIVE',\n\t\t\t{ silentMs, requestId, retriable: true },\n\t\t)\n\t\tthis.name = 'LeaderUnresponsiveError'\n\t}\n}\n\n/**\n * Thrown to pending storage requests when their bridge was torn down, typically\n * because this tab was just promoted to leader or the leader handed over.\n * Retriable: the next attempt goes to the new leader.\n */\nexport class BridgeTerminatedError extends KoraError {\n\tconstructor(operation: string, reason: string) {\n\t\tsuper(\n\t\t\t`Storage request \"${operation}\" was interrupted (${reason}); retry it against the new storage leader.`,\n\t\t\t'BRIDGE_TERMINATED',\n\t\t\t{ operation, reason, retriable: true },\n\t\t)\n\t\tthis.name = 'BridgeTerminatedError'\n\t}\n}\n\n/** Thrown when a storage request is cancelled through its `AbortSignal`. */\nexport class RequestAbortedError extends KoraError {\n\tconstructor(operation: string, requestId: string) {\n\t\tsuper(\n\t\t\t`Storage request \"${operation}\" was aborted by the caller. A write may already have reached the leader; retry with the same requestId to stay idempotent.`,\n\t\t\t'REQUEST_ABORTED',\n\t\t\t{ operation, requestId },\n\t\t)\n\t\tthis.name = 'RequestAbortedError'\n\t}\n}\n\n/**\n * Thrown by `deleteDatabase()` when the database is open in some tab or worker\n * on this origin. Close it everywhere (or call `app.close()`) and retry.\n */\nexport class StorageInUseError extends KoraError {\n\tconstructor(dbName: string) {\n\t\tsuper(\n\t\t\t`Database \"${dbName}\" is open in a tab on this origin; close it before deleting it.`,\n\t\t\t'STORAGE_IN_USE',\n\t\t\t{ dbName },\n\t\t)\n\t\tthis.name = 'StorageInUseError'\n\t}\n}\n\n/**\n * Thrown by `deleteDatabase()` when the database still holds operations that\n * never reached the server. Sync first, or pass `{ force: true }` to discard them.\n */\nexport class UnsyncedDataError extends KoraError {\n\tconstructor(dbName: string) {\n\t\tsuper(\n\t\t\t`Database \"${dbName}\" has unsynced operations; deleting it would lose them. Sync first, or pass { force: true } to discard them deliberately.`,\n\t\t\t'UNSYNCED_DATA',\n\t\t\t{ dbName },\n\t\t)\n\t\tthis.name = 'UnsyncedDataError'\n\t}\n}\n\n/**\n * Thrown when a database's data lives in a storage backend this runtime cannot\n * read (for example it was written to OPFS, and OPFS is unavailable now). Kora\n * refuses to start an empty, disjoint copy rather than hide the existing data.\n */\nexport class StorageBackendMismatchError extends KoraError {\n\tconstructor(dbName: string, recorded: string, requested: string, reason: string) {\n\t\tsuper(\n\t\t\t`Database \"${dbName}\" is stored in ${recorded}, but this session can only use ${requested} (${reason}). Kora will not start a separate empty copy; reload in a browser profile where ${recorded} is available.`,\n\t\t\t'STORAGE_BACKEND_MISMATCH',\n\t\t\t{ dbName, recorded, requested, reason },\n\t\t)\n\t\tthis.name = 'StorageBackendMismatchError'\n\t}\n}\n\n/**\n * Thrown when IndexedDB persistence operations fail (serialize/deserialize).\n */\nexport class PersistenceError extends KoraError {\n\tconstructor(message: string, context?: Record<string, unknown>) {\n\t\tsuper(`Persistence error: ${message}`, 'PERSISTENCE_ERROR', context)\n\t\tthis.name = 'PersistenceError'\n\t}\n}\n\n/**\n * Thrown by a local write while the store's pinned node id (an auth device id) belongs\n * to another user than the one signed in (F9): a browser two users shared. The write\n * could only be stored under the owner's node and would later upload as the owner's.\n */\nexport class NodeOwnedByAnotherUserError extends KoraError {\n\tconstructor(nodeId: string, principal: string | null) {\n\t\tsuper(\n\t\t\t\"This device belongs to another account, so changes made while this account is signed in here cannot be saved: they would be uploaded as the device owner's. Sign in as the device's owner, or use this account on its own device.\",\n\t\t\t'NODE_OWNED_BY_ANOTHER_USER',\n\t\t\t{ nodeId, principal },\n\t\t)\n\t\tthis.name = 'NodeOwnedByAnotherUserError'\n\t}\n}\n"],"mappings":"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","names":[]}
|
|
@@ -3,10 +3,10 @@ import {
|
|
|
3
3
|
FOLD_BASE_TABLE,
|
|
4
4
|
FOLD_SNAPSHOT_TABLE,
|
|
5
5
|
SEQ_CONFLICTS_TABLE
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-KTVNANUH.js";
|
|
7
7
|
import {
|
|
8
8
|
TERMINAL_REJECTIONS_TABLE
|
|
9
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-QW4UU7NG.js";
|
|
10
10
|
import {
|
|
11
11
|
deserializeOperation,
|
|
12
12
|
deserializeOperationWithCollection,
|
|
@@ -756,7 +756,7 @@ function toBytes(value) {
|
|
|
756
756
|
return bytes;
|
|
757
757
|
}
|
|
758
758
|
async function restoreBackup(adapter, schema, data, options) {
|
|
759
|
-
const { Store } = await import("./store-
|
|
759
|
+
const { Store } = await import("./store-HNGX4KTT.js");
|
|
760
760
|
const store = new Store({ schema, adapter });
|
|
761
761
|
await store.open();
|
|
762
762
|
try {
|
|
@@ -807,4 +807,4 @@ export {
|
|
|
807
807
|
convertBackupV1,
|
|
808
808
|
restoreBackup
|
|
809
809
|
};
|
|
810
|
-
//# sourceMappingURL=chunk-
|
|
810
|
+
//# sourceMappingURL=chunk-UC2LGFPV.js.map
|