@korajs/core 1.0.0-beta.1 → 1.0.0-beta.10
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/bindings/index.d.cts +6 -3
- package/dist/bindings/index.d.ts +6 -3
- package/dist/{build-scope-map-DtTr9C7A.d.cts → build-scope-map-B8dpCt6P.d.cts} +1 -1
- package/dist/{build-scope-map-DvOHbYjn.d.ts → build-scope-map-CDpIGOsf.d.ts} +1 -1
- package/dist/{events-CqDkr1LN.d.cts → events-3z0Tema3.d.cts} +68 -1
- package/dist/{events-CqDkr1LN.d.ts → events-3z0Tema3.d.ts} +68 -1
- package/dist/index.cjs +129 -36
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +86 -6
- package/dist/index.d.ts +86 -6
- package/dist/index.js +126 -36
- package/dist/index.js.map +1 -1
- package/dist/internal.d.cts +2 -2
- package/dist/internal.d.ts +2 -2
- package/dist/{operation-CFoY-ZmB.d.cts → operation-BvBgl-4c.d.cts} +1 -1
- package/dist/{operation-BJtu18Fi.d.ts → operation-ClhEbhoV.d.ts} +1 -1
- package/package.json +1 -1
package/dist/index.d.cts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { R as RandomSource, B as BlobRef, C as CollectionDefinition, A as AtomicOpType, a as AtomicOp, F as FieldBuilder, b as CustomResolver, M as MigrationDefinition, S as SchemaDefinition, c as RelationDefinition, O as OperationType, E as EnumFieldBuilder, J as JsonFieldBuilder, d as ArrayFieldBuilder, e as FieldKind, f as Operation, V as VersionVector, g as MigrationStep, h as StateMachineConstraint, T as TransitionMap, i as TransitionValidationResult } from './events-
|
|
2
|
-
export { j as CONNECTION_QUALITIES, k as ConnectionQuality, l as Constraint, m as FieldDescriptor, n as FieldMergeStrategy, H as HLCTimestamp, K as KoraEvent, o as KoraEventByType, p as KoraEventEmitter, q as KoraEventListener, r as KoraEventType, s as MERGE_STRATEGIES, t as MergeStrategy, u as MergeTrace, v as MigrationBuilder, w as OnDeleteAction, x as OperationInput, y as RelationType, z as RollbackBuilder, D as SecretMode, G as SequenceConfig, I as StateMachineDefinition, L as SyncDiagnosticsSnapshot, N as SyncRuleDefinition, P as TimeSource, Q as migrate, U as t } from './events-
|
|
3
|
-
export { H as HybridLogicalClock, M as MAX_LOGICAL, c as createOperation, i as isValidOperation, v as verifyOperationIntegrity } from './operation-
|
|
4
|
-
export { S as ScopeMap, b as buildScopeMap } from './build-scope-map-
|
|
1
|
+
import { R as RandomSource, B as BlobRef, C as CollectionDefinition, A as AtomicOpType, a as AtomicOp, F as FieldBuilder, b as CustomResolver, M as MigrationDefinition, S as SchemaDefinition, c as RelationDefinition, O as OperationType, E as EnumFieldBuilder, J as JsonFieldBuilder, d as ArrayFieldBuilder, e as FieldKind, f as Operation, V as VersionVector, g as MigrationStep, h as StateMachineConstraint, T as TransitionMap, i as TransitionValidationResult } from './events-3z0Tema3.cjs';
|
|
2
|
+
export { j as CONNECTION_QUALITIES, k as ConnectionQuality, l as Constraint, m as FieldDescriptor, n as FieldMergeStrategy, H as HLCTimestamp, K as KoraEvent, o as KoraEventByType, p as KoraEventEmitter, q as KoraEventListener, r as KoraEventType, s as MERGE_STRATEGIES, t as MergeStrategy, u as MergeTrace, v as MigrationBuilder, w as OnDeleteAction, x as OperationInput, y as RelationType, z as RollbackBuilder, D as SecretMode, G as SequenceConfig, I as StateMachineDefinition, L as SyncDiagnosticsSnapshot, N as SyncRuleDefinition, P as TimeSource, Q as migrate, U as t } from './events-3z0Tema3.cjs';
|
|
3
|
+
export { H as HybridLogicalClock, M as MAX_LOGICAL, c as createOperation, i as isValidOperation, v as verifyOperationIntegrity } from './operation-BvBgl-4c.cjs';
|
|
4
|
+
export { S as ScopeMap, b as buildScopeMap } from './build-scope-map-B8dpCt6P.cjs';
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* Base error class for all Kora errors.
|
|
@@ -359,6 +359,20 @@ interface AtomicOpSentinel {
|
|
|
359
359
|
* @returns true if the value is an AtomicOpSentinel
|
|
360
360
|
*/
|
|
361
361
|
declare function isAtomicOp(value: unknown): value is AtomicOpSentinel;
|
|
362
|
+
/**
|
|
363
|
+
* Apply a plain atomic op (`{ type, value }`) to a current value, producing the
|
|
364
|
+
* concrete result. This is the single source of truth for atomic-op semantics:
|
|
365
|
+
* {@link resolveAtomicOp} (sentinel form, used on the write path) delegates here,
|
|
366
|
+
* and the server's materialization replays stored atomic ops through it, so the
|
|
367
|
+
* server's materialized view composes concurrent atomic writes identically to the
|
|
368
|
+
* client. Folding this over a field's writes in HLC order yields, for a same-type
|
|
369
|
+
* atomic chain, exactly the composed intent (sum of increments, max of maxes, ...).
|
|
370
|
+
*
|
|
371
|
+
* @param currentValue - The current value of the field
|
|
372
|
+
* @param atomicOp - The atomic op to apply (type + operand)
|
|
373
|
+
* @returns The resulting concrete value
|
|
374
|
+
*/
|
|
375
|
+
declare function applyAtomicOp(currentValue: unknown, atomicOp: AtomicOp): unknown;
|
|
362
376
|
/**
|
|
363
377
|
* Resolve an atomic op sentinel against a current value to produce a concrete result.
|
|
364
378
|
*
|
|
@@ -437,6 +451,40 @@ declare const op: {
|
|
|
437
451
|
*/
|
|
438
452
|
declare function toAtomicOp(sentinel: AtomicOpSentinel): AtomicOp;
|
|
439
453
|
|
|
454
|
+
/** One operation's contribution to a record's materialized state. */
|
|
455
|
+
interface ReplayOperation {
|
|
456
|
+
type: string;
|
|
457
|
+
data: Record<string, unknown> | null;
|
|
458
|
+
/**
|
|
459
|
+
* Atomic-op intents carried by this operation, per field. When present, a field
|
|
460
|
+
* is composed (rather than overwritten) if the previous writer on that field was
|
|
461
|
+
* an atomic op of the same type. Absent means every field resolves by
|
|
462
|
+
* last-write-wins.
|
|
463
|
+
*/
|
|
464
|
+
atomicOps?: Record<string, AtomicOp> | null;
|
|
465
|
+
}
|
|
466
|
+
/**
|
|
467
|
+
* Replay a list of operations (MUST be pre-sorted in HLC total order —
|
|
468
|
+
* wallTime, then logical, then nodeId) to produce the current state of a single
|
|
469
|
+
* record, composing atomic-op intents.
|
|
470
|
+
*
|
|
471
|
+
* Per field, an atomic op composes onto the running value only when the field's
|
|
472
|
+
* previous writer was an atomic op of the SAME type (a same-type atomic chain:
|
|
473
|
+
* concurrent increments sum, maxes take the max, appends accumulate); any other
|
|
474
|
+
* write, including the first atomic write after a plain set, resolves by
|
|
475
|
+
* last-write-wins on the resolved value. Because operations are folded in HLC order,
|
|
476
|
+
* the current op always wins that last-write-wins comparison, so the result is
|
|
477
|
+
* deterministic and identical on every replica that folds the same operation set.
|
|
478
|
+
*
|
|
479
|
+
* This is the single definition of atomic materialization shared by the client
|
|
480
|
+
* (`@korajs/store` apply pipeline) and the server (`@korajs/server` materialization),
|
|
481
|
+
* so both converge to the same value for any number of concurrent atomic writers.
|
|
482
|
+
*
|
|
483
|
+
* Returns the record field data (without `id`) or null if the record was deleted
|
|
484
|
+
* or never inserted.
|
|
485
|
+
*/
|
|
486
|
+
declare function replayOperationsForRecord(ops: ReplayOperation[]): Record<string, unknown> | null;
|
|
487
|
+
|
|
440
488
|
/**
|
|
441
489
|
* Canonical JSON-safe encoding of binary bytes stored inside `op.data` /
|
|
442
490
|
* `op.previousData` (currently richtext field values). A raw Uint8Array
|
|
@@ -605,6 +653,38 @@ declare function generateSQL(collectionName: string, collection: CollectionDefin
|
|
|
605
653
|
*/
|
|
606
654
|
declare function generateFullDDL(schema: SchemaDefinition): string[];
|
|
607
655
|
|
|
656
|
+
/**
|
|
657
|
+
* Quote a SQL identifier (table or column name) for safe interpolation into
|
|
658
|
+
* generated SQL.
|
|
659
|
+
*
|
|
660
|
+
* Uses standard double-quote quoting, which both SQLite and PostgreSQL honor,
|
|
661
|
+
* and doubles any embedded double-quote per the SQL standard. Quoting is what
|
|
662
|
+
* lets a valid JavaScript identifier — camelCase (`formResponses`), PascalCase
|
|
663
|
+
* (`UserProfiles`), or a word that happens to be a SQL keyword (`order`,
|
|
664
|
+
* `select`) — round-trip through generated DDL and queries without ever
|
|
665
|
+
* producing invalid or ambiguous SQL.
|
|
666
|
+
*
|
|
667
|
+
* Schema validation (see `validateCollectionName` / `validateFieldName`)
|
|
668
|
+
* already restricts identifiers to `[A-Za-z][A-Za-z0-9_]*`, so the escape here
|
|
669
|
+
* never actually fires in practice; it is defense in depth so this helper is
|
|
670
|
+
* correct for any input rather than only for pre-validated names.
|
|
671
|
+
*
|
|
672
|
+
* Consistency is the contract: every path that emits an identifier for a
|
|
673
|
+
* user-defined collection or field MUST go through this helper, because a
|
|
674
|
+
* quoted `CREATE TABLE "order"` paired with an unquoted `SELECT ... FROM order`
|
|
675
|
+
* would reintroduce the exact keyword-collision bug this guards against.
|
|
676
|
+
*
|
|
677
|
+
* @param name - The raw identifier (collection name, field/column name).
|
|
678
|
+
* @returns The identifier wrapped in double quotes, ready to splice into SQL.
|
|
679
|
+
*
|
|
680
|
+
* @example
|
|
681
|
+
* ```typescript
|
|
682
|
+
* quoteIdent('formResponses') // => '"formResponses"'
|
|
683
|
+
* quoteIdent('order') // => '"order"'
|
|
684
|
+
* ```
|
|
685
|
+
*/
|
|
686
|
+
declare function quoteIdent(name: string): string;
|
|
687
|
+
|
|
608
688
|
/**
|
|
609
689
|
* Validates a record's data against a collection's field definitions.
|
|
610
690
|
* Applies defaults, rejects auto fields, and type-checks each value.
|
|
@@ -854,7 +934,7 @@ declare function migrationStepsToSQL(steps: readonly MigrationStep[]): string[];
|
|
|
854
934
|
*
|
|
855
935
|
* const rollbackSQL = rollbackStepsToSQL(migration)
|
|
856
936
|
* // ['DROP INDEX IF EXISTS idx_todos_priority',
|
|
857
|
-
* // 'ALTER TABLE todos DROP COLUMN priority']
|
|
937
|
+
* // 'ALTER TABLE "todos" DROP COLUMN "priority"']
|
|
858
938
|
* ```
|
|
859
939
|
*/
|
|
860
940
|
declare function rollbackStepsToSQL(migration: MigrationDefinition): string[];
|
|
@@ -1045,4 +1125,4 @@ interface ProtoOutput {
|
|
|
1045
1125
|
*/
|
|
1046
1126
|
declare function generateProtoDefinitions(schema: SchemaDefinition): ProtoOutput;
|
|
1047
1127
|
|
|
1048
|
-
export { APPLY_FAILURE_CODES, APPLY_RESULTS, AppNotReadyError, type ApplyFailureReason, type ApplyResult, ArrayFieldBuilder, AtomicOp, type AtomicOpSentinel, AtomicOpType, BlobRef, type BlobRefMetadata, CausalTracker, ClockDriftError, CollectionDefinition, type CollectionInput, type ConstraintInput, CustomResolver, EnumFieldBuilder, FieldBuilder, FieldKind, type FieldKindToType, type InferFieldType, type InferInsertInput, type InferRecord, type InferUpdateInput, InvalidTimestampError, KORA_ERROR_FIX_SUGGESTIONS, type KoraBytesValue, KoraError, MergeConflictError, MigrationDefinition, MigrationRollbackError, MigrationStep, MissingSecretKeyError, Operation, OperationError, type OperationLog, type OperationTransform, OperationType, type ProtoOutput, RandomSource, RelationDefinition, type RelationInput, RemoteClockDriftError, type ReversibleMigration, SchemaDefinition, type SchemaInput, SchemaValidationError, type SecretKeyProvider, StateMachineConstraint, type StateMachineInput, StorageError, SyncError, TransitionMap, TransitionValidationResult, type TypedSchemaDefinition, VersionVector, advanceVector, applyOperationTransforms, base64ToBytes, buildStateMachineConstraints, bytesToBase64, canAutoRollback, collectSchemaScopeFields, collectSchemaScopeValueKeys, computeDelta, createBlobRef, createReversibleMigration, createVersionVector, decodeBytesFromOpData, decryptSecret, defaultApplyFailureReason, defaultSequenceFormat, defineSchema, deserializeVector, dominates, encodeBytesForOpData, encryptSecret, extractScopeValuesFromClaims, extractTimestamp, formatSequenceValue, generateFullDDL, generateProtoDefinitions, generateRollbackSteps, generateSQL, generateUUIDv7, getCollectionScopeBindings, getKoraErrorFix, getTransitionMap, hasSchemaSyncRules, hashBlob, hashSecret, isApplyFailure, isAtomicOp, isBlobRef, isCollectionSyncScoped, isKoraBytesValue, isLegacyNumericByteObject, isValidUUIDv7, mergeVectors, migrationStepsToSQL, op, resolveAtomicOp, revealSecret, rollbackStepsToSQL, serializeVector, toAtomicOp, transformSecretFieldsForWrite, validateRecord, validateTransition, vectorsEqual, verifySecret, verifySecretValue };
|
|
1128
|
+
export { APPLY_FAILURE_CODES, APPLY_RESULTS, AppNotReadyError, type ApplyFailureReason, type ApplyResult, ArrayFieldBuilder, AtomicOp, type AtomicOpSentinel, AtomicOpType, BlobRef, type BlobRefMetadata, CausalTracker, ClockDriftError, CollectionDefinition, type CollectionInput, type ConstraintInput, CustomResolver, EnumFieldBuilder, FieldBuilder, FieldKind, type FieldKindToType, type InferFieldType, type InferInsertInput, type InferRecord, type InferUpdateInput, InvalidTimestampError, KORA_ERROR_FIX_SUGGESTIONS, type KoraBytesValue, KoraError, MergeConflictError, MigrationDefinition, MigrationRollbackError, MigrationStep, MissingSecretKeyError, Operation, OperationError, type OperationLog, type OperationTransform, OperationType, type ProtoOutput, RandomSource, RelationDefinition, type RelationInput, RemoteClockDriftError, type ReplayOperation, type ReversibleMigration, SchemaDefinition, type SchemaInput, SchemaValidationError, type SecretKeyProvider, StateMachineConstraint, type StateMachineInput, StorageError, SyncError, TransitionMap, TransitionValidationResult, type TypedSchemaDefinition, VersionVector, advanceVector, applyAtomicOp, applyOperationTransforms, base64ToBytes, buildStateMachineConstraints, bytesToBase64, canAutoRollback, collectSchemaScopeFields, collectSchemaScopeValueKeys, computeDelta, createBlobRef, createReversibleMigration, createVersionVector, decodeBytesFromOpData, decryptSecret, defaultApplyFailureReason, defaultSequenceFormat, defineSchema, deserializeVector, dominates, encodeBytesForOpData, encryptSecret, extractScopeValuesFromClaims, extractTimestamp, formatSequenceValue, generateFullDDL, generateProtoDefinitions, generateRollbackSteps, generateSQL, generateUUIDv7, getCollectionScopeBindings, getKoraErrorFix, getTransitionMap, hasSchemaSyncRules, hashBlob, hashSecret, isApplyFailure, isAtomicOp, isBlobRef, isCollectionSyncScoped, isKoraBytesValue, isLegacyNumericByteObject, isValidUUIDv7, mergeVectors, migrationStepsToSQL, op, quoteIdent, replayOperationsForRecord, resolveAtomicOp, revealSecret, rollbackStepsToSQL, serializeVector, toAtomicOp, transformSecretFieldsForWrite, validateRecord, validateTransition, vectorsEqual, verifySecret, verifySecretValue };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { R as RandomSource, B as BlobRef, C as CollectionDefinition, A as AtomicOpType, a as AtomicOp, F as FieldBuilder, b as CustomResolver, M as MigrationDefinition, S as SchemaDefinition, c as RelationDefinition, O as OperationType, E as EnumFieldBuilder, J as JsonFieldBuilder, d as ArrayFieldBuilder, e as FieldKind, f as Operation, V as VersionVector, g as MigrationStep, h as StateMachineConstraint, T as TransitionMap, i as TransitionValidationResult } from './events-
|
|
2
|
-
export { j as CONNECTION_QUALITIES, k as ConnectionQuality, l as Constraint, m as FieldDescriptor, n as FieldMergeStrategy, H as HLCTimestamp, K as KoraEvent, o as KoraEventByType, p as KoraEventEmitter, q as KoraEventListener, r as KoraEventType, s as MERGE_STRATEGIES, t as MergeStrategy, u as MergeTrace, v as MigrationBuilder, w as OnDeleteAction, x as OperationInput, y as RelationType, z as RollbackBuilder, D as SecretMode, G as SequenceConfig, I as StateMachineDefinition, L as SyncDiagnosticsSnapshot, N as SyncRuleDefinition, P as TimeSource, Q as migrate, U as t } from './events-
|
|
3
|
-
export { H as HybridLogicalClock, M as MAX_LOGICAL, c as createOperation, i as isValidOperation, v as verifyOperationIntegrity } from './operation-
|
|
4
|
-
export { S as ScopeMap, b as buildScopeMap } from './build-scope-map-
|
|
1
|
+
import { R as RandomSource, B as BlobRef, C as CollectionDefinition, A as AtomicOpType, a as AtomicOp, F as FieldBuilder, b as CustomResolver, M as MigrationDefinition, S as SchemaDefinition, c as RelationDefinition, O as OperationType, E as EnumFieldBuilder, J as JsonFieldBuilder, d as ArrayFieldBuilder, e as FieldKind, f as Operation, V as VersionVector, g as MigrationStep, h as StateMachineConstraint, T as TransitionMap, i as TransitionValidationResult } from './events-3z0Tema3.js';
|
|
2
|
+
export { j as CONNECTION_QUALITIES, k as ConnectionQuality, l as Constraint, m as FieldDescriptor, n as FieldMergeStrategy, H as HLCTimestamp, K as KoraEvent, o as KoraEventByType, p as KoraEventEmitter, q as KoraEventListener, r as KoraEventType, s as MERGE_STRATEGIES, t as MergeStrategy, u as MergeTrace, v as MigrationBuilder, w as OnDeleteAction, x as OperationInput, y as RelationType, z as RollbackBuilder, D as SecretMode, G as SequenceConfig, I as StateMachineDefinition, L as SyncDiagnosticsSnapshot, N as SyncRuleDefinition, P as TimeSource, Q as migrate, U as t } from './events-3z0Tema3.js';
|
|
3
|
+
export { H as HybridLogicalClock, M as MAX_LOGICAL, c as createOperation, i as isValidOperation, v as verifyOperationIntegrity } from './operation-ClhEbhoV.js';
|
|
4
|
+
export { S as ScopeMap, b as buildScopeMap } from './build-scope-map-CDpIGOsf.js';
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* Base error class for all Kora errors.
|
|
@@ -359,6 +359,20 @@ interface AtomicOpSentinel {
|
|
|
359
359
|
* @returns true if the value is an AtomicOpSentinel
|
|
360
360
|
*/
|
|
361
361
|
declare function isAtomicOp(value: unknown): value is AtomicOpSentinel;
|
|
362
|
+
/**
|
|
363
|
+
* Apply a plain atomic op (`{ type, value }`) to a current value, producing the
|
|
364
|
+
* concrete result. This is the single source of truth for atomic-op semantics:
|
|
365
|
+
* {@link resolveAtomicOp} (sentinel form, used on the write path) delegates here,
|
|
366
|
+
* and the server's materialization replays stored atomic ops through it, so the
|
|
367
|
+
* server's materialized view composes concurrent atomic writes identically to the
|
|
368
|
+
* client. Folding this over a field's writes in HLC order yields, for a same-type
|
|
369
|
+
* atomic chain, exactly the composed intent (sum of increments, max of maxes, ...).
|
|
370
|
+
*
|
|
371
|
+
* @param currentValue - The current value of the field
|
|
372
|
+
* @param atomicOp - The atomic op to apply (type + operand)
|
|
373
|
+
* @returns The resulting concrete value
|
|
374
|
+
*/
|
|
375
|
+
declare function applyAtomicOp(currentValue: unknown, atomicOp: AtomicOp): unknown;
|
|
362
376
|
/**
|
|
363
377
|
* Resolve an atomic op sentinel against a current value to produce a concrete result.
|
|
364
378
|
*
|
|
@@ -437,6 +451,40 @@ declare const op: {
|
|
|
437
451
|
*/
|
|
438
452
|
declare function toAtomicOp(sentinel: AtomicOpSentinel): AtomicOp;
|
|
439
453
|
|
|
454
|
+
/** One operation's contribution to a record's materialized state. */
|
|
455
|
+
interface ReplayOperation {
|
|
456
|
+
type: string;
|
|
457
|
+
data: Record<string, unknown> | null;
|
|
458
|
+
/**
|
|
459
|
+
* Atomic-op intents carried by this operation, per field. When present, a field
|
|
460
|
+
* is composed (rather than overwritten) if the previous writer on that field was
|
|
461
|
+
* an atomic op of the same type. Absent means every field resolves by
|
|
462
|
+
* last-write-wins.
|
|
463
|
+
*/
|
|
464
|
+
atomicOps?: Record<string, AtomicOp> | null;
|
|
465
|
+
}
|
|
466
|
+
/**
|
|
467
|
+
* Replay a list of operations (MUST be pre-sorted in HLC total order —
|
|
468
|
+
* wallTime, then logical, then nodeId) to produce the current state of a single
|
|
469
|
+
* record, composing atomic-op intents.
|
|
470
|
+
*
|
|
471
|
+
* Per field, an atomic op composes onto the running value only when the field's
|
|
472
|
+
* previous writer was an atomic op of the SAME type (a same-type atomic chain:
|
|
473
|
+
* concurrent increments sum, maxes take the max, appends accumulate); any other
|
|
474
|
+
* write, including the first atomic write after a plain set, resolves by
|
|
475
|
+
* last-write-wins on the resolved value. Because operations are folded in HLC order,
|
|
476
|
+
* the current op always wins that last-write-wins comparison, so the result is
|
|
477
|
+
* deterministic and identical on every replica that folds the same operation set.
|
|
478
|
+
*
|
|
479
|
+
* This is the single definition of atomic materialization shared by the client
|
|
480
|
+
* (`@korajs/store` apply pipeline) and the server (`@korajs/server` materialization),
|
|
481
|
+
* so both converge to the same value for any number of concurrent atomic writers.
|
|
482
|
+
*
|
|
483
|
+
* Returns the record field data (without `id`) or null if the record was deleted
|
|
484
|
+
* or never inserted.
|
|
485
|
+
*/
|
|
486
|
+
declare function replayOperationsForRecord(ops: ReplayOperation[]): Record<string, unknown> | null;
|
|
487
|
+
|
|
440
488
|
/**
|
|
441
489
|
* Canonical JSON-safe encoding of binary bytes stored inside `op.data` /
|
|
442
490
|
* `op.previousData` (currently richtext field values). A raw Uint8Array
|
|
@@ -605,6 +653,38 @@ declare function generateSQL(collectionName: string, collection: CollectionDefin
|
|
|
605
653
|
*/
|
|
606
654
|
declare function generateFullDDL(schema: SchemaDefinition): string[];
|
|
607
655
|
|
|
656
|
+
/**
|
|
657
|
+
* Quote a SQL identifier (table or column name) for safe interpolation into
|
|
658
|
+
* generated SQL.
|
|
659
|
+
*
|
|
660
|
+
* Uses standard double-quote quoting, which both SQLite and PostgreSQL honor,
|
|
661
|
+
* and doubles any embedded double-quote per the SQL standard. Quoting is what
|
|
662
|
+
* lets a valid JavaScript identifier — camelCase (`formResponses`), PascalCase
|
|
663
|
+
* (`UserProfiles`), or a word that happens to be a SQL keyword (`order`,
|
|
664
|
+
* `select`) — round-trip through generated DDL and queries without ever
|
|
665
|
+
* producing invalid or ambiguous SQL.
|
|
666
|
+
*
|
|
667
|
+
* Schema validation (see `validateCollectionName` / `validateFieldName`)
|
|
668
|
+
* already restricts identifiers to `[A-Za-z][A-Za-z0-9_]*`, so the escape here
|
|
669
|
+
* never actually fires in practice; it is defense in depth so this helper is
|
|
670
|
+
* correct for any input rather than only for pre-validated names.
|
|
671
|
+
*
|
|
672
|
+
* Consistency is the contract: every path that emits an identifier for a
|
|
673
|
+
* user-defined collection or field MUST go through this helper, because a
|
|
674
|
+
* quoted `CREATE TABLE "order"` paired with an unquoted `SELECT ... FROM order`
|
|
675
|
+
* would reintroduce the exact keyword-collision bug this guards against.
|
|
676
|
+
*
|
|
677
|
+
* @param name - The raw identifier (collection name, field/column name).
|
|
678
|
+
* @returns The identifier wrapped in double quotes, ready to splice into SQL.
|
|
679
|
+
*
|
|
680
|
+
* @example
|
|
681
|
+
* ```typescript
|
|
682
|
+
* quoteIdent('formResponses') // => '"formResponses"'
|
|
683
|
+
* quoteIdent('order') // => '"order"'
|
|
684
|
+
* ```
|
|
685
|
+
*/
|
|
686
|
+
declare function quoteIdent(name: string): string;
|
|
687
|
+
|
|
608
688
|
/**
|
|
609
689
|
* Validates a record's data against a collection's field definitions.
|
|
610
690
|
* Applies defaults, rejects auto fields, and type-checks each value.
|
|
@@ -854,7 +934,7 @@ declare function migrationStepsToSQL(steps: readonly MigrationStep[]): string[];
|
|
|
854
934
|
*
|
|
855
935
|
* const rollbackSQL = rollbackStepsToSQL(migration)
|
|
856
936
|
* // ['DROP INDEX IF EXISTS idx_todos_priority',
|
|
857
|
-
* // 'ALTER TABLE todos DROP COLUMN priority']
|
|
937
|
+
* // 'ALTER TABLE "todos" DROP COLUMN "priority"']
|
|
858
938
|
* ```
|
|
859
939
|
*/
|
|
860
940
|
declare function rollbackStepsToSQL(migration: MigrationDefinition): string[];
|
|
@@ -1045,4 +1125,4 @@ interface ProtoOutput {
|
|
|
1045
1125
|
*/
|
|
1046
1126
|
declare function generateProtoDefinitions(schema: SchemaDefinition): ProtoOutput;
|
|
1047
1127
|
|
|
1048
|
-
export { APPLY_FAILURE_CODES, APPLY_RESULTS, AppNotReadyError, type ApplyFailureReason, type ApplyResult, ArrayFieldBuilder, AtomicOp, type AtomicOpSentinel, AtomicOpType, BlobRef, type BlobRefMetadata, CausalTracker, ClockDriftError, CollectionDefinition, type CollectionInput, type ConstraintInput, CustomResolver, EnumFieldBuilder, FieldBuilder, FieldKind, type FieldKindToType, type InferFieldType, type InferInsertInput, type InferRecord, type InferUpdateInput, InvalidTimestampError, KORA_ERROR_FIX_SUGGESTIONS, type KoraBytesValue, KoraError, MergeConflictError, MigrationDefinition, MigrationRollbackError, MigrationStep, MissingSecretKeyError, Operation, OperationError, type OperationLog, type OperationTransform, OperationType, type ProtoOutput, RandomSource, RelationDefinition, type RelationInput, RemoteClockDriftError, type ReversibleMigration, SchemaDefinition, type SchemaInput, SchemaValidationError, type SecretKeyProvider, StateMachineConstraint, type StateMachineInput, StorageError, SyncError, TransitionMap, TransitionValidationResult, type TypedSchemaDefinition, VersionVector, advanceVector, applyOperationTransforms, base64ToBytes, buildStateMachineConstraints, bytesToBase64, canAutoRollback, collectSchemaScopeFields, collectSchemaScopeValueKeys, computeDelta, createBlobRef, createReversibleMigration, createVersionVector, decodeBytesFromOpData, decryptSecret, defaultApplyFailureReason, defaultSequenceFormat, defineSchema, deserializeVector, dominates, encodeBytesForOpData, encryptSecret, extractScopeValuesFromClaims, extractTimestamp, formatSequenceValue, generateFullDDL, generateProtoDefinitions, generateRollbackSteps, generateSQL, generateUUIDv7, getCollectionScopeBindings, getKoraErrorFix, getTransitionMap, hasSchemaSyncRules, hashBlob, hashSecret, isApplyFailure, isAtomicOp, isBlobRef, isCollectionSyncScoped, isKoraBytesValue, isLegacyNumericByteObject, isValidUUIDv7, mergeVectors, migrationStepsToSQL, op, resolveAtomicOp, revealSecret, rollbackStepsToSQL, serializeVector, toAtomicOp, transformSecretFieldsForWrite, validateRecord, validateTransition, vectorsEqual, verifySecret, verifySecretValue };
|
|
1128
|
+
export { APPLY_FAILURE_CODES, APPLY_RESULTS, AppNotReadyError, type ApplyFailureReason, type ApplyResult, ArrayFieldBuilder, AtomicOp, type AtomicOpSentinel, AtomicOpType, BlobRef, type BlobRefMetadata, CausalTracker, ClockDriftError, CollectionDefinition, type CollectionInput, type ConstraintInput, CustomResolver, EnumFieldBuilder, FieldBuilder, FieldKind, type FieldKindToType, type InferFieldType, type InferInsertInput, type InferRecord, type InferUpdateInput, InvalidTimestampError, KORA_ERROR_FIX_SUGGESTIONS, type KoraBytesValue, KoraError, MergeConflictError, MigrationDefinition, MigrationRollbackError, MigrationStep, MissingSecretKeyError, Operation, OperationError, type OperationLog, type OperationTransform, OperationType, type ProtoOutput, RandomSource, RelationDefinition, type RelationInput, RemoteClockDriftError, type ReplayOperation, type ReversibleMigration, SchemaDefinition, type SchemaInput, SchemaValidationError, type SecretKeyProvider, StateMachineConstraint, type StateMachineInput, StorageError, SyncError, TransitionMap, TransitionValidationResult, type TypedSchemaDefinition, VersionVector, advanceVector, applyAtomicOp, applyOperationTransforms, base64ToBytes, buildStateMachineConstraints, bytesToBase64, canAutoRollback, collectSchemaScopeFields, collectSchemaScopeValueKeys, computeDelta, createBlobRef, createReversibleMigration, createVersionVector, decodeBytesFromOpData, decryptSecret, defaultApplyFailureReason, defaultSequenceFormat, defineSchema, deserializeVector, dominates, encodeBytesForOpData, encryptSecret, extractScopeValuesFromClaims, extractTimestamp, formatSequenceValue, generateFullDDL, generateProtoDefinitions, generateRollbackSteps, generateSQL, generateUUIDv7, getCollectionScopeBindings, getKoraErrorFix, getTransitionMap, hasSchemaSyncRules, hashBlob, hashSecret, isApplyFailure, isAtomicOp, isBlobRef, isCollectionSyncScoped, isKoraBytesValue, isLegacyNumericByteObject, isValidUUIDv7, mergeVectors, migrationStepsToSQL, op, quoteIdent, replayOperationsForRecord, resolveAtomicOp, revealSecret, rollbackStepsToSQL, serializeVector, toAtomicOp, transformSecretFieldsForWrite, validateRecord, validateTransition, vectorsEqual, verifySecret, verifySecretValue };
|
package/dist/index.js
CHANGED
|
@@ -365,32 +365,36 @@ var KORA_ATOMIC_OP = /* @__PURE__ */ Symbol.for("kora.atomic_op");
|
|
|
365
365
|
function isAtomicOp(value) {
|
|
366
366
|
return typeof value === "object" && value !== null && KORA_ATOMIC_OP in value && value[KORA_ATOMIC_OP] === true;
|
|
367
367
|
}
|
|
368
|
-
function
|
|
369
|
-
switch (
|
|
368
|
+
function applyAtomicOp(currentValue, atomicOp) {
|
|
369
|
+
switch (atomicOp.type) {
|
|
370
370
|
case "increment": {
|
|
371
371
|
const current = typeof currentValue === "number" ? currentValue : 0;
|
|
372
|
-
const operand =
|
|
372
|
+
const operand = atomicOp.value;
|
|
373
373
|
return current + operand;
|
|
374
374
|
}
|
|
375
375
|
case "max": {
|
|
376
376
|
const current = typeof currentValue === "number" ? currentValue : Number.NEGATIVE_INFINITY;
|
|
377
|
-
return Math.max(current,
|
|
377
|
+
return Math.max(current, atomicOp.value);
|
|
378
378
|
}
|
|
379
379
|
case "min": {
|
|
380
380
|
const current = typeof currentValue === "number" ? currentValue : Number.POSITIVE_INFINITY;
|
|
381
|
-
return Math.min(current,
|
|
381
|
+
return Math.min(current, atomicOp.value);
|
|
382
382
|
}
|
|
383
383
|
case "append": {
|
|
384
384
|
const current = Array.isArray(currentValue) ? [...currentValue] : [];
|
|
385
|
-
current.push(
|
|
385
|
+
current.push(atomicOp.value);
|
|
386
386
|
return current;
|
|
387
387
|
}
|
|
388
388
|
case "remove": {
|
|
389
389
|
if (!Array.isArray(currentValue)) return [];
|
|
390
|
-
|
|
390
|
+
const target = JSON.stringify(atomicOp.value);
|
|
391
|
+
return currentValue.filter((item) => JSON.stringify(item) !== target);
|
|
391
392
|
}
|
|
392
393
|
}
|
|
393
394
|
}
|
|
395
|
+
function resolveAtomicOp(currentValue, sentinel) {
|
|
396
|
+
return applyAtomicOp(currentValue, { type: sentinel.type, value: sentinel.value });
|
|
397
|
+
}
|
|
394
398
|
function createSentinel(type, value) {
|
|
395
399
|
return Object.freeze({
|
|
396
400
|
[KORA_ATOMIC_OP]: true,
|
|
@@ -458,6 +462,46 @@ function toAtomicOp(sentinel) {
|
|
|
458
462
|
return { type: sentinel.type, value: sentinel.value };
|
|
459
463
|
}
|
|
460
464
|
|
|
465
|
+
// src/operations/replay-record.ts
|
|
466
|
+
function replayOperationsForRecord(ops) {
|
|
467
|
+
let record = null;
|
|
468
|
+
let deleted = false;
|
|
469
|
+
const lastWriterKind = {};
|
|
470
|
+
for (const op2 of ops) {
|
|
471
|
+
switch (op2.type) {
|
|
472
|
+
case "insert":
|
|
473
|
+
if (op2.data) {
|
|
474
|
+
record = { ...op2.data };
|
|
475
|
+
deleted = false;
|
|
476
|
+
for (const field of Object.keys(op2.data)) {
|
|
477
|
+
lastWriterKind[field] = "lww";
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
break;
|
|
481
|
+
case "update":
|
|
482
|
+
if (op2.data) {
|
|
483
|
+
const next = { ...record ?? {} };
|
|
484
|
+
for (const [field, value] of Object.entries(op2.data)) {
|
|
485
|
+
const atomicOp = op2.atomicOps?.[field];
|
|
486
|
+
if (atomicOp && lastWriterKind[field] === `atomic:${atomicOp.type}`) {
|
|
487
|
+
next[field] = applyAtomicOp(next[field], atomicOp);
|
|
488
|
+
} else {
|
|
489
|
+
next[field] = value;
|
|
490
|
+
}
|
|
491
|
+
lastWriterKind[field] = atomicOp ? `atomic:${atomicOp.type}` : "lww";
|
|
492
|
+
}
|
|
493
|
+
record = next;
|
|
494
|
+
deleted = false;
|
|
495
|
+
}
|
|
496
|
+
break;
|
|
497
|
+
case "delete":
|
|
498
|
+
deleted = true;
|
|
499
|
+
break;
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
return deleted ? null : record;
|
|
503
|
+
}
|
|
504
|
+
|
|
461
505
|
// src/operations/op-data-binary.ts
|
|
462
506
|
function isKoraBytesValue(value) {
|
|
463
507
|
if (typeof value !== "object" || value === null) {
|
|
@@ -708,7 +752,7 @@ function validateStateMachineDefinition(collectionName, sm, fields) {
|
|
|
708
752
|
}
|
|
709
753
|
|
|
710
754
|
// src/schema/define.ts
|
|
711
|
-
var COLLECTION_NAME_RE = /^[
|
|
755
|
+
var COLLECTION_NAME_RE = /^[A-Za-z][A-Za-z0-9_]*$/;
|
|
712
756
|
var FIELD_NAME_RE = /^[a-z][a-zA-Z0-9_]*$/;
|
|
713
757
|
var RESERVED_FIELDS = /* @__PURE__ */ new Set(["id", "_created_at", "_updated_at", "_version", "_deleted"]);
|
|
714
758
|
function defineSchema(input) {
|
|
@@ -773,7 +817,7 @@ function validateVersion(version) {
|
|
|
773
817
|
function validateCollectionName(name) {
|
|
774
818
|
if (!COLLECTION_NAME_RE.test(name)) {
|
|
775
819
|
throw new SchemaValidationError(
|
|
776
|
-
`Collection name "${name}" is invalid. Must
|
|
820
|
+
`Collection name "${name}" is invalid. Must start with a letter and contain only letters, numbers, and underscores.`,
|
|
777
821
|
{ collection: name }
|
|
778
822
|
);
|
|
779
823
|
}
|
|
@@ -965,6 +1009,11 @@ function validateRelation(name, relation, collections) {
|
|
|
965
1009
|
}
|
|
966
1010
|
}
|
|
967
1011
|
|
|
1012
|
+
// src/schema/quote-ident.ts
|
|
1013
|
+
function quoteIdent(name) {
|
|
1014
|
+
return `"${name.replace(/"/g, '""')}"`;
|
|
1015
|
+
}
|
|
1016
|
+
|
|
968
1017
|
// src/schema/sql-gen.ts
|
|
969
1018
|
function generateSQL(collectionName, collection, relations) {
|
|
970
1019
|
const statements = [];
|
|
@@ -976,7 +1025,7 @@ function generateSQL(collectionName, collection, relations) {
|
|
|
976
1025
|
if (relations) {
|
|
977
1026
|
for (const rel of Object.values(relations)) {
|
|
978
1027
|
if (rel.from === collectionName && rel.field === fieldName) {
|
|
979
|
-
colDef += ` REFERENCES ${rel.to}(id)`;
|
|
1028
|
+
colDef += ` REFERENCES ${quoteIdent(rel.to)}(id)`;
|
|
980
1029
|
fkFields.push(fieldName);
|
|
981
1030
|
break;
|
|
982
1031
|
}
|
|
@@ -989,36 +1038,40 @@ function generateSQL(collectionName, collection, relations) {
|
|
|
989
1038
|
columns.push("_version TEXT NOT NULL DEFAULT ''");
|
|
990
1039
|
columns.push("_field_versions TEXT NOT NULL DEFAULT '{}'");
|
|
991
1040
|
columns.push("_deleted INTEGER NOT NULL DEFAULT 0");
|
|
992
|
-
statements.push(
|
|
1041
|
+
statements.push(
|
|
1042
|
+
`CREATE TABLE IF NOT EXISTS ${quoteIdent(collectionName)} (
|
|
993
1043
|
${columns.join(",\n ")}
|
|
994
|
-
)`
|
|
1044
|
+
)`
|
|
1045
|
+
);
|
|
995
1046
|
for (const [fieldName, descriptor] of Object.entries(collection.fields)) {
|
|
996
1047
|
const colDef = columnDefinition(fieldName, descriptor);
|
|
997
|
-
statements.push(
|
|
998
|
-
|
|
1048
|
+
statements.push(
|
|
1049
|
+
`--kora:safe-alter
|
|
1050
|
+
ALTER TABLE ${quoteIdent(collectionName)} ADD COLUMN ${colDef}`
|
|
1051
|
+
);
|
|
999
1052
|
}
|
|
1000
1053
|
statements.push(
|
|
1001
1054
|
`--kora:safe-alter
|
|
1002
|
-
ALTER TABLE ${collectionName} ADD COLUMN _version TEXT NOT NULL DEFAULT ''`
|
|
1055
|
+
ALTER TABLE ${quoteIdent(collectionName)} ADD COLUMN _version TEXT NOT NULL DEFAULT ''`
|
|
1003
1056
|
);
|
|
1004
1057
|
statements.push(
|
|
1005
1058
|
`--kora:safe-alter
|
|
1006
|
-
ALTER TABLE ${collectionName} ADD COLUMN _field_versions TEXT NOT NULL DEFAULT '{}'`
|
|
1059
|
+
ALTER TABLE ${quoteIdent(collectionName)} ADD COLUMN _field_versions TEXT NOT NULL DEFAULT '{}'`
|
|
1007
1060
|
);
|
|
1008
1061
|
for (const indexField of collection.indexes) {
|
|
1009
1062
|
statements.push(
|
|
1010
|
-
`CREATE INDEX IF NOT EXISTS idx_${collectionName}_${indexField} ON ${collectionName} (${indexField})`
|
|
1063
|
+
`CREATE INDEX IF NOT EXISTS idx_${collectionName}_${indexField} ON ${quoteIdent(collectionName)} (${quoteIdent(indexField)})`
|
|
1011
1064
|
);
|
|
1012
1065
|
}
|
|
1013
1066
|
for (const fkField of fkFields) {
|
|
1014
1067
|
if (!indexedFields.has(fkField)) {
|
|
1015
1068
|
statements.push(
|
|
1016
|
-
`CREATE INDEX IF NOT EXISTS idx_${collectionName}_${fkField} ON ${collectionName} (${fkField})`
|
|
1069
|
+
`CREATE INDEX IF NOT EXISTS idx_${collectionName}_${fkField} ON ${quoteIdent(collectionName)} (${quoteIdent(fkField)})`
|
|
1017
1070
|
);
|
|
1018
1071
|
}
|
|
1019
1072
|
}
|
|
1020
1073
|
statements.push(
|
|
1021
|
-
`CREATE TABLE IF NOT EXISTS _kora_ops_${collectionName} (
|
|
1074
|
+
`CREATE TABLE IF NOT EXISTS ${quoteIdent(`_kora_ops_${collectionName}`)} (
|
|
1022
1075
|
id TEXT PRIMARY KEY NOT NULL,
|
|
1023
1076
|
node_id TEXT NOT NULL,
|
|
1024
1077
|
type TEXT NOT NULL,
|
|
@@ -1032,7 +1085,7 @@ ALTER TABLE ${collectionName} ADD COLUMN _field_versions TEXT NOT NULL DEFAULT '
|
|
|
1032
1085
|
)`
|
|
1033
1086
|
);
|
|
1034
1087
|
statements.push(
|
|
1035
|
-
`CREATE INDEX IF NOT EXISTS idx_kora_ops_${collectionName}_record_id ON _kora_ops_${collectionName} (record_id)`
|
|
1088
|
+
`CREATE INDEX IF NOT EXISTS idx_kora_ops_${collectionName}_record_id ON ${quoteIdent(`_kora_ops_${collectionName}`)} (record_id)`
|
|
1036
1089
|
);
|
|
1037
1090
|
return statements;
|
|
1038
1091
|
}
|
|
@@ -1050,6 +1103,9 @@ function generateFullDDL(schema) {
|
|
|
1050
1103
|
statements.push(
|
|
1051
1104
|
"CREATE TABLE IF NOT EXISTS _kora_sync_queue (\n id TEXT PRIMARY KEY NOT NULL,\n payload TEXT NOT NULL\n)"
|
|
1052
1105
|
);
|
|
1106
|
+
statements.push(
|
|
1107
|
+
"CREATE TABLE IF NOT EXISTS _kora_sync_rejected (\n operation_id TEXT PRIMARY KEY NOT NULL,\n collection TEXT NOT NULL,\n record_id TEXT NOT NULL,\n code TEXT NOT NULL,\n message TEXT NOT NULL,\n retriable INTEGER NOT NULL,\n rejected_at INTEGER NOT NULL\n)"
|
|
1108
|
+
);
|
|
1053
1109
|
statements.push(
|
|
1054
1110
|
"CREATE TABLE IF NOT EXISTS _kora_audit_traces (\n id TEXT PRIMARY KEY NOT NULL,\n recorded_at INTEGER NOT NULL,\n event_type TEXT NOT NULL,\n collection TEXT NOT NULL,\n record_id TEXT NOT NULL,\n field TEXT NOT NULL,\n strategy TEXT NOT NULL,\n tier INTEGER NOT NULL,\n constraint_name TEXT,\n trace_json TEXT NOT NULL\n)"
|
|
1055
1111
|
);
|
|
@@ -1066,7 +1122,7 @@ function generateFullDDL(schema) {
|
|
|
1066
1122
|
}
|
|
1067
1123
|
function columnDefinition(fieldName, descriptor) {
|
|
1068
1124
|
const sqlType = mapFieldType(descriptor);
|
|
1069
|
-
const parts = [fieldName, sqlType];
|
|
1125
|
+
const parts = [quoteIdent(fieldName), sqlType];
|
|
1070
1126
|
if (descriptor.required && descriptor.defaultValue === void 0 && !descriptor.auto) {
|
|
1071
1127
|
parts.push("NOT NULL");
|
|
1072
1128
|
}
|
|
@@ -1075,7 +1131,7 @@ function columnDefinition(fieldName, descriptor) {
|
|
|
1075
1131
|
}
|
|
1076
1132
|
if (descriptor.kind === "enum" && descriptor.enumValues) {
|
|
1077
1133
|
const values = descriptor.enumValues.map((v) => `'${v}'`).join(", ");
|
|
1078
|
-
parts.push(`CHECK (${fieldName} IN (${values}))`);
|
|
1134
|
+
parts.push(`CHECK (${quoteIdent(fieldName)} IN (${values}))`);
|
|
1079
1135
|
}
|
|
1080
1136
|
return parts.join(" ");
|
|
1081
1137
|
}
|
|
@@ -1717,10 +1773,17 @@ function validateFieldValue(collection, fieldName, descriptor, value) {
|
|
|
1717
1773
|
break;
|
|
1718
1774
|
}
|
|
1719
1775
|
case "json": {
|
|
1720
|
-
|
|
1776
|
+
const nonJson = findNonJsonSerializable(value, fieldName);
|
|
1777
|
+
if (nonJson) {
|
|
1721
1778
|
throw new SchemaValidationError(
|
|
1722
|
-
`Field "${fieldName}" in collection "${collection}" must be JSON-serializable
|
|
1723
|
-
{
|
|
1779
|
+
`Field "${fieldName}" in collection "${collection}" must be JSON-serializable; found ${nonJson.receivedType} at "${nonJson.path}"`,
|
|
1780
|
+
{
|
|
1781
|
+
collection,
|
|
1782
|
+
field: fieldName,
|
|
1783
|
+
expectedType: "json",
|
|
1784
|
+
receivedType: nonJson.receivedType,
|
|
1785
|
+
path: nonJson.path
|
|
1786
|
+
}
|
|
1724
1787
|
);
|
|
1725
1788
|
}
|
|
1726
1789
|
break;
|
|
@@ -1757,21 +1820,41 @@ function isPlainObject(value) {
|
|
|
1757
1820
|
const proto = Object.getPrototypeOf(value);
|
|
1758
1821
|
return proto === null || proto === Object.prototype;
|
|
1759
1822
|
}
|
|
1760
|
-
function
|
|
1823
|
+
function findNonJsonSerializable(value, path, seen = /* @__PURE__ */ new WeakSet()) {
|
|
1761
1824
|
if (value === null) {
|
|
1762
|
-
return
|
|
1825
|
+
return null;
|
|
1763
1826
|
}
|
|
1764
1827
|
const type = typeof value;
|
|
1765
1828
|
if (type === "string" || type === "number" || type === "boolean") {
|
|
1766
|
-
|
|
1829
|
+
if (type === "number" && !Number.isFinite(value)) {
|
|
1830
|
+
return { path, receivedType: describeType(value) };
|
|
1831
|
+
}
|
|
1832
|
+
return null;
|
|
1833
|
+
}
|
|
1834
|
+
if (type === "undefined" || type === "function" || type === "symbol" || type === "bigint") {
|
|
1835
|
+
return { path, receivedType: describeType(value) };
|
|
1767
1836
|
}
|
|
1768
1837
|
if (Array.isArray(value)) {
|
|
1769
|
-
|
|
1838
|
+
for (let index = 0; index < value.length; index++) {
|
|
1839
|
+
const invalid = findNonJsonSerializable(value[index], `${path}[${index}]`, seen);
|
|
1840
|
+
if (invalid) return invalid;
|
|
1841
|
+
}
|
|
1842
|
+
return null;
|
|
1770
1843
|
}
|
|
1771
1844
|
if (type === "object") {
|
|
1772
|
-
|
|
1845
|
+
const objectValue = value;
|
|
1846
|
+
if (seen.has(objectValue)) {
|
|
1847
|
+
return { path, receivedType: "circular object" };
|
|
1848
|
+
}
|
|
1849
|
+
seen.add(objectValue);
|
|
1850
|
+
for (const [key, nestedValue] of Object.entries(objectValue)) {
|
|
1851
|
+
const invalid = findNonJsonSerializable(nestedValue, `${path}.${key}`, seen);
|
|
1852
|
+
if (invalid) return invalid;
|
|
1853
|
+
}
|
|
1854
|
+
seen.delete(objectValue);
|
|
1855
|
+
return null;
|
|
1773
1856
|
}
|
|
1774
|
-
return
|
|
1857
|
+
return { path, receivedType: describeType(value) };
|
|
1775
1858
|
}
|
|
1776
1859
|
function describeType(value) {
|
|
1777
1860
|
if (value === null) {
|
|
@@ -2212,14 +2295,18 @@ function migrationStepsToSQL(steps) {
|
|
|
2212
2295
|
statements.push(addFieldSQL(step.collection, step.field, step.descriptor));
|
|
2213
2296
|
break;
|
|
2214
2297
|
case "removeField":
|
|
2215
|
-
statements.push(
|
|
2298
|
+
statements.push(
|
|
2299
|
+
`ALTER TABLE ${quoteIdent(step.collection)} DROP COLUMN ${quoteIdent(step.field)}`
|
|
2300
|
+
);
|
|
2216
2301
|
break;
|
|
2217
2302
|
case "renameField":
|
|
2218
|
-
statements.push(
|
|
2303
|
+
statements.push(
|
|
2304
|
+
`ALTER TABLE ${quoteIdent(step.collection)} RENAME COLUMN ${quoteIdent(step.from)} TO ${quoteIdent(step.to)}`
|
|
2305
|
+
);
|
|
2219
2306
|
break;
|
|
2220
2307
|
case "addIndex":
|
|
2221
2308
|
statements.push(
|
|
2222
|
-
`CREATE INDEX IF NOT EXISTS idx_${step.collection}_${step.field} ON ${step.collection} (${step.field})`
|
|
2309
|
+
`CREATE INDEX IF NOT EXISTS idx_${step.collection}_${step.field} ON ${quoteIdent(step.collection)} (${quoteIdent(step.field)})`
|
|
2223
2310
|
);
|
|
2224
2311
|
break;
|
|
2225
2312
|
case "removeIndex":
|
|
@@ -2237,13 +2324,13 @@ function rollbackStepsToSQL(migration) {
|
|
|
2237
2324
|
}
|
|
2238
2325
|
function addFieldSQL(collection, field, descriptor) {
|
|
2239
2326
|
const sqlType = mapFieldType2(descriptor);
|
|
2240
|
-
const parts = [`ALTER TABLE ${collection} ADD COLUMN ${field}`, sqlType];
|
|
2327
|
+
const parts = [`ALTER TABLE ${quoteIdent(collection)} ADD COLUMN ${quoteIdent(field)}`, sqlType];
|
|
2241
2328
|
if (descriptor.defaultValue !== void 0) {
|
|
2242
2329
|
parts.push(`DEFAULT ${sqlDefault2(descriptor.defaultValue)}`);
|
|
2243
2330
|
}
|
|
2244
2331
|
if (descriptor.kind === "enum" && descriptor.enumValues) {
|
|
2245
2332
|
const values = descriptor.enumValues.map((v) => `'${v}'`).join(", ");
|
|
2246
|
-
parts.push(`CHECK (${field} IN (${values}))`);
|
|
2333
|
+
parts.push(`CHECK (${quoteIdent(field)} IN (${values}))`);
|
|
2247
2334
|
}
|
|
2248
2335
|
return parts.join(" ");
|
|
2249
2336
|
}
|
|
@@ -2545,6 +2632,7 @@ export {
|
|
|
2545
2632
|
StorageError,
|
|
2546
2633
|
SyncError,
|
|
2547
2634
|
advanceVector,
|
|
2635
|
+
applyAtomicOp,
|
|
2548
2636
|
applyOperationTransforms,
|
|
2549
2637
|
base64ToBytes,
|
|
2550
2638
|
buildScopeMap,
|
|
@@ -2593,6 +2681,8 @@ export {
|
|
|
2593
2681
|
migrate,
|
|
2594
2682
|
migrationStepsToSQL,
|
|
2595
2683
|
op,
|
|
2684
|
+
quoteIdent,
|
|
2685
|
+
replayOperationsForRecord,
|
|
2596
2686
|
resolveAtomicOp,
|
|
2597
2687
|
revealSecret,
|
|
2598
2688
|
rollbackStepsToSQL,
|