@rocicorp/zero 1.11.0-canary.41 → 1.11.0-canary.42

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.
Files changed (103) hide show
  1. package/out/replicache/src/deleted-clients.d.ts +2 -2
  2. package/out/replicache/src/deleted-clients.js +2 -2
  3. package/out/replicache/src/deleted-clients.js.map +1 -1
  4. package/out/replicache/src/persist/client-group-gc.d.ts +1 -1
  5. package/out/replicache/src/persist/client-group-gc.js +1 -1
  6. package/out/replicache/src/persist/client-group-gc.js.map +1 -1
  7. package/out/replicache/src/replicache-options.d.ts +1 -1
  8. package/out/shared/src/browser-env.d.ts +1 -1
  9. package/out/shared/src/browser-env.js.map +1 -1
  10. package/out/shared/src/set-utils.d.ts +1 -1
  11. package/out/shared/src/set-utils.js +1 -1
  12. package/out/shared/src/set-utils.js.map +1 -1
  13. package/out/zero/package.js +1 -1
  14. package/out/zero/package.js.map +1 -1
  15. package/out/zero-cache/src/server/change-streamer.d.ts.map +1 -1
  16. package/out/zero-cache/src/server/change-streamer.js +2 -0
  17. package/out/zero-cache/src/server/change-streamer.js.map +1 -1
  18. package/out/zero-cache/src/services/change-source/pg/backfill-stream.d.ts.map +1 -1
  19. package/out/zero-cache/src/services/change-source/pg/backfill-stream.js +4 -2
  20. package/out/zero-cache/src/services/change-source/pg/backfill-stream.js.map +1 -1
  21. package/out/zero-cache/src/services/change-source/pg/initial-sync.d.ts.map +1 -1
  22. package/out/zero-cache/src/services/change-source/pg/initial-sync.js +17 -6
  23. package/out/zero-cache/src/services/change-source/pg/initial-sync.js.map +1 -1
  24. package/out/zero-cache/src/types/pg-patch.d.ts +21 -0
  25. package/out/zero-cache/src/types/pg-patch.d.ts.map +1 -0
  26. package/out/zero-cache/src/types/pg-patch.js +29 -0
  27. package/out/zero-cache/src/types/pg-patch.js.map +1 -0
  28. package/out/zero-client/src/client/connection-manager.js +1 -1
  29. package/out/zero-client/src/client/connection-manager.js.map +1 -1
  30. package/out/zero-client/src/client/custom.d.ts +1 -1
  31. package/out/zero-client/src/client/custom.js +1 -1
  32. package/out/zero-client/src/client/custom.js.map +1 -1
  33. package/out/zero-client/src/client/options.d.ts +6 -6
  34. package/out/zero-client/src/client/options.js.map +1 -1
  35. package/out/zero-client/src/client/version.js +1 -1
  36. package/out/zero-client/src/client/zero.d.ts +1 -1
  37. package/out/zero-client/src/client/zero.js +1 -1
  38. package/out/zero-client/src/client/zero.js.map +1 -1
  39. package/out/zero-protocol/src/inspect-up.d.ts +3 -3
  40. package/out/zero-protocol/src/inspect-up.d.ts.map +1 -1
  41. package/out/zero-protocol/src/inspect-up.js +2 -2
  42. package/out/zero-protocol/src/inspect-up.js.map +1 -1
  43. package/out/zero-react/src/use-zero-online.d.ts +1 -1
  44. package/out/zero-react/src/use-zero-online.js +1 -1
  45. package/out/zero-react/src/use-zero-online.js.map +1 -1
  46. package/out/zero-react/src/zero-provider.d.ts +1 -1
  47. package/out/zero-react/src/zero-provider.js +1 -1
  48. package/out/zero-react/src/zero-provider.js.map +1 -1
  49. package/out/zero-server/src/closable-db-transaction.d.ts +1 -1
  50. package/out/zero-server/src/closable-db-transaction.js +1 -1
  51. package/out/zero-server/src/closable-db-transaction.js.map +1 -1
  52. package/out/zero-server/src/custom.d.ts +1 -1
  53. package/out/zero-server/src/custom.js +1 -1
  54. package/out/zero-server/src/custom.js.map +1 -1
  55. package/out/zero-server/src/process-mutations.d.ts +3 -3
  56. package/out/zero-server/src/process-mutations.js +1 -1
  57. package/out/zero-server/src/process-mutations.js.map +1 -1
  58. package/out/zero-server/src/push-processor.d.ts +1 -1
  59. package/out/zero-server/src/push-processor.js +1 -1
  60. package/out/zero-server/src/push-processor.js.map +1 -1
  61. package/out/zero-server/src/queries/process-queries.d.ts +3 -3
  62. package/out/zero-server/src/queries/process-queries.js +2 -2
  63. package/out/zero-server/src/queries/process-queries.js.map +1 -1
  64. package/out/zero-server/src/schema.d.ts +1 -1
  65. package/out/zero-server/src/schema.js +1 -1
  66. package/out/zero-server/src/schema.js.map +1 -1
  67. package/out/zero-server/src/zql-database.d.ts +2 -2
  68. package/out/zero-server/src/zql-database.js +2 -2
  69. package/out/zero-server/src/zql-database.js.map +1 -1
  70. package/out/zero-solid/src/use-query.d.ts +2 -2
  71. package/out/zero-solid/src/use-query.js +1 -1
  72. package/out/zero-solid/src/use-query.js.map +1 -1
  73. package/out/zero-solid/src/use-zero-online.d.ts +1 -1
  74. package/out/zero-solid/src/use-zero-online.js +1 -1
  75. package/out/zero-solid/src/use-zero-online.js.map +1 -1
  76. package/out/zero-solid/src/use-zero.d.ts +2 -2
  77. package/out/zero-solid/src/use-zero.js +2 -2
  78. package/out/zero-solid/src/use-zero.js.map +1 -1
  79. package/out/zql/src/ivm/change.d.ts +3 -3
  80. package/out/zql/src/ivm/change.js.map +1 -1
  81. package/out/zql/src/ivm/data.d.ts +1 -1
  82. package/out/zql/src/ivm/data.js.map +1 -1
  83. package/out/zql/src/ivm/maybe-split-and-push-edit-change.d.ts +1 -1
  84. package/out/zql/src/ivm/maybe-split-and-push-edit-change.js +1 -1
  85. package/out/zql/src/ivm/maybe-split-and-push-edit-change.js.map +1 -1
  86. package/out/zql/src/mutate/custom.d.ts +3 -3
  87. package/out/zql/src/mutate/custom.js.map +1 -1
  88. package/out/zql/src/query/create-builder.d.ts +1 -1
  89. package/out/zql/src/query/create-builder.d.ts.map +1 -1
  90. package/out/zql/src/query/create-builder.js +1 -1
  91. package/out/zql/src/query/create-builder.js.map +1 -1
  92. package/out/zql/src/query/named.d.ts +2 -2
  93. package/out/zql/src/query/named.js +2 -2
  94. package/out/zql/src/query/named.js.map +1 -1
  95. package/out/zql/src/query/query-impl.js +3 -3
  96. package/out/zql/src/query/query-impl.js.map +1 -1
  97. package/out/zql/src/query/query-internals.d.ts +5 -5
  98. package/out/zql/src/query/query-internals.js.map +1 -1
  99. package/out/zql/src/query/query-transitions.d.ts +5 -5
  100. package/out/zql/src/query/query-transitions.js +2 -2
  101. package/out/zql/src/query/query-transitions.js.map +1 -1
  102. package/out/zql/src/query/query.d.ts +8 -8
  103. package/package.json +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"query-transitions.js","names":["#bounded","#firstKey","#firstValue","#firstDelta","#first","#rest","#restSize","#maybeSweep","#sweepAt","#byHash"],"sources":["../../../../../zql/src/query/query-transitions.ts"],"sourcesContent":["import {deepEqual, type ReadonlyJSONValue} from '../../../shared/src/json.ts';\nimport {getOrInsertComputed, newMap} from '../../../shared/src/map.ts';\n\n/**\n * A transition tree for interning immutable builder objects, modelled on V8's\n * hidden-class transitions.\n *\n * Each node keeps a map from a *transition* (an operation plus its arguments) to\n * the node that operation produces. Applying the same operation to the same node\n * twice therefore yields the same object both times.\n *\n * ## Why not hash?\n *\n * Hashing the resulting AST would work but is far too expensive to do on every\n * builder call. Instead a transition is identified by three things, in\n * decreasing order of how cheap they are to compare:\n *\n * 1. `key` — a short string naming the operation. Callers are expected to hand\n * in a *stable* string rather than build one per call; see below.\n * 2. `value` — the operation's varying argument, when it is a primitive, used\n * directly as a `Map` key.\n * 3. `delta` — anything left over, compared with {@linkcode deepEqual}.\n *\n * Splitting 1 from 2 is what makes this fast. Folding the value into the key\n * instead means building a fresh string on every call, and a fresh string has to\n * be hashed from scratch before a `Map` can look it up: measured at ~148ns,\n * against ~10ns to look up the caller's own string, whose hash V8 has already\n * cached on the string object. That difference is larger than everything else a\n * transition does.\n *\n * Comparing only the delta is sound because the parent node is already canonical:\n * two children of the same parent are equal exactly when the operations that\n * produced them are equal. We never deep-compare a whole AST.\n *\n * ## Liveness\n *\n * Children point back at their parents strongly (see `QueryImpl`), so holding a\n * query holds its whole ancestor spine and re-deriving it from the root gives\n * the same object back. The other direction has to be weak, or an interned root\n * — which lives as long as its schema — would retain every query ever derived\n * from it.\n *\n * Weak is expensive, though: `new WeakRef` costs on the order of a hundred\n * nanoseconds, more than everything else a transition does put together, and it\n * adds GC work later. So the *first* transition out of a node is held strongly\n * instead, in fields on this object, with no `WeakRef` at all.\n *\n * That is safe because strong first-children form a path, not a tree: each node\n * pins at most one child, which pins at most one of its own. The retained set\n * per root is bounded by the depth of a builder chain — a handful of queries —\n * rather than by how many distinct queries an app builds. Everything past the\n * first child is weak, so the unbounded cases (a search box typing into\n * `where:title:=:s<text>`, a row id per list item) stay collectible.\n *\n * It is also where the wins are: a chain like `q.where(…).orderBy(…).limit(…)`\n * gives every node exactly one child, so building it allocates no `WeakRef`s and\n * looks up through a field compare rather than a hash lookup.\n *\n * What a collected child leaves behind is a cleared `WeakRef` and its map key. A\n * lookup drops those as it walks, which covers every key that is asked for\n * again. A key that is *never* asked for again is handled by the sweep.\n *\n * ## Best effort\n *\n * Interning is a cache, not a guarantee: a missed lookup only costs an extra\n * object. See {@linkcode MAX_SCAN} for the one case where we deliberately give\n * up.\n *\n * ## Identity vs. hash\n *\n * Identity implies an equal query hash, but the tree alone does not give the\n * converse. `normalizeAST` sorts `related` and `and`/`or` conditions, so\n * `q.where(a).where(b)` and `q.where(b).where(a)` hash the same while being\n * distinct paths, and so distinct nodes. {@linkcode HashIndex} is the second\n * level that folds those together once they have been hashed. Callers that\n * need the normalizing behavior for a query that may not have been must keep\n * using the hash.\n */\n\n/**\n * The delta identifying a transition, or `undefined` when the key already\n * determines the result on its own.\n */\nexport type Delta = ReadonlyJSONValue | undefined;\n\ntype Entry<T extends object> = {\n readonly delta: Delta;\n readonly ref: WeakRef<T>;\n};\n\n/**\n * A bucket holds a bare entry in the common single-entry case and is promoted to\n * a `Set` only on collision, so the usual transition allocates no container.\n *\n * A `Set` rather than an array: deleting during iteration is well defined, so a\n * lookup prunes collected entries and stops at its match in a single pass, and\n * removing an entry is O(1) rather than a scan.\n */\ntype Bucket<T extends object> = Entry<T> | Set<Entry<T>>;\n\n/** The varying argument of a transition, when it is one. */\nexport type TransitionValue = string | number | boolean | null | undefined;\n\n/**\n * The longest `deepEqual` scan we are willing to do on a lookup.\n *\n * This is a *latency* bound, not a memory bound — memory is already handled,\n * since entries are weak and are pruned both by the sweep and by any lookup that\n * walks past them, so a bucket only ever holds children that are still alive.\n *\n * Note what happens at the limit: we stop *storing*, we do not evict. Evicting\n * would de-intern objects that are still in use, and on a sequential sweep of a\n * live set larger than the limit it drives the hit rate to zero — each lookup\n * throws out the entry the next one needs. Refusing to store instead leaves\n * every already-shared sibling shared and degrades the surplus to the old\n * behavior of a fresh instance per call.\n *\n * Buckets should rarely get near this. Keys carry the discriminating value\n * wherever it is cheap to, and a key that carries it is *exact*, so it costs no\n * comparison at all: every simple comparison goes that way, and so does a\n * one-hop `exists`, via its sub-query's identity. What is left sharing a bucket\n * is two-hop `exists`, the `and`/`or` trees an expression factory builds, and\n * relationships whose callback varies.\n */\nexport const MAX_SCAN = 64;\n\n/** How large the weak map must get before its first sweep. */\nconst SWEEP_INITIAL = 64;\n\n/** The transitions out of one node. */\nexport class Transitions<T extends object> {\n // The first transition, held strongly and in place. See \"Liveness\" above.\n #firstKey: string | undefined;\n #firstValue: TransitionValue;\n #firstDelta: Delta;\n #first: T | undefined;\n\n // Transitions whose key space is finite and comes from the schema rather than\n // from user data, held strongly. See `storeBounded`.\n #bounded: Map<string, T> | undefined;\n\n // Everything else, weakly. Allocated only if there is a second. Nested so the\n // value stays a `Map` key of its own rather than part of the string.\n #rest: Map<string, Map<TransitionValue, Bucket<T>>> | undefined;\n #sweepAt = SWEEP_INITIAL;\n #restSize = 0;\n\n lookupBounded(key: string): T | undefined {\n return this.#bounded?.get(key);\n }\n\n /**\n * Records a transition whose key space is finite and schema-derived — the base\n * sub-query handed to a `related`/`exists` callback, keyed by relationship\n * name.\n *\n * These are held strongly. Retention is bounded by the number of relationships\n * on the table, so it cannot grow with what an app queries for, and in exchange\n * a relationship traversal allocates no `WeakRef`. Anything keyed by a *value*\n * — a row id, a search string, a limit — must not come through here.\n */\n storeBounded(key: string, value: T): void {\n (this.#bounded ??= new Map()).set(key, value);\n }\n\n lookup(key: string, value: TransitionValue, delta: Delta): T | undefined {\n if (\n this.#firstKey === key &&\n this.#firstValue === value &&\n deepEqual(this.#firstDelta, delta)\n ) {\n return this.#first;\n }\n const byValue = this.#rest?.get(key);\n if (byValue === undefined) {\n return undefined;\n }\n const bucket = byValue.get(value);\n if (bucket === undefined) {\n return undefined;\n }\n\n if (!(bucket instanceof Set)) {\n const found = bucket.ref.deref();\n if (found === undefined) {\n byValue.delete(value);\n this.#restSize--;\n return undefined;\n }\n // deepEqual short-circuits on reference equality, which is the fast path\n // we rely on for deltas built out of already-interned sub-queries.\n return deepEqual(bucket.delta, delta) ? found : undefined;\n }\n\n // One pass: drop what has been collected, stop at the match. Deleting from a\n // Set while iterating it is well defined — the iterator simply skips what is\n // gone — so this stays O(n) with no compaction step, and returns early on a\n // hit rather than walking the whole bucket first.\n for (const entry of bucket) {\n const found = entry.ref.deref();\n if (found === undefined) {\n bucket.delete(entry);\n this.#restSize--;\n continue;\n }\n if (deepEqual(entry.delta, delta)) {\n return found;\n }\n }\n if (bucket.size === 0) {\n byValue.delete(value);\n }\n return undefined;\n }\n\n store(key: string, value: TransitionValue, delta: Delta, node: T): void {\n if (this.#firstKey === undefined) {\n this.#firstKey = key;\n this.#firstValue = value;\n this.#firstDelta = delta;\n this.#first = node;\n return;\n }\n\n const rest = (this.#rest ??= new Map());\n const byValue = getOrInsertComputed(rest, key, newMap);\n\n const entry: Entry<T> = {delta, ref: new WeakRef(node)};\n const bucket = byValue.get(value);\n\n if (bucket === undefined) {\n byValue.set(value, entry);\n } else if (!(bucket instanceof Set)) {\n byValue.set(value, new Set([bucket, entry]));\n } else {\n if (bucket.size >= MAX_SCAN) {\n // Leave the bucket alone; see MAX_SCAN. `node` is simply not shared.\n // A store only follows a failed lookup, which has just pruned, so this\n // is measured against entries that are actually still alive.\n return;\n }\n bucket.add(entry);\n }\n this.#restSize++;\n this.#maybeSweep(rest);\n }\n\n /**\n * Re-points the transition that produced `from` at `to`, so the next lookup\n * along that path yields `to` instead. This is how {@linkcode HashIndex}\n * converges two paths on one node: `from` keeps its identity, since it has\n * already been handed out, and its path is redirected for the rebuilds that\n * follow.\n *\n * Scans rather than taking a key. This runs once per redirected path, so a\n * walk here is cheaper than the fields every node would need to carry to\n * name its own transition.\n */\n replace(from: T, to: T): boolean {\n if (this.#first === from) {\n this.#first = to;\n return true;\n }\n const rest = this.#rest;\n if (rest === undefined) {\n return false;\n }\n for (const byValue of rest.values()) {\n for (const [value, bucket] of byValue) {\n if (!(bucket instanceof Set)) {\n if (bucket.ref.deref() === from) {\n byValue.set(value, {delta: bucket.delta, ref: new WeakRef(to)});\n return true;\n }\n continue;\n }\n for (const entry of bucket) {\n if (entry.ref.deref() === from) {\n bucket.delete(entry);\n bucket.add({delta: entry.delta, ref: new WeakRef(to)});\n return true;\n }\n }\n }\n }\n return false;\n }\n\n /**\n * Drops entries for collected values, if the map has grown enough to be worth\n * walking.\n *\n * A lookup already prunes whatever it walks past, which covers every key that\n * is asked for again. This is for the key that is not: a miss on a fresh key\n * returns without touching any existing entry, so nothing would ever notice\n * that those had died. Doubling the threshold off whatever survives keeps this\n * amortized O(1) per insert whether or not there was anything to reclaim, and\n * it is self-limiting — only a node that keeps taking inserts can keep\n * accumulating, and taking inserts is what triggers the sweep.\n */\n #maybeSweep(rest: Map<string, Map<TransitionValue, Bucket<T>>>): void {\n if (this.#restSize < this.#sweepAt) {\n return;\n }\n let live = 0;\n for (const [key, byValue] of rest) {\n for (const [value, bucket] of byValue) {\n if (!(bucket instanceof Set)) {\n if (bucket.ref.deref() === undefined) {\n byValue.delete(value);\n } else {\n live++;\n }\n continue;\n }\n for (const entry of bucket) {\n if (entry.ref.deref() === undefined) {\n bucket.delete(entry);\n } else {\n live++;\n }\n }\n if (bucket.size === 0) {\n byValue.delete(value);\n }\n }\n if (byValue.size === 0) {\n rest.delete(key);\n }\n }\n this.#restSize = live;\n this.#sweepAt = Math.max(SWEEP_INITIAL, live * 2);\n }\n}\n\n/**\n * The second level of interning: nodes keyed by *content* rather than by the\n * path that built them.\n *\n * The transition tree cannot see that `q.where(a).where(b)` and\n * `q.where(b).where(a)` are the same query. `normalizeAST` sorts conditions\n * and `related` entries, so the two build byte-identical ASTs, but they are\n * different paths and so different nodes. This index closes that gap, keyed by\n * the query's hash.\n *\n * It is consulted when a query is *hashed*, not when it is built. Hashing on\n * every tree miss would make a cold chain pay for a hash per node, where the\n * uninterned code paid for one at the end, if at all -- and server-side queries\n * are built cold, once, per request. The consumers that key by hash already\n * hash the queries they care about, so doing the work there costs one map\n * lookup on top of a hash that was being computed anyway, and nothing at all\n * for a query nobody hashes.\n *\n * On a hit the query being hashed keeps its identity, since it has already\n * been handed out. Its *transition* is re-pointed at the existing node instead\n * (see {@linkcode Transitions.replace}), so the next rebuild along that path\n * yields the shared instance. As with the tree this is a cache: the rebuild\n * after a redirect is what converges, not the call that found the duplicate.\n *\n * One index per root, which is what scopes it. Two roots never share an index,\n * so queries against different schemas or different delegates -- neither of\n * which the hash covers -- can never be conflated. Entries are weak: a cleared\n * one is dropped by the lookup that finds it, or by a sweep once the map has\n * doubled since the last one.\n */\nexport class HashIndex<T extends object> {\n readonly #byHash = new Map<string, WeakRef<T>>();\n #sweepAt = SWEEP_INITIAL;\n\n get(hash: string): T | undefined {\n const ref = this.#byHash.get(hash);\n if (ref === undefined) {\n return undefined;\n }\n const node = ref.deref();\n if (node === undefined) {\n this.#byHash.delete(hash);\n }\n return node;\n }\n\n set(hash: string, node: T): void {\n const byHash = this.#byHash;\n byHash.set(hash, new WeakRef(node));\n if (byHash.size < this.#sweepAt) {\n return;\n }\n for (const [h, ref] of byHash) {\n if (ref.deref() === undefined) {\n byHash.delete(h);\n }\n }\n this.#sweepAt = Math.max(SWEEP_INITIAL, byHash.size * 2);\n }\n}\n"],"mappings":";;;AA+HA,IAAM,gBAAgB;;AAGtB,IAAa,cAAb,MAA2C;CAEzC;CACA;CACA;CACA;CAIA;CAIA;CACA,WAAW;CACX,YAAY;CAEZ,cAAc,KAA4B;EACxC,OAAO,KAAKA,UAAU,IAAI,GAAG;CAC/B;;;;;;;;;;;CAYA,aAAa,KAAa,OAAgB;EACxC,CAAC,KAAKA,6BAAa,IAAI,IAAI,GAAG,IAAI,KAAK,KAAK;CAC9C;CAEA,OAAO,KAAa,OAAwB,OAA6B;EACvE,IACE,KAAKC,cAAc,OACnB,KAAKC,gBAAgB,SACrB,UAAU,KAAKC,aAAa,KAAK,GAEjC,OAAO,KAAKC;EAEd,MAAM,UAAU,KAAKC,OAAO,IAAI,GAAG;EACnC,IAAI,YAAY,KAAA,GACd;EAEF,MAAM,SAAS,QAAQ,IAAI,KAAK;EAChC,IAAI,WAAW,KAAA,GACb;EAGF,IAAI,EAAE,kBAAkB,MAAM;GAC5B,MAAM,QAAQ,OAAO,IAAI,MAAM;GAC/B,IAAI,UAAU,KAAA,GAAW;IACvB,QAAQ,OAAO,KAAK;IACpB,KAAKC;IACL;GACF;GAGA,OAAO,UAAU,OAAO,OAAO,KAAK,IAAI,QAAQ,KAAA;EAClD;EAMA,KAAK,MAAM,SAAS,QAAQ;GAC1B,MAAM,QAAQ,MAAM,IAAI,MAAM;GAC9B,IAAI,UAAU,KAAA,GAAW;IACvB,OAAO,OAAO,KAAK;IACnB,KAAKA;IACL;GACF;GACA,IAAI,UAAU,MAAM,OAAO,KAAK,GAC9B,OAAO;EAEX;EACA,IAAI,OAAO,SAAS,GAClB,QAAQ,OAAO,KAAK;CAGxB;CAEA,MAAM,KAAa,OAAwB,OAAc,MAAe;EACtE,IAAI,KAAKL,cAAc,KAAA,GAAW;GAChC,KAAKA,YAAY;GACjB,KAAKC,cAAc;GACnB,KAAKC,cAAc;GACnB,KAAKC,SAAS;GACd;EACF;EAEA,MAAM,OAAQ,KAAKC,0BAAU,IAAI,IAAI;EACrC,MAAM,UAAU,oBAAoB,MAAM,KAAK,MAAM;EAErD,MAAM,QAAkB;GAAC;GAAO,KAAK,IAAI,QAAQ,IAAI;EAAC;EACtD,MAAM,SAAS,QAAQ,IAAI,KAAK;EAEhC,IAAI,WAAW,KAAA,GACb,QAAQ,IAAI,OAAO,KAAK;OACnB,IAAI,EAAE,kBAAkB,MAC7B,QAAQ,IAAI,OAAO,IAAI,IAAI,CAAC,QAAQ,KAAK,CAAC,CAAC;OACtC;GACL,IAAI,OAAO,QAAA,IAIT;GAEF,OAAO,IAAI,KAAK;EAClB;EACA,KAAKC;EACL,KAAKC,YAAY,IAAI;CACvB;;;;;;;;;;;;CAaA,QAAQ,MAAS,IAAgB;EAC/B,IAAI,KAAKH,WAAW,MAAM;GACxB,KAAKA,SAAS;GACd,OAAO;EACT;EACA,MAAM,OAAO,KAAKC;EAClB,IAAI,SAAS,KAAA,GACX,OAAO;EAET,KAAK,MAAM,WAAW,KAAK,OAAO,GAChC,KAAK,MAAM,CAAC,OAAO,WAAW,SAAS;GACrC,IAAI,EAAE,kBAAkB,MAAM;IAC5B,IAAI,OAAO,IAAI,MAAM,MAAM,MAAM;KAC/B,QAAQ,IAAI,OAAO;MAAC,OAAO,OAAO;MAAO,KAAK,IAAI,QAAQ,EAAE;KAAC,CAAC;KAC9D,OAAO;IACT;IACA;GACF;GACA,KAAK,MAAM,SAAS,QAClB,IAAI,MAAM,IAAI,MAAM,MAAM,MAAM;IAC9B,OAAO,OAAO,KAAK;IACnB,OAAO,IAAI;KAAC,OAAO,MAAM;KAAO,KAAK,IAAI,QAAQ,EAAE;IAAC,CAAC;IACrD,OAAO;GACT;EAEJ;EAEF,OAAO;CACT;;;;;;;;;;;;;CAcA,YAAY,MAA0D;EACpE,IAAI,KAAKC,YAAY,KAAKE,UACxB;EAEF,IAAI,OAAO;EACX,KAAK,MAAM,CAAC,KAAK,YAAY,MAAM;GACjC,KAAK,MAAM,CAAC,OAAO,WAAW,SAAS;IACrC,IAAI,EAAE,kBAAkB,MAAM;KAC5B,IAAI,OAAO,IAAI,MAAM,MAAM,KAAA,GACzB,QAAQ,OAAO,KAAK;UAEpB;KAEF;IACF;IACA,KAAK,MAAM,SAAS,QAClB,IAAI,MAAM,IAAI,MAAM,MAAM,KAAA,GACxB,OAAO,OAAO,KAAK;SAEnB;IAGJ,IAAI,OAAO,SAAS,GAClB,QAAQ,OAAO,KAAK;GAExB;GACA,IAAI,QAAQ,SAAS,GACnB,KAAK,OAAO,GAAG;EAEnB;EACA,KAAKF,YAAY;EACjB,KAAKE,WAAW,KAAK,IAAI,eAAe,OAAO,CAAC;CAClD;AACF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAgCA,IAAa,YAAb,MAAyC;CACvC,0BAAmB,IAAI,IAAwB;CAC/C,WAAW;CAEX,IAAI,MAA6B;EAC/B,MAAM,MAAM,KAAKC,QAAQ,IAAI,IAAI;EACjC,IAAI,QAAQ,KAAA,GACV;EAEF,MAAM,OAAO,IAAI,MAAM;EACvB,IAAI,SAAS,KAAA,GACX,KAAKA,QAAQ,OAAO,IAAI;EAE1B,OAAO;CACT;CAEA,IAAI,MAAc,MAAe;EAC/B,MAAM,SAAS,KAAKA;EACpB,OAAO,IAAI,MAAM,IAAI,QAAQ,IAAI,CAAC;EAClC,IAAI,OAAO,OAAO,KAAKD,UACrB;EAEF,KAAK,MAAM,CAAC,GAAG,QAAQ,QACrB,IAAI,IAAI,MAAM,MAAM,KAAA,GAClB,OAAO,OAAO,CAAC;EAGnB,KAAKA,WAAW,KAAK,IAAI,eAAe,OAAO,OAAO,CAAC;CACzD;AACF"}
1
+ {"version":3,"file":"query-transitions.js","names":["#bounded","#firstKey","#firstValue","#firstDelta","#first","#rest","#restSize","#maybeSweep","#sweepAt","#byHash"],"sources":["../../../../../zql/src/query/query-transitions.ts"],"sourcesContent":["import {deepEqual, type ReadonlyJSONValue} from '../../../shared/src/json.ts';\nimport {getOrInsertComputed, newMap} from '../../../shared/src/map.ts';\n\n/**\n * A transition tree for interning immutable builder objects, modelled on V8's\n * hidden-class transitions.\n *\n * Each node keeps a map from a *transition* (an operation plus its arguments) to\n * the node that operation produces. Applying the same operation to the same node\n * twice therefore yields the same object both times.\n *\n * ## Why not hash?\n *\n * Hashing the resulting AST would work but is far too expensive to do on every\n * builder call. Instead a transition is identified by three things, in\n * decreasing order of how cheap they are to compare:\n *\n * 1. `key` — a short string naming the operation. Callers are expected to hand\n * in a *stable* string rather than build one per call; see below.\n * 2. `value` — the operation's varying argument, when it is a primitive, used\n * directly as a `Map` key.\n * 3. `delta` — anything left over, compared with {@link deepEqual}.\n *\n * Splitting 1 from 2 is what makes this fast. Folding the value into the key\n * instead means building a fresh string on every call, and a fresh string has to\n * be hashed from scratch before a `Map` can look it up: measured at ~148ns,\n * against ~10ns to look up the caller's own string, whose hash V8 has already\n * cached on the string object. That difference is larger than everything else a\n * transition does.\n *\n * Comparing only the delta is sound because the parent node is already canonical:\n * two children of the same parent are equal exactly when the operations that\n * produced them are equal. We never deep-compare a whole AST.\n *\n * ## Liveness\n *\n * Children point back at their parents strongly (see `QueryImpl`), so holding a\n * query holds its whole ancestor spine and re-deriving it from the root gives\n * the same object back. The other direction has to be weak, or an interned root\n * — which lives as long as its schema — would retain every query ever derived\n * from it.\n *\n * Weak is expensive, though: `new WeakRef` costs on the order of a hundred\n * nanoseconds, more than everything else a transition does put together, and it\n * adds GC work later. So the *first* transition out of a node is held strongly\n * instead, in fields on this object, with no `WeakRef` at all.\n *\n * That is safe because strong first-children form a path, not a tree: each node\n * pins at most one child, which pins at most one of its own. The retained set\n * per root is bounded by the depth of a builder chain — a handful of queries —\n * rather than by how many distinct queries an app builds. Everything past the\n * first child is weak, so the unbounded cases (a search box typing into\n * `where:title:=:s<text>`, a row id per list item) stay collectible.\n *\n * It is also where the wins are: a chain like `q.where(…).orderBy(…).limit(…)`\n * gives every node exactly one child, so building it allocates no `WeakRef`s and\n * looks up through a field compare rather than a hash lookup.\n *\n * What a collected child leaves behind is a cleared `WeakRef` and its map key. A\n * lookup drops those as it walks, which covers every key that is asked for\n * again. A key that is *never* asked for again is handled by the sweep.\n *\n * ## Best effort\n *\n * Interning is a cache, not a guarantee: a missed lookup only costs an extra\n * object. See {@link MAX_SCAN} for the one case where we deliberately give\n * up.\n *\n * ## Identity vs. hash\n *\n * Identity implies an equal query hash, but the tree alone does not give the\n * converse. `normalizeAST` sorts `related` and `and`/`or` conditions, so\n * `q.where(a).where(b)` and `q.where(b).where(a)` hash the same while being\n * distinct paths, and so distinct nodes. {@link HashIndex} is the second\n * level that folds those together once they have been hashed. Callers that\n * need the normalizing behavior for a query that may not have been must keep\n * using the hash.\n */\n\n/**\n * The delta identifying a transition, or `undefined` when the key already\n * determines the result on its own.\n */\nexport type Delta = ReadonlyJSONValue | undefined;\n\ntype Entry<T extends object> = {\n readonly delta: Delta;\n readonly ref: WeakRef<T>;\n};\n\n/**\n * A bucket holds a bare entry in the common single-entry case and is promoted to\n * a `Set` only on collision, so the usual transition allocates no container.\n *\n * A `Set` rather than an array: deleting during iteration is well defined, so a\n * lookup prunes collected entries and stops at its match in a single pass, and\n * removing an entry is O(1) rather than a scan.\n */\ntype Bucket<T extends object> = Entry<T> | Set<Entry<T>>;\n\n/** The varying argument of a transition, when it is one. */\nexport type TransitionValue = string | number | boolean | null | undefined;\n\n/**\n * The longest `deepEqual` scan we are willing to do on a lookup.\n *\n * This is a *latency* bound, not a memory bound — memory is already handled,\n * since entries are weak and are pruned both by the sweep and by any lookup that\n * walks past them, so a bucket only ever holds children that are still alive.\n *\n * Note what happens at the limit: we stop *storing*, we do not evict. Evicting\n * would de-intern objects that are still in use, and on a sequential sweep of a\n * live set larger than the limit it drives the hit rate to zero — each lookup\n * throws out the entry the next one needs. Refusing to store instead leaves\n * every already-shared sibling shared and degrades the surplus to the old\n * behavior of a fresh instance per call.\n *\n * Buckets should rarely get near this. Keys carry the discriminating value\n * wherever it is cheap to, and a key that carries it is *exact*, so it costs no\n * comparison at all: every simple comparison goes that way, and so does a\n * one-hop `exists`, via its sub-query's identity. What is left sharing a bucket\n * is two-hop `exists`, the `and`/`or` trees an expression factory builds, and\n * relationships whose callback varies.\n */\nexport const MAX_SCAN = 64;\n\n/** How large the weak map must get before its first sweep. */\nconst SWEEP_INITIAL = 64;\n\n/** The transitions out of one node. */\nexport class Transitions<T extends object> {\n // The first transition, held strongly and in place. See \"Liveness\" above.\n #firstKey: string | undefined;\n #firstValue: TransitionValue;\n #firstDelta: Delta;\n #first: T | undefined;\n\n // Transitions whose key space is finite and comes from the schema rather than\n // from user data, held strongly. See `storeBounded`.\n #bounded: Map<string, T> | undefined;\n\n // Everything else, weakly. Allocated only if there is a second. Nested so the\n // value stays a `Map` key of its own rather than part of the string.\n #rest: Map<string, Map<TransitionValue, Bucket<T>>> | undefined;\n #sweepAt = SWEEP_INITIAL;\n #restSize = 0;\n\n lookupBounded(key: string): T | undefined {\n return this.#bounded?.get(key);\n }\n\n /**\n * Records a transition whose key space is finite and schema-derived — the base\n * sub-query handed to a `related`/`exists` callback, keyed by relationship\n * name.\n *\n * These are held strongly. Retention is bounded by the number of relationships\n * on the table, so it cannot grow with what an app queries for, and in exchange\n * a relationship traversal allocates no `WeakRef`. Anything keyed by a *value*\n * — a row id, a search string, a limit — must not come through here.\n */\n storeBounded(key: string, value: T): void {\n (this.#bounded ??= new Map()).set(key, value);\n }\n\n lookup(key: string, value: TransitionValue, delta: Delta): T | undefined {\n if (\n this.#firstKey === key &&\n this.#firstValue === value &&\n deepEqual(this.#firstDelta, delta)\n ) {\n return this.#first;\n }\n const byValue = this.#rest?.get(key);\n if (byValue === undefined) {\n return undefined;\n }\n const bucket = byValue.get(value);\n if (bucket === undefined) {\n return undefined;\n }\n\n if (!(bucket instanceof Set)) {\n const found = bucket.ref.deref();\n if (found === undefined) {\n byValue.delete(value);\n this.#restSize--;\n return undefined;\n }\n // deepEqual short-circuits on reference equality, which is the fast path\n // we rely on for deltas built out of already-interned sub-queries.\n return deepEqual(bucket.delta, delta) ? found : undefined;\n }\n\n // One pass: drop what has been collected, stop at the match. Deleting from a\n // Set while iterating it is well defined — the iterator simply skips what is\n // gone — so this stays O(n) with no compaction step, and returns early on a\n // hit rather than walking the whole bucket first.\n for (const entry of bucket) {\n const found = entry.ref.deref();\n if (found === undefined) {\n bucket.delete(entry);\n this.#restSize--;\n continue;\n }\n if (deepEqual(entry.delta, delta)) {\n return found;\n }\n }\n if (bucket.size === 0) {\n byValue.delete(value);\n }\n return undefined;\n }\n\n store(key: string, value: TransitionValue, delta: Delta, node: T): void {\n if (this.#firstKey === undefined) {\n this.#firstKey = key;\n this.#firstValue = value;\n this.#firstDelta = delta;\n this.#first = node;\n return;\n }\n\n const rest = (this.#rest ??= new Map());\n const byValue = getOrInsertComputed(rest, key, newMap);\n\n const entry: Entry<T> = {delta, ref: new WeakRef(node)};\n const bucket = byValue.get(value);\n\n if (bucket === undefined) {\n byValue.set(value, entry);\n } else if (!(bucket instanceof Set)) {\n byValue.set(value, new Set([bucket, entry]));\n } else {\n if (bucket.size >= MAX_SCAN) {\n // Leave the bucket alone; see MAX_SCAN. `node` is simply not shared.\n // A store only follows a failed lookup, which has just pruned, so this\n // is measured against entries that are actually still alive.\n return;\n }\n bucket.add(entry);\n }\n this.#restSize++;\n this.#maybeSweep(rest);\n }\n\n /**\n * Re-points the transition that produced `from` at `to`, so the next lookup\n * along that path yields `to` instead. This is how {@link HashIndex}\n * converges two paths on one node: `from` keeps its identity, since it has\n * already been handed out, and its path is redirected for the rebuilds that\n * follow.\n *\n * Scans rather than taking a key. This runs once per redirected path, so a\n * walk here is cheaper than the fields every node would need to carry to\n * name its own transition.\n */\n replace(from: T, to: T): boolean {\n if (this.#first === from) {\n this.#first = to;\n return true;\n }\n const rest = this.#rest;\n if (rest === undefined) {\n return false;\n }\n for (const byValue of rest.values()) {\n for (const [value, bucket] of byValue) {\n if (!(bucket instanceof Set)) {\n if (bucket.ref.deref() === from) {\n byValue.set(value, {delta: bucket.delta, ref: new WeakRef(to)});\n return true;\n }\n continue;\n }\n for (const entry of bucket) {\n if (entry.ref.deref() === from) {\n bucket.delete(entry);\n bucket.add({delta: entry.delta, ref: new WeakRef(to)});\n return true;\n }\n }\n }\n }\n return false;\n }\n\n /**\n * Drops entries for collected values, if the map has grown enough to be worth\n * walking.\n *\n * A lookup already prunes whatever it walks past, which covers every key that\n * is asked for again. This is for the key that is not: a miss on a fresh key\n * returns without touching any existing entry, so nothing would ever notice\n * that those had died. Doubling the threshold off whatever survives keeps this\n * amortized O(1) per insert whether or not there was anything to reclaim, and\n * it is self-limiting — only a node that keeps taking inserts can keep\n * accumulating, and taking inserts is what triggers the sweep.\n */\n #maybeSweep(rest: Map<string, Map<TransitionValue, Bucket<T>>>): void {\n if (this.#restSize < this.#sweepAt) {\n return;\n }\n let live = 0;\n for (const [key, byValue] of rest) {\n for (const [value, bucket] of byValue) {\n if (!(bucket instanceof Set)) {\n if (bucket.ref.deref() === undefined) {\n byValue.delete(value);\n } else {\n live++;\n }\n continue;\n }\n for (const entry of bucket) {\n if (entry.ref.deref() === undefined) {\n bucket.delete(entry);\n } else {\n live++;\n }\n }\n if (bucket.size === 0) {\n byValue.delete(value);\n }\n }\n if (byValue.size === 0) {\n rest.delete(key);\n }\n }\n this.#restSize = live;\n this.#sweepAt = Math.max(SWEEP_INITIAL, live * 2);\n }\n}\n\n/**\n * The second level of interning: nodes keyed by *content* rather than by the\n * path that built them.\n *\n * The transition tree cannot see that `q.where(a).where(b)` and\n * `q.where(b).where(a)` are the same query. `normalizeAST` sorts conditions\n * and `related` entries, so the two build byte-identical ASTs, but they are\n * different paths and so different nodes. This index closes that gap, keyed by\n * the query's hash.\n *\n * It is consulted when a query is *hashed*, not when it is built. Hashing on\n * every tree miss would make a cold chain pay for a hash per node, where the\n * uninterned code paid for one at the end, if at all -- and server-side queries\n * are built cold, once, per request. The consumers that key by hash already\n * hash the queries they care about, so doing the work there costs one map\n * lookup on top of a hash that was being computed anyway, and nothing at all\n * for a query nobody hashes.\n *\n * On a hit the query being hashed keeps its identity, since it has already\n * been handed out. Its *transition* is re-pointed at the existing node instead\n * (see {@link Transitions.replace}), so the next rebuild along that path\n * yields the shared instance. As with the tree this is a cache: the rebuild\n * after a redirect is what converges, not the call that found the duplicate.\n *\n * One index per root, which is what scopes it. Two roots never share an index,\n * so queries against different schemas or different delegates -- neither of\n * which the hash covers -- can never be conflated. Entries are weak: a cleared\n * one is dropped by the lookup that finds it, or by a sweep once the map has\n * doubled since the last one.\n */\nexport class HashIndex<T extends object> {\n readonly #byHash = new Map<string, WeakRef<T>>();\n #sweepAt = SWEEP_INITIAL;\n\n get(hash: string): T | undefined {\n const ref = this.#byHash.get(hash);\n if (ref === undefined) {\n return undefined;\n }\n const node = ref.deref();\n if (node === undefined) {\n this.#byHash.delete(hash);\n }\n return node;\n }\n\n set(hash: string, node: T): void {\n const byHash = this.#byHash;\n byHash.set(hash, new WeakRef(node));\n if (byHash.size < this.#sweepAt) {\n return;\n }\n for (const [h, ref] of byHash) {\n if (ref.deref() === undefined) {\n byHash.delete(h);\n }\n }\n this.#sweepAt = Math.max(SWEEP_INITIAL, byHash.size * 2);\n }\n}\n"],"mappings":"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"}
@@ -42,7 +42,7 @@ export type ExistsOptions<TScalar extends boolean = boolean> = {
42
42
  *
43
43
  * The server only honors this when the subquery is *provably* limited to one
44
44
  * row: every column of the table's primary key, or of one of its
45
- * {@linkcode unique} keys, constrained to a literal by `=`. A subquery that
45
+ * {@link unique} keys, constrained to a literal by `=`. A subquery that
46
46
  * does not do so silently falls back to a plain `EXISTS`, so the type rejects
47
47
  * `scalar: true` there rather than letting the hint go quietly unused.
48
48
  *
@@ -54,7 +54,7 @@ export type ExistsOptions<TScalar extends boolean = boolean> = {
54
54
  declare const pinnedColumns: unique symbol;
55
55
  /**
56
56
  * The keys that make a subquery over `TTable` provably single-row: its primary
57
- * key, plus any {@linkcode unique} keys the schema declares.
57
+ * key, plus any {@link unique} keys the schema declares.
58
58
  */
59
59
  type UniqueKeysOf<TTable extends string, TSchema extends ZeroSchema> = TSchema['tables'][TTable] extends {
60
60
  readonly uniqueKeys: infer TUnique extends readonly (readonly string[])[];
@@ -203,11 +203,11 @@ export interface Query<TTable extends keyof TSchema['tables'] & string, TSchema
203
203
  orderBy<TSelector extends Selector<PullTableSchema<TTable, TSchema>>>(field: TSelector, direction: 'asc' | 'desc'): Query<TTable, TSchema, TReturn, TPinned>;
204
204
  one(): Query<TTable, TSchema, TReturn | undefined, TPinned>;
205
205
  /**
206
- * @deprecated Use {@linkcode RunOptions} with {@linkcode zero.run} instead.
206
+ * @deprecated Use {@link RunOptions} with {@link zero.run} instead.
207
207
  */
208
208
  run(options?: RunOptions): Promise<HumanReadable<TReturn>>;
209
209
  /**
210
- * @deprecated Use {@linkcode PreloadOptions} with {@linkcode zero.preload} instead.
210
+ * @deprecated Use {@link PreloadOptions} with {@link zero.preload} instead.
211
211
  */
212
212
  preload(options?: PreloadOptions): {
213
213
  cleanup: () => void;
@@ -215,18 +215,18 @@ export interface Query<TTable extends keyof TSchema['tables'] & string, TSchema
215
215
  cached: Promise<void>;
216
216
  };
217
217
  /**
218
- * @deprecated Use {@linkcode MaterializeOptions} with {@linkcode zero.materialize} instead.
218
+ * @deprecated Use {@link MaterializeOptions} with {@link zero.materialize} instead.
219
219
  */
220
220
  materialize(ttl?: TTL): TypedView<HumanReadable<TReturn>>;
221
221
  /**
222
- * @deprecated Use {@linkcode MaterializeOptions} with {@linkcode zero.materialize} instead.
222
+ * @deprecated Use {@link MaterializeOptions} with {@link zero.materialize} instead.
223
223
  */
224
224
  materialize<T>(factory: ViewFactory<TTable, TSchema, TReturn, T>, ttl?: TTL): T;
225
225
  }
226
226
  export type PreloadOptions = {
227
227
  /**
228
228
  * Time To Live. This is the amount of time to keep the rows associated with
229
- * this query after {@linkcode cleanup} has been called.
229
+ * this query after {@link cleanup} has been called.
230
230
  */
231
231
  ttl?: TTL | undefined;
232
232
  };
@@ -240,7 +240,7 @@ export type HumanReadable<T> = undefined extends T ? Expand<T> : Expand<T>[];
240
240
  */
241
241
  export type HumanReadableRecursive<T> = undefined extends T ? ExpandRecursive<T> : ExpandRecursive<T>[];
242
242
  /**
243
- * The kind of results we want to wait for when using {@linkcode run} on {@linkcode Query}.
243
+ * The kind of results we want to wait for when using {@link run} on {@link Query}.
244
244
  *
245
245
  * `unknown` means we don't want to wait for the server to return results. The result is a
246
246
  * snapshot of the data at the time the query was run. It never waits: while the client is
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rocicorp/zero",
3
- "version": "1.11.0-canary.41",
3
+ "version": "1.11.0-canary.42",
4
4
  "description": "Zero is a web framework for serverless web development.",
5
5
  "homepage": "https://zero.rocicorp.dev",
6
6
  "bugs": {