@korajs/store 1.0.0-beta.10 → 1.0.0-beta.12

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.cjs CHANGED
@@ -3050,6 +3050,10 @@ var SubscriptionManager = class {
3050
3050
  * Schedules a microtask flush to batch multiple mutations in the same tick.
3051
3051
  */
3052
3052
  notify(collection, _operation) {
3053
+ this.invalidate(collection);
3054
+ }
3055
+ /** Invalidate a collection after a local-view change that has no domain operation. */
3056
+ invalidate(collection) {
3053
3057
  this.pendingCollections.add(collection);
3054
3058
  this.scheduleFlush();
3055
3059
  }
@@ -3792,6 +3796,9 @@ var Store = class {
3792
3796
  */
3793
3797
  async open() {
3794
3798
  await this.adapter.open(this.schema);
3799
+ await this.adapter.execute(
3800
+ "CREATE TABLE IF NOT EXISTS _kora_scope_retractions (collection TEXT NOT NULL, record_id TEXT NOT NULL, PRIMARY KEY (collection, record_id))"
3801
+ );
3795
3802
  await this.runMigrationsIfNeeded();
3796
3803
  this.nodeId = await this.loadOrGenerateNodeId();
3797
3804
  this.clock = new import_core23.HybridLogicalClock(this.nodeId);
@@ -3911,6 +3918,22 @@ var Store = class {
3911
3918
  [op.id]
3912
3919
  );
3913
3920
  if (existing.length > 0) {
3921
+ const retracted = await this.adapter.query(
3922
+ "SELECT record_id FROM _kora_scope_retractions WHERE collection = ? AND record_id = ?",
3923
+ [collection, op.recordId]
3924
+ );
3925
+ if (retracted.length > 0) {
3926
+ await this.adapter.transaction(async (tx) => {
3927
+ await tx.execute(`UPDATE ${(0, import_core23.quoteIdent)(collection)} SET _deleted = 0 WHERE id = ?`, [
3928
+ op.recordId
3929
+ ]);
3930
+ await tx.execute(
3931
+ "DELETE FROM _kora_scope_retractions WHERE collection = ? AND record_id = ?",
3932
+ [collection, op.recordId]
3933
+ );
3934
+ });
3935
+ this.subscriptionManager.invalidate(collection);
3936
+ }
3914
3937
  return "duplicate";
3915
3938
  }
3916
3939
  if (this.clock) {
@@ -4096,6 +4119,49 @@ var Store = class {
4096
4119
  this.subscriptionManager.notify(collection, op);
4097
4120
  return "applied";
4098
4121
  }
4122
+ /**
4123
+ * Hide a record from this client's authorized materialized view. This writes no
4124
+ * replicated operation and therefore cannot be mistaken for a domain deletion.
4125
+ * The marker lets a later authoritative backfill reactivate the retained row.
4126
+ */
4127
+ async applyScopeRetraction(collection, recordId) {
4128
+ this.ensureOpen();
4129
+ if (!this.schema.collections[collection]) return;
4130
+ await this.adapter.transaction(async (tx) => {
4131
+ await tx.execute(`UPDATE ${(0, import_core23.quoteIdent)(collection)} SET _deleted = 1 WHERE id = ?`, [recordId]);
4132
+ await tx.execute(
4133
+ "INSERT OR REPLACE INTO _kora_scope_retractions (collection, record_id) VALUES (?, ?)",
4134
+ [collection, recordId]
4135
+ );
4136
+ });
4137
+ this.subscriptionManager.invalidate(collection);
4138
+ }
4139
+ /** Hide all live rows that no longer match a newly accepted server scope. */
4140
+ async applyScopeNarrowing(scopes) {
4141
+ this.ensureOpen();
4142
+ const retractions = [];
4143
+ for (const [collection, definition] of Object.entries(this.schema.collections)) {
4144
+ const rows = await this.adapter.query(
4145
+ `SELECT * FROM ${(0, import_core23.quoteIdent)(collection)} WHERE _deleted = 0`
4146
+ );
4147
+ const predicate = scopes[collection];
4148
+ for (const row of rows) {
4149
+ const record = deserializeRecord(row, definition.fields);
4150
+ const matches = predicate !== void 0 && Object.entries(predicate).every(([field, expected]) => {
4151
+ if (expected && typeof expected === "object" && !Array.isArray(expected) && "$in" in expected) {
4152
+ const values = expected.$in;
4153
+ return Array.isArray(values) && values.some((value) => Object.is(record[field], value));
4154
+ }
4155
+ return Object.is(record[field], expected);
4156
+ });
4157
+ if (!matches) {
4158
+ await this.applyScopeRetraction(collection, String(record.id));
4159
+ retractions.push({ collection, recordId: String(record.id) });
4160
+ }
4161
+ }
4162
+ }
4163
+ return retractions;
4164
+ }
4099
4165
  /**
4100
4166
  * Materialize update/delete operations that were logged for a record BEFORE
4101
4167
  * its insert arrived (reordered delivery). Runs inside the insert's write