@korajs/store 1.0.0-beta.11 → 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.d.cts CHANGED
@@ -397,6 +397,8 @@ declare class SubscriptionManager {
397
397
  * Schedules a microtask flush to batch multiple mutations in the same tick.
398
398
  */
399
399
  notify(collection: string, _operation: Operation): void;
400
+ /** Invalidate a collection after a local-view change that has no domain operation. */
401
+ invalidate(collection: string): void;
400
402
  /**
401
403
  * Immediately flush all pending notifications.
402
404
  * Useful for testing. In production, flushing happens via microtask.
@@ -766,6 +768,17 @@ declare class Store implements OperationLog {
766
768
  * and updates the version vector.
767
769
  */
768
770
  applyRemoteOperation(op: Operation, options?: ApplyRemoteOptions): Promise<ApplyResult>;
771
+ /**
772
+ * Hide a record from this client's authorized materialized view. This writes no
773
+ * replicated operation and therefore cannot be mistaken for a domain deletion.
774
+ * The marker lets a later authoritative backfill reactivate the retained row.
775
+ */
776
+ applyScopeRetraction(collection: string, recordId: string): Promise<void>;
777
+ /** Hide all live rows that no longer match a newly accepted server scope. */
778
+ applyScopeNarrowing(scopes: Record<string, Record<string, unknown>>): Promise<Array<{
779
+ collection: string;
780
+ recordId: string;
781
+ }>>;
769
782
  /**
770
783
  * Materialize update/delete operations that were logged for a record BEFORE
771
784
  * its insert arrived (reordered delivery). Runs inside the insert's write
package/dist/index.d.ts CHANGED
@@ -397,6 +397,8 @@ declare class SubscriptionManager {
397
397
  * Schedules a microtask flush to batch multiple mutations in the same tick.
398
398
  */
399
399
  notify(collection: string, _operation: Operation): void;
400
+ /** Invalidate a collection after a local-view change that has no domain operation. */
401
+ invalidate(collection: string): void;
400
402
  /**
401
403
  * Immediately flush all pending notifications.
402
404
  * Useful for testing. In production, flushing happens via microtask.
@@ -766,6 +768,17 @@ declare class Store implements OperationLog {
766
768
  * and updates the version vector.
767
769
  */
768
770
  applyRemoteOperation(op: Operation, options?: ApplyRemoteOptions): Promise<ApplyResult>;
771
+ /**
772
+ * Hide a record from this client's authorized materialized view. This writes no
773
+ * replicated operation and therefore cannot be mistaken for a domain deletion.
774
+ * The marker lets a later authoritative backfill reactivate the retained row.
775
+ */
776
+ applyScopeRetraction(collection: string, recordId: string): Promise<void>;
777
+ /** Hide all live rows that no longer match a newly accepted server scope. */
778
+ applyScopeNarrowing(scopes: Record<string, Record<string, unknown>>): Promise<Array<{
779
+ collection: string;
780
+ recordId: string;
781
+ }>>;
769
782
  /**
770
783
  * Materialize update/delete operations that were logged for a record BEFORE
771
784
  * its insert arrived (reordered delivery). Runs inside the insert's write
package/dist/index.js CHANGED
@@ -1350,6 +1350,10 @@ var SubscriptionManager = class {
1350
1350
  * Schedules a microtask flush to batch multiple mutations in the same tick.
1351
1351
  */
1352
1352
  notify(collection, _operation) {
1353
+ this.invalidate(collection);
1354
+ }
1355
+ /** Invalidate a collection after a local-view change that has no domain operation. */
1356
+ invalidate(collection) {
1353
1357
  this.pendingCollections.add(collection);
1354
1358
  this.scheduleFlush();
1355
1359
  }
@@ -2097,6 +2101,9 @@ var Store = class {
2097
2101
  */
2098
2102
  async open() {
2099
2103
  await this.adapter.open(this.schema);
2104
+ await this.adapter.execute(
2105
+ "CREATE TABLE IF NOT EXISTS _kora_scope_retractions (collection TEXT NOT NULL, record_id TEXT NOT NULL, PRIMARY KEY (collection, record_id))"
2106
+ );
2100
2107
  await this.runMigrationsIfNeeded();
2101
2108
  this.nodeId = await this.loadOrGenerateNodeId();
2102
2109
  this.clock = new HybridLogicalClock3(this.nodeId);
@@ -2216,6 +2223,22 @@ var Store = class {
2216
2223
  [op.id]
2217
2224
  );
2218
2225
  if (existing.length > 0) {
2226
+ const retracted = await this.adapter.query(
2227
+ "SELECT record_id FROM _kora_scope_retractions WHERE collection = ? AND record_id = ?",
2228
+ [collection, op.recordId]
2229
+ );
2230
+ if (retracted.length > 0) {
2231
+ await this.adapter.transaction(async (tx) => {
2232
+ await tx.execute(`UPDATE ${quoteIdent7(collection)} SET _deleted = 0 WHERE id = ?`, [
2233
+ op.recordId
2234
+ ]);
2235
+ await tx.execute(
2236
+ "DELETE FROM _kora_scope_retractions WHERE collection = ? AND record_id = ?",
2237
+ [collection, op.recordId]
2238
+ );
2239
+ });
2240
+ this.subscriptionManager.invalidate(collection);
2241
+ }
2219
2242
  return "duplicate";
2220
2243
  }
2221
2244
  if (this.clock) {
@@ -2401,6 +2424,49 @@ var Store = class {
2401
2424
  this.subscriptionManager.notify(collection, op);
2402
2425
  return "applied";
2403
2426
  }
2427
+ /**
2428
+ * Hide a record from this client's authorized materialized view. This writes no
2429
+ * replicated operation and therefore cannot be mistaken for a domain deletion.
2430
+ * The marker lets a later authoritative backfill reactivate the retained row.
2431
+ */
2432
+ async applyScopeRetraction(collection, recordId) {
2433
+ this.ensureOpen();
2434
+ if (!this.schema.collections[collection]) return;
2435
+ await this.adapter.transaction(async (tx) => {
2436
+ await tx.execute(`UPDATE ${quoteIdent7(collection)} SET _deleted = 1 WHERE id = ?`, [recordId]);
2437
+ await tx.execute(
2438
+ "INSERT OR REPLACE INTO _kora_scope_retractions (collection, record_id) VALUES (?, ?)",
2439
+ [collection, recordId]
2440
+ );
2441
+ });
2442
+ this.subscriptionManager.invalidate(collection);
2443
+ }
2444
+ /** Hide all live rows that no longer match a newly accepted server scope. */
2445
+ async applyScopeNarrowing(scopes) {
2446
+ this.ensureOpen();
2447
+ const retractions = [];
2448
+ for (const [collection, definition] of Object.entries(this.schema.collections)) {
2449
+ const rows = await this.adapter.query(
2450
+ `SELECT * FROM ${quoteIdent7(collection)} WHERE _deleted = 0`
2451
+ );
2452
+ const predicate = scopes[collection];
2453
+ for (const row of rows) {
2454
+ const record = deserializeRecord(row, definition.fields);
2455
+ const matches = predicate !== void 0 && Object.entries(predicate).every(([field, expected]) => {
2456
+ if (expected && typeof expected === "object" && !Array.isArray(expected) && "$in" in expected) {
2457
+ const values = expected.$in;
2458
+ return Array.isArray(values) && values.some((value) => Object.is(record[field], value));
2459
+ }
2460
+ return Object.is(record[field], expected);
2461
+ });
2462
+ if (!matches) {
2463
+ await this.applyScopeRetraction(collection, String(record.id));
2464
+ retractions.push({ collection, recordId: String(record.id) });
2465
+ }
2466
+ }
2467
+ }
2468
+ return retractions;
2469
+ }
2404
2470
  /**
2405
2471
  * Materialize update/delete operations that were logged for a record BEFORE
2406
2472
  * its insert arrived (reordered delivery). Runs inside the insert's write