@anuma/sdk 5.0.0 → 5.0.1

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.
@@ -21056,7 +21056,7 @@ __decorateClass([
21056
21056
  ], VaultFolder.prototype, "context", 2);
21057
21057
 
21058
21058
  // src/lib/db/schema.ts
21059
- var SDK_SCHEMA_VERSION = 42;
21059
+ var SDK_SCHEMA_VERSION = 43;
21060
21060
  var sdkSchema = (0, import_watermelondb19.appSchema)({
21061
21061
  version: SDK_SCHEMA_VERSION,
21062
21062
  tables: [
@@ -21104,7 +21104,16 @@ var sdkSchema = (0, import_watermelondb19.appSchema)({
21104
21104
  { name: "conversation_id", type: "string", isIndexed: true },
21105
21105
  { name: "title", type: "string" },
21106
21106
  { name: "project_id", type: "string", isOptional: true, isIndexed: true },
21107
- { name: "created_at", type: "number" },
21107
+ // Indexed to match every other list-sorted created_at in this schema.
21108
+ // Note this single-column index is NOT what makes the conversation list
21109
+ // reads fast on native SQLite — they all filter is_deleted as well, and
21110
+ // SQLite will not combine the two indexes, so the composite created by
21111
+ // the v42 migration is what its planner actually uses. This declaration
21112
+ // still earns its keep on the other two adapters: LokiJS builds its
21113
+ // binary indices from `isIndexed` (and ignores `sql` migration steps
21114
+ // outright), and Postgres gathers statistics via autovacuum, so its
21115
+ // planner can use a single-column index the SQLite planner skips.
21116
+ { name: "created_at", type: "number", isIndexed: true },
21108
21117
  { name: "updated_at", type: "number" },
21109
21118
  { name: "is_deleted", type: "boolean", isIndexed: true },
21110
21119
  { name: "pinned_at", type: "number", isOptional: true }
@@ -21971,6 +21980,49 @@ var sdkMigrations = (0, import_migrations2.schemaMigrations)({
21971
21980
  ]
21972
21981
  })
21973
21982
  ]
21983
+ },
21984
+ // v42 -> v43: Give the conversation list reads an index they can actually
21985
+ // sort on. All five of them (getConversationsOp, getConversationsLazyOp,
21986
+ // getConversationsByProjectOp, getConversationsByProjectLazyOp and the
21987
+ // keyset getConversationsPageOp) emit `where is_deleted is 0 ... order by
21988
+ // created_at desc`, so before this they resolved is_deleted through its
21989
+ // index and then built a temp B-tree over every live row just to return
21990
+ // the newest page.
21991
+ //
21992
+ // The index is COMPOSITE, and that is the whole point — a bare index on
21993
+ // created_at does not fix this. SQLite will not combine two single-column
21994
+ // indexes here, so with `is_deleted` indexed and `created_at` indexed
21995
+ // separately it still picks conversations_is_deleted and still temp-sorts.
21996
+ // It only prefers a lone created_at index once ANALYZE has populated
21997
+ // sqlite_stat1, and nothing ever runs ANALYZE: not WatermelonDB, not its
21998
+ // native SQLite bindings, not this SDK. Every device is permanently in the
21999
+ // no-statistics state, so the index has to satisfy the filter and the sort
22000
+ // in one structure. `(is_deleted, created_at)` does: equality on the
22001
+ // leading column, ordered scan on the second, and the keyset boundary
22002
+ // becomes a range seek on the same index.
22003
+ //
22004
+ // unsafeExecuteSql, not addColumns: both columns already exist and
22005
+ // WatermelonDB has no add-index migration step, so raw SQL is the only way
22006
+ // to build one in place (same shape as the v19 -> v20 memory_vault
22007
+ // migration). IF NOT EXISTS keeps the step idempotent.
22008
+ //
22009
+ // Two limits worth knowing before someone re-measures this and finds it
22010
+ // missing. First, the LokiJS (web) adapter discards `sql` steps entirely,
22011
+ // so existing browser databases gain nothing — web's only lever is the
22012
+ // `isIndexed` flag on the column itself. Second, this index reaches
22013
+ // MIGRATED databases only: WatermelonDB builds a fresh database purely from
22014
+ // the encoded schema, and its schema format cannot express a composite
22015
+ // index. The one hook that could (`unsafeSql` on the table) is a function,
22016
+ // and the LokiJS adapter posts the schema to its worker, so attaching it
22017
+ // makes the whole schema fail structuredClone and takes the database down
22018
+ // at setup for anyone on `useWebWorker: true`. Not worth it for an index.
22019
+ {
22020
+ toVersion: 43,
22021
+ steps: [
22022
+ (0, import_migrations2.unsafeExecuteSql)(
22023
+ "CREATE INDEX IF NOT EXISTS conversations_is_deleted_created_at ON conversations (is_deleted, created_at);"
22024
+ )
22025
+ ]
21974
22026
  }
21975
22027
  ]
21976
22028
  });
@@ -8779,8 +8779,12 @@ declare function useChatStorage(options: UseChatStorageOptions): UseChatStorageR
8779
8779
  * `topics_updated_at` is a SECOND timestamp because every topic writer pins
8780
8780
  * `updated_at` on purpose (recall's recency multiplier) and both sync paths
8781
8781
  * key on `updated_at`, so a topic-only change would neither upload nor merge
8782
+ * - v43: Added a (is_deleted, created_at) index to conversations. Every
8783
+ * conversation list read filters is_deleted and orders by created_at DESC,
8784
+ * which previously meant a temp B-tree sort of the whole live set on every
8785
+ * read. Structural only — no column added, no data rewritten
8782
8786
  */
8783
- declare const SDK_SCHEMA_VERSION = 42;
8787
+ declare const SDK_SCHEMA_VERSION = 43;
8784
8788
  /**
8785
8789
  * Combined WatermelonDB schema for all SDK storage modules.
8786
8790
  *
@@ -8867,6 +8871,7 @@ declare const sdkSchema: Readonly<{
8867
8871
  * - v39 → v40: Added `fact_type`, `archived_at`, `trust_tier` columns to memory_vault for typed memory + decay + Tier-0 security (all nullable + plaintext, NULL backfill)
8868
8872
  * - v40 → v41: Added `visibility`, `twin_opt_in`, `published_at`, `geohash` columns to memory_vault for the People Nearby cross-user visibility axis (two-tier `private | public`; null/unknown grandfathered as 'private')
8869
8873
  * - v41 → v42: Added `topics` + `topics_updated_at` columns to memory_vault, making a memory's topics the durable synced record and `entity`/`memory_entity` a device-local index over it (null `topics` = pre-v42, backfilled from the row's current links by the sweep)
8874
+ * - v42 → v43: Added a composite `(is_deleted, created_at)` index to conversations so the list reads stop temp-sorting (structural only, no data rewritten)
8870
8875
  */
8871
8876
  declare const sdkMigrations: Readonly<{
8872
8877
  validated: true;
@@ -8779,8 +8779,12 @@ declare function useChatStorage(options: UseChatStorageOptions): UseChatStorageR
8779
8779
  * `topics_updated_at` is a SECOND timestamp because every topic writer pins
8780
8780
  * `updated_at` on purpose (recall's recency multiplier) and both sync paths
8781
8781
  * key on `updated_at`, so a topic-only change would neither upload nor merge
8782
+ * - v43: Added a (is_deleted, created_at) index to conversations. Every
8783
+ * conversation list read filters is_deleted and orders by created_at DESC,
8784
+ * which previously meant a temp B-tree sort of the whole live set on every
8785
+ * read. Structural only — no column added, no data rewritten
8782
8786
  */
8783
- declare const SDK_SCHEMA_VERSION = 42;
8787
+ declare const SDK_SCHEMA_VERSION = 43;
8784
8788
  /**
8785
8789
  * Combined WatermelonDB schema for all SDK storage modules.
8786
8790
  *
@@ -8867,6 +8871,7 @@ declare const sdkSchema: Readonly<{
8867
8871
  * - v39 → v40: Added `fact_type`, `archived_at`, `trust_tier` columns to memory_vault for typed memory + decay + Tier-0 security (all nullable + plaintext, NULL backfill)
8868
8872
  * - v40 → v41: Added `visibility`, `twin_opt_in`, `published_at`, `geohash` columns to memory_vault for the People Nearby cross-user visibility axis (two-tier `private | public`; null/unknown grandfathered as 'private')
8869
8873
  * - v41 → v42: Added `topics` + `topics_updated_at` columns to memory_vault, making a memory's topics the durable synced record and `entity`/`memory_entity` a device-local index over it (null `topics` = pre-v42, backfilled from the row's current links by the sweep)
8874
+ * - v42 → v43: Added a composite `(is_deleted, created_at)` index to conversations so the list reads stop temp-sorting (structural only, no data rewritten)
8870
8875
  */
8871
8876
  declare const sdkMigrations: Readonly<{
8872
8877
  validated: true;
@@ -11980,7 +11980,7 @@ __decorateClass([
11980
11980
  ], VaultFolder.prototype, "context", 2);
11981
11981
 
11982
11982
  // src/lib/db/schema.ts
11983
- var SDK_SCHEMA_VERSION = 42;
11983
+ var SDK_SCHEMA_VERSION = 43;
11984
11984
  var sdkSchema = appSchema2({
11985
11985
  version: SDK_SCHEMA_VERSION,
11986
11986
  tables: [
@@ -12028,7 +12028,16 @@ var sdkSchema = appSchema2({
12028
12028
  { name: "conversation_id", type: "string", isIndexed: true },
12029
12029
  { name: "title", type: "string" },
12030
12030
  { name: "project_id", type: "string", isOptional: true, isIndexed: true },
12031
- { name: "created_at", type: "number" },
12031
+ // Indexed to match every other list-sorted created_at in this schema.
12032
+ // Note this single-column index is NOT what makes the conversation list
12033
+ // reads fast on native SQLite — they all filter is_deleted as well, and
12034
+ // SQLite will not combine the two indexes, so the composite created by
12035
+ // the v42 migration is what its planner actually uses. This declaration
12036
+ // still earns its keep on the other two adapters: LokiJS builds its
12037
+ // binary indices from `isIndexed` (and ignores `sql` migration steps
12038
+ // outright), and Postgres gathers statistics via autovacuum, so its
12039
+ // planner can use a single-column index the SQLite planner skips.
12040
+ { name: "created_at", type: "number", isIndexed: true },
12032
12041
  { name: "updated_at", type: "number" },
12033
12042
  { name: "is_deleted", type: "boolean", isIndexed: true },
12034
12043
  { name: "pinned_at", type: "number", isOptional: true }
@@ -12895,6 +12904,49 @@ var sdkMigrations = schemaMigrations2({
12895
12904
  ]
12896
12905
  })
12897
12906
  ]
12907
+ },
12908
+ // v42 -> v43: Give the conversation list reads an index they can actually
12909
+ // sort on. All five of them (getConversationsOp, getConversationsLazyOp,
12910
+ // getConversationsByProjectOp, getConversationsByProjectLazyOp and the
12911
+ // keyset getConversationsPageOp) emit `where is_deleted is 0 ... order by
12912
+ // created_at desc`, so before this they resolved is_deleted through its
12913
+ // index and then built a temp B-tree over every live row just to return
12914
+ // the newest page.
12915
+ //
12916
+ // The index is COMPOSITE, and that is the whole point — a bare index on
12917
+ // created_at does not fix this. SQLite will not combine two single-column
12918
+ // indexes here, so with `is_deleted` indexed and `created_at` indexed
12919
+ // separately it still picks conversations_is_deleted and still temp-sorts.
12920
+ // It only prefers a lone created_at index once ANALYZE has populated
12921
+ // sqlite_stat1, and nothing ever runs ANALYZE: not WatermelonDB, not its
12922
+ // native SQLite bindings, not this SDK. Every device is permanently in the
12923
+ // no-statistics state, so the index has to satisfy the filter and the sort
12924
+ // in one structure. `(is_deleted, created_at)` does: equality on the
12925
+ // leading column, ordered scan on the second, and the keyset boundary
12926
+ // becomes a range seek on the same index.
12927
+ //
12928
+ // unsafeExecuteSql, not addColumns: both columns already exist and
12929
+ // WatermelonDB has no add-index migration step, so raw SQL is the only way
12930
+ // to build one in place (same shape as the v19 -> v20 memory_vault
12931
+ // migration). IF NOT EXISTS keeps the step idempotent.
12932
+ //
12933
+ // Two limits worth knowing before someone re-measures this and finds it
12934
+ // missing. First, the LokiJS (web) adapter discards `sql` steps entirely,
12935
+ // so existing browser databases gain nothing — web's only lever is the
12936
+ // `isIndexed` flag on the column itself. Second, this index reaches
12937
+ // MIGRATED databases only: WatermelonDB builds a fresh database purely from
12938
+ // the encoded schema, and its schema format cannot express a composite
12939
+ // index. The one hook that could (`unsafeSql` on the table) is a function,
12940
+ // and the LokiJS adapter posts the schema to its worker, so attaching it
12941
+ // makes the whole schema fail structuredClone and takes the database down
12942
+ // at setup for anyone on `useWebWorker: true`. Not worth it for an index.
12943
+ {
12944
+ toVersion: 43,
12945
+ steps: [
12946
+ unsafeExecuteSql(
12947
+ "CREATE INDEX IF NOT EXISTS conversations_is_deleted_created_at ON conversations (is_deleted, created_at);"
12948
+ )
12949
+ ]
12898
12950
  }
12899
12951
  ]
12900
12952
  });
@@ -23629,7 +23629,7 @@ __decorateClass([
23629
23629
  ], VaultFolder.prototype, "context", 2);
23630
23630
 
23631
23631
  // src/lib/db/schema.ts
23632
- var SDK_SCHEMA_VERSION = 42;
23632
+ var SDK_SCHEMA_VERSION = 43;
23633
23633
  var sdkSchema = (0, import_watermelondb20.appSchema)({
23634
23634
  version: SDK_SCHEMA_VERSION,
23635
23635
  tables: [
@@ -23677,7 +23677,16 @@ var sdkSchema = (0, import_watermelondb20.appSchema)({
23677
23677
  { name: "conversation_id", type: "string", isIndexed: true },
23678
23678
  { name: "title", type: "string" },
23679
23679
  { name: "project_id", type: "string", isOptional: true, isIndexed: true },
23680
- { name: "created_at", type: "number" },
23680
+ // Indexed to match every other list-sorted created_at in this schema.
23681
+ // Note this single-column index is NOT what makes the conversation list
23682
+ // reads fast on native SQLite — they all filter is_deleted as well, and
23683
+ // SQLite will not combine the two indexes, so the composite created by
23684
+ // the v42 migration is what its planner actually uses. This declaration
23685
+ // still earns its keep on the other two adapters: LokiJS builds its
23686
+ // binary indices from `isIndexed` (and ignores `sql` migration steps
23687
+ // outright), and Postgres gathers statistics via autovacuum, so its
23688
+ // planner can use a single-column index the SQLite planner skips.
23689
+ { name: "created_at", type: "number", isIndexed: true },
23681
23690
  { name: "updated_at", type: "number" },
23682
23691
  { name: "is_deleted", type: "boolean", isIndexed: true },
23683
23692
  { name: "pinned_at", type: "number", isOptional: true }
@@ -24544,6 +24553,49 @@ var sdkMigrations = (0, import_migrations2.schemaMigrations)({
24544
24553
  ]
24545
24554
  })
24546
24555
  ]
24556
+ },
24557
+ // v42 -> v43: Give the conversation list reads an index they can actually
24558
+ // sort on. All five of them (getConversationsOp, getConversationsLazyOp,
24559
+ // getConversationsByProjectOp, getConversationsByProjectLazyOp and the
24560
+ // keyset getConversationsPageOp) emit `where is_deleted is 0 ... order by
24561
+ // created_at desc`, so before this they resolved is_deleted through its
24562
+ // index and then built a temp B-tree over every live row just to return
24563
+ // the newest page.
24564
+ //
24565
+ // The index is COMPOSITE, and that is the whole point — a bare index on
24566
+ // created_at does not fix this. SQLite will not combine two single-column
24567
+ // indexes here, so with `is_deleted` indexed and `created_at` indexed
24568
+ // separately it still picks conversations_is_deleted and still temp-sorts.
24569
+ // It only prefers a lone created_at index once ANALYZE has populated
24570
+ // sqlite_stat1, and nothing ever runs ANALYZE: not WatermelonDB, not its
24571
+ // native SQLite bindings, not this SDK. Every device is permanently in the
24572
+ // no-statistics state, so the index has to satisfy the filter and the sort
24573
+ // in one structure. `(is_deleted, created_at)` does: equality on the
24574
+ // leading column, ordered scan on the second, and the keyset boundary
24575
+ // becomes a range seek on the same index.
24576
+ //
24577
+ // unsafeExecuteSql, not addColumns: both columns already exist and
24578
+ // WatermelonDB has no add-index migration step, so raw SQL is the only way
24579
+ // to build one in place (same shape as the v19 -> v20 memory_vault
24580
+ // migration). IF NOT EXISTS keeps the step idempotent.
24581
+ //
24582
+ // Two limits worth knowing before someone re-measures this and finds it
24583
+ // missing. First, the LokiJS (web) adapter discards `sql` steps entirely,
24584
+ // so existing browser databases gain nothing — web's only lever is the
24585
+ // `isIndexed` flag on the column itself. Second, this index reaches
24586
+ // MIGRATED databases only: WatermelonDB builds a fresh database purely from
24587
+ // the encoded schema, and its schema format cannot express a composite
24588
+ // index. The one hook that could (`unsafeSql` on the table) is a function,
24589
+ // and the LokiJS adapter posts the schema to its worker, so attaching it
24590
+ // makes the whole schema fail structuredClone and takes the database down
24591
+ // at setup for anyone on `useWebWorker: true`. Not worth it for an index.
24592
+ {
24593
+ toVersion: 43,
24594
+ steps: [
24595
+ (0, import_migrations2.unsafeExecuteSql)(
24596
+ "CREATE INDEX IF NOT EXISTS conversations_is_deleted_created_at ON conversations (is_deleted, created_at);"
24597
+ )
24598
+ ]
24547
24599
  }
24548
24600
  ]
24549
24601
  });
@@ -10393,8 +10393,12 @@ declare class WatermelonChatStorageAdapter implements ChatStorageAdapter {
10393
10393
  * `topics_updated_at` is a SECOND timestamp because every topic writer pins
10394
10394
  * `updated_at` on purpose (recall's recency multiplier) and both sync paths
10395
10395
  * key on `updated_at`, so a topic-only change would neither upload nor merge
10396
+ * - v43: Added a (is_deleted, created_at) index to conversations. Every
10397
+ * conversation list read filters is_deleted and orders by created_at DESC,
10398
+ * which previously meant a temp B-tree sort of the whole live set on every
10399
+ * read. Structural only — no column added, no data rewritten
10396
10400
  */
10397
- declare const SDK_SCHEMA_VERSION = 42;
10401
+ declare const SDK_SCHEMA_VERSION = 43;
10398
10402
  /**
10399
10403
  * Combined WatermelonDB schema for all SDK storage modules.
10400
10404
  *
@@ -10481,6 +10485,7 @@ declare const sdkSchema: Readonly<{
10481
10485
  * - v39 → v40: Added `fact_type`, `archived_at`, `trust_tier` columns to memory_vault for typed memory + decay + Tier-0 security (all nullable + plaintext, NULL backfill)
10482
10486
  * - v40 → v41: Added `visibility`, `twin_opt_in`, `published_at`, `geohash` columns to memory_vault for the People Nearby cross-user visibility axis (two-tier `private | public`; null/unknown grandfathered as 'private')
10483
10487
  * - v41 → v42: Added `topics` + `topics_updated_at` columns to memory_vault, making a memory's topics the durable synced record and `entity`/`memory_entity` a device-local index over it (null `topics` = pre-v42, backfilled from the row's current links by the sweep)
10488
+ * - v42 → v43: Added a composite `(is_deleted, created_at)` index to conversations so the list reads stop temp-sorting (structural only, no data rewritten)
10484
10489
  */
10485
10490
  declare const sdkMigrations: Readonly<{
10486
10491
  validated: true;
@@ -10393,8 +10393,12 @@ declare class WatermelonChatStorageAdapter implements ChatStorageAdapter {
10393
10393
  * `topics_updated_at` is a SECOND timestamp because every topic writer pins
10394
10394
  * `updated_at` on purpose (recall's recency multiplier) and both sync paths
10395
10395
  * key on `updated_at`, so a topic-only change would neither upload nor merge
10396
+ * - v43: Added a (is_deleted, created_at) index to conversations. Every
10397
+ * conversation list read filters is_deleted and orders by created_at DESC,
10398
+ * which previously meant a temp B-tree sort of the whole live set on every
10399
+ * read. Structural only — no column added, no data rewritten
10396
10400
  */
10397
- declare const SDK_SCHEMA_VERSION = 42;
10401
+ declare const SDK_SCHEMA_VERSION = 43;
10398
10402
  /**
10399
10403
  * Combined WatermelonDB schema for all SDK storage modules.
10400
10404
  *
@@ -10481,6 +10485,7 @@ declare const sdkSchema: Readonly<{
10481
10485
  * - v39 → v40: Added `fact_type`, `archived_at`, `trust_tier` columns to memory_vault for typed memory + decay + Tier-0 security (all nullable + plaintext, NULL backfill)
10482
10486
  * - v40 → v41: Added `visibility`, `twin_opt_in`, `published_at`, `geohash` columns to memory_vault for the People Nearby cross-user visibility axis (two-tier `private | public`; null/unknown grandfathered as 'private')
10483
10487
  * - v41 → v42: Added `topics` + `topics_updated_at` columns to memory_vault, making a memory's topics the durable synced record and `entity`/`memory_entity` a device-local index over it (null `topics` = pre-v42, backfilled from the row's current links by the sweep)
10488
+ * - v42 → v43: Added a composite `(is_deleted, created_at)` index to conversations so the list reads stop temp-sorting (structural only, no data rewritten)
10484
10489
  */
10485
10490
  declare const sdkMigrations: Readonly<{
10486
10491
  validated: true;
@@ -14064,7 +14064,7 @@ __decorateClass([
14064
14064
  ], VaultFolder.prototype, "context", 2);
14065
14065
 
14066
14066
  // src/lib/db/schema.ts
14067
- var SDK_SCHEMA_VERSION = 42;
14067
+ var SDK_SCHEMA_VERSION = 43;
14068
14068
  var sdkSchema = appSchema2({
14069
14069
  version: SDK_SCHEMA_VERSION,
14070
14070
  tables: [
@@ -14112,7 +14112,16 @@ var sdkSchema = appSchema2({
14112
14112
  { name: "conversation_id", type: "string", isIndexed: true },
14113
14113
  { name: "title", type: "string" },
14114
14114
  { name: "project_id", type: "string", isOptional: true, isIndexed: true },
14115
- { name: "created_at", type: "number" },
14115
+ // Indexed to match every other list-sorted created_at in this schema.
14116
+ // Note this single-column index is NOT what makes the conversation list
14117
+ // reads fast on native SQLite — they all filter is_deleted as well, and
14118
+ // SQLite will not combine the two indexes, so the composite created by
14119
+ // the v42 migration is what its planner actually uses. This declaration
14120
+ // still earns its keep on the other two adapters: LokiJS builds its
14121
+ // binary indices from `isIndexed` (and ignores `sql` migration steps
14122
+ // outright), and Postgres gathers statistics via autovacuum, so its
14123
+ // planner can use a single-column index the SQLite planner skips.
14124
+ { name: "created_at", type: "number", isIndexed: true },
14116
14125
  { name: "updated_at", type: "number" },
14117
14126
  { name: "is_deleted", type: "boolean", isIndexed: true },
14118
14127
  { name: "pinned_at", type: "number", isOptional: true }
@@ -14979,6 +14988,49 @@ var sdkMigrations = schemaMigrations2({
14979
14988
  ]
14980
14989
  })
14981
14990
  ]
14991
+ },
14992
+ // v42 -> v43: Give the conversation list reads an index they can actually
14993
+ // sort on. All five of them (getConversationsOp, getConversationsLazyOp,
14994
+ // getConversationsByProjectOp, getConversationsByProjectLazyOp and the
14995
+ // keyset getConversationsPageOp) emit `where is_deleted is 0 ... order by
14996
+ // created_at desc`, so before this they resolved is_deleted through its
14997
+ // index and then built a temp B-tree over every live row just to return
14998
+ // the newest page.
14999
+ //
15000
+ // The index is COMPOSITE, and that is the whole point — a bare index on
15001
+ // created_at does not fix this. SQLite will not combine two single-column
15002
+ // indexes here, so with `is_deleted` indexed and `created_at` indexed
15003
+ // separately it still picks conversations_is_deleted and still temp-sorts.
15004
+ // It only prefers a lone created_at index once ANALYZE has populated
15005
+ // sqlite_stat1, and nothing ever runs ANALYZE: not WatermelonDB, not its
15006
+ // native SQLite bindings, not this SDK. Every device is permanently in the
15007
+ // no-statistics state, so the index has to satisfy the filter and the sort
15008
+ // in one structure. `(is_deleted, created_at)` does: equality on the
15009
+ // leading column, ordered scan on the second, and the keyset boundary
15010
+ // becomes a range seek on the same index.
15011
+ //
15012
+ // unsafeExecuteSql, not addColumns: both columns already exist and
15013
+ // WatermelonDB has no add-index migration step, so raw SQL is the only way
15014
+ // to build one in place (same shape as the v19 -> v20 memory_vault
15015
+ // migration). IF NOT EXISTS keeps the step idempotent.
15016
+ //
15017
+ // Two limits worth knowing before someone re-measures this and finds it
15018
+ // missing. First, the LokiJS (web) adapter discards `sql` steps entirely,
15019
+ // so existing browser databases gain nothing — web's only lever is the
15020
+ // `isIndexed` flag on the column itself. Second, this index reaches
15021
+ // MIGRATED databases only: WatermelonDB builds a fresh database purely from
15022
+ // the encoded schema, and its schema format cannot express a composite
15023
+ // index. The one hook that could (`unsafeSql` on the table) is a function,
15024
+ // and the LokiJS adapter posts the schema to its worker, so attaching it
15025
+ // makes the whole schema fail structuredClone and takes the database down
15026
+ // at setup for anyone on `useWebWorker: true`. Not worth it for an index.
15027
+ {
15028
+ toVersion: 43,
15029
+ steps: [
15030
+ unsafeExecuteSql(
15031
+ "CREATE INDEX IF NOT EXISTS conversations_is_deleted_created_at ON conversations (is_deleted, created_at);"
15032
+ )
15033
+ ]
14982
15034
  }
14983
15035
  ]
14984
15036
  });
@@ -24525,7 +24525,7 @@ __decorateClass([
24525
24525
  ], VaultFolder.prototype, "context", 2);
24526
24526
 
24527
24527
  // src/lib/db/schema.ts
24528
- var SDK_SCHEMA_VERSION = 42;
24528
+ var SDK_SCHEMA_VERSION = 43;
24529
24529
  var sdkSchema = (0, import_watermelondb12.appSchema)({
24530
24530
  version: SDK_SCHEMA_VERSION,
24531
24531
  tables: [
@@ -24573,7 +24573,16 @@ var sdkSchema = (0, import_watermelondb12.appSchema)({
24573
24573
  { name: "conversation_id", type: "string", isIndexed: true },
24574
24574
  { name: "title", type: "string" },
24575
24575
  { name: "project_id", type: "string", isOptional: true, isIndexed: true },
24576
- { name: "created_at", type: "number" },
24576
+ // Indexed to match every other list-sorted created_at in this schema.
24577
+ // Note this single-column index is NOT what makes the conversation list
24578
+ // reads fast on native SQLite — they all filter is_deleted as well, and
24579
+ // SQLite will not combine the two indexes, so the composite created by
24580
+ // the v42 migration is what its planner actually uses. This declaration
24581
+ // still earns its keep on the other two adapters: LokiJS builds its
24582
+ // binary indices from `isIndexed` (and ignores `sql` migration steps
24583
+ // outright), and Postgres gathers statistics via autovacuum, so its
24584
+ // planner can use a single-column index the SQLite planner skips.
24585
+ { name: "created_at", type: "number", isIndexed: true },
24577
24586
  { name: "updated_at", type: "number" },
24578
24587
  { name: "is_deleted", type: "boolean", isIndexed: true },
24579
24588
  { name: "pinned_at", type: "number", isOptional: true }
@@ -25440,6 +25449,49 @@ var sdkMigrations = (0, import_migrations.schemaMigrations)({
25440
25449
  ]
25441
25450
  })
25442
25451
  ]
25452
+ },
25453
+ // v42 -> v43: Give the conversation list reads an index they can actually
25454
+ // sort on. All five of them (getConversationsOp, getConversationsLazyOp,
25455
+ // getConversationsByProjectOp, getConversationsByProjectLazyOp and the
25456
+ // keyset getConversationsPageOp) emit `where is_deleted is 0 ... order by
25457
+ // created_at desc`, so before this they resolved is_deleted through its
25458
+ // index and then built a temp B-tree over every live row just to return
25459
+ // the newest page.
25460
+ //
25461
+ // The index is COMPOSITE, and that is the whole point — a bare index on
25462
+ // created_at does not fix this. SQLite will not combine two single-column
25463
+ // indexes here, so with `is_deleted` indexed and `created_at` indexed
25464
+ // separately it still picks conversations_is_deleted and still temp-sorts.
25465
+ // It only prefers a lone created_at index once ANALYZE has populated
25466
+ // sqlite_stat1, and nothing ever runs ANALYZE: not WatermelonDB, not its
25467
+ // native SQLite bindings, not this SDK. Every device is permanently in the
25468
+ // no-statistics state, so the index has to satisfy the filter and the sort
25469
+ // in one structure. `(is_deleted, created_at)` does: equality on the
25470
+ // leading column, ordered scan on the second, and the keyset boundary
25471
+ // becomes a range seek on the same index.
25472
+ //
25473
+ // unsafeExecuteSql, not addColumns: both columns already exist and
25474
+ // WatermelonDB has no add-index migration step, so raw SQL is the only way
25475
+ // to build one in place (same shape as the v19 -> v20 memory_vault
25476
+ // migration). IF NOT EXISTS keeps the step idempotent.
25477
+ //
25478
+ // Two limits worth knowing before someone re-measures this and finds it
25479
+ // missing. First, the LokiJS (web) adapter discards `sql` steps entirely,
25480
+ // so existing browser databases gain nothing — web's only lever is the
25481
+ // `isIndexed` flag on the column itself. Second, this index reaches
25482
+ // MIGRATED databases only: WatermelonDB builds a fresh database purely from
25483
+ // the encoded schema, and its schema format cannot express a composite
25484
+ // index. The one hook that could (`unsafeSql` on the table) is a function,
25485
+ // and the LokiJS adapter posts the schema to its worker, so attaching it
25486
+ // makes the whole schema fail structuredClone and takes the database down
25487
+ // at setup for anyone on `useWebWorker: true`. Not worth it for an index.
25488
+ {
25489
+ toVersion: 43,
25490
+ steps: [
25491
+ (0, import_migrations.unsafeExecuteSql)(
25492
+ "CREATE INDEX IF NOT EXISTS conversations_is_deleted_created_at ON conversations (is_deleted, created_at);"
25493
+ )
25494
+ ]
25443
25495
  }
25444
25496
  ]
25445
25497
  });
@@ -87,8 +87,12 @@ import { ResultCallback } from '@nozbe/watermelondb/utils/fp/Result';
87
87
  * `topics_updated_at` is a SECOND timestamp because every topic writer pins
88
88
  * `updated_at` on purpose (recall's recency multiplier) and both sync paths
89
89
  * key on `updated_at`, so a topic-only change would neither upload nor merge
90
+ * - v43: Added a (is_deleted, created_at) index to conversations. Every
91
+ * conversation list read filters is_deleted and orders by created_at DESC,
92
+ * which previously meant a temp B-tree sort of the whole live set on every
93
+ * read. Structural only — no column added, no data rewritten
90
94
  */
91
- declare const SDK_SCHEMA_VERSION = 42;
95
+ declare const SDK_SCHEMA_VERSION = 43;
92
96
  /**
93
97
  * Combined WatermelonDB schema for all SDK storage modules.
94
98
  *
@@ -175,6 +179,7 @@ declare const sdkSchema: Readonly<{
175
179
  * - v39 → v40: Added `fact_type`, `archived_at`, `trust_tier` columns to memory_vault for typed memory + decay + Tier-0 security (all nullable + plaintext, NULL backfill)
176
180
  * - v40 → v41: Added `visibility`, `twin_opt_in`, `published_at`, `geohash` columns to memory_vault for the People Nearby cross-user visibility axis (two-tier `private | public`; null/unknown grandfathered as 'private')
177
181
  * - v41 → v42: Added `topics` + `topics_updated_at` columns to memory_vault, making a memory's topics the durable synced record and `entity`/`memory_entity` a device-local index over it (null `topics` = pre-v42, backfilled from the row's current links by the sweep)
182
+ * - v42 → v43: Added a composite `(is_deleted, created_at)` index to conversations so the list reads stop temp-sorting (structural only, no data rewritten)
178
183
  */
179
184
  declare const sdkMigrations: Readonly<{
180
185
  validated: true;
@@ -87,8 +87,12 @@ import { ResultCallback } from '@nozbe/watermelondb/utils/fp/Result';
87
87
  * `topics_updated_at` is a SECOND timestamp because every topic writer pins
88
88
  * `updated_at` on purpose (recall's recency multiplier) and both sync paths
89
89
  * key on `updated_at`, so a topic-only change would neither upload nor merge
90
+ * - v43: Added a (is_deleted, created_at) index to conversations. Every
91
+ * conversation list read filters is_deleted and orders by created_at DESC,
92
+ * which previously meant a temp B-tree sort of the whole live set on every
93
+ * read. Structural only — no column added, no data rewritten
90
94
  */
91
- declare const SDK_SCHEMA_VERSION = 42;
95
+ declare const SDK_SCHEMA_VERSION = 43;
92
96
  /**
93
97
  * Combined WatermelonDB schema for all SDK storage modules.
94
98
  *
@@ -175,6 +179,7 @@ declare const sdkSchema: Readonly<{
175
179
  * - v39 → v40: Added `fact_type`, `archived_at`, `trust_tier` columns to memory_vault for typed memory + decay + Tier-0 security (all nullable + plaintext, NULL backfill)
176
180
  * - v40 → v41: Added `visibility`, `twin_opt_in`, `published_at`, `geohash` columns to memory_vault for the People Nearby cross-user visibility axis (two-tier `private | public`; null/unknown grandfathered as 'private')
177
181
  * - v41 → v42: Added `topics` + `topics_updated_at` columns to memory_vault, making a memory's topics the durable synced record and `entity`/`memory_entity` a device-local index over it (null `topics` = pre-v42, backfilled from the row's current links by the sweep)
182
+ * - v42 → v43: Added a composite `(is_deleted, created_at)` index to conversations so the list reads stop temp-sorting (structural only, no data rewritten)
178
183
  */
179
184
  declare const sdkMigrations: Readonly<{
180
185
  validated: true;
@@ -1319,7 +1319,7 @@ __decorateClass([
1319
1319
  ], VaultFolder.prototype, "context", 2);
1320
1320
 
1321
1321
  // src/lib/db/schema.ts
1322
- var SDK_SCHEMA_VERSION = 42;
1322
+ var SDK_SCHEMA_VERSION = 43;
1323
1323
  var sdkSchema = (0, import_watermelondb12.appSchema)({
1324
1324
  version: SDK_SCHEMA_VERSION,
1325
1325
  tables: [
@@ -1367,7 +1367,16 @@ var sdkSchema = (0, import_watermelondb12.appSchema)({
1367
1367
  { name: "conversation_id", type: "string", isIndexed: true },
1368
1368
  { name: "title", type: "string" },
1369
1369
  { name: "project_id", type: "string", isOptional: true, isIndexed: true },
1370
- { name: "created_at", type: "number" },
1370
+ // Indexed to match every other list-sorted created_at in this schema.
1371
+ // Note this single-column index is NOT what makes the conversation list
1372
+ // reads fast on native SQLite — they all filter is_deleted as well, and
1373
+ // SQLite will not combine the two indexes, so the composite created by
1374
+ // the v42 migration is what its planner actually uses. This declaration
1375
+ // still earns its keep on the other two adapters: LokiJS builds its
1376
+ // binary indices from `isIndexed` (and ignores `sql` migration steps
1377
+ // outright), and Postgres gathers statistics via autovacuum, so its
1378
+ // planner can use a single-column index the SQLite planner skips.
1379
+ { name: "created_at", type: "number", isIndexed: true },
1371
1380
  { name: "updated_at", type: "number" },
1372
1381
  { name: "is_deleted", type: "boolean", isIndexed: true },
1373
1382
  { name: "pinned_at", type: "number", isOptional: true }
@@ -2234,6 +2243,49 @@ var sdkMigrations = (0, import_migrations.schemaMigrations)({
2234
2243
  ]
2235
2244
  })
2236
2245
  ]
2246
+ },
2247
+ // v42 -> v43: Give the conversation list reads an index they can actually
2248
+ // sort on. All five of them (getConversationsOp, getConversationsLazyOp,
2249
+ // getConversationsByProjectOp, getConversationsByProjectLazyOp and the
2250
+ // keyset getConversationsPageOp) emit `where is_deleted is 0 ... order by
2251
+ // created_at desc`, so before this they resolved is_deleted through its
2252
+ // index and then built a temp B-tree over every live row just to return
2253
+ // the newest page.
2254
+ //
2255
+ // The index is COMPOSITE, and that is the whole point — a bare index on
2256
+ // created_at does not fix this. SQLite will not combine two single-column
2257
+ // indexes here, so with `is_deleted` indexed and `created_at` indexed
2258
+ // separately it still picks conversations_is_deleted and still temp-sorts.
2259
+ // It only prefers a lone created_at index once ANALYZE has populated
2260
+ // sqlite_stat1, and nothing ever runs ANALYZE: not WatermelonDB, not its
2261
+ // native SQLite bindings, not this SDK. Every device is permanently in the
2262
+ // no-statistics state, so the index has to satisfy the filter and the sort
2263
+ // in one structure. `(is_deleted, created_at)` does: equality on the
2264
+ // leading column, ordered scan on the second, and the keyset boundary
2265
+ // becomes a range seek on the same index.
2266
+ //
2267
+ // unsafeExecuteSql, not addColumns: both columns already exist and
2268
+ // WatermelonDB has no add-index migration step, so raw SQL is the only way
2269
+ // to build one in place (same shape as the v19 -> v20 memory_vault
2270
+ // migration). IF NOT EXISTS keeps the step idempotent.
2271
+ //
2272
+ // Two limits worth knowing before someone re-measures this and finds it
2273
+ // missing. First, the LokiJS (web) adapter discards `sql` steps entirely,
2274
+ // so existing browser databases gain nothing — web's only lever is the
2275
+ // `isIndexed` flag on the column itself. Second, this index reaches
2276
+ // MIGRATED databases only: WatermelonDB builds a fresh database purely from
2277
+ // the encoded schema, and its schema format cannot express a composite
2278
+ // index. The one hook that could (`unsafeSql` on the table) is a function,
2279
+ // and the LokiJS adapter posts the schema to its worker, so attaching it
2280
+ // makes the whole schema fail structuredClone and takes the database down
2281
+ // at setup for anyone on `useWebWorker: true`. Not worth it for an index.
2282
+ {
2283
+ toVersion: 43,
2284
+ steps: [
2285
+ (0, import_migrations.unsafeExecuteSql)(
2286
+ "CREATE INDEX IF NOT EXISTS conversations_is_deleted_created_at ON conversations (is_deleted, created_at);"
2287
+ )
2288
+ ]
2237
2289
  }
2238
2290
  ]
2239
2291
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@anuma/sdk",
3
- "version": "5.0.0",
3
+ "version": "5.0.1",
4
4
  "description": "",
5
5
  "main": "./dist/index.cjs",
6
6
  "module": "./dist/index.mjs",