js-bao 0.12.0-alpha.0 → 0.12.0-alpha.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.
package/README.md CHANGED
@@ -1559,6 +1559,18 @@ See the `examples/` directory for complete working examples:
1559
1559
  Changes to the on-disk behaviour of `js-bao/cloudflare/do` (`DurableObjectEngine`)
1560
1560
  that a store's owner can observe. Newest first.
1561
1561
 
1562
+ - **`idx_records_type` is retired** (#3876). The primary key `(_type, _id)`
1563
+ already leads with `_type`, so a new store no longer builds the index and an
1564
+ existing one loses it on its next open, removed by definition under any name
1565
+ (an ingest-suffixed copy included); a record insert writes one row fewer.
1566
+ Dropping a field index or a unique constraint keeps the physical index while
1567
+ another registration still maps to the same definition, under the name it
1568
+ already has; a later index whose canonical name that kept index holds is
1569
+ created as `<canonical name>_2` (or the next free suffix). Duplicate copies of a
1570
+ definition the engine owns (base, retired or registered) collapse to one on
1571
+ open; an index created outside the engine is never dropped. Index definitions
1572
+ now compare with quoted literals verbatim, so `WHERE _type = 'a b'` and
1573
+ `WHERE _type = 'ab'` are different indexes. Client stores are unchanged.
1562
1574
  - **`_indexes.created_at` reports the first registration** (#3875). Re-registering
1563
1575
  a field index whose `field_type` and `is_unique` are unchanged no longer
1564
1576
  rewrites its `_indexes` row: it writes no row, and `created_at` keeps the time
@@ -2658,6 +2658,7 @@ __export(cloudflare_do_exports, {
2658
2658
  checkAutoStampsReservedFields: () => checkAutoStampsReservedFields,
2659
2659
  checkReservedFieldName: () => checkReservedFieldName,
2660
2660
  clearMetaSyncCache: () => clearMetaSyncCache,
2661
+ collapseDuplicateIndexes: () => collapseDuplicateIndexes,
2661
2662
  compareRecordIds: () => compareRecordIds,
2662
2663
  completeOverlayEntries: () => completeOverlayEntries,
2663
2664
  createDatabaseDO: () => createDatabaseDO,
@@ -2677,6 +2678,7 @@ __export(cloudflare_do_exports, {
2677
2678
  format2StringSetMembers: () => format2StringSetMembers,
2678
2679
  groupOverlayEntries: () => groupOverlayEntries,
2679
2680
  handleRequest: () => handleRequest,
2681
+ indexDefinitionKey: () => indexDefinitionKey,
2680
2682
  inferFieldType: () => inferFieldType,
2681
2683
  isAnchorId: () => isAnchorId,
2682
2684
  isFormat2StringSetYMap: () => isFormat2StringSetYMap,
@@ -2841,6 +2843,11 @@ var DatabaseEngine = class {
2841
2843
  // src/schema/JsonSchemaDDL.ts
2842
2844
  init_sql();
2843
2845
  init_sql();
2846
+ var RECORDS_TYPE_INDEX_SPEC = {
2847
+ table: "records",
2848
+ expressions: "_type",
2849
+ canonicalName: "idx_records_type"
2850
+ };
2844
2851
  var JsonSchemaDDL = class {
2845
2852
  /**
2846
2853
  * Generate CREATE TABLE statement for the records table
@@ -2924,14 +2931,16 @@ CREATE TABLE IF NOT EXISTS stringset_index (
2924
2931
  * A document whose tables were swapped by a bulk-load ingest carries these
2925
2932
  * under session-suffixed names; creating them by name on the next cold open
2926
2933
  * would build a second copy of each over the whole table.
2934
+ *
2935
+ * `records(_type)` is a base index only in yjs mode (#3876). The server's
2936
+ * primary key `(_type, _id)` already leads with `_type`, so on a
2937
+ * `DurableObjectEngine` the index served no plan the primary key's autoindex
2938
+ * could not and cost a row on every insert; the yjs-mode list stays in step
2939
+ * with the yjs-mode DDL, which clients keep.
2927
2940
  */
2928
2941
  static baseIndexSpecs(options) {
2929
2942
  const specs = [
2930
- {
2931
- table: "records",
2932
- expressions: "_type",
2933
- canonicalName: "idx_records_type"
2934
- },
2943
+ ...options.includeDocIdColumn ? [{ ...RECORDS_TYPE_INDEX_SPEC }] : [],
2935
2944
  {
2936
2945
  table: "stringset_index",
2937
2946
  expressions: "_type, field, value",
@@ -2960,22 +2969,28 @@ CREATE TABLE IF NOT EXISTS stringset_index (
2960
2969
  return specs;
2961
2970
  }
2962
2971
  /**
2963
- * The base indexes this build RETIRED, as specs (#3688).
2972
+ * The base indexes RETIRED, as specs, each removed BY DEFINITION for the
2973
+ * same reason the others are created that way: a document a bulk-load
2974
+ * ingest swapped carries it under a session-suffixed name, which a
2975
+ * `DROP INDEX IF EXISTS <canonical name>` would walk straight past.
2964
2976
  *
2965
- * `(_type, field, _record_id)` is a left prefix of the member table's UNIQUE
2966
- * autoindex, so it never served a plan the autoindex could not; what it did
2967
- * do was cost a second index write per member insert. It is removed BY
2968
- * DEFINITION for the same reason the others are created that way: a document
2969
- * a bulk-load ingest swapped carries it under a session-suffixed name, which
2970
- * a `DROP INDEX IF EXISTS idx_ss_type_field_record` would walk straight past.
2977
+ * - `idx_ss_type_field_record` (#3688): `(_type, field, _record_id)` is a
2978
+ * left prefix of the member table's UNIQUE autoindex, so it never served a
2979
+ * plan the autoindex could not; what it did do was cost a second index
2980
+ * write per member insert.
2981
+ * - `idx_records_type` (#3876), on the server only: the primary key
2982
+ * `(_type, _id)` leads with `_type`, so every `WHERE _type = ?` plans onto
2983
+ * its autoindex (covering, for a count), and the index cost a row on every
2984
+ * insert. Clients' yjs-mode DDL keeps it.
2971
2985
  */
2972
- static retiredBaseIndexSpecs(_options) {
2986
+ static retiredBaseIndexSpecs(options) {
2973
2987
  return [
2974
2988
  {
2975
2989
  table: "stringset_index",
2976
2990
  expressions: "_type, field, _record_id",
2977
2991
  canonicalName: "idx_ss_type_field_record"
2978
- }
2992
+ },
2993
+ ...options.includeDocIdColumn ? [] : [{ ...RECORDS_TYPE_INDEX_SPEC }]
2979
2994
  ];
2980
2995
  }
2981
2996
  /**
@@ -3421,55 +3436,134 @@ var JsonSchemaEngine = class extends DatabaseEngine {
3421
3436
  init_sql();
3422
3437
 
3423
3438
  // src/utils/physicalIndex.ts
3439
+ init_sql();
3424
3440
  var INDEX_SQL = /^\s*CREATE\s+(UNIQUE\s+)?INDEX\s+(?:IF\s+NOT\s+EXISTS\s+)?"?([A-Za-z0-9_$]+)"?\s+ON\s+"?([A-Za-z0-9_$]+)"?\s*\(([\s\S]*)\)\s*(?:WHERE\s+([\s\S]+))?$/i;
3441
+ var TIGHT = /* @__PURE__ */ new Set(["(", ")", ",", "=", "|"]);
3442
+ function normalizeSqlText(text) {
3443
+ let out = "";
3444
+ let pendingSpace = false;
3445
+ let i = 0;
3446
+ const emit = (chunk) => {
3447
+ const spaced = pendingSpace && out.length > 0 && !TIGHT.has(out[out.length - 1]) && !TIGHT.has(chunk[0]);
3448
+ if (spaced) out += " ";
3449
+ pendingSpace = false;
3450
+ out += chunk;
3451
+ };
3452
+ while (i < text.length) {
3453
+ const ch = text[i];
3454
+ if (ch === "'" || ch === '"') {
3455
+ let j = i + 1;
3456
+ for (; ; ) {
3457
+ if (j >= text.length) break;
3458
+ if (text[j] === ch) {
3459
+ if (text[j + 1] === ch) {
3460
+ j += 2;
3461
+ continue;
3462
+ }
3463
+ break;
3464
+ }
3465
+ j++;
3466
+ }
3467
+ emit(text.slice(i, j + 1));
3468
+ i = j + 1;
3469
+ continue;
3470
+ }
3471
+ if (/\s/.test(ch)) {
3472
+ pendingSpace = true;
3473
+ i++;
3474
+ continue;
3475
+ }
3476
+ emit(ch);
3477
+ i++;
3478
+ }
3479
+ return out;
3480
+ }
3425
3481
  function shapeOf(options) {
3426
- const strip = (text) => text.replace(/\s+/g, "");
3427
3482
  return [
3428
3483
  options.unique ? "UNIQUE" : "INDEX",
3429
- strip(options.expressions),
3430
- options.where ? strip(options.where) : ""
3431
- ].join("\0");
3484
+ normalizeSqlText(options.expressions),
3485
+ options.where ? normalizeSqlText(options.where) : ""
3486
+ ].join(" ");
3432
3487
  }
3433
- function shapeOfSpec(spec) {
3488
+ function indexDefinitionKey(spec) {
3434
3489
  return shapeOf({
3435
3490
  unique: Boolean(spec.unique),
3436
3491
  expressions: spec.expressions,
3437
3492
  where: spec.where ?? null
3438
3493
  });
3439
3494
  }
3440
- function resolvePhysicalIndex(exec, spec) {
3441
- const wanted = shapeOfSpec(spec);
3495
+ function namedIndexesOf(exec, table) {
3442
3496
  const rows = exec(
3443
3497
  `SELECT name, sql FROM sqlite_master
3444
- WHERE type = 'index' AND tbl_name = ? AND sql IS NOT NULL`,
3445
- [spec.table]
3498
+ WHERE type = 'index' AND tbl_name = ? AND sql IS NOT NULL
3499
+ ORDER BY name`,
3500
+ [table]
3446
3501
  );
3502
+ const named = [];
3447
3503
  for (const row of rows) {
3448
3504
  const match = INDEX_SQL.exec(String(row.sql));
3449
3505
  if (!match) continue;
3450
- const shape = shapeOf({
3451
- unique: Boolean(match[1]),
3452
- expressions: match[4] ?? "",
3453
- where: match[5] ?? null
3506
+ named.push({
3507
+ name: String(row.name),
3508
+ key: shapeOf({
3509
+ unique: Boolean(match[1]),
3510
+ expressions: match[4] ?? "",
3511
+ where: match[5] ?? null
3512
+ })
3454
3513
  });
3455
- if (shape === wanted) return String(row.name);
3456
3514
  }
3457
- return null;
3515
+ return named;
3516
+ }
3517
+ function resolvePhysicalIndex(exec, spec) {
3518
+ const wanted = indexDefinitionKey(spec);
3519
+ const found = namedIndexesOf(exec, spec.table).find(
3520
+ (index) => index.key === wanted
3521
+ );
3522
+ return found ? found.name : null;
3523
+ }
3524
+ function unusedIndexName(exec, canonicalName) {
3525
+ const taken = (name) => exec("SELECT 1 FROM sqlite_master WHERE name = ? COLLATE NOCASE", [name]).length > 0;
3526
+ if (!taken(canonicalName)) return canonicalName;
3527
+ for (let n = 2; ; n++) {
3528
+ const candidate = `${canonicalName}_${n}`;
3529
+ if (!taken(candidate)) return candidate;
3530
+ }
3458
3531
  }
3459
3532
  function ensurePhysicalIndex(exec, spec) {
3460
3533
  if (resolvePhysicalIndex(exec, spec) !== null) return "exists";
3461
3534
  exec(
3462
- `CREATE ${spec.unique ? "UNIQUE " : ""}INDEX IF NOT EXISTS ${spec.canonicalName} ON ${spec.table} (${spec.expressions})` + (spec.where ? ` WHERE ${spec.where}` : "")
3535
+ `CREATE ${spec.unique ? "UNIQUE " : ""}INDEX IF NOT EXISTS ${unusedIndexName(exec, spec.canonicalName)} ON ${spec.table} (${spec.expressions})` + (spec.where ? ` WHERE ${spec.where}` : "")
3463
3536
  );
3464
3537
  return "created";
3465
3538
  }
3466
3539
  function removePhysicalIndex(exec, spec) {
3467
- const name = resolvePhysicalIndex(exec, spec);
3468
- if (name === null) return;
3469
- exec(`DROP INDEX IF EXISTS ${name}`);
3540
+ const wanted = indexDefinitionKey(spec);
3541
+ for (const index of namedIndexesOf(exec, spec.table)) {
3542
+ if (index.key !== wanted) continue;
3543
+ exec(`DROP INDEX IF EXISTS ${quoteIdentifier(index.name)}`);
3544
+ }
3545
+ }
3546
+ function collapseDuplicateIndexes(exec, table, ownedKeys) {
3547
+ const kept = /* @__PURE__ */ new Set();
3548
+ const dropped = [];
3549
+ for (const index of namedIndexesOf(exec, table)) {
3550
+ if (!ownedKeys.has(index.key)) continue;
3551
+ if (!kept.has(index.key)) {
3552
+ kept.add(index.key);
3553
+ continue;
3554
+ }
3555
+ exec(`DROP INDEX IF EXISTS ${quoteIdentifier(index.name)}`);
3556
+ dropped.push(index.name);
3557
+ }
3558
+ return dropped;
3470
3559
  }
3471
3560
 
3472
3561
  // src/engines/cloudflare/DurableObjectEngine.ts
3562
+ function ownedDefinitionKeys(specs, table) {
3563
+ return new Set(
3564
+ specs.filter((spec) => spec.table === table).map(indexDefinitionKey)
3565
+ );
3566
+ }
3473
3567
  var DurableObjectEngine = class extends JsonSchemaEngine {
3474
3568
  sql;
3475
3569
  storage;
@@ -3522,8 +3616,85 @@ var DurableObjectEngine = class extends JsonSchemaEngine {
3522
3616
  for (const spec of JsonSchemaDDL.retiredBaseIndexSpecs(this.schemaOptions)) {
3523
3617
  removePhysicalIndex(this.indexExec, spec);
3524
3618
  }
3619
+ const owned = [
3620
+ ...JsonSchemaDDL.baseIndexSpecs(this.schemaOptions),
3621
+ ...JsonSchemaDDL.retiredBaseIndexSpecs(this.schemaOptions),
3622
+ ...this.registeredIndexSpecs()
3623
+ ];
3624
+ for (const table of ["records", "stringset_index"]) {
3625
+ const dropped = collapseDuplicateIndexes(
3626
+ this.indexExec,
3627
+ table,
3628
+ ownedDefinitionKeys(owned, table)
3629
+ );
3630
+ if (dropped.length > 0) {
3631
+ console.info("Durable Object engine collapsed duplicate indexes", {
3632
+ table,
3633
+ dropped
3634
+ });
3635
+ }
3636
+ }
3525
3637
  this.initialized = true;
3526
3638
  }
3639
+ /**
3640
+ * The index each registration row maps to (#3876): `fieldIndexSpec` per
3641
+ * `_indexes` row, `uniqueConstraintIndexSpec` per `_unique_constraints` row.
3642
+ * A row whose `fields_json` does not parse, or whose names are not valid
3643
+ * identifiers, maps to nothing.
3644
+ */
3645
+ registeredIndexSpecs() {
3646
+ const specs = [];
3647
+ try {
3648
+ for (const row of this.execSqlSync(
3649
+ "SELECT model_name, field_name FROM _indexes"
3650
+ )) {
3651
+ try {
3652
+ specs.push(
3653
+ JsonSchemaDDL.fieldIndexSpec(
3654
+ String(row.model_name),
3655
+ String(row.field_name)
3656
+ )
3657
+ );
3658
+ } catch (e) {
3659
+ console.debug("Skipping an unusable index registration:", e);
3660
+ }
3661
+ }
3662
+ } catch (e) {
3663
+ console.debug("Could not read index registrations:", e);
3664
+ }
3665
+ try {
3666
+ for (const row of this.execSqlSync(
3667
+ "SELECT model_name, constraint_name, fields_json FROM _unique_constraints"
3668
+ )) {
3669
+ try {
3670
+ specs.push(
3671
+ JsonSchemaDDL.uniqueConstraintIndexSpec(
3672
+ String(row.model_name),
3673
+ String(row.constraint_name),
3674
+ JSON.parse(String(row.fields_json))
3675
+ )
3676
+ );
3677
+ } catch (e) {
3678
+ console.debug("Skipping an unusable unique-constraint registration:", e);
3679
+ }
3680
+ }
3681
+ } catch (e) {
3682
+ console.debug("Could not read unique-constraint registrations:", e);
3683
+ }
3684
+ return specs;
3685
+ }
3686
+ /**
3687
+ * Whether a remaining registration still maps to `spec`'s definition
3688
+ * (#3876). A field index and a unique constraint over the same field — or
3689
+ * two constraints over the same fields — share one physical index since
3690
+ * #3434, so a drop removes it only when no other declaration needs it. The
3691
+ * count is derived from the registration tables at drop time, not stored.
3692
+ */
3693
+ definitionStillDeclared(spec) {
3694
+ return ownedDefinitionKeys(this.registeredIndexSpecs(), spec.table).has(
3695
+ indexDefinitionKey(spec)
3696
+ );
3697
+ }
3527
3698
  /**
3528
3699
  * Whether this open renamed the pre-underscore column names.
3529
3700
  *
@@ -3679,10 +3850,9 @@ var DurableObjectEngine = class extends JsonSchemaEngine {
3679
3850
  "DELETE FROM _indexes WHERE model_name = ? AND field_name = ?",
3680
3851
  [modelName, fieldName]
3681
3852
  );
3682
- removePhysicalIndex(
3683
- this.indexExec,
3684
- JsonSchemaDDL.fieldIndexSpec(modelName, fieldName)
3685
- );
3853
+ const spec = JsonSchemaDDL.fieldIndexSpec(modelName, fieldName);
3854
+ if (this.definitionStillDeclared(spec)) return;
3855
+ removePhysicalIndex(this.indexExec, spec);
3686
3856
  }
3687
3857
  /**
3688
3858
  * List all registered indexes, optionally filtered by model.
@@ -3747,10 +3917,13 @@ var DurableObjectEngine = class extends JsonSchemaEngine {
3747
3917
  } catch {
3748
3918
  return;
3749
3919
  }
3750
- removePhysicalIndex(
3751
- this.indexExec,
3752
- JsonSchemaDDL.uniqueConstraintIndexSpec(modelName, constraintName, fields)
3920
+ const spec = JsonSchemaDDL.uniqueConstraintIndexSpec(
3921
+ modelName,
3922
+ constraintName,
3923
+ fields
3753
3924
  );
3925
+ if (this.definitionStillDeclared(spec)) return;
3926
+ removePhysicalIndex(this.indexExec, spec);
3754
3927
  }
3755
3928
  /**
3756
3929
  * List composite unique constraints, optionally filtered by model.
@@ -2096,12 +2096,16 @@ declare abstract class DatabaseEngine {
2096
2096
  *
2097
2097
  * ## What counts as the same definition
2098
2098
  *
2099
- * The expression list and the `WHERE` clause, with whitespace removed and
2100
- * CASE PRESERVED, plus whether the index is UNIQUE. Case matters: a partial
2099
+ * The expression list and the `WHERE` clause, with whitespace normalised
2100
+ * OUTSIDE quoted literals and every literal compared verbatim, CASE PRESERVED
2101
+ * throughout, plus whether the index is UNIQUE. Case matters: a partial
2101
2102
  * index's predicate carries a model name (`WHERE _type = 'Note'`), and `Note`
2102
- * and `note` are different models. The table name is not part of the
2103
- * comparison because the lookup is already scoped to one table — and because
2104
- * SQLite requotes it on a rename.
2103
+ * and `note` are different models. Literals matter for the same reason, and
2104
+ * more since #3876 made the comparison drive DROPS: `WHERE _type = 'a b'` and
2105
+ * `WHERE _type = 'ab'` are two different partial indexes, and a comparison
2106
+ * that stripped the space inside the quotes would drop one for the other. The
2107
+ * table name is not part of the comparison because the lookup is already
2108
+ * scoped to one table — and because SQLite requotes it on a rename.
2105
2109
  */
2106
2110
  /** The SQL executor this module needs: a query and its bindings, rows back. */
2107
2111
  type PhysicalIndexExec = (query: string, params?: unknown[]) => Array<Record<string, any>>;
@@ -2116,27 +2120,55 @@ interface PhysicalIndexSpec {
2116
2120
  /** The name to create it under when nothing equivalent exists. */
2117
2121
  canonicalName: string;
2118
2122
  }
2123
+ /**
2124
+ * The key two index definitions share exactly when they are the same index
2125
+ * (#3876): UNIQUE-ness, expression list and predicate, whitespace normalised
2126
+ * outside literals, literals and case preserved. What `resolvePhysicalIndex`,
2127
+ * `ensurePhysicalIndex`, `removePhysicalIndex`, the engine's reference-counted
2128
+ * drops and `collapseDuplicateIndexes` all compare.
2129
+ */
2130
+ declare function indexDefinitionKey(spec: PhysicalIndexSpec): string;
2119
2131
  /**
2120
2132
  * The NAME of an index on `spec.table` with `spec`'s definition, or null.
2121
- *
2122
- * Autoindexes (those `sqlite_master` records with a null `sql`) are skipped:
2123
- * they belong to a constraint, not to a caller, and cannot be created or
2124
- * dropped by name anyway.
2133
+ * The first by name when there are several.
2125
2134
  */
2126
2135
  declare function resolvePhysicalIndex(exec: PhysicalIndexExec, spec: PhysicalIndexSpec): string | null;
2127
2136
  /**
2128
2137
  * Create `spec` under its canonical name unless an equivalent index already
2129
2138
  * exists under some name. Answers which of the two happened.
2139
+ *
2140
+ * When the canonical name is held by something else, the index is created
2141
+ * under the first free `<canonicalName>_<n>`: the holder is a different
2142
+ * definition (an equal one would have resolved above), and every reader
2143
+ * resolves indexes by definition, never by name.
2130
2144
  */
2131
2145
  declare function ensurePhysicalIndex(exec: PhysicalIndexExec, spec: PhysicalIndexSpec): "created" | "exists";
2132
2146
  /**
2133
- * Drop the index matching `spec`, by the name it actually has.
2147
+ * Drop EVERY index matching `spec`, by the names they actually have.
2134
2148
  *
2135
2149
  * A no-op when there is none — the callers are all idempotent "make sure this
2136
2150
  * is gone" paths, and a swapped table's index under a foreign name is exactly
2137
- * the one a `DROP INDEX IF EXISTS <canonicalName>` would miss.
2151
+ * the one a `DROP INDEX IF EXISTS <canonicalName>` would miss. All of them,
2152
+ * not the first (#3876): a store carrying the canonical copy and an
2153
+ * ingest-suffixed one converges in one pass. The names come from
2154
+ * `sqlite_master`, where a quoted keyword such as `"select"` is stored bare,
2155
+ * so they are quoted back.
2138
2156
  */
2139
2157
  declare function removePhysicalIndex(exec: PhysicalIndexExec, spec: PhysicalIndexSpec): void;
2158
+ /**
2159
+ * Drop the extra copies of every definition in `ownedKeys` on `table`
2160
+ * (#3876), keeping the first of each by name. Answers the names it dropped.
2161
+ *
2162
+ * Databases built before #3434 could carry a field index and a unique
2163
+ * constraint's index over one definition, and every insert and `_data` update
2164
+ * paid for both. Only OWNED definitions are touched — ones the caller would
2165
+ * itself create — so an index a consumer made by hand is never dropped, even
2166
+ * when it duplicates another. `ownedKeys` are the keys of definitions on
2167
+ * `table` itself: the key leaves the table out, so a set built across tables
2168
+ * would claim `stringset_index(_type)` for a spec over `records(_type)`.
2169
+ * `DROP INDEX` writes no row.
2170
+ */
2171
+ declare function collapseDuplicateIndexes(exec: PhysicalIndexExec, table: string, ownedKeys: ReadonlySet<string>): string[];
2140
2172
 
2141
2173
  /**
2142
2174
  * JSON Schema DDL Generator
@@ -2181,19 +2213,30 @@ declare class JsonSchemaDDL {
2181
2213
  * A document whose tables were swapped by a bulk-load ingest carries these
2182
2214
  * under session-suffixed names; creating them by name on the next cold open
2183
2215
  * would build a second copy of each over the whole table.
2216
+ *
2217
+ * `records(_type)` is a base index only in yjs mode (#3876). The server's
2218
+ * primary key `(_type, _id)` already leads with `_type`, so on a
2219
+ * `DurableObjectEngine` the index served no plan the primary key's autoindex
2220
+ * could not and cost a row on every insert; the yjs-mode list stays in step
2221
+ * with the yjs-mode DDL, which clients keep.
2184
2222
  */
2185
2223
  static baseIndexSpecs(options: JsonSchemaOptions): PhysicalIndexSpec[];
2186
2224
  /**
2187
- * The base indexes this build RETIRED, as specs (#3688).
2225
+ * The base indexes RETIRED, as specs, each removed BY DEFINITION for the
2226
+ * same reason the others are created that way: a document a bulk-load
2227
+ * ingest swapped carries it under a session-suffixed name, which a
2228
+ * `DROP INDEX IF EXISTS <canonical name>` would walk straight past.
2188
2229
  *
2189
- * `(_type, field, _record_id)` is a left prefix of the member table's UNIQUE
2190
- * autoindex, so it never served a plan the autoindex could not; what it did
2191
- * do was cost a second index write per member insert. It is removed BY
2192
- * DEFINITION for the same reason the others are created that way: a document
2193
- * a bulk-load ingest swapped carries it under a session-suffixed name, which
2194
- * a `DROP INDEX IF EXISTS idx_ss_type_field_record` would walk straight past.
2230
+ * - `idx_ss_type_field_record` (#3688): `(_type, field, _record_id)` is a
2231
+ * left prefix of the member table's UNIQUE autoindex, so it never served a
2232
+ * plan the autoindex could not; what it did do was cost a second index
2233
+ * write per member insert.
2234
+ * - `idx_records_type` (#3876), on the server only: the primary key
2235
+ * `(_type, _id)` leads with `_type`, so every `WHERE _type = ?` plans onto
2236
+ * its autoindex (covering, for a count), and the index cost a row on every
2237
+ * insert. Clients' yjs-mode DDL keeps it.
2195
2238
  */
2196
- static retiredBaseIndexSpecs(_options: JsonSchemaOptions): PhysicalIndexSpec[];
2239
+ static retiredBaseIndexSpecs(options: JsonSchemaOptions): PhysicalIndexSpec[];
2197
2240
  /**
2198
2241
  * Drop the retired base indexes BY NAME, for the name-based engines (#3688).
2199
2242
  *
@@ -2644,6 +2687,21 @@ declare class DurableObjectEngine extends JsonSchemaEngine {
2644
2687
  * really is identified by its name.
2645
2688
  */
2646
2689
  initializeSchema(): Promise<void>;
2690
+ /**
2691
+ * The index each registration row maps to (#3876): `fieldIndexSpec` per
2692
+ * `_indexes` row, `uniqueConstraintIndexSpec` per `_unique_constraints` row.
2693
+ * A row whose `fields_json` does not parse, or whose names are not valid
2694
+ * identifiers, maps to nothing.
2695
+ */
2696
+ private registeredIndexSpecs;
2697
+ /**
2698
+ * Whether a remaining registration still maps to `spec`'s definition
2699
+ * (#3876). A field index and a unique constraint over the same field — or
2700
+ * two constraints over the same fields — share one physical index since
2701
+ * #3434, so a drop removes it only when no other declaration needs it. The
2702
+ * count is derived from the registration tables at drop time, not stored.
2703
+ */
2704
+ private definitionStillDeclared;
2647
2705
  /**
2648
2706
  * Whether this open renamed the pre-underscore column names.
2649
2707
  *
@@ -7059,4 +7117,4 @@ declare class SnapshotPartitioner {
7059
7117
  private take;
7060
7118
  }
7061
7119
 
7062
- export { type AfterQueryContext, type AfterQueryResult, type AggregateRequest, type AggregateResponse, type AggregationOperation, type AggregationOptions, type AggregationResult, type BatchOperation, type BatchOperationResult, type BatchRequest, type BatchResponse, type BeforeDeleteContext, type BeforeQueryContext, type BeforeQueryResult, type BeforeSaveContext, CURSOR_SORT_ALIAS_PREFIX, type CountRequest, type CountResponse, DEFAULT_OVERLAY_TABLES, DEFAULT_SNAPSHOT_PARTITION, type DatabaseDOConfig, type DatabaseDOHooks, type DeleteRequest, type DeleteResponse, type DiscoveredConstraint, type DiscoveredField, type DiscoveredModel, type DiscoveredRelationship, type DiscoveredSchema, type DropIndexRequest, type DropIndexResponse, type DropUniqueConstraintRequest, type DropUniqueConstraintResponse, DurableObjectEngine, type DurableObjectEngineOptions, type DurableObjectId, type DurableObjectNamespace, type DurableObjectState, type DurableObjectStorage, type DurableObjectStub, type EncodedSnapshotChunk, type Env, type ErrorResponse, FORMAT2_MANIFEST_VERSION, FORMAT2_MIN_MANIFEST_VERSION, FilterTooManyBindsError, Format2CompositeFieldError, type GroupByField, type HookContext, type HookResult, INGEST_CHUNK_LIMITS, InListTooLargeError, type IncrementRequest, type IncrementResponse, type IndexEntry, type IndexListResponse, type IngestChunkDescriptorLike, IngestChunkError, type IngestChunkErrorCode, type IngestChunkLine, JsonQueryTranslator, type JsonQueryTranslatorOptions, JsonSchemaDDL, type JsonSchemaOptions, type ModelMetaDeclaration, OVERLAY_KEY_SEPARATOR, OVERLAY_MARKER_DELETED, OVERLAY_MARKER_REPLACE, type OverlayMutation, type OverlayRecordEntry, type OverlaySql, type OverlayTables, PROJECTED_JSON_ALIAS_PREFIX, type ParsedOverlayKey, type PatchRequest, type PatchResponse, type PhysicalIndexExec, type PhysicalIndexSpec, QUERY_IN_LIST_MAX_VALUES, QUERY_STATEMENT_MAX_BOUND_PARAMS, type QueryRequest, type QueryResponse, type RegisterIndexRequest, type RegisterIndexResponse, type RegisterUniqueConstraintRequest, type RegisterUniqueConstraintResponse, SNAPSHOT_ENCODE_SEGMENT_BYTES, type SaveRequest, type SaveResponse, type SchemaConstraintInfo, type SchemaFieldInfo, type SchemaModelInfo, type SchemaResponse, type SnapshotChunkAddress, type SnapshotChunkEntry, SnapshotChunkIntegrityError, type SnapshotEncodeTimings, type SnapshotIngestInfo, type SnapshotIngestRange, type SnapshotManifest, SnapshotManifestInvalidError, SnapshotManifestUnsupportedError, type SnapshotOverlayEntry, type SnapshotPartitionConfig, type SnapshotPartitionDescriptor, SnapshotPartitioner, type SnapshotRow, SnapshotRowFormatError, type SqlStorage, type SqlStorageCursor, type StringSetMembership, type StringSetUpdateRequest, type StringSetUpdateResponse, type SyncIndexesRequest, type SyncIndexesResponse, type UniqueConstraintEntry, type UniqueConstraintListResponse, UniqueStringsetError, type UniqueStringsetViolation, applyOverlayMutation, assertNoFormat2CompositeFields, buildSafeAlias, checkAutoStampsReservedFields, checkReservedFieldName, clearMetaSyncCache, compareRecordIds, completeOverlayEntries, createDatabaseDO, cursorMintingRecord, cursorSortAlias, decodeOverlaySegment, deterministicIdentifierHash, discoverDeclaredModel, discoverModelNames, discoverSchema, encodeOverlayMutation, encodeOverlaySegment, encodeSnapshotChunk, ensurePhysicalIndex, findUniqueStringsetInToml, foldTouchedOverlayKeys, format2StringSetMembers, groupOverlayEntries, handleRequest, inferFieldType, isAnchorId, isFormat2StringSetYMap, lineBytes, loadSchemaFromTomlString, materializeOverlayRecord, omitStringsetConstraints, overlayFieldKey, overlayMarkerKey, overlayMemberKey, parseOverlayKey, parseRecordRow, parseSnapshotChunkPath, projectOverlayEntry, projectedJsonAlias, readIngestChunk, readOverlayRecord, readSnapshotChunk, registerFunctionDefault, removePhysicalIndex, resolveChunkOrdinal, resolvePhysicalIndex, schemaToToml, snapshotChunkLines, snapshotChunkSegments, snapshotDeclaredSchema, snapshotRowAsOverlayEntry, snapshotStringSetFields, stripCursorSortValues, syncInferredMeta, syncModelMeta, syncModelMetaFromDeclaration, uniqueStringsetMessage, uniqueStringsetViolations, validateSnapshotManifest };
7120
+ export { type AfterQueryContext, type AfterQueryResult, type AggregateRequest, type AggregateResponse, type AggregationOperation, type AggregationOptions, type AggregationResult, type BatchOperation, type BatchOperationResult, type BatchRequest, type BatchResponse, type BeforeDeleteContext, type BeforeQueryContext, type BeforeQueryResult, type BeforeSaveContext, CURSOR_SORT_ALIAS_PREFIX, type CountRequest, type CountResponse, DEFAULT_OVERLAY_TABLES, DEFAULT_SNAPSHOT_PARTITION, type DatabaseDOConfig, type DatabaseDOHooks, type DeleteRequest, type DeleteResponse, type DiscoveredConstraint, type DiscoveredField, type DiscoveredModel, type DiscoveredRelationship, type DiscoveredSchema, type DropIndexRequest, type DropIndexResponse, type DropUniqueConstraintRequest, type DropUniqueConstraintResponse, DurableObjectEngine, type DurableObjectEngineOptions, type DurableObjectId, type DurableObjectNamespace, type DurableObjectState, type DurableObjectStorage, type DurableObjectStub, type EncodedSnapshotChunk, type Env, type ErrorResponse, FORMAT2_MANIFEST_VERSION, FORMAT2_MIN_MANIFEST_VERSION, FilterTooManyBindsError, Format2CompositeFieldError, type GroupByField, type HookContext, type HookResult, INGEST_CHUNK_LIMITS, InListTooLargeError, type IncrementRequest, type IncrementResponse, type IndexEntry, type IndexListResponse, type IngestChunkDescriptorLike, IngestChunkError, type IngestChunkErrorCode, type IngestChunkLine, JsonQueryTranslator, type JsonQueryTranslatorOptions, JsonSchemaDDL, type JsonSchemaOptions, type ModelMetaDeclaration, OVERLAY_KEY_SEPARATOR, OVERLAY_MARKER_DELETED, OVERLAY_MARKER_REPLACE, type OverlayMutation, type OverlayRecordEntry, type OverlaySql, type OverlayTables, PROJECTED_JSON_ALIAS_PREFIX, type ParsedOverlayKey, type PatchRequest, type PatchResponse, type PhysicalIndexExec, type PhysicalIndexSpec, QUERY_IN_LIST_MAX_VALUES, QUERY_STATEMENT_MAX_BOUND_PARAMS, type QueryRequest, type QueryResponse, type RegisterIndexRequest, type RegisterIndexResponse, type RegisterUniqueConstraintRequest, type RegisterUniqueConstraintResponse, SNAPSHOT_ENCODE_SEGMENT_BYTES, type SaveRequest, type SaveResponse, type SchemaConstraintInfo, type SchemaFieldInfo, type SchemaModelInfo, type SchemaResponse, type SnapshotChunkAddress, type SnapshotChunkEntry, SnapshotChunkIntegrityError, type SnapshotEncodeTimings, type SnapshotIngestInfo, type SnapshotIngestRange, type SnapshotManifest, SnapshotManifestInvalidError, SnapshotManifestUnsupportedError, type SnapshotOverlayEntry, type SnapshotPartitionConfig, type SnapshotPartitionDescriptor, SnapshotPartitioner, type SnapshotRow, SnapshotRowFormatError, type SqlStorage, type SqlStorageCursor, type StringSetMembership, type StringSetUpdateRequest, type StringSetUpdateResponse, type SyncIndexesRequest, type SyncIndexesResponse, type UniqueConstraintEntry, type UniqueConstraintListResponse, UniqueStringsetError, type UniqueStringsetViolation, applyOverlayMutation, assertNoFormat2CompositeFields, buildSafeAlias, checkAutoStampsReservedFields, checkReservedFieldName, clearMetaSyncCache, collapseDuplicateIndexes, compareRecordIds, completeOverlayEntries, createDatabaseDO, cursorMintingRecord, cursorSortAlias, decodeOverlaySegment, deterministicIdentifierHash, discoverDeclaredModel, discoverModelNames, discoverSchema, encodeOverlayMutation, encodeOverlaySegment, encodeSnapshotChunk, ensurePhysicalIndex, findUniqueStringsetInToml, foldTouchedOverlayKeys, format2StringSetMembers, groupOverlayEntries, handleRequest, indexDefinitionKey, inferFieldType, isAnchorId, isFormat2StringSetYMap, lineBytes, loadSchemaFromTomlString, materializeOverlayRecord, omitStringsetConstraints, overlayFieldKey, overlayMarkerKey, overlayMemberKey, parseOverlayKey, parseRecordRow, parseSnapshotChunkPath, projectOverlayEntry, projectedJsonAlias, readIngestChunk, readOverlayRecord, readSnapshotChunk, registerFunctionDefault, removePhysicalIndex, resolveChunkOrdinal, resolvePhysicalIndex, schemaToToml, snapshotChunkLines, snapshotChunkSegments, snapshotDeclaredSchema, snapshotRowAsOverlayEntry, snapshotStringSetFields, stripCursorSortValues, syncInferredMeta, syncModelMeta, syncModelMetaFromDeclaration, uniqueStringsetMessage, uniqueStringsetViolations, validateSnapshotManifest };
@@ -2096,12 +2096,16 @@ declare abstract class DatabaseEngine {
2096
2096
  *
2097
2097
  * ## What counts as the same definition
2098
2098
  *
2099
- * The expression list and the `WHERE` clause, with whitespace removed and
2100
- * CASE PRESERVED, plus whether the index is UNIQUE. Case matters: a partial
2099
+ * The expression list and the `WHERE` clause, with whitespace normalised
2100
+ * OUTSIDE quoted literals and every literal compared verbatim, CASE PRESERVED
2101
+ * throughout, plus whether the index is UNIQUE. Case matters: a partial
2101
2102
  * index's predicate carries a model name (`WHERE _type = 'Note'`), and `Note`
2102
- * and `note` are different models. The table name is not part of the
2103
- * comparison because the lookup is already scoped to one table — and because
2104
- * SQLite requotes it on a rename.
2103
+ * and `note` are different models. Literals matter for the same reason, and
2104
+ * more since #3876 made the comparison drive DROPS: `WHERE _type = 'a b'` and
2105
+ * `WHERE _type = 'ab'` are two different partial indexes, and a comparison
2106
+ * that stripped the space inside the quotes would drop one for the other. The
2107
+ * table name is not part of the comparison because the lookup is already
2108
+ * scoped to one table — and because SQLite requotes it on a rename.
2105
2109
  */
2106
2110
  /** The SQL executor this module needs: a query and its bindings, rows back. */
2107
2111
  type PhysicalIndexExec = (query: string, params?: unknown[]) => Array<Record<string, any>>;
@@ -2116,27 +2120,55 @@ interface PhysicalIndexSpec {
2116
2120
  /** The name to create it under when nothing equivalent exists. */
2117
2121
  canonicalName: string;
2118
2122
  }
2123
+ /**
2124
+ * The key two index definitions share exactly when they are the same index
2125
+ * (#3876): UNIQUE-ness, expression list and predicate, whitespace normalised
2126
+ * outside literals, literals and case preserved. What `resolvePhysicalIndex`,
2127
+ * `ensurePhysicalIndex`, `removePhysicalIndex`, the engine's reference-counted
2128
+ * drops and `collapseDuplicateIndexes` all compare.
2129
+ */
2130
+ declare function indexDefinitionKey(spec: PhysicalIndexSpec): string;
2119
2131
  /**
2120
2132
  * The NAME of an index on `spec.table` with `spec`'s definition, or null.
2121
- *
2122
- * Autoindexes (those `sqlite_master` records with a null `sql`) are skipped:
2123
- * they belong to a constraint, not to a caller, and cannot be created or
2124
- * dropped by name anyway.
2133
+ * The first by name when there are several.
2125
2134
  */
2126
2135
  declare function resolvePhysicalIndex(exec: PhysicalIndexExec, spec: PhysicalIndexSpec): string | null;
2127
2136
  /**
2128
2137
  * Create `spec` under its canonical name unless an equivalent index already
2129
2138
  * exists under some name. Answers which of the two happened.
2139
+ *
2140
+ * When the canonical name is held by something else, the index is created
2141
+ * under the first free `<canonicalName>_<n>`: the holder is a different
2142
+ * definition (an equal one would have resolved above), and every reader
2143
+ * resolves indexes by definition, never by name.
2130
2144
  */
2131
2145
  declare function ensurePhysicalIndex(exec: PhysicalIndexExec, spec: PhysicalIndexSpec): "created" | "exists";
2132
2146
  /**
2133
- * Drop the index matching `spec`, by the name it actually has.
2147
+ * Drop EVERY index matching `spec`, by the names they actually have.
2134
2148
  *
2135
2149
  * A no-op when there is none — the callers are all idempotent "make sure this
2136
2150
  * is gone" paths, and a swapped table's index under a foreign name is exactly
2137
- * the one a `DROP INDEX IF EXISTS <canonicalName>` would miss.
2151
+ * the one a `DROP INDEX IF EXISTS <canonicalName>` would miss. All of them,
2152
+ * not the first (#3876): a store carrying the canonical copy and an
2153
+ * ingest-suffixed one converges in one pass. The names come from
2154
+ * `sqlite_master`, where a quoted keyword such as `"select"` is stored bare,
2155
+ * so they are quoted back.
2138
2156
  */
2139
2157
  declare function removePhysicalIndex(exec: PhysicalIndexExec, spec: PhysicalIndexSpec): void;
2158
+ /**
2159
+ * Drop the extra copies of every definition in `ownedKeys` on `table`
2160
+ * (#3876), keeping the first of each by name. Answers the names it dropped.
2161
+ *
2162
+ * Databases built before #3434 could carry a field index and a unique
2163
+ * constraint's index over one definition, and every insert and `_data` update
2164
+ * paid for both. Only OWNED definitions are touched — ones the caller would
2165
+ * itself create — so an index a consumer made by hand is never dropped, even
2166
+ * when it duplicates another. `ownedKeys` are the keys of definitions on
2167
+ * `table` itself: the key leaves the table out, so a set built across tables
2168
+ * would claim `stringset_index(_type)` for a spec over `records(_type)`.
2169
+ * `DROP INDEX` writes no row.
2170
+ */
2171
+ declare function collapseDuplicateIndexes(exec: PhysicalIndexExec, table: string, ownedKeys: ReadonlySet<string>): string[];
2140
2172
 
2141
2173
  /**
2142
2174
  * JSON Schema DDL Generator
@@ -2181,19 +2213,30 @@ declare class JsonSchemaDDL {
2181
2213
  * A document whose tables were swapped by a bulk-load ingest carries these
2182
2214
  * under session-suffixed names; creating them by name on the next cold open
2183
2215
  * would build a second copy of each over the whole table.
2216
+ *
2217
+ * `records(_type)` is a base index only in yjs mode (#3876). The server's
2218
+ * primary key `(_type, _id)` already leads with `_type`, so on a
2219
+ * `DurableObjectEngine` the index served no plan the primary key's autoindex
2220
+ * could not and cost a row on every insert; the yjs-mode list stays in step
2221
+ * with the yjs-mode DDL, which clients keep.
2184
2222
  */
2185
2223
  static baseIndexSpecs(options: JsonSchemaOptions): PhysicalIndexSpec[];
2186
2224
  /**
2187
- * The base indexes this build RETIRED, as specs (#3688).
2225
+ * The base indexes RETIRED, as specs, each removed BY DEFINITION for the
2226
+ * same reason the others are created that way: a document a bulk-load
2227
+ * ingest swapped carries it under a session-suffixed name, which a
2228
+ * `DROP INDEX IF EXISTS <canonical name>` would walk straight past.
2188
2229
  *
2189
- * `(_type, field, _record_id)` is a left prefix of the member table's UNIQUE
2190
- * autoindex, so it never served a plan the autoindex could not; what it did
2191
- * do was cost a second index write per member insert. It is removed BY
2192
- * DEFINITION for the same reason the others are created that way: a document
2193
- * a bulk-load ingest swapped carries it under a session-suffixed name, which
2194
- * a `DROP INDEX IF EXISTS idx_ss_type_field_record` would walk straight past.
2230
+ * - `idx_ss_type_field_record` (#3688): `(_type, field, _record_id)` is a
2231
+ * left prefix of the member table's UNIQUE autoindex, so it never served a
2232
+ * plan the autoindex could not; what it did do was cost a second index
2233
+ * write per member insert.
2234
+ * - `idx_records_type` (#3876), on the server only: the primary key
2235
+ * `(_type, _id)` leads with `_type`, so every `WHERE _type = ?` plans onto
2236
+ * its autoindex (covering, for a count), and the index cost a row on every
2237
+ * insert. Clients' yjs-mode DDL keeps it.
2195
2238
  */
2196
- static retiredBaseIndexSpecs(_options: JsonSchemaOptions): PhysicalIndexSpec[];
2239
+ static retiredBaseIndexSpecs(options: JsonSchemaOptions): PhysicalIndexSpec[];
2197
2240
  /**
2198
2241
  * Drop the retired base indexes BY NAME, for the name-based engines (#3688).
2199
2242
  *
@@ -2644,6 +2687,21 @@ declare class DurableObjectEngine extends JsonSchemaEngine {
2644
2687
  * really is identified by its name.
2645
2688
  */
2646
2689
  initializeSchema(): Promise<void>;
2690
+ /**
2691
+ * The index each registration row maps to (#3876): `fieldIndexSpec` per
2692
+ * `_indexes` row, `uniqueConstraintIndexSpec` per `_unique_constraints` row.
2693
+ * A row whose `fields_json` does not parse, or whose names are not valid
2694
+ * identifiers, maps to nothing.
2695
+ */
2696
+ private registeredIndexSpecs;
2697
+ /**
2698
+ * Whether a remaining registration still maps to `spec`'s definition
2699
+ * (#3876). A field index and a unique constraint over the same field — or
2700
+ * two constraints over the same fields — share one physical index since
2701
+ * #3434, so a drop removes it only when no other declaration needs it. The
2702
+ * count is derived from the registration tables at drop time, not stored.
2703
+ */
2704
+ private definitionStillDeclared;
2647
2705
  /**
2648
2706
  * Whether this open renamed the pre-underscore column names.
2649
2707
  *
@@ -7059,4 +7117,4 @@ declare class SnapshotPartitioner {
7059
7117
  private take;
7060
7118
  }
7061
7119
 
7062
- export { type AfterQueryContext, type AfterQueryResult, type AggregateRequest, type AggregateResponse, type AggregationOperation, type AggregationOptions, type AggregationResult, type BatchOperation, type BatchOperationResult, type BatchRequest, type BatchResponse, type BeforeDeleteContext, type BeforeQueryContext, type BeforeQueryResult, type BeforeSaveContext, CURSOR_SORT_ALIAS_PREFIX, type CountRequest, type CountResponse, DEFAULT_OVERLAY_TABLES, DEFAULT_SNAPSHOT_PARTITION, type DatabaseDOConfig, type DatabaseDOHooks, type DeleteRequest, type DeleteResponse, type DiscoveredConstraint, type DiscoveredField, type DiscoveredModel, type DiscoveredRelationship, type DiscoveredSchema, type DropIndexRequest, type DropIndexResponse, type DropUniqueConstraintRequest, type DropUniqueConstraintResponse, DurableObjectEngine, type DurableObjectEngineOptions, type DurableObjectId, type DurableObjectNamespace, type DurableObjectState, type DurableObjectStorage, type DurableObjectStub, type EncodedSnapshotChunk, type Env, type ErrorResponse, FORMAT2_MANIFEST_VERSION, FORMAT2_MIN_MANIFEST_VERSION, FilterTooManyBindsError, Format2CompositeFieldError, type GroupByField, type HookContext, type HookResult, INGEST_CHUNK_LIMITS, InListTooLargeError, type IncrementRequest, type IncrementResponse, type IndexEntry, type IndexListResponse, type IngestChunkDescriptorLike, IngestChunkError, type IngestChunkErrorCode, type IngestChunkLine, JsonQueryTranslator, type JsonQueryTranslatorOptions, JsonSchemaDDL, type JsonSchemaOptions, type ModelMetaDeclaration, OVERLAY_KEY_SEPARATOR, OVERLAY_MARKER_DELETED, OVERLAY_MARKER_REPLACE, type OverlayMutation, type OverlayRecordEntry, type OverlaySql, type OverlayTables, PROJECTED_JSON_ALIAS_PREFIX, type ParsedOverlayKey, type PatchRequest, type PatchResponse, type PhysicalIndexExec, type PhysicalIndexSpec, QUERY_IN_LIST_MAX_VALUES, QUERY_STATEMENT_MAX_BOUND_PARAMS, type QueryRequest, type QueryResponse, type RegisterIndexRequest, type RegisterIndexResponse, type RegisterUniqueConstraintRequest, type RegisterUniqueConstraintResponse, SNAPSHOT_ENCODE_SEGMENT_BYTES, type SaveRequest, type SaveResponse, type SchemaConstraintInfo, type SchemaFieldInfo, type SchemaModelInfo, type SchemaResponse, type SnapshotChunkAddress, type SnapshotChunkEntry, SnapshotChunkIntegrityError, type SnapshotEncodeTimings, type SnapshotIngestInfo, type SnapshotIngestRange, type SnapshotManifest, SnapshotManifestInvalidError, SnapshotManifestUnsupportedError, type SnapshotOverlayEntry, type SnapshotPartitionConfig, type SnapshotPartitionDescriptor, SnapshotPartitioner, type SnapshotRow, SnapshotRowFormatError, type SqlStorage, type SqlStorageCursor, type StringSetMembership, type StringSetUpdateRequest, type StringSetUpdateResponse, type SyncIndexesRequest, type SyncIndexesResponse, type UniqueConstraintEntry, type UniqueConstraintListResponse, UniqueStringsetError, type UniqueStringsetViolation, applyOverlayMutation, assertNoFormat2CompositeFields, buildSafeAlias, checkAutoStampsReservedFields, checkReservedFieldName, clearMetaSyncCache, compareRecordIds, completeOverlayEntries, createDatabaseDO, cursorMintingRecord, cursorSortAlias, decodeOverlaySegment, deterministicIdentifierHash, discoverDeclaredModel, discoverModelNames, discoverSchema, encodeOverlayMutation, encodeOverlaySegment, encodeSnapshotChunk, ensurePhysicalIndex, findUniqueStringsetInToml, foldTouchedOverlayKeys, format2StringSetMembers, groupOverlayEntries, handleRequest, inferFieldType, isAnchorId, isFormat2StringSetYMap, lineBytes, loadSchemaFromTomlString, materializeOverlayRecord, omitStringsetConstraints, overlayFieldKey, overlayMarkerKey, overlayMemberKey, parseOverlayKey, parseRecordRow, parseSnapshotChunkPath, projectOverlayEntry, projectedJsonAlias, readIngestChunk, readOverlayRecord, readSnapshotChunk, registerFunctionDefault, removePhysicalIndex, resolveChunkOrdinal, resolvePhysicalIndex, schemaToToml, snapshotChunkLines, snapshotChunkSegments, snapshotDeclaredSchema, snapshotRowAsOverlayEntry, snapshotStringSetFields, stripCursorSortValues, syncInferredMeta, syncModelMeta, syncModelMetaFromDeclaration, uniqueStringsetMessage, uniqueStringsetViolations, validateSnapshotManifest };
7120
+ export { type AfterQueryContext, type AfterQueryResult, type AggregateRequest, type AggregateResponse, type AggregationOperation, type AggregationOptions, type AggregationResult, type BatchOperation, type BatchOperationResult, type BatchRequest, type BatchResponse, type BeforeDeleteContext, type BeforeQueryContext, type BeforeQueryResult, type BeforeSaveContext, CURSOR_SORT_ALIAS_PREFIX, type CountRequest, type CountResponse, DEFAULT_OVERLAY_TABLES, DEFAULT_SNAPSHOT_PARTITION, type DatabaseDOConfig, type DatabaseDOHooks, type DeleteRequest, type DeleteResponse, type DiscoveredConstraint, type DiscoveredField, type DiscoveredModel, type DiscoveredRelationship, type DiscoveredSchema, type DropIndexRequest, type DropIndexResponse, type DropUniqueConstraintRequest, type DropUniqueConstraintResponse, DurableObjectEngine, type DurableObjectEngineOptions, type DurableObjectId, type DurableObjectNamespace, type DurableObjectState, type DurableObjectStorage, type DurableObjectStub, type EncodedSnapshotChunk, type Env, type ErrorResponse, FORMAT2_MANIFEST_VERSION, FORMAT2_MIN_MANIFEST_VERSION, FilterTooManyBindsError, Format2CompositeFieldError, type GroupByField, type HookContext, type HookResult, INGEST_CHUNK_LIMITS, InListTooLargeError, type IncrementRequest, type IncrementResponse, type IndexEntry, type IndexListResponse, type IngestChunkDescriptorLike, IngestChunkError, type IngestChunkErrorCode, type IngestChunkLine, JsonQueryTranslator, type JsonQueryTranslatorOptions, JsonSchemaDDL, type JsonSchemaOptions, type ModelMetaDeclaration, OVERLAY_KEY_SEPARATOR, OVERLAY_MARKER_DELETED, OVERLAY_MARKER_REPLACE, type OverlayMutation, type OverlayRecordEntry, type OverlaySql, type OverlayTables, PROJECTED_JSON_ALIAS_PREFIX, type ParsedOverlayKey, type PatchRequest, type PatchResponse, type PhysicalIndexExec, type PhysicalIndexSpec, QUERY_IN_LIST_MAX_VALUES, QUERY_STATEMENT_MAX_BOUND_PARAMS, type QueryRequest, type QueryResponse, type RegisterIndexRequest, type RegisterIndexResponse, type RegisterUniqueConstraintRequest, type RegisterUniqueConstraintResponse, SNAPSHOT_ENCODE_SEGMENT_BYTES, type SaveRequest, type SaveResponse, type SchemaConstraintInfo, type SchemaFieldInfo, type SchemaModelInfo, type SchemaResponse, type SnapshotChunkAddress, type SnapshotChunkEntry, SnapshotChunkIntegrityError, type SnapshotEncodeTimings, type SnapshotIngestInfo, type SnapshotIngestRange, type SnapshotManifest, SnapshotManifestInvalidError, SnapshotManifestUnsupportedError, type SnapshotOverlayEntry, type SnapshotPartitionConfig, type SnapshotPartitionDescriptor, SnapshotPartitioner, type SnapshotRow, SnapshotRowFormatError, type SqlStorage, type SqlStorageCursor, type StringSetMembership, type StringSetUpdateRequest, type StringSetUpdateResponse, type SyncIndexesRequest, type SyncIndexesResponse, type UniqueConstraintEntry, type UniqueConstraintListResponse, UniqueStringsetError, type UniqueStringsetViolation, applyOverlayMutation, assertNoFormat2CompositeFields, buildSafeAlias, checkAutoStampsReservedFields, checkReservedFieldName, clearMetaSyncCache, collapseDuplicateIndexes, compareRecordIds, completeOverlayEntries, createDatabaseDO, cursorMintingRecord, cursorSortAlias, decodeOverlaySegment, deterministicIdentifierHash, discoverDeclaredModel, discoverModelNames, discoverSchema, encodeOverlayMutation, encodeOverlaySegment, encodeSnapshotChunk, ensurePhysicalIndex, findUniqueStringsetInToml, foldTouchedOverlayKeys, format2StringSetMembers, groupOverlayEntries, handleRequest, indexDefinitionKey, inferFieldType, isAnchorId, isFormat2StringSetYMap, lineBytes, loadSchemaFromTomlString, materializeOverlayRecord, omitStringsetConstraints, overlayFieldKey, overlayMarkerKey, overlayMemberKey, parseOverlayKey, parseRecordRow, parseSnapshotChunkPath, projectOverlayEntry, projectedJsonAlias, readIngestChunk, readOverlayRecord, readSnapshotChunk, registerFunctionDefault, removePhysicalIndex, resolveChunkOrdinal, resolvePhysicalIndex, schemaToToml, snapshotChunkLines, snapshotChunkSegments, snapshotDeclaredSchema, snapshotRowAsOverlayEntry, snapshotStringSetFields, stripCursorSortValues, syncInferredMeta, syncModelMeta, syncModelMetaFromDeclaration, uniqueStringsetMessage, uniqueStringsetViolations, validateSnapshotManifest };
@@ -2718,6 +2718,11 @@ var DatabaseEngine = class {
2718
2718
  // src/schema/JsonSchemaDDL.ts
2719
2719
  init_sql();
2720
2720
  init_sql();
2721
+ var RECORDS_TYPE_INDEX_SPEC = {
2722
+ table: "records",
2723
+ expressions: "_type",
2724
+ canonicalName: "idx_records_type"
2725
+ };
2721
2726
  var JsonSchemaDDL = class {
2722
2727
  /**
2723
2728
  * Generate CREATE TABLE statement for the records table
@@ -2801,14 +2806,16 @@ CREATE TABLE IF NOT EXISTS stringset_index (
2801
2806
  * A document whose tables were swapped by a bulk-load ingest carries these
2802
2807
  * under session-suffixed names; creating them by name on the next cold open
2803
2808
  * would build a second copy of each over the whole table.
2809
+ *
2810
+ * `records(_type)` is a base index only in yjs mode (#3876). The server's
2811
+ * primary key `(_type, _id)` already leads with `_type`, so on a
2812
+ * `DurableObjectEngine` the index served no plan the primary key's autoindex
2813
+ * could not and cost a row on every insert; the yjs-mode list stays in step
2814
+ * with the yjs-mode DDL, which clients keep.
2804
2815
  */
2805
2816
  static baseIndexSpecs(options) {
2806
2817
  const specs = [
2807
- {
2808
- table: "records",
2809
- expressions: "_type",
2810
- canonicalName: "idx_records_type"
2811
- },
2818
+ ...options.includeDocIdColumn ? [{ ...RECORDS_TYPE_INDEX_SPEC }] : [],
2812
2819
  {
2813
2820
  table: "stringset_index",
2814
2821
  expressions: "_type, field, value",
@@ -2837,22 +2844,28 @@ CREATE TABLE IF NOT EXISTS stringset_index (
2837
2844
  return specs;
2838
2845
  }
2839
2846
  /**
2840
- * The base indexes this build RETIRED, as specs (#3688).
2847
+ * The base indexes RETIRED, as specs, each removed BY DEFINITION for the
2848
+ * same reason the others are created that way: a document a bulk-load
2849
+ * ingest swapped carries it under a session-suffixed name, which a
2850
+ * `DROP INDEX IF EXISTS <canonical name>` would walk straight past.
2841
2851
  *
2842
- * `(_type, field, _record_id)` is a left prefix of the member table's UNIQUE
2843
- * autoindex, so it never served a plan the autoindex could not; what it did
2844
- * do was cost a second index write per member insert. It is removed BY
2845
- * DEFINITION for the same reason the others are created that way: a document
2846
- * a bulk-load ingest swapped carries it under a session-suffixed name, which
2847
- * a `DROP INDEX IF EXISTS idx_ss_type_field_record` would walk straight past.
2852
+ * - `idx_ss_type_field_record` (#3688): `(_type, field, _record_id)` is a
2853
+ * left prefix of the member table's UNIQUE autoindex, so it never served a
2854
+ * plan the autoindex could not; what it did do was cost a second index
2855
+ * write per member insert.
2856
+ * - `idx_records_type` (#3876), on the server only: the primary key
2857
+ * `(_type, _id)` leads with `_type`, so every `WHERE _type = ?` plans onto
2858
+ * its autoindex (covering, for a count), and the index cost a row on every
2859
+ * insert. Clients' yjs-mode DDL keeps it.
2848
2860
  */
2849
- static retiredBaseIndexSpecs(_options) {
2861
+ static retiredBaseIndexSpecs(options) {
2850
2862
  return [
2851
2863
  {
2852
2864
  table: "stringset_index",
2853
2865
  expressions: "_type, field, _record_id",
2854
2866
  canonicalName: "idx_ss_type_field_record"
2855
- }
2867
+ },
2868
+ ...options.includeDocIdColumn ? [] : [{ ...RECORDS_TYPE_INDEX_SPEC }]
2856
2869
  ];
2857
2870
  }
2858
2871
  /**
@@ -3298,55 +3311,134 @@ var JsonSchemaEngine = class extends DatabaseEngine {
3298
3311
  init_sql();
3299
3312
 
3300
3313
  // src/utils/physicalIndex.ts
3314
+ init_sql();
3301
3315
  var INDEX_SQL = /^\s*CREATE\s+(UNIQUE\s+)?INDEX\s+(?:IF\s+NOT\s+EXISTS\s+)?"?([A-Za-z0-9_$]+)"?\s+ON\s+"?([A-Za-z0-9_$]+)"?\s*\(([\s\S]*)\)\s*(?:WHERE\s+([\s\S]+))?$/i;
3316
+ var TIGHT = /* @__PURE__ */ new Set(["(", ")", ",", "=", "|"]);
3317
+ function normalizeSqlText(text) {
3318
+ let out = "";
3319
+ let pendingSpace = false;
3320
+ let i = 0;
3321
+ const emit = (chunk) => {
3322
+ const spaced = pendingSpace && out.length > 0 && !TIGHT.has(out[out.length - 1]) && !TIGHT.has(chunk[0]);
3323
+ if (spaced) out += " ";
3324
+ pendingSpace = false;
3325
+ out += chunk;
3326
+ };
3327
+ while (i < text.length) {
3328
+ const ch = text[i];
3329
+ if (ch === "'" || ch === '"') {
3330
+ let j = i + 1;
3331
+ for (; ; ) {
3332
+ if (j >= text.length) break;
3333
+ if (text[j] === ch) {
3334
+ if (text[j + 1] === ch) {
3335
+ j += 2;
3336
+ continue;
3337
+ }
3338
+ break;
3339
+ }
3340
+ j++;
3341
+ }
3342
+ emit(text.slice(i, j + 1));
3343
+ i = j + 1;
3344
+ continue;
3345
+ }
3346
+ if (/\s/.test(ch)) {
3347
+ pendingSpace = true;
3348
+ i++;
3349
+ continue;
3350
+ }
3351
+ emit(ch);
3352
+ i++;
3353
+ }
3354
+ return out;
3355
+ }
3302
3356
  function shapeOf(options) {
3303
- const strip = (text) => text.replace(/\s+/g, "");
3304
3357
  return [
3305
3358
  options.unique ? "UNIQUE" : "INDEX",
3306
- strip(options.expressions),
3307
- options.where ? strip(options.where) : ""
3308
- ].join("\0");
3359
+ normalizeSqlText(options.expressions),
3360
+ options.where ? normalizeSqlText(options.where) : ""
3361
+ ].join(" ");
3309
3362
  }
3310
- function shapeOfSpec(spec) {
3363
+ function indexDefinitionKey(spec) {
3311
3364
  return shapeOf({
3312
3365
  unique: Boolean(spec.unique),
3313
3366
  expressions: spec.expressions,
3314
3367
  where: spec.where ?? null
3315
3368
  });
3316
3369
  }
3317
- function resolvePhysicalIndex(exec, spec) {
3318
- const wanted = shapeOfSpec(spec);
3370
+ function namedIndexesOf(exec, table) {
3319
3371
  const rows = exec(
3320
3372
  `SELECT name, sql FROM sqlite_master
3321
- WHERE type = 'index' AND tbl_name = ? AND sql IS NOT NULL`,
3322
- [spec.table]
3373
+ WHERE type = 'index' AND tbl_name = ? AND sql IS NOT NULL
3374
+ ORDER BY name`,
3375
+ [table]
3323
3376
  );
3377
+ const named = [];
3324
3378
  for (const row of rows) {
3325
3379
  const match = INDEX_SQL.exec(String(row.sql));
3326
3380
  if (!match) continue;
3327
- const shape = shapeOf({
3328
- unique: Boolean(match[1]),
3329
- expressions: match[4] ?? "",
3330
- where: match[5] ?? null
3381
+ named.push({
3382
+ name: String(row.name),
3383
+ key: shapeOf({
3384
+ unique: Boolean(match[1]),
3385
+ expressions: match[4] ?? "",
3386
+ where: match[5] ?? null
3387
+ })
3331
3388
  });
3332
- if (shape === wanted) return String(row.name);
3333
3389
  }
3334
- return null;
3390
+ return named;
3391
+ }
3392
+ function resolvePhysicalIndex(exec, spec) {
3393
+ const wanted = indexDefinitionKey(spec);
3394
+ const found = namedIndexesOf(exec, spec.table).find(
3395
+ (index) => index.key === wanted
3396
+ );
3397
+ return found ? found.name : null;
3398
+ }
3399
+ function unusedIndexName(exec, canonicalName) {
3400
+ const taken = (name) => exec("SELECT 1 FROM sqlite_master WHERE name = ? COLLATE NOCASE", [name]).length > 0;
3401
+ if (!taken(canonicalName)) return canonicalName;
3402
+ for (let n = 2; ; n++) {
3403
+ const candidate = `${canonicalName}_${n}`;
3404
+ if (!taken(candidate)) return candidate;
3405
+ }
3335
3406
  }
3336
3407
  function ensurePhysicalIndex(exec, spec) {
3337
3408
  if (resolvePhysicalIndex(exec, spec) !== null) return "exists";
3338
3409
  exec(
3339
- `CREATE ${spec.unique ? "UNIQUE " : ""}INDEX IF NOT EXISTS ${spec.canonicalName} ON ${spec.table} (${spec.expressions})` + (spec.where ? ` WHERE ${spec.where}` : "")
3410
+ `CREATE ${spec.unique ? "UNIQUE " : ""}INDEX IF NOT EXISTS ${unusedIndexName(exec, spec.canonicalName)} ON ${spec.table} (${spec.expressions})` + (spec.where ? ` WHERE ${spec.where}` : "")
3340
3411
  );
3341
3412
  return "created";
3342
3413
  }
3343
3414
  function removePhysicalIndex(exec, spec) {
3344
- const name = resolvePhysicalIndex(exec, spec);
3345
- if (name === null) return;
3346
- exec(`DROP INDEX IF EXISTS ${name}`);
3415
+ const wanted = indexDefinitionKey(spec);
3416
+ for (const index of namedIndexesOf(exec, spec.table)) {
3417
+ if (index.key !== wanted) continue;
3418
+ exec(`DROP INDEX IF EXISTS ${quoteIdentifier(index.name)}`);
3419
+ }
3420
+ }
3421
+ function collapseDuplicateIndexes(exec, table, ownedKeys) {
3422
+ const kept = /* @__PURE__ */ new Set();
3423
+ const dropped = [];
3424
+ for (const index of namedIndexesOf(exec, table)) {
3425
+ if (!ownedKeys.has(index.key)) continue;
3426
+ if (!kept.has(index.key)) {
3427
+ kept.add(index.key);
3428
+ continue;
3429
+ }
3430
+ exec(`DROP INDEX IF EXISTS ${quoteIdentifier(index.name)}`);
3431
+ dropped.push(index.name);
3432
+ }
3433
+ return dropped;
3347
3434
  }
3348
3435
 
3349
3436
  // src/engines/cloudflare/DurableObjectEngine.ts
3437
+ function ownedDefinitionKeys(specs, table) {
3438
+ return new Set(
3439
+ specs.filter((spec) => spec.table === table).map(indexDefinitionKey)
3440
+ );
3441
+ }
3350
3442
  var DurableObjectEngine = class extends JsonSchemaEngine {
3351
3443
  sql;
3352
3444
  storage;
@@ -3399,8 +3491,85 @@ var DurableObjectEngine = class extends JsonSchemaEngine {
3399
3491
  for (const spec of JsonSchemaDDL.retiredBaseIndexSpecs(this.schemaOptions)) {
3400
3492
  removePhysicalIndex(this.indexExec, spec);
3401
3493
  }
3494
+ const owned = [
3495
+ ...JsonSchemaDDL.baseIndexSpecs(this.schemaOptions),
3496
+ ...JsonSchemaDDL.retiredBaseIndexSpecs(this.schemaOptions),
3497
+ ...this.registeredIndexSpecs()
3498
+ ];
3499
+ for (const table of ["records", "stringset_index"]) {
3500
+ const dropped = collapseDuplicateIndexes(
3501
+ this.indexExec,
3502
+ table,
3503
+ ownedDefinitionKeys(owned, table)
3504
+ );
3505
+ if (dropped.length > 0) {
3506
+ console.info("Durable Object engine collapsed duplicate indexes", {
3507
+ table,
3508
+ dropped
3509
+ });
3510
+ }
3511
+ }
3402
3512
  this.initialized = true;
3403
3513
  }
3514
+ /**
3515
+ * The index each registration row maps to (#3876): `fieldIndexSpec` per
3516
+ * `_indexes` row, `uniqueConstraintIndexSpec` per `_unique_constraints` row.
3517
+ * A row whose `fields_json` does not parse, or whose names are not valid
3518
+ * identifiers, maps to nothing.
3519
+ */
3520
+ registeredIndexSpecs() {
3521
+ const specs = [];
3522
+ try {
3523
+ for (const row of this.execSqlSync(
3524
+ "SELECT model_name, field_name FROM _indexes"
3525
+ )) {
3526
+ try {
3527
+ specs.push(
3528
+ JsonSchemaDDL.fieldIndexSpec(
3529
+ String(row.model_name),
3530
+ String(row.field_name)
3531
+ )
3532
+ );
3533
+ } catch (e) {
3534
+ console.debug("Skipping an unusable index registration:", e);
3535
+ }
3536
+ }
3537
+ } catch (e) {
3538
+ console.debug("Could not read index registrations:", e);
3539
+ }
3540
+ try {
3541
+ for (const row of this.execSqlSync(
3542
+ "SELECT model_name, constraint_name, fields_json FROM _unique_constraints"
3543
+ )) {
3544
+ try {
3545
+ specs.push(
3546
+ JsonSchemaDDL.uniqueConstraintIndexSpec(
3547
+ String(row.model_name),
3548
+ String(row.constraint_name),
3549
+ JSON.parse(String(row.fields_json))
3550
+ )
3551
+ );
3552
+ } catch (e) {
3553
+ console.debug("Skipping an unusable unique-constraint registration:", e);
3554
+ }
3555
+ }
3556
+ } catch (e) {
3557
+ console.debug("Could not read unique-constraint registrations:", e);
3558
+ }
3559
+ return specs;
3560
+ }
3561
+ /**
3562
+ * Whether a remaining registration still maps to `spec`'s definition
3563
+ * (#3876). A field index and a unique constraint over the same field — or
3564
+ * two constraints over the same fields — share one physical index since
3565
+ * #3434, so a drop removes it only when no other declaration needs it. The
3566
+ * count is derived from the registration tables at drop time, not stored.
3567
+ */
3568
+ definitionStillDeclared(spec) {
3569
+ return ownedDefinitionKeys(this.registeredIndexSpecs(), spec.table).has(
3570
+ indexDefinitionKey(spec)
3571
+ );
3572
+ }
3404
3573
  /**
3405
3574
  * Whether this open renamed the pre-underscore column names.
3406
3575
  *
@@ -3556,10 +3725,9 @@ var DurableObjectEngine = class extends JsonSchemaEngine {
3556
3725
  "DELETE FROM _indexes WHERE model_name = ? AND field_name = ?",
3557
3726
  [modelName, fieldName]
3558
3727
  );
3559
- removePhysicalIndex(
3560
- this.indexExec,
3561
- JsonSchemaDDL.fieldIndexSpec(modelName, fieldName)
3562
- );
3728
+ const spec = JsonSchemaDDL.fieldIndexSpec(modelName, fieldName);
3729
+ if (this.definitionStillDeclared(spec)) return;
3730
+ removePhysicalIndex(this.indexExec, spec);
3563
3731
  }
3564
3732
  /**
3565
3733
  * List all registered indexes, optionally filtered by model.
@@ -3624,10 +3792,13 @@ var DurableObjectEngine = class extends JsonSchemaEngine {
3624
3792
  } catch {
3625
3793
  return;
3626
3794
  }
3627
- removePhysicalIndex(
3628
- this.indexExec,
3629
- JsonSchemaDDL.uniqueConstraintIndexSpec(modelName, constraintName, fields)
3795
+ const spec = JsonSchemaDDL.uniqueConstraintIndexSpec(
3796
+ modelName,
3797
+ constraintName,
3798
+ fields
3630
3799
  );
3800
+ if (this.definitionStillDeclared(spec)) return;
3801
+ removePhysicalIndex(this.indexExec, spec);
3631
3802
  }
3632
3803
  /**
3633
3804
  * List composite unique constraints, optionally filtered by model.
@@ -7969,6 +8140,7 @@ export {
7969
8140
  checkAutoStampsReservedFields,
7970
8141
  checkReservedFieldName,
7971
8142
  clearMetaSyncCache,
8143
+ collapseDuplicateIndexes,
7972
8144
  compareRecordIds,
7973
8145
  completeOverlayEntries,
7974
8146
  createDatabaseDO,
@@ -7988,6 +8160,7 @@ export {
7988
8160
  format2StringSetMembers,
7989
8161
  groupOverlayEntries,
7990
8162
  handleRequest,
8163
+ indexDefinitionKey,
7991
8164
  inferFieldType,
7992
8165
  isAnchorId,
7993
8166
  isFormat2StringSetYMap,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "js-bao",
3
- "version": "0.12.0-alpha.0",
3
+ "version": "0.12.0-alpha.1",
4
4
  "description": "A library providing data modeling capabilities which support live updates and queries.",
5
5
  "types": "dist/index.d.ts",
6
6
  "type": "module",