@lunora/db 1.0.0-alpha.153 → 1.0.0-alpha.155
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 +31 -1
- package/dist/collections/index.d.mts +2 -2
- package/dist/collections/index.d.ts +2 -2
- package/dist/collections/index.mjs +1 -1
- package/dist/index.d.mts +4 -4
- package/dist/index.d.ts +4 -4
- package/dist/index.mjs +1 -1
- package/dist/mutators/index.d.mts +2 -2
- package/dist/mutators/index.d.ts +2 -2
- package/dist/mutators/index.mjs +1 -1
- package/dist/packem_shared/CHECKPOINT_FALLBACK_MS-Do8X5Q7k.mjs +1 -0
- package/dist/packem_shared/DIRECT_TRANSACTION_METADATA_KEY-CCBmcauu.mjs +1 -0
- package/dist/packem_shared/{collection-options.d-QNwn1hBH.d.mts → collection-options.d-BbEYKjp2.d.mts} +13 -2
- package/dist/packem_shared/{collection-options.d-QNwn1hBH.d.ts → collection-options.d-BbEYKjp2.d.ts} +13 -2
- package/dist/packem_shared/defineCollections-C6YbtHxm.mjs +1 -0
- package/dist/packem_shared/{index.d-IIvTftRH.d.mts → index.d-CCVTzFFn.d.mts} +12 -2
- package/dist/packem_shared/{index.d-D326yMXQ.d.ts → index.d-DCVj2sHQ.d.ts} +12 -2
- package/dist/packem_shared/{index.d-BlRQftDD.d.ts → index.d-G0N0-lGJ.d.ts} +17 -9
- package/dist/packem_shared/{index.d-Bpi6cYCP.d.mts → index.d-cl7LWrQo.d.mts} +17 -9
- package/package.json +2 -2
- package/dist/packem_shared/CHECKPOINT_FALLBACK_MS-DgrLzaWr.mjs +0 -1
- package/dist/packem_shared/DIRECT_TRANSACTION_METADATA_KEY-C8-I3T2D.mjs +0 -1
- package/dist/packem_shared/defineCollections-BU_uBD8S.mjs +0 -1
package/README.md
CHANGED
|
@@ -65,7 +65,7 @@ export const createCollections = (client: LunoraClient) =>
|
|
|
65
65
|
defineCollections(client, {
|
|
66
66
|
messages: {
|
|
67
67
|
list: api.messages.list,
|
|
68
|
-
scopeBy: "channelId", //
|
|
68
|
+
scopeBy: "channelId", // the .shardBy column — db.scope.messages({ channelId }) syncs that channel's shard
|
|
69
69
|
insert: {
|
|
70
70
|
mutation: api.messages.send,
|
|
71
71
|
optimistic: (input: Omit<Doc<"messages">, "_id" | "_creationTime">, id) => ({
|
|
@@ -103,6 +103,36 @@ every sync tick). The framework adapters add a `useMutator` / `createMutator` /
|
|
|
103
103
|
`mutator` hook over a bound handle. See the
|
|
104
104
|
**[local-first guide](https://lunora.sh/docs/concepts/local-first)**.
|
|
105
105
|
|
|
106
|
+
On a `scopeBy` shape collection, give `bindMutators` the same `scopeBy` so each
|
|
107
|
+
push reaches the shard its args name — the shard the collection syncs:
|
|
108
|
+
|
|
109
|
+
```ts
|
|
110
|
+
const { checkpoints, config, scope } = lunoraCollectionOptions({ client, shape: { name: "messagesByChannel" }, scopeBy: "channelId" });
|
|
111
|
+
const send = bindMutators(client, { checkpoints, collections, scopeBy: "channelId" }, mutators);
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
## Upgrading: scoped collections now use their shard
|
|
115
|
+
|
|
116
|
+
Earlier alphas sent every subscription and insert of a `scopeBy` collection with
|
|
117
|
+
no `shardKey` to the default shard (`__root__`); they now go to the shard the
|
|
118
|
+
scoped value names.
|
|
119
|
+
|
|
120
|
+
- **Authorize shard access.** Configure `authorizeShard` on the worker (or
|
|
121
|
+
`allowUnauthenticatedShardAccess: true` when every table has row-level
|
|
122
|
+
security); otherwise scoped subscriptions and writes fail with `403`
|
|
123
|
+
`FORBIDDEN_SHARD`.
|
|
124
|
+
- **Move existing rows.** Rows written before the upgrade are still in the
|
|
125
|
+
default shard, and scoped reads do not look there. Before the upgraded client
|
|
126
|
+
writes to any channel shard, run `lunora export --tables <table>` (it reaches
|
|
127
|
+
the default shard while the table has no registered shard keys) and then
|
|
128
|
+
`lunora import` the file: import re-buckets each row by the table's `.shardBy`
|
|
129
|
+
field and keeps its `_id`, skipping any `_id` the target shard already holds.
|
|
130
|
+
Import deletes nothing, so then remove the default shard's copies with a
|
|
131
|
+
one-off mutation run via `lunora run <fn> --shard __root__`.
|
|
132
|
+
|
|
133
|
+
See [Upgrading](https://lunora.sh/docs/packages/db#upgrading-scoped-collections-now-use-their-shard)
|
|
134
|
+
for the full recipe.
|
|
135
|
+
|
|
106
136
|
> This README covers the basics. For the full API, options, and guides, see the **[documentation](https://lunora.sh/docs/packages/db)**.
|
|
107
137
|
|
|
108
138
|
## Related
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
export { type b as CheckpointRegistry, type L as LunoraCollectionConfig, type f as LunoraCollectionOptions, i as createCheckpointRegistry, m as lunoraCollectionOptions } from "../packem_shared/collection-options.d-
|
|
2
|
-
export { type C as CollectionDef, type I as InsertBinding, type L as LunoraDb, d as defineCollections } from "../packem_shared/index.d-
|
|
1
|
+
export { type b as CheckpointRegistry, type L as LunoraCollectionConfig, type f as LunoraCollectionOptions, i as createCheckpointRegistry, m as lunoraCollectionOptions } from "../packem_shared/collection-options.d-BbEYKjp2.mjs";
|
|
2
|
+
export { type C as CollectionDef, type I as InsertBinding, type L as LunoraDb, d as defineCollections } from "../packem_shared/index.d-cl7LWrQo.mjs";
|
|
3
3
|
import '@lunora/client';
|
|
4
4
|
import '@tanstack/db';
|
|
5
5
|
import '@tanstack/offline-transactions';
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
export { type b as CheckpointRegistry, type L as LunoraCollectionConfig, type f as LunoraCollectionOptions, i as createCheckpointRegistry, m as lunoraCollectionOptions } from "../packem_shared/collection-options.d-
|
|
2
|
-
export { type C as CollectionDef, type I as InsertBinding, type L as LunoraDb, d as defineCollections } from "../packem_shared/index.d-
|
|
1
|
+
export { type b as CheckpointRegistry, type L as LunoraCollectionConfig, type f as LunoraCollectionOptions, i as createCheckpointRegistry, m as lunoraCollectionOptions } from "../packem_shared/collection-options.d-BbEYKjp2.js";
|
|
2
|
+
export { type C as CollectionDef, type I as InsertBinding, type L as LunoraDb, d as defineCollections } from "../packem_shared/index.d-G0N0-lGJ.js";
|
|
3
3
|
import '@lunora/client';
|
|
4
4
|
import '@tanstack/db';
|
|
5
5
|
import '@tanstack/offline-transactions';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{createCheckpointRegistry as t,lunoraCollectionOptions as r}from"../packem_shared/CHECKPOINT_FALLBACK_MS-
|
|
1
|
+
import{createCheckpointRegistry as t,lunoraCollectionOptions as r}from"../packem_shared/CHECKPOINT_FALLBACK_MS-Do8X5Q7k.mjs";import{defineCollections as n}from"../packem_shared/defineCollections-C6YbtHxm.mjs";export{t as createCheckpointRegistry,n as defineCollections,r as lunoraCollectionOptions};
|
package/dist/index.d.mts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { Collection } from '@tanstack/db';
|
|
2
|
-
import { R as Row } from "./packem_shared/collection-options.d-
|
|
3
|
-
export { C as CHECKPOINT_FALLBACK_MS, type a as CheckpointFallbackEvent, type b as CheckpointRegistry, type c as CheckpointRegistryOptions, type d as CheckpointRegistryStats, type e as CheckpointWatermark, type E as ExecutorOutboxSinkOptions, type L as LunoraCollectionConfig, type f as LunoraCollectionOptions, O as OUTBOX_MUTATION_FN_NAME, type g as OutboxExecutor, type h as OutboxMutationMetadata, type S as SyncWriter, i as createCheckpointRegistry, j as createExecutorOutboxSink, k as createOptimisticOnlineDetector, l as getShardCheckpoints, m as lunoraCollectionOptions, n as makeDiffEmit, r as releaseShardCheckpoints, o as runOutboxMutation, t as toMap } from "./packem_shared/collection-options.d-
|
|
4
|
-
export { type C as CollectionDef, type D as DefineCollectionsOptions, type I as InsertBinding, type L as LunoraDb, type W as WriteRejectedEvent, d as defineCollections } from "./packem_shared/index.d-
|
|
5
|
-
export { type B as BindMutatorsContext, type a as BoundMutatorApi, type b as BoundMutators, type C as ClientMutatorContext, type c as ClientMutatorDef, type d as CollectionMap, D as DIRECT_TRANSACTION_METADATA_KEY, type M as MutatorReference, type e as MutatorRejectedEvent, f as bindMutators, g as defineMutator, i as initMutators } from "./packem_shared/index.d-
|
|
2
|
+
import { R as Row } from "./packem_shared/collection-options.d-BbEYKjp2.mjs";
|
|
3
|
+
export { C as CHECKPOINT_FALLBACK_MS, type a as CheckpointFallbackEvent, type b as CheckpointRegistry, type c as CheckpointRegistryOptions, type d as CheckpointRegistryStats, type e as CheckpointWatermark, type E as ExecutorOutboxSinkOptions, type L as LunoraCollectionConfig, type f as LunoraCollectionOptions, O as OUTBOX_MUTATION_FN_NAME, type g as OutboxExecutor, type h as OutboxMutationMetadata, type S as SyncWriter, i as createCheckpointRegistry, j as createExecutorOutboxSink, k as createOptimisticOnlineDetector, l as getShardCheckpoints, m as lunoraCollectionOptions, n as makeDiffEmit, r as releaseShardCheckpoints, o as runOutboxMutation, t as toMap } from "./packem_shared/collection-options.d-BbEYKjp2.mjs";
|
|
4
|
+
export { type C as CollectionDef, type D as DefineCollectionsOptions, type I as InsertBinding, type L as LunoraDb, type W as WriteRejectedEvent, d as defineCollections } from "./packem_shared/index.d-cl7LWrQo.mjs";
|
|
5
|
+
export { type B as BindMutatorsContext, type a as BoundMutatorApi, type b as BoundMutators, type C as ClientMutatorContext, type c as ClientMutatorDef, type d as CollectionMap, D as DIRECT_TRANSACTION_METADATA_KEY, type M as MutatorReference, type e as MutatorRejectedEvent, f as bindMutators, g as defineMutator, i as initMutators } from "./packem_shared/index.d-CCVTzFFn.mjs";
|
|
6
6
|
import '@lunora/client';
|
|
7
7
|
import '@tanstack/offline-transactions';
|
|
8
8
|
/** One row to insert. `_id` may be pre-minted client-side so the optimistic row keys match the persisted one. */
|
package/dist/index.d.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { Collection } from '@tanstack/db';
|
|
2
|
-
import { R as Row } from "./packem_shared/collection-options.d-
|
|
3
|
-
export { C as CHECKPOINT_FALLBACK_MS, type a as CheckpointFallbackEvent, type b as CheckpointRegistry, type c as CheckpointRegistryOptions, type d as CheckpointRegistryStats, type e as CheckpointWatermark, type E as ExecutorOutboxSinkOptions, type L as LunoraCollectionConfig, type f as LunoraCollectionOptions, O as OUTBOX_MUTATION_FN_NAME, type g as OutboxExecutor, type h as OutboxMutationMetadata, type S as SyncWriter, i as createCheckpointRegistry, j as createExecutorOutboxSink, k as createOptimisticOnlineDetector, l as getShardCheckpoints, m as lunoraCollectionOptions, n as makeDiffEmit, r as releaseShardCheckpoints, o as runOutboxMutation, t as toMap } from "./packem_shared/collection-options.d-
|
|
4
|
-
export { type C as CollectionDef, type D as DefineCollectionsOptions, type I as InsertBinding, type L as LunoraDb, type W as WriteRejectedEvent, d as defineCollections } from "./packem_shared/index.d-
|
|
5
|
-
export { type B as BindMutatorsContext, type a as BoundMutatorApi, type b as BoundMutators, type C as ClientMutatorContext, type c as ClientMutatorDef, type d as CollectionMap, D as DIRECT_TRANSACTION_METADATA_KEY, type M as MutatorReference, type e as MutatorRejectedEvent, f as bindMutators, g as defineMutator, i as initMutators } from "./packem_shared/index.d-
|
|
2
|
+
import { R as Row } from "./packem_shared/collection-options.d-BbEYKjp2.js";
|
|
3
|
+
export { C as CHECKPOINT_FALLBACK_MS, type a as CheckpointFallbackEvent, type b as CheckpointRegistry, type c as CheckpointRegistryOptions, type d as CheckpointRegistryStats, type e as CheckpointWatermark, type E as ExecutorOutboxSinkOptions, type L as LunoraCollectionConfig, type f as LunoraCollectionOptions, O as OUTBOX_MUTATION_FN_NAME, type g as OutboxExecutor, type h as OutboxMutationMetadata, type S as SyncWriter, i as createCheckpointRegistry, j as createExecutorOutboxSink, k as createOptimisticOnlineDetector, l as getShardCheckpoints, m as lunoraCollectionOptions, n as makeDiffEmit, r as releaseShardCheckpoints, o as runOutboxMutation, t as toMap } from "./packem_shared/collection-options.d-BbEYKjp2.js";
|
|
4
|
+
export { type C as CollectionDef, type D as DefineCollectionsOptions, type I as InsertBinding, type L as LunoraDb, type W as WriteRejectedEvent, d as defineCollections } from "./packem_shared/index.d-G0N0-lGJ.js";
|
|
5
|
+
export { type B as BindMutatorsContext, type a as BoundMutatorApi, type b as BoundMutators, type C as ClientMutatorContext, type c as ClientMutatorDef, type d as CollectionMap, D as DIRECT_TRANSACTION_METADATA_KEY, type M as MutatorReference, type e as MutatorRejectedEvent, f as bindMutators, g as defineMutator, i as initMutators } from "./packem_shared/index.d-DCVj2sHQ.js";
|
|
6
6
|
import '@lunora/client';
|
|
7
7
|
import '@tanstack/offline-transactions';
|
|
8
8
|
/** One row to insert. `_id` may be pre-minted client-side so the optimistic row keys match the persisted one. */
|
package/dist/index.mjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{applyPlanToCollections as r,applyPlanToDb as i}from"./packem_shared/applyPlanToCollections-C_eyqNF9.mjs";import{CHECKPOINT_FALLBACK_MS as a,createCheckpointRegistry as p,getShardCheckpoints as c,lunoraCollectionOptions as l,releaseShardCheckpoints as s}from"./packem_shared/CHECKPOINT_FALLBACK_MS-
|
|
1
|
+
import{applyPlanToCollections as r,applyPlanToDb as i}from"./packem_shared/applyPlanToCollections-C_eyqNF9.mjs";import{CHECKPOINT_FALLBACK_MS as a,createCheckpointRegistry as p,getShardCheckpoints as c,lunoraCollectionOptions as l,releaseShardCheckpoints as s}from"./packem_shared/CHECKPOINT_FALLBACK_MS-Do8X5Q7k.mjs";import{defineCollections as O}from"./packem_shared/defineCollections-C6YbtHxm.mjs";import{DIRECT_TRANSACTION_METADATA_KEY as f,bindMutators as u,defineMutator as x,initMutators as A}from"./packem_shared/DIRECT_TRANSACTION_METADATA_KEY-CCBmcauu.mjs";import{OUTBOX_MUTATION_FN_NAME as m,createExecutorOutboxSink as E,createOptimisticOnlineDetector as _,makeDiffEmit as N,runOutboxMutation as S,toMap as d}from"./packem_shared/OUTBOX_MUTATION_FN_NAME-rGyegnLw.mjs";const t="0.0.0";export{a as CHECKPOINT_FALLBACK_MS,f as DIRECT_TRANSACTION_METADATA_KEY,m as OUTBOX_MUTATION_FN_NAME,t as VERSION,r as applyPlanToCollections,i as applyPlanToDb,u as bindMutators,p as createCheckpointRegistry,E as createExecutorOutboxSink,_ as createOptimisticOnlineDetector,O as defineCollections,x as defineMutator,c as getShardCheckpoints,A as initMutators,l as lunoraCollectionOptions,N as makeDiffEmit,s as releaseShardCheckpoints,S as runOutboxMutation,d as toMap};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
export { type B as BindMutatorsContext, type a as BoundMutatorApi, type b as BoundMutators, type C as ClientMutatorContext, type c as ClientMutatorDef, type d as CollectionMap, type e as MutatorRejectedEvent, f as bindMutators, g as defineMutator, i as initMutators } from "../packem_shared/index.d-
|
|
1
|
+
export { type B as BindMutatorsContext, type a as BoundMutatorApi, type b as BoundMutators, type C as ClientMutatorContext, type c as ClientMutatorDef, type d as CollectionMap, type e as MutatorRejectedEvent, f as bindMutators, g as defineMutator, i as initMutators } from "../packem_shared/index.d-CCVTzFFn.mjs";
|
|
2
2
|
import '@lunora/client';
|
|
3
3
|
import '@tanstack/db';
|
|
4
|
-
import "../packem_shared/collection-options.d-
|
|
4
|
+
import "../packem_shared/collection-options.d-BbEYKjp2.mjs";
|
|
5
5
|
import '@tanstack/offline-transactions';
|
package/dist/mutators/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
export { type B as BindMutatorsContext, type a as BoundMutatorApi, type b as BoundMutators, type C as ClientMutatorContext, type c as ClientMutatorDef, type d as CollectionMap, type e as MutatorRejectedEvent, f as bindMutators, g as defineMutator, i as initMutators } from "../packem_shared/index.d-
|
|
1
|
+
export { type B as BindMutatorsContext, type a as BoundMutatorApi, type b as BoundMutators, type C as ClientMutatorContext, type c as ClientMutatorDef, type d as CollectionMap, type e as MutatorRejectedEvent, f as bindMutators, g as defineMutator, i as initMutators } from "../packem_shared/index.d-DCVj2sHQ.js";
|
|
2
2
|
import '@lunora/client';
|
|
3
3
|
import '@tanstack/db';
|
|
4
|
-
import "../packem_shared/collection-options.d-
|
|
4
|
+
import "../packem_shared/collection-options.d-BbEYKjp2.js";
|
|
5
5
|
import '@tanstack/offline-transactions';
|
package/dist/mutators/index.mjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{bindMutators as r,defineMutator as i,initMutators as a}from"../packem_shared/DIRECT_TRANSACTION_METADATA_KEY-
|
|
1
|
+
import{bindMutators as r,defineMutator as i,initMutators as a}from"../packem_shared/DIRECT_TRANSACTION_METADATA_KEY-CCBmcauu.mjs";export{r as bindMutators,i as defineMutator,a as initMutators};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{LunoraError as F}from"@lunora/errors";import{BTreeIndex as _}from"@tanstack/db";import{toMap as x,makeDiffEmit as B}from"./OUTBOX_MUTATION_FN_NAME-rGyegnLw.mjs";const E=()=>{let e=Number.NEGATIVE_INFINITY;const o=[];return{advance:t=>{if(!(t<=e)){e=t;for(let n=o.length-1;n>=0;n-=1){const i=o[n];i&&i.threshold<=e&&(i.resolve(),o.splice(n,1))}}},await:t=>t<=e?Promise.resolve():new Promise(n=>{o.push({resolve:n,threshold:t})}),passed:t=>t<=e,reset:()=>{for(const t of o.splice(0))t.resolve();e=Number.NEGATIVE_INFINITY},waiting:()=>o.length}};let N=!1;const W=e=>{N||(N=!0,console.warn(`[@lunora/db] released an optimistic overlay via the ${String(e.waitedMs)}ms checkpoint fallback: the server confirmed ${e.kind} ${String(e.watermark)} but no sync frame ever echoed it. A dropped shape poke or \`settled\` frame is the usual cause — inspect the subscription rather than raising \`fallbackMs\`. (Reported once per process.)`))},g=new WeakMap,T=new WeakMap,y=new WeakMap,S=new WeakMap,O=e=>{const o=(y.get(e)??0)-1;if(o>0){y.set(e,o);return}y.delete(e),S.get(e)?.()},R=new WeakSet,$=e=>{const o={acknowledge:t=>{e().acknowledge(t)},awaitCheckpoint:async t=>e().awaitCheckpoint(t),awaitMutationId:async t=>e().awaitMutationId(t),dispose:()=>{e().dispose()},resolve:t=>{e().resolve(t)},stats:()=>e().stats()};return R.add(o),o},G=3e3,P=(e={})=>{const o=e.fallbackMs??G,t=e.onFallback??W,n=E(),i=E();let l=0;const d=new Set,h=(s,a)=>{for(const c of d)c.kind===s&&c.threshold<=a&&(clearTimeout(c.handle),d.delete(c))},p=(s,a)=>{if(o<=0)return;const c=s==="checkpoint"?n:i;if(c.passed(a))return;for(const v of d)if(v.kind===s&&v.threshold>=a)return;const k={handle:setTimeout(()=>{if(d.delete(k),!c.passed(a)){l+=1,c.advance(a);try{t({kind:s,waitedMs:o,watermark:a})}catch{}}},o),kind:s,threshold:a};d.add(k)},u={acknowledge:({checkpoint:s,mutationId:a})=>{s!==void 0&&p("checkpoint",s),a!==void 0&&p("mutationId",a)},awaitCheckpoint:s=>n.await(s),awaitMutationId:s=>i.await(s),dispose:()=>{for(const s of d)clearTimeout(s.handle);d.clear()},resolve:({checkpoint:s,mutationId:a})=>{s!==void 0&&(n.advance(s),h("checkpoint",s)),a!==void 0&&(i.advance(a),h("mutationId",a))},stats:()=>({fallbacks:l,pendingCheckpointWaiters:n.waiting(),pendingMutationWaiters:i.waiting()})};return S.set(u,()=>{u.dispose(),n.reset(),i.reset()}),u},Q=e=>{const o=g.get(e);if(o){for(const t of o.values())t.resolve({checkpoint:Number.POSITIVE_INFINITY,mutationId:Number.POSITIVE_INFINITY}),t.dispose();g.delete(e)}},L=e=>{const o=e.currentIdentity();if(g.has(e)&&T.get(e)!==o)for(const t of g.get(e)?.values()??[])S.get(t)?.();T.set(e,o)},V=(e,o,t)=>{L(e);let n=g.get(e);n||(n=new Map,g.set(e,n));const i=o??"",l=n.get(i);if(l)return l;const d=P(t);return n.set(i,d),d},Y=(e,o,t)=>{const n=o[e];if(typeof n=="string"||typeof n=="number")return String(n);throw new F("BAD_REQUEST",`@lunora/db: ${t} has no "${e}" — a \`scopeBy\` collection routes by that field's value`)},j=e=>R.has(e),D=e=>{y.set(e,(y.get(e)??0)+1)},q=e=>(y.get(e)??0)>0,z=e=>{if(e.list===void 0==(e.shape===void 0))throw new F("INTERNAL","lunoraCollectionOptions: pass exactly one of `list` or `shape`");const o=e.getKey??(r=>r._id),t=e.shape?.shardKey??e.shardKey,n=e.scopeBy!==void 0&&t===void 0;let i;const l=()=>t??i,d=()=>e.checkpoints??V(e.client,l());let h;const p=()=>{const r=d();return r!==h&&(h=r,D(r)),r},u=()=>{h!==void 0&&e.checkpoints===void 0&&O(h),h=void 0};n||p();const s=new Map;let a,c,k,v,w;const b=(r,m)=>{p();const I=f=>{a?.(x(f,o)),m?.(),e.shape===void 0&&(p().resolve({mutationId:w??e.client.confirmedMutationWatermark(l())}),w=void 0)},C=f=>k?.(f),M=f=>{f.rowsFollow===!0&&f.mutationId!==void 0&&(w=f.mutationId),p().resolve(f)};return e.shape!==void 0?e.client.subscribeShape({args:r,name:e.shape.name},I,{onCheckpoint:M,onError:C,shardKey:l()}):e.client.subscribe(e.list,r,I,{onCheckpoint:M,onError:C,shardKey:l()})},K={autoIndex:"eager",defaultIndexType:_,getKey:o,id:e.id??e.list?.__lunoraRef??`shape:${e.shape?.name??""}`,...e.load==="eager"?{startSync:!0}:{},sync:{sync:r=>(a=B(s,r),k=I=>{r.markReady(),e.onError?.(I)},e.scopeBy===void 0?c=b(e.shape?.args??{},()=>{r.markReady()}):(r.markReady(),v!==void 0&&(c=b(v,void 0))),()=>{a=void 0,k=void 0,u(),c?.(),c=void 0,s.clear()})}},A=r=>{if(e.scopeBy===void 0)return;const m=n&&r!==void 0?Y(e.scopeBy,r,"scope args"):i;n&&(r===void 0||m!==i)&&u(),v=r,i=m,c?.(),c=void 0,w=void 0,a?.(new Map),r!==void 0&&a!==void 0&&(c=b(r,void 0))};return{checkpoints:n&&e.checkpoints===void 0?$(d):d(),config:K,scope:A}};export{G as CHECKPOINT_FALLBACK_MS,P as createCheckpointRegistry,V as getShardCheckpoints,q as hasCheckpointsAttached,j as isScopeFollowingCheckpoints,z as lunoraCollectionOptions,D as markCheckpointsAttached,Q as releaseShardCheckpoints,Y as scopedShardKey,L as syncShardCheckpointIdentity};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{isAuthReplayFailure as C}from"@lunora/client";import{LunoraError as d}from"@lunora/errors";import{createTransaction as E}from"@tanstack/db";import{isScopeFollowingCheckpoints as R,scopedShardKey as A,getShardCheckpoints as I,hasCheckpointsAttached as T,syncShardCheckpointIdentity as g}from"./CHECKPOINT_FALLBACK_MS-Do8X5Q7k.mjs";import{assertSecureRandom as b,runOutboxMutation as N,toNonRetriable as B}from"./OUTBOX_MUTATION_FN_NAME-rGyegnLw.mjs";const O="__tanstack_db_direct",F="defineMutator: `serverRef` must be a generated mutator reference (api.mutators.*) or a 'namespace:fn' string",j=r=>{const e=typeof r=="string"?r:r?.__lunoraRef;if(typeof e!="string"||e.length===0)throw new d("INTERNAL",F);return e},q=r=>({__lunoraClientMutator:!0,apply:r.apply,serverRef:j(r.serverRef)}),D=(r,e,k)=>{b("bindMutators");const h=32;if(e.checkpoints&&R(e.checkpoints)&&e.scopeBy===void 0&&e.shardKey===void 0)throw new d("BAD_REQUEST","bindMutators: `checkpoints` from a `scopeBy` collection needs the same `scopeBy` (or a pinned `shardKey`) here, so each push reaches the shard it gates on");const w=t=>e.shardKey??(e.scopeBy===void 0?void 0:A(e.scopeBy,t,"the mutator args")),u=new Map;let m=r.currentIdentity();const f=()=>{g(r);const t=r.currentIdentity();t!==m&&(u.clear(),m=t)},v=t=>{f();const o=Math.max(u.get(t??"")??0,r.confirmedMutationWatermark(t))+1;return u.set(t??"",o),o};let l=Promise.resolve();const M=(t,o,n)=>{const c=l.then(async()=>{for(let i=0;;i+=1){const a=v(n);try{const{applied:s}=await r.callMutator(t,o,{clientSeq:a,shardKey:n});if(s)return a}catch(s){throw f(),u.set(n??"",r.confirmedMutationWatermark(n)),s}if(i>=h)throw new d("INTERNAL",`lunora: custom mutator "${t}" could not claim a fresh client sequence after ${String(h)} attempts`)}});return l=c.then(()=>{},()=>{}),c},_=t=>{if(e.checkpoints===!1)return;if(e.checkpoints&&!R(e.checkpoints))return e.checkpoints;const o=I(r,t);return T(o)?o:void 0},S=(t,o,n,c)=>{const{onWriteRejected:i}=e;i&&t.isPersisted.promise.catch(a=>{const s=a instanceof Error?a:new Error(String(a));try{i({args:c,code:s.code,error:s,mutator:o,serverRef:n})}catch{}})},y={};for(const[t,o]of Object.entries(k))y[t]=n=>{const c=w(n),i=E({autoCommit:!0,metadata:{[O]:!0,serverRef:o.serverRef},mutationFn:async()=>{let a=0;try{await N(async()=>{a=await M(o.serverRef,n,c)})}catch(p){throw C(p)?B(p):p}const s=_(c);s&&(s.acknowledge({mutationId:a}),await s.awaitMutationId(a))}});return i.mutate(()=>{o.apply({collections:e.collections},n)}),S(i,t,o.serverRef,n),i};return y},z=()=>({bindMutators:D,defineMutator:q});export{O as DIRECT_TRANSACTION_METADATA_KEY,D as bindMutators,q as defineMutator,z as initMutators};
|
|
@@ -344,7 +344,13 @@ interface LunoraCollectionConfig<TRow extends Row> {
|
|
|
344
344
|
load?: "eager" | "lazy";
|
|
345
345
|
/** Notified when the underlying subscription errors; the collection always leaves `loading` regardless. */
|
|
346
346
|
onError?: (error: SubscriptionError) => void;
|
|
347
|
-
/**
|
|
347
|
+
/**
|
|
348
|
+
* When set, the collection stays empty until {@link LunoraCollectionOptions.scope}
|
|
349
|
+
* points it at args. Name the table's `.shardBy(...)` column: unless
|
|
350
|
+
* {@link LunoraCollectionConfig.shardKey} pins a shard, the scoped value IS the
|
|
351
|
+
* shard key, so `scope({ channelId })` subscribes to that channel's shard and
|
|
352
|
+
* re-scoping moves the subscription. Scope args without the field throw.
|
|
353
|
+
*/
|
|
348
354
|
scopeBy?: string;
|
|
349
355
|
/** A replication shape as the sync source (partial replication). Mutually exclusive with {@link LunoraCollectionConfig.list}. */
|
|
350
356
|
shape?: ShapeSource;
|
|
@@ -356,12 +362,17 @@ interface LunoraCollectionConfig<TRow extends Row> {
|
|
|
356
362
|
* default ("") watermark bucket, which must not be compared against a
|
|
357
363
|
* per-shard mutator's sequence line (it would drop a sharded overlay early or
|
|
358
364
|
* hang it forever).
|
|
365
|
+
*
|
|
366
|
+
* Takes precedence over {@link LunoraCollectionConfig.scopeBy}: set both only
|
|
367
|
+
* when every scope lives on one shard (e.g. a per-tenant shard scoped by a
|
|
368
|
+
* non-shard column). Leave it unset on a `scopeBy` collection over a
|
|
369
|
+
* `.shardBy(...)` table so the shard follows the scope.
|
|
359
370
|
*/
|
|
360
371
|
shardKey?: string;
|
|
361
372
|
}
|
|
362
373
|
/** The result of {@link lunoraCollectionOptions}: a TanStack collection config plus its sync controls. */
|
|
363
374
|
interface LunoraCollectionOptions<TRow extends Row> {
|
|
364
|
-
/** Resolves optimistic-overlay drops against the server's confirmed watermarks. */
|
|
375
|
+
/** Resolves optimistic-overlay drops against the server's confirmed watermarks — the current scope's shard for a `scopeBy` collection. */
|
|
365
376
|
checkpoints: CheckpointRegistry;
|
|
366
377
|
/** Pass to TanStack's `createCollection`. */
|
|
367
378
|
config: CollectionConfig<TRow, string>;
|
package/dist/packem_shared/{collection-options.d-QNwn1hBH.d.ts → collection-options.d-BbEYKjp2.d.ts}
RENAMED
|
@@ -344,7 +344,13 @@ interface LunoraCollectionConfig<TRow extends Row> {
|
|
|
344
344
|
load?: "eager" | "lazy";
|
|
345
345
|
/** Notified when the underlying subscription errors; the collection always leaves `loading` regardless. */
|
|
346
346
|
onError?: (error: SubscriptionError) => void;
|
|
347
|
-
/**
|
|
347
|
+
/**
|
|
348
|
+
* When set, the collection stays empty until {@link LunoraCollectionOptions.scope}
|
|
349
|
+
* points it at args. Name the table's `.shardBy(...)` column: unless
|
|
350
|
+
* {@link LunoraCollectionConfig.shardKey} pins a shard, the scoped value IS the
|
|
351
|
+
* shard key, so `scope({ channelId })` subscribes to that channel's shard and
|
|
352
|
+
* re-scoping moves the subscription. Scope args without the field throw.
|
|
353
|
+
*/
|
|
348
354
|
scopeBy?: string;
|
|
349
355
|
/** A replication shape as the sync source (partial replication). Mutually exclusive with {@link LunoraCollectionConfig.list}. */
|
|
350
356
|
shape?: ShapeSource;
|
|
@@ -356,12 +362,17 @@ interface LunoraCollectionConfig<TRow extends Row> {
|
|
|
356
362
|
* default ("") watermark bucket, which must not be compared against a
|
|
357
363
|
* per-shard mutator's sequence line (it would drop a sharded overlay early or
|
|
358
364
|
* hang it forever).
|
|
365
|
+
*
|
|
366
|
+
* Takes precedence over {@link LunoraCollectionConfig.scopeBy}: set both only
|
|
367
|
+
* when every scope lives on one shard (e.g. a per-tenant shard scoped by a
|
|
368
|
+
* non-shard column). Leave it unset on a `scopeBy` collection over a
|
|
369
|
+
* `.shardBy(...)` table so the shard follows the scope.
|
|
359
370
|
*/
|
|
360
371
|
shardKey?: string;
|
|
361
372
|
}
|
|
362
373
|
/** The result of {@link lunoraCollectionOptions}: a TanStack collection config plus its sync controls. */
|
|
363
374
|
interface LunoraCollectionOptions<TRow extends Row> {
|
|
364
|
-
/** Resolves optimistic-overlay drops against the server's confirmed watermarks. */
|
|
375
|
+
/** Resolves optimistic-overlay drops against the server's confirmed watermarks — the current scope's shard for a `scopeBy` collection. */
|
|
365
376
|
checkpoints: CheckpointRegistry;
|
|
366
377
|
/** Pass to TanStack's `createCollection`. */
|
|
367
378
|
config: CollectionConfig<TRow, string>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{lunoraCollectionOptions as _,scopedShardKey as T}from"./CHECKPOINT_FALLBACK_MS-Do8X5Q7k.mjs";import{isAuthReplayFailure as A}from"@lunora/client";import{createCollection as D,safeRandomUUID as U}from"@tanstack/db";import{startOfflineExecutor as j,NonRetriableError as q}from"@tanstack/offline-transactions";import{assertSecureRandom as L,createOutboxCarrier as W,createOptimisticOnlineDetector as $,OUTBOX_MUTATION_FN_NAME as R,registerOutboxCarrier as k,takeOutboxRejection as S,toNonRetriable as I,runOutboxMutation as H,terminalVerdict as P}from"./OUTBOX_MUTATION_FN_NAME-rGyegnLw.mjs";const M=new WeakMap,V=(n,s)=>{let o=M.get(n);o===void 0&&(o=new Map,M.set(n,o));const d=o.get(s)??0;o.set(s,d+1),d===1&&console.warn(`[@lunora/db] table "${s}" is bound by more than one defineCollections call on this client. Each call creates its own live collection and outbox for the table, so the two can drift and derived indexes built from one can silently read stale rows. Bind every table in a single defineCollections call (see the "One source of truth per table" docs section).`)},K=(n,s)=>{try{n.onWriteRejected?.(s)}catch{}},X=(n,s)=>{const o=n.replayIdentityVerdict(s?.identity);if(o.verdict==="mismatch")throw new q("outbox write dropped: identity changed since it was queued");if(o.verdict==="unknown")throw new Error("outbox write deferred: no identity established yet");return o.credential},z=6e4,ee=(n,s,o={})=>{L("defineCollections");const d={},l=new Map,O=async(t,e,r)=>{const i=l.get(t);if(n.getAuthToken()===i?.refused)throw Date.now()-i.since<z?new Error("outbox write deferred: waiting for a fresh credential"):(l.delete(t),I(i.error));const y=n.getAuthToken(),u=X(n,e);try{await H(async()=>r(u))}catch(a){throw A(a)?i!==void 0?(l.delete(t),I(a)):(l.set(t,{error:a,refused:y,since:Date.now()}),new Error("outbox write deferred: its credential was refused",{cause:a})):a}l.delete(t)},x=(t,e)=>{const r=P(e);return r!==void 0&&l.delete(t),r},B={},p={},E=Object.entries(s);for(const[t,e]of E){const r=e.insert;V(n,t);const{config:i,scope:y}=_({client:n,getKey:e.getKey,id:t,list:e.list,...e.load===void 0?{}:{load:e.load},onError:e.onError,scopeBy:e.scopeBy,shardKey:e.shardKey});d[t]=D(i),e.scopeBy!==void 0&&(B[t]=y),r&&(p[t]=async({idempotencyKey:u,transaction:a})=>{const c=a.metadata,h=c!==void 0&&c.shardKey===void 0&&e.shardKey===void 0&&e.scopeBy!==void 0;for(const[w,g]of a.mutations.entries()){const b=g.modified,v=`${u}:${String(w)}`;try{await O(v,c,async f=>n.mutation(r.mutation,r.toArgs(b),{mutationId:v,replayBaseline:h?null:c?.baselineSeq??null,replayCredential:f,shardKey:h?T(e.scopeBy,b,"the queued row"):c?.shardKey}))}catch(C){const f=x(v,C);throw f!==void 0?(K(o,{code:f.code,collection:t,error:f,row:b}),f):C}}})}p[R]=async({transaction:t})=>{const e=t.metadata;if(e)try{const r=async i=>n.mutation({__lunoraRef:e.functionPath},e.args,{mutationId:e.idempotencyKey,replayBaseline:e.baselineSeq??null,replayCredential:i,shardKey:e.shardKey});await O(e.idempotencyKey,e,r),S(e.idempotencyKey)}catch(r){const i=x(e.idempotencyKey,r);throw i!==void 0?(S(e.idempotencyKey)?.(),K(o,{code:i.code,collection:e.functionPath,error:i}),i):r}};const F=W(),m=j({collections:{...d,[R]:F},mutationFns:p,onlineDetector:$(),...o.onLeadershipChange?{onLeadershipChange:o.onLeadershipChange}:{},...o.onStorageFailure?{onStorageFailure:o.onStorageFailure}:{},onUnknownMutationFn:(t,e)=>{K(o,{code:"UNKNOWN_MUTATION_FN",collection:t,error:new Error(`offline write dropped: mutation "${t}" no longer exists (removed or renamed in a deploy?)`),row:e.mutations[0]?.modified})}});k(m,F);const N={};for(const[t,e]of E){const r=e.insert,i=d[t];!r||!i||(N[t]=y=>{const u=U(),a=r.optimistic(y,u),c=e.shardKey??(e.scopeBy===void 0?void 0:T(e.scopeBy,a,"the inserted row")),h={baselineSeq:n.currentBaseline(c),identity:n.currentIdentity(),shardKey:c},w=m.createOfflineTransaction({autoCommit:!1,metadata:h,mutationFnName:t}),g=w.mutate(()=>{i.insert(a)});return w.commit().catch(()=>{}),{id:u,transaction:g}})}return{actions:N,collections:d,executor:m,pendingCount:()=>m.getPendingCount(),scope:B}};export{ee as defineCollections};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { LunoraClient } from '@lunora/client';
|
|
2
2
|
import { Collection, Transaction } from '@tanstack/db';
|
|
3
|
-
import { b as CheckpointRegistry } from "./collection-options.d-
|
|
3
|
+
import { b as CheckpointRegistry } from "./collection-options.d-BbEYKjp2.mjs";
|
|
4
4
|
/**
|
|
5
5
|
* TanStack DB's "direct transaction" marker.
|
|
6
6
|
*
|
|
@@ -161,7 +161,17 @@ interface BindMutatorsContext<TCollections extends CollectionMap = CollectionMap
|
|
|
161
161
|
* second one.
|
|
162
162
|
*/
|
|
163
163
|
onWriteRejected?: (event: MutatorRejectedEvent) => void;
|
|
164
|
-
/**
|
|
164
|
+
/**
|
|
165
|
+
* Route each call to the shard its own args name: the value of this field
|
|
166
|
+
* (the table's `.shardBy(...)` column) is the shard key, exactly as for a
|
|
167
|
+
* `scopeBy` collection's inserts — so `sendMessage({ channelId, … })` lands
|
|
168
|
+
* on that channel's shard. Required when `checkpoints` comes from a
|
|
169
|
+
* `scopeBy` collection, unless `shardKey` pins one shard; a call whose args
|
|
170
|
+
* lack the field throws.
|
|
171
|
+
* {@link BindMutatorsContext.shardKey} takes precedence.
|
|
172
|
+
*/
|
|
173
|
+
scopeBy?: string;
|
|
174
|
+
/** Pins every push to one shard. Takes precedence over {@link BindMutatorsContext.scopeBy}. */
|
|
165
175
|
shardKey?: string;
|
|
166
176
|
}
|
|
167
177
|
/** Calling a bound mutator runs the optimistic body + pushes the server write; returns the TanStack transaction. */
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { LunoraClient } from '@lunora/client';
|
|
2
2
|
import { Collection, Transaction } from '@tanstack/db';
|
|
3
|
-
import { b as CheckpointRegistry } from "./collection-options.d-
|
|
3
|
+
import { b as CheckpointRegistry } from "./collection-options.d-BbEYKjp2.js";
|
|
4
4
|
/**
|
|
5
5
|
* TanStack DB's "direct transaction" marker.
|
|
6
6
|
*
|
|
@@ -161,7 +161,17 @@ interface BindMutatorsContext<TCollections extends CollectionMap = CollectionMap
|
|
|
161
161
|
* second one.
|
|
162
162
|
*/
|
|
163
163
|
onWriteRejected?: (event: MutatorRejectedEvent) => void;
|
|
164
|
-
/**
|
|
164
|
+
/**
|
|
165
|
+
* Route each call to the shard its own args name: the value of this field
|
|
166
|
+
* (the table's `.shardBy(...)` column) is the shard key, exactly as for a
|
|
167
|
+
* `scopeBy` collection's inserts — so `sendMessage({ channelId, … })` lands
|
|
168
|
+
* on that channel's shard. Required when `checkpoints` comes from a
|
|
169
|
+
* `scopeBy` collection, unless `shardKey` pins one shard; a call whose args
|
|
170
|
+
* lack the field throws.
|
|
171
|
+
* {@link BindMutatorsContext.shardKey} takes precedence.
|
|
172
|
+
*/
|
|
173
|
+
scopeBy?: string;
|
|
174
|
+
/** Pins every push to one shard. Takes precedence over {@link BindMutatorsContext.scopeBy}. */
|
|
165
175
|
shardKey?: string;
|
|
166
176
|
}
|
|
167
177
|
/** Calling a bound mutator runs the optimistic body + pushes the server write; returns the TanStack transaction. */
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { R as Row } from "./collection-options.d-
|
|
1
|
+
import { R as Row } from "./collection-options.d-BbEYKjp2.js";
|
|
2
2
|
import { FunctionReference, SubscriptionError, LunoraClient } from '@lunora/client';
|
|
3
3
|
import { Transaction, Collection } from '@tanstack/db';
|
|
4
4
|
import { OfflineExecutor, StorageDiagnostic } from '@tanstack/offline-transactions';
|
|
@@ -46,16 +46,24 @@ interface CollectionDef<TList extends FunctionReference, TInput = never> {
|
|
|
46
46
|
* `loading` on error, and forwards the error here if supplied.
|
|
47
47
|
*/
|
|
48
48
|
onError?: (error: SubscriptionError) => void;
|
|
49
|
-
/**
|
|
49
|
+
/**
|
|
50
|
+
* The table's `.shardBy(...)` column; makes the collection re-pointable via
|
|
51
|
+
* `scope`. Unless `shardKey` pins a shard, its value is the shard key:
|
|
52
|
+
* `scope({ channelId })` subscribes to that channel's shard, and each
|
|
53
|
+
* `insert` goes to the shard its own row's value names, captured when queued
|
|
54
|
+
* so an offline write replays there even after the app re-scopes. Scope args
|
|
55
|
+
* or an inserted row without the field throw.
|
|
56
|
+
*/
|
|
50
57
|
scopeBy?: string;
|
|
51
58
|
/**
|
|
52
|
-
*
|
|
53
|
-
* frames advance the checkpoint gate from) to
|
|
54
|
-
*
|
|
55
|
-
*
|
|
56
|
-
*
|
|
57
|
-
*
|
|
58
|
-
*
|
|
59
|
+
* Pins the `list` subscription (and the confirmed-mutation watermark its
|
|
60
|
+
* frames advance the checkpoint gate from) to one shard's DO — so a sharded
|
|
61
|
+
* collection's overlay gate compares against that shard's mutator sequence
|
|
62
|
+
* line, not the default ("") watermark bucket. `insert` writes are routed
|
|
63
|
+
* with it too: subscriptions and the writes they observe have to land on the
|
|
64
|
+
* same Durable Object, and the server derives no shard from a mutation's args.
|
|
65
|
+
* Takes precedence over `scopeBy`; leave it unset when the shard should
|
|
66
|
+
* follow the scope.
|
|
59
67
|
*/
|
|
60
68
|
shardKey?: string;
|
|
61
69
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { R as Row } from "./collection-options.d-
|
|
1
|
+
import { R as Row } from "./collection-options.d-BbEYKjp2.mjs";
|
|
2
2
|
import { FunctionReference, SubscriptionError, LunoraClient } from '@lunora/client';
|
|
3
3
|
import { Transaction, Collection } from '@tanstack/db';
|
|
4
4
|
import { OfflineExecutor, StorageDiagnostic } from '@tanstack/offline-transactions';
|
|
@@ -46,16 +46,24 @@ interface CollectionDef<TList extends FunctionReference, TInput = never> {
|
|
|
46
46
|
* `loading` on error, and forwards the error here if supplied.
|
|
47
47
|
*/
|
|
48
48
|
onError?: (error: SubscriptionError) => void;
|
|
49
|
-
/**
|
|
49
|
+
/**
|
|
50
|
+
* The table's `.shardBy(...)` column; makes the collection re-pointable via
|
|
51
|
+
* `scope`. Unless `shardKey` pins a shard, its value is the shard key:
|
|
52
|
+
* `scope({ channelId })` subscribes to that channel's shard, and each
|
|
53
|
+
* `insert` goes to the shard its own row's value names, captured when queued
|
|
54
|
+
* so an offline write replays there even after the app re-scopes. Scope args
|
|
55
|
+
* or an inserted row without the field throw.
|
|
56
|
+
*/
|
|
50
57
|
scopeBy?: string;
|
|
51
58
|
/**
|
|
52
|
-
*
|
|
53
|
-
* frames advance the checkpoint gate from) to
|
|
54
|
-
*
|
|
55
|
-
*
|
|
56
|
-
*
|
|
57
|
-
*
|
|
58
|
-
*
|
|
59
|
+
* Pins the `list` subscription (and the confirmed-mutation watermark its
|
|
60
|
+
* frames advance the checkpoint gate from) to one shard's DO — so a sharded
|
|
61
|
+
* collection's overlay gate compares against that shard's mutator sequence
|
|
62
|
+
* line, not the default ("") watermark bucket. `insert` writes are routed
|
|
63
|
+
* with it too: subscriptions and the writes they observe have to land on the
|
|
64
|
+
* same Durable Object, and the server derives no shard from a mutation's args.
|
|
65
|
+
* Takes precedence over `scopeBy`; leave it unset when the shard should
|
|
66
|
+
* follow the scope.
|
|
59
67
|
*/
|
|
60
68
|
shardKey?: string;
|
|
61
69
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lunora/db",
|
|
3
|
-
"version": "1.0.0-alpha.
|
|
3
|
+
"version": "1.0.0-alpha.155",
|
|
4
4
|
"description": "TanStack DB binding: typed, live-synced collections and a durable offline outbox over the Lunora client",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"cloudflare",
|
|
@@ -54,7 +54,7 @@
|
|
|
54
54
|
"access": "public"
|
|
55
55
|
},
|
|
56
56
|
"dependencies": {
|
|
57
|
-
"@lunora/client": "1.0.0-alpha.
|
|
57
|
+
"@lunora/client": "1.0.0-alpha.153",
|
|
58
58
|
"@lunora/errors": "1.0.0-alpha.45"
|
|
59
59
|
},
|
|
60
60
|
"peerDependencies": {
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{LunoraError as M}from"@lunora/errors";import{BTreeIndex as T}from"@tanstack/db";import{toMap as E,makeDiffEmit as S}from"./OUTBOX_MUTATION_FN_NAME-rGyegnLw.mjs";const I=()=>{let e=Number.NEGATIVE_INFINITY;const a=[];return{advance:r=>{if(!(r<=e)){e=r;for(let n=a.length-1;n>=0;n-=1){const c=a[n];c&&c.threshold<=e&&(c.resolve(),a.splice(n,1))}}},await:r=>r<=e?Promise.resolve():new Promise(n=>{a.push({resolve:n,threshold:r})}),passed:r=>r<=e,reset:()=>{for(const r of a.splice(0))r.resolve();e=Number.NEGATIVE_INFINITY},waiting:()=>a.length}};let w=!1;const F=e=>{w||(w=!0,console.warn(`[@lunora/db] released an optimistic overlay via the ${String(e.waitedMs)}ms checkpoint fallback: the server confirmed ${e.kind} ${String(e.watermark)} but no sync frame ever echoed it. A dropped shape poke or \`settled\` frame is the usual cause — inspect the subscription rather than raising \`fallbackMs\`. (Reported once per process.)`))},y=new WeakMap,b=new WeakMap,C=new WeakSet,N=new WeakMap,K=3e3,R=(e={})=>{const a=e.fallbackMs??K,r=e.onFallback??F,n=I(),c=I();let d=0;const o=new Set,h=(t,i)=>{for(const l of o)l.kind===t&&l.threshold<=i&&(clearTimeout(l.handle),o.delete(l))},p=(t,i)=>{if(a<=0)return;const l=t==="checkpoint"?n:c;if(l.passed(i))return;for(const m of o)if(m.kind===t&&m.threshold>=i)return;const s={handle:setTimeout(()=>{if(o.delete(s),!l.passed(i)){d+=1,l.advance(i);try{r({kind:t,waitedMs:a,watermark:i})}catch{}}},a),kind:t,threshold:i};o.add(s)},u={acknowledge:({checkpoint:t,mutationId:i})=>{t!==void 0&&p("checkpoint",t),i!==void 0&&p("mutationId",i)},awaitCheckpoint:t=>n.await(t),awaitMutationId:t=>c.await(t),dispose:()=>{for(const t of o)clearTimeout(t.handle);o.clear()},resolve:({checkpoint:t,mutationId:i})=>{t!==void 0&&(n.advance(t),h("checkpoint",t)),i!==void 0&&(c.advance(i),h("mutationId",i))},stats:()=>({fallbacks:d,pendingCheckpointWaiters:n.waiting(),pendingMutationWaiters:c.waiting()})};return N.set(u,()=>{u.dispose(),n.reset(),c.reset()}),u},G=e=>{const a=y.get(e);if(a){for(const r of a.values())r.resolve({checkpoint:Number.POSITIVE_INFINITY,mutationId:Number.POSITIVE_INFINITY}),r.dispose();y.delete(e)}},A=e=>{const a=e.currentIdentity();if(y.has(e)&&b.get(e)!==a)for(const r of y.get(e)?.values()??[])N.get(r)?.();b.set(e,a)},_=(e,a,r)=>{A(e);let n=y.get(e);n||(n=new Map,y.set(e,n));const c=a??"",d=n.get(c);if(d)return d;const o=R(r);return n.set(c,o),o},W=e=>{C.add(e)},P=e=>C.has(e),L=e=>{if(e.list===void 0==(e.shape===void 0))throw new M("INTERNAL","lunoraCollectionOptions: pass exactly one of `list` or `shape`");const a=e.getKey??(s=>s._id),r=()=>{const s=e.checkpoints??_(e.client,e.shape?.shardKey??e.shardKey);return W(s),s},n=r(),c=new Map;let d,o,h,p,u;const t=(s,m)=>{const k=f=>{d?.(E(f,a)),m?.(),e.shape===void 0&&(r().resolve({mutationId:u??e.client.confirmedMutationWatermark(e.shardKey)}),u=void 0)},v=f=>h?.(f),g=f=>{f.rowsFollow===!0&&f.mutationId!==void 0&&(u=f.mutationId),r().resolve(f)};return e.shape!==void 0?e.client.subscribeShape({args:s,name:e.shape.name},k,{onCheckpoint:g,onError:v,shardKey:e.shape.shardKey}):e.client.subscribe(e.list,s,k,{onCheckpoint:g,onError:v,shardKey:e.shardKey})},i={autoIndex:"eager",defaultIndexType:T,getKey:a,id:e.id??e.list?.__lunoraRef??`shape:${e.shape?.name??""}`,...e.load==="eager"?{startSync:!0}:{},sync:{sync:s=>(d=S(c,s),h=k=>{s.markReady(),e.onError?.(k)},e.scopeBy===void 0?o=t(e.shape?.args??{},()=>{s.markReady()}):(s.markReady(),p!==void 0&&(o=t(p,void 0))),()=>{d=void 0,h=void 0,o?.(),o=void 0,c.clear()})}};return{checkpoints:n,config:i,scope:s=>{e.scopeBy!==void 0&&(p=s,o?.(),o=void 0,d?.(new Map),s!==void 0&&d!==void 0&&(o=t(s,void 0)))}}};export{K as CHECKPOINT_FALLBACK_MS,R as createCheckpointRegistry,_ as getShardCheckpoints,P as hasCheckpointsAttached,L as lunoraCollectionOptions,W as markCheckpointsAttached,G as releaseShardCheckpoints,A as syncShardCheckpointIdentity};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{isAuthReplayFailure as k}from"@lunora/client";import{LunoraError as l}from"@lunora/errors";import{createTransaction as v}from"@tanstack/db";import{getShardCheckpoints as C,hasCheckpointsAttached as I,syncShardCheckpointIdentity as A}from"./CHECKPOINT_FALLBACK_MS-DgrLzaWr.mjs";import{assertSecureRandom as E,runOutboxMutation as S,toNonRetriable as T}from"./OUTBOX_MUTATION_FN_NAME-rGyegnLw.mjs";const b="__tanstack_db_direct",g="defineMutator: `serverRef` must be a generated mutator reference (api.mutators.*) or a 'namespace:fn' string",N=t=>{const e=typeof t=="string"?t:t?.__lunoraRef;if(typeof e!="string"||e.length===0)throw new l("INTERNAL",g);return e},K=t=>({__lunoraClientMutator:!0,apply:t.apply,serverRef:N(t.serverRef)}),O=(t,e,y)=>{E("bindMutators");const d=32;let u=0,m=t.currentIdentity();const h=()=>{A(t);const r=t.currentIdentity();r!==m&&(u=0,m=r)},R=()=>(h(),u=Math.max(u,t.confirmedMutationWatermark(e.shardKey))+1,u);let p=Promise.resolve();const M=(r,a)=>{const s=p.then(async()=>{for(let i=0;;i+=1){const n=R();try{const{applied:o}=await t.callMutator(r,a,{clientSeq:n,shardKey:e.shardKey});if(o)return n}catch(o){throw h(),u=t.confirmedMutationWatermark(e.shardKey),o}if(i>=d)throw new l("INTERNAL",`lunora: custom mutator "${r}" could not claim a fresh client sequence after ${String(d)} attempts`)}});return p=s.then(()=>{},()=>{}),s},w=()=>{if(e.checkpoints===!1)return;if(e.checkpoints)return e.checkpoints;const r=C(t,e.shardKey);return I(r)?r:void 0},_=(r,a,s,i)=>{const{onWriteRejected:n}=e;n&&r.isPersisted.promise.catch(o=>{const c=o instanceof Error?o:new Error(String(o));try{n({args:i,code:c.code,error:c,mutator:a,serverRef:s})}catch{}})},f={};for(const[r,a]of Object.entries(y))f[r]=s=>{const i=v({autoCommit:!0,metadata:{[b]:!0,serverRef:a.serverRef},mutationFn:async()=>{let n=0;try{await S(async()=>{n=await M(a.serverRef,s)})}catch(c){throw k(c)?T(c):c}const o=w();o&&(o.acknowledge({mutationId:n}),await o.awaitMutationId(n))}});return i.mutate(()=>{a.apply({collections:e.collections},s)}),_(i,r,a.serverRef,s),i};return f},W=()=>({bindMutators:O,defineMutator:K});export{b as DIRECT_TRANSACTION_METADATA_KEY,O as bindMutators,K as defineMutator,W as initMutators};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{lunoraCollectionOptions as M}from"./CHECKPOINT_FALLBACK_MS-DgrLzaWr.mjs";import{isAuthReplayFailure as S}from"@lunora/client";import{createCollection as _,safeRandomUUID as A}from"@tanstack/db";import{startOfflineExecutor as D,NonRetriableError as U}from"@tanstack/offline-transactions";import{assertSecureRandom as j,createOutboxCarrier as L,createOptimisticOnlineDetector as q,OUTBOX_MUTATION_FN_NAME as N,registerOutboxCarrier as W,takeOutboxRejection as T,toNonRetriable as R,runOutboxMutation as $,terminalVerdict as k}from"./OUTBOX_MUTATION_FN_NAME-rGyegnLw.mjs";const B=new WeakMap,H=(n,s)=>{let o=B.get(n);o===void 0&&(o=new Map,B.set(n,o));const c=o.get(s)??0;o.set(s,c+1),c===1&&console.warn(`[@lunora/db] table "${s}" is bound by more than one defineCollections call on this client. Each call creates its own live collection and outbox for the table, so the two can drift and derived indexes built from one can silently read stale rows. Bind every table in a single defineCollections call (see the "One source of truth per table" docs section).`)},b=(n,s)=>{try{n.onWriteRejected?.(s)}catch{}},P=(n,s)=>{const o=n.replayIdentityVerdict(s?.identity);if(o.verdict==="mismatch")throw new U("outbox write dropped: identity changed since it was queued");if(o.verdict==="unknown")throw new Error("outbox write deferred: no identity established yet");return o.credential},V=6e4,Y=(n,s,o={})=>{j("defineCollections");const c={},d=new Map,C=async(t,e,r)=>{const i=d.get(t);if(n.getAuthToken()===i?.refused)throw Date.now()-i.since<V?new Error("outbox write deferred: waiting for a fresh credential"):(d.delete(t),R(i.error));const m=n.getAuthToken(),l=P(n,e);try{await $(async()=>r(l))}catch(a){throw S(a)?i!==void 0?(d.delete(t),R(a)):(d.set(t,{error:a,refused:m,since:Date.now()}),new Error("outbox write deferred: its credential was refused",{cause:a})):a}d.delete(t)},K=(t,e)=>{const r=k(e);return r!==void 0&&d.delete(t),r},O={},y={},x=Object.entries(s);for(const[t,e]of x){const r=e.insert;H(n,t);const{config:i,scope:m}=M({client:n,getKey:e.getKey,id:t,list:e.list,...e.load===void 0?{}:{load:e.load},onError:e.onError,scopeBy:e.scopeBy,shardKey:e.shardKey});c[t]=_(i),e.scopeBy!==void 0&&(O[t]=m),r&&(y[t]=async({idempotencyKey:l,transaction:a})=>{const u=a.metadata;for(const[w,I]of a.mutations.entries()){const F=I.modified,p=`${l}:${String(w)}`;try{await C(p,u,async f=>n.mutation(r.mutation,r.toArgs(F),{mutationId:p,replayBaseline:u?.baselineSeq??null,replayCredential:f,shardKey:u?.shardKey}))}catch(g){const f=K(p,g);throw f!==void 0?(b(o,{code:f.code,collection:t,error:f,row:F}),f):g}}})}y[N]=async({transaction:t})=>{const e=t.metadata;if(e)try{const r=async i=>n.mutation({__lunoraRef:e.functionPath},e.args,{mutationId:e.idempotencyKey,replayBaseline:e.baselineSeq??null,replayCredential:i,shardKey:e.shardKey});await C(e.idempotencyKey,e,r),T(e.idempotencyKey)}catch(r){const i=K(e.idempotencyKey,r);throw i!==void 0?(T(e.idempotencyKey)?.(),b(o,{code:i.code,collection:e.functionPath,error:i}),i):r}};const v=L(),h=D({collections:{...c,[N]:v},mutationFns:y,onlineDetector:q(),...o.onLeadershipChange?{onLeadershipChange:o.onLeadershipChange}:{},...o.onStorageFailure?{onStorageFailure:o.onStorageFailure}:{},onUnknownMutationFn:(t,e)=>{b(o,{code:"UNKNOWN_MUTATION_FN",collection:t,error:new Error(`offline write dropped: mutation "${t}" no longer exists (removed or renamed in a deploy?)`),row:e.mutations[0]?.modified})}});W(h,v);const E={};for(const[t,e]of x){const r=e.insert,i=c[t];!r||!i||(E[t]=m=>{const l=A(),a={baselineSeq:n.currentBaseline(e.shardKey),identity:n.currentIdentity(),shardKey:e.shardKey},u=h.createOfflineTransaction({autoCommit:!1,metadata:a,mutationFnName:t}),w=u.mutate(()=>{i.insert(r.optimistic(m,l))});return u.commit().catch(()=>{}),{id:l,transaction:w}})}return{actions:E,collections:c,executor:h,pendingCount:()=>h.getPendingCount(),scope:O}};export{Y as defineCollections};
|