@monlite/core 1.1.0 → 1.2.0

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/index.d.cts CHANGED
@@ -27,6 +27,24 @@ declare class Collection<T = Doc> {
27
27
  private columnDdl;
28
28
  /** Auto-additive migration: add declared columns missing from an existing table. */
29
29
  private migrateColumns;
30
+ private foreignKeysOn;
31
+ private declaredIndexDdl;
32
+ /**
33
+ * Reconcile the physical table to the declared schema, performing the changes
34
+ * the auto-additive path can't: **dropping** columns, **renaming** them, and
35
+ * **changing a column's type/constraints** — via a safe, transactional table
36
+ * rebuild that preserves data. Structured collections only.
37
+ *
38
+ * Pass `rename` to map an existing physical column to a new declared name, and
39
+ * `drop` to acknowledge columns that the new schema removes (an unacknowledged
40
+ * column drop throws, so data is never lost by accident).
41
+ *
42
+ * ```ts
43
+ * const users = db.collection("users", { schema: { name: "TEXT", age: "INTEGER" } });
44
+ * await users.$migrate({ rename: { fullname: "name" }, drop: ["legacy"] });
45
+ * ```
46
+ */
47
+ $migrate(options?: MigrateOptions): Promise<void>;
30
48
  private rowToDoc;
31
49
  private encodeColumn;
32
50
  private insertColumns;
@@ -167,6 +185,13 @@ interface CollectionOptions {
167
185
  schema?: CollectionSchema;
168
186
  }
169
187
  type CollectionMode = "document" | "structured";
188
+ /** Options for {@link Collection.$migrate}. */
189
+ interface MigrateOptions {
190
+ /** Rename existing physical columns: `{ oldName: newDeclaredName }`. */
191
+ rename?: Record<string, string>;
192
+ /** Acknowledge physical columns to drop (not present in the new schema). */
193
+ drop?: string[];
194
+ }
170
195
  /** A column as reported by {@link Monlite.$schema}. */
171
196
  interface ColumnInfo {
172
197
  name: string;
@@ -747,4 +772,4 @@ declare function objectId(): string;
747
772
  /** True when a value looks like a monlite/ObjectId id (24 hex chars). */
748
773
  declare function isObjectId(value: unknown): value is string;
749
774
 
750
- export { type AggregateArgs, type AggregateResult, type ApplyResult, Collection, type CollectionMode, type CollectionOptions, type CollectionSchema, type ColumnDef, type ColumnInfo, type ColumnType, type ConflictResolver, type ConflictRow, type CountArgs, type CreateArgs, type CreateManyArgs, Monlite as Db, type DeleteArgs, type Doc, type Driver, type DriverName, type EncryptionOptions, type ExplainResult, type FieldFilter, type FieldSelection, type FilterInput, type FindFirstArgs, type FindManyArgs, type GroupByArgs, type GroupByResult, type HavingComparison, type HavingInput, type LiveEvent, type LocalChange, Monlite, MonliteConstraintError, MonliteEncryptionError, MonliteError, MonliteForeignKeyError, MonliteNotNullError, type MonliteOptions, type MonlitePlugin, MonliteQueryError, MonliteUniqueConstraintError, type OrderBy, type PluginChange, type PreparedStatement, type RemoteChange, type Select, type SortOrder, type SyncOp, type SyncStateRow, SyncStore, type SystemFields, type UpdateArgs, type UpdateData, type UpdateOperators, type UpsertArgs, type Version, type WatchHandle, type WhereInput as WhereClause, type WhereInput, type WithId, compareVersions, createDb, isObjectId, makeVersion, normalizeDriverError, objectId, versionTs };
775
+ export { type AggregateArgs, type AggregateResult, type ApplyResult, Collection, type CollectionMode, type CollectionOptions, type CollectionSchema, type ColumnDef, type ColumnInfo, type ColumnType, type ConflictResolver, type ConflictRow, type CountArgs, type CreateArgs, type CreateManyArgs, Monlite as Db, type DeleteArgs, type Doc, type Driver, type DriverName, type EncryptionOptions, type ExplainResult, type FieldFilter, type FieldSelection, type FilterInput, type FindFirstArgs, type FindManyArgs, type GroupByArgs, type GroupByResult, type HavingComparison, type HavingInput, type LiveEvent, type LocalChange, type MigrateOptions, Monlite, MonliteConstraintError, MonliteEncryptionError, MonliteError, MonliteForeignKeyError, MonliteNotNullError, type MonliteOptions, type MonlitePlugin, MonliteQueryError, MonliteUniqueConstraintError, type OrderBy, type PluginChange, type PreparedStatement, type RemoteChange, type Select, type SortOrder, type SyncOp, type SyncStateRow, SyncStore, type SystemFields, type UpdateArgs, type UpdateData, type UpdateOperators, type UpsertArgs, type Version, type WatchHandle, type WhereInput as WhereClause, type WhereInput, type WithId, compareVersions, createDb, isObjectId, makeVersion, normalizeDriverError, objectId, versionTs };
package/dist/index.d.ts CHANGED
@@ -27,6 +27,24 @@ declare class Collection<T = Doc> {
27
27
  private columnDdl;
28
28
  /** Auto-additive migration: add declared columns missing from an existing table. */
29
29
  private migrateColumns;
30
+ private foreignKeysOn;
31
+ private declaredIndexDdl;
32
+ /**
33
+ * Reconcile the physical table to the declared schema, performing the changes
34
+ * the auto-additive path can't: **dropping** columns, **renaming** them, and
35
+ * **changing a column's type/constraints** — via a safe, transactional table
36
+ * rebuild that preserves data. Structured collections only.
37
+ *
38
+ * Pass `rename` to map an existing physical column to a new declared name, and
39
+ * `drop` to acknowledge columns that the new schema removes (an unacknowledged
40
+ * column drop throws, so data is never lost by accident).
41
+ *
42
+ * ```ts
43
+ * const users = db.collection("users", { schema: { name: "TEXT", age: "INTEGER" } });
44
+ * await users.$migrate({ rename: { fullname: "name" }, drop: ["legacy"] });
45
+ * ```
46
+ */
47
+ $migrate(options?: MigrateOptions): Promise<void>;
30
48
  private rowToDoc;
31
49
  private encodeColumn;
32
50
  private insertColumns;
@@ -167,6 +185,13 @@ interface CollectionOptions {
167
185
  schema?: CollectionSchema;
168
186
  }
169
187
  type CollectionMode = "document" | "structured";
188
+ /** Options for {@link Collection.$migrate}. */
189
+ interface MigrateOptions {
190
+ /** Rename existing physical columns: `{ oldName: newDeclaredName }`. */
191
+ rename?: Record<string, string>;
192
+ /** Acknowledge physical columns to drop (not present in the new schema). */
193
+ drop?: string[];
194
+ }
170
195
  /** A column as reported by {@link Monlite.$schema}. */
171
196
  interface ColumnInfo {
172
197
  name: string;
@@ -747,4 +772,4 @@ declare function objectId(): string;
747
772
  /** True when a value looks like a monlite/ObjectId id (24 hex chars). */
748
773
  declare function isObjectId(value: unknown): value is string;
749
774
 
750
- export { type AggregateArgs, type AggregateResult, type ApplyResult, Collection, type CollectionMode, type CollectionOptions, type CollectionSchema, type ColumnDef, type ColumnInfo, type ColumnType, type ConflictResolver, type ConflictRow, type CountArgs, type CreateArgs, type CreateManyArgs, Monlite as Db, type DeleteArgs, type Doc, type Driver, type DriverName, type EncryptionOptions, type ExplainResult, type FieldFilter, type FieldSelection, type FilterInput, type FindFirstArgs, type FindManyArgs, type GroupByArgs, type GroupByResult, type HavingComparison, type HavingInput, type LiveEvent, type LocalChange, Monlite, MonliteConstraintError, MonliteEncryptionError, MonliteError, MonliteForeignKeyError, MonliteNotNullError, type MonliteOptions, type MonlitePlugin, MonliteQueryError, MonliteUniqueConstraintError, type OrderBy, type PluginChange, type PreparedStatement, type RemoteChange, type Select, type SortOrder, type SyncOp, type SyncStateRow, SyncStore, type SystemFields, type UpdateArgs, type UpdateData, type UpdateOperators, type UpsertArgs, type Version, type WatchHandle, type WhereInput as WhereClause, type WhereInput, type WithId, compareVersions, createDb, isObjectId, makeVersion, normalizeDriverError, objectId, versionTs };
775
+ export { type AggregateArgs, type AggregateResult, type ApplyResult, Collection, type CollectionMode, type CollectionOptions, type CollectionSchema, type ColumnDef, type ColumnInfo, type ColumnType, type ConflictResolver, type ConflictRow, type CountArgs, type CreateArgs, type CreateManyArgs, Monlite as Db, type DeleteArgs, type Doc, type Driver, type DriverName, type EncryptionOptions, type ExplainResult, type FieldFilter, type FieldSelection, type FilterInput, type FindFirstArgs, type FindManyArgs, type GroupByArgs, type GroupByResult, type HavingComparison, type HavingInput, type LiveEvent, type LocalChange, type MigrateOptions, Monlite, MonliteConstraintError, MonliteEncryptionError, MonliteError, MonliteForeignKeyError, MonliteNotNullError, type MonliteOptions, type MonlitePlugin, MonliteQueryError, MonliteUniqueConstraintError, type OrderBy, type PluginChange, type PreparedStatement, type RemoteChange, type Select, type SortOrder, type SyncOp, type SyncStateRow, SyncStore, type SystemFields, type UpdateArgs, type UpdateData, type UpdateOperators, type UpsertArgs, type Version, type WatchHandle, type WhereInput as WhereClause, type WhereInput, type WithId, compareVersions, createDb, isObjectId, makeVersion, normalizeDriverError, objectId, versionTs };
package/dist/index.js CHANGED
@@ -790,6 +790,124 @@ var Collection = class {
790
790
  }
791
791
  }
792
792
  }
793
+ foreignKeysOn() {
794
+ try {
795
+ const row = this.db.prepare(`PRAGMA foreign_keys`).get();
796
+ return !!row?.foreign_keys;
797
+ } catch {
798
+ return true;
799
+ }
800
+ }
801
+ declaredIndexDdl() {
802
+ const out = [];
803
+ for (const field of this.columnOrder) {
804
+ if (this.columnDefs[field].index) {
805
+ out.push(
806
+ `CREATE INDEX IF NOT EXISTS "idx_${this.name}_${field}" ON "${this.name}"("${field}")`
807
+ );
808
+ }
809
+ }
810
+ return out;
811
+ }
812
+ /**
813
+ * Reconcile the physical table to the declared schema, performing the changes
814
+ * the auto-additive path can't: **dropping** columns, **renaming** them, and
815
+ * **changing a column's type/constraints** — via a safe, transactional table
816
+ * rebuild that preserves data. Structured collections only.
817
+ *
818
+ * Pass `rename` to map an existing physical column to a new declared name, and
819
+ * `drop` to acknowledge columns that the new schema removes (an unacknowledged
820
+ * column drop throws, so data is never lost by accident).
821
+ *
822
+ * ```ts
823
+ * const users = db.collection("users", { schema: { name: "TEXT", age: "INTEGER" } });
824
+ * await users.$migrate({ rename: { fullname: "name" }, drop: ["legacy"] });
825
+ * ```
826
+ */
827
+ async $migrate(options = {}) {
828
+ if (this.mode !== "structured") {
829
+ throw new MonliteError(
830
+ `$migrate() is only available on structured collections (declare a schema).`
831
+ );
832
+ }
833
+ this.ensureTable();
834
+ const rename = options.rename ?? {};
835
+ const drop = new Set(options.drop ?? []);
836
+ const SYSTEM = /* @__PURE__ */ new Set(["_id", "created_at", "updated_at", "data"]);
837
+ const physical = this.db.prepare(`PRAGMA table_info("${this.name}")`).all().map((r) => r.name).filter((n) => !SYSTEM.has(n));
838
+ const physicalSet = new Set(physical);
839
+ const targetSet = new Set(this.columnOrder);
840
+ const renamedFrom = {};
841
+ for (const [from, to] of Object.entries(rename)) {
842
+ if (!physicalSet.has(from)) {
843
+ throw new MonliteError(
844
+ `Cannot rename "${from}": no such column in "${this.name}".`
845
+ );
846
+ }
847
+ if (!targetSet.has(to)) {
848
+ throw new MonliteError(
849
+ `Cannot rename "${from}" to "${to}": "${to}" is not in the schema.`
850
+ );
851
+ }
852
+ renamedFrom[to] = from;
853
+ }
854
+ const renameSources = new Set(Object.keys(rename));
855
+ for (const col of physical) {
856
+ if (targetSet.has(col) || renameSources.has(col) || drop.has(col))
857
+ continue;
858
+ throw new MonliteError(
859
+ `Column "${col}" exists in "${this.name}" but isn't in the schema. Add it to the schema, or list it in \`drop\` to remove it.`
860
+ );
861
+ }
862
+ const tmp = `__mon_migrate_${this.name}`;
863
+ const newCols = [
864
+ `_id TEXT PRIMARY KEY`,
865
+ `created_at INTEGER NOT NULL`,
866
+ `updated_at INTEGER NOT NULL`,
867
+ `data TEXT NOT NULL DEFAULT '{}'`,
868
+ ...this.columnOrder.map((f) => this.columnDdl(f, false))
869
+ ];
870
+ const destCols = [
871
+ "_id",
872
+ "created_at",
873
+ "updated_at",
874
+ "data",
875
+ ...this.columnOrder.map((f) => `"${f}"`)
876
+ ].join(", ");
877
+ const srcCols = [
878
+ "_id",
879
+ "created_at",
880
+ "updated_at",
881
+ "data",
882
+ ...this.columnOrder.map((t) => {
883
+ const src = renamedFrom[t] ?? (physicalSet.has(t) ? t : null);
884
+ return src ? `"${src}"` : "NULL";
885
+ })
886
+ ].join(", ");
887
+ const fkOn = this.foreignKeysOn();
888
+ if (fkOn) this.db.exec(`PRAGMA foreign_keys = OFF`);
889
+ try {
890
+ this.guard(
891
+ () => this.db.transaction(() => {
892
+ this.db.exec(`DROP TABLE IF EXISTS "${tmp}"`);
893
+ this.db.exec(
894
+ `CREATE TABLE "${tmp}" (
895
+ ${newCols.join(",\n ")}
896
+ )`
897
+ );
898
+ this.db.exec(
899
+ `INSERT INTO "${tmp}" (${destCols}) SELECT ${srcCols} FROM "${this.name}"`
900
+ );
901
+ this.db.exec(`DROP TABLE "${this.name}"`);
902
+ this.db.exec(`ALTER TABLE "${tmp}" RENAME TO "${this.name}"`);
903
+ for (const ddl of this.declaredIndexDdl()) this.db.exec(ddl);
904
+ })
905
+ );
906
+ } finally {
907
+ if (fkOn) this.db.exec(`PRAGMA foreign_keys = ON`);
908
+ }
909
+ this.insertSqlCache = void 0;
910
+ }
793
911
  /* --------------------------- row <-> doc -------------------------- */
794
912
  rowToDoc(row) {
795
913
  const doc = this.mode === "document" ? JSON.parse(row.data) : JSON.parse(row.data ?? "{}");