refactor(scope): replace _scopeCursor bridge with reactive useScopedLiveQuery hook

The previous fix wired Dexie's `_scopeCursor` infra-table as a side-
channel between Svelte $state (active-space + current-user) and Dexie
liveQuery: every scoped query touched the table on read so liveQuery
subscribed to it, every setActiveSpace bumped the table so liveQuery
re-ran. Worked, but smelled — hidden side-effect inside `scopedTable`,
scope state pretending to be a Dexie row, +1 roundtrip per query, and
`current-user.ts` had to dynamic-import Dexie just to pump the bridge.

Replacement: a Svelte 5 `$effect`-based hook that owns scope-tracking
explicitly. The dependency now lives in the reactive layer (which is
where it belongs), not as a side-effect in the data layer.

What changes:

- New `data/scope/use-scoped-live-query.svelte.ts`. The hook reads a
  module-level `scopeTick` `$state` counter inside its `$effect`.
  Both `onActiveSpaceChanged` (existing) and `onCurrentUserChanged`
  (new, added to `current-user.ts`) bump the tick on real changes.
  Effect re-fires → previous Dexie subscription unsubscribes → fresh
  one created with up-to-date `getInScopeSpaceIds()`. Same return
  shape as `useLiveQueryWithDefault` for drop-in migration.

- `current-user.ts` gains an `onCurrentUserChanged` event bus,
  symmetric to `active-space.svelte.ts#onActiveSpaceChanged`. Stays
  a plain `.ts` (no runes) so it remains a leaf and works in the
  test runner without the Svelte preprocessor — the rename to
  `.svelte.ts` was tried earlier and reverted because of test
  fallout (commit `01e6b9f04`).

- 53 module `queries.ts` files migrated from
  `useLiveQueryWithDefault` → `useScopedLiveQuery`. The choice of
  hook now documents at the call-site whether the query is
  scope-aware. Pure mechanical find-replace — no logic changes.

What goes away:

- `data/scope/cursor.ts` deleted.
- `touchScopeCursor()` calls removed from `scopedTable` /
  `scopedAnd` / `scopedGet`. Functions are pure data-layer again,
  no implicit reactive subscriptions.
- `bumpScopeCursor()` calls removed from `setActiveSpace` and both
  `loadActiveSpace` branches. Setter is pure state-update, no
  Dexie write side-effect.
- `current-user.ts` no longer dynamic-imports `scope/cursor` —
  `setCurrentUserId` is a clean three-line setter again.
- Dexie v46 drops the `_scopeCursor` table (`stores: { _scopeCursor:
  null }`). v45 stays declared so existing browsers' version chain
  remains contiguous; the v46 deletion runs once on next open. No
  user data lost — the table only ever held a transient bumpedAt
  row.

Existing 14 scope regression tests still pass. Type-check + theme-
token validators are clean.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Till JS 2026-04-25 12:15:22 +02:00
parent 167d616cf7
commit 636138b2d4
57 changed files with 358 additions and 378 deletions

View file

@ -2,38 +2,69 @@
* Single source of truth for the current authenticated user id.
*
* Why a separate module?
* The data layer (database.ts hooks) needs to know who is writing each
* record so it can stamp `userId` automatically. Importing the auth store
* directly would couple the data layer to UI state and create a circular
* dependency. Instead, the root layout pushes the current user id here on
* every auth state change.
* The data layer (database.ts hooks) needs to know who is writing
* each record so it can stamp `userId` automatically. Importing the
* auth store directly would couple the data layer to UI state and
* create a circular dependency. Instead, the root layout pushes the
* current user id here on every auth state change.
*
* Reactive consumers:
* This module stays a plain `.ts` (no runes) so it remains a leaf in
* the dependency graph and works inside the test runner without the
* Svelte preprocessor. To still notify reactive consumers of changes,
* we expose `onCurrentUserChanged(handler)` same pattern as
* `onActiveSpaceChanged` in scope/active-space.svelte.ts. The
* `useScopedLiveQuery` hook subscribes to both buses and re-runs
* liveQueries whenever either side flips.
*
* Guest mode: when no user is signed in, records are stamped with the
* `GUEST_USER_ID` sentinel. The mana-sync backend treats these as anonymous
* and rejects them at the RLS layer once auth is required.
* `GUEST_USER_ID` sentinel. The mana-sync backend treats these as
* anonymous and rejects them at the RLS layer once auth is required.
*/
export const GUEST_USER_ID = 'guest';
let currentUserId: string | null = null;
type CurrentUserChangedHandler = (userId: string | null) => void;
const handlers: CurrentUserChangedHandler[] = [];
/**
* Updates the active user. Pass `null` for sign-out / guest.
*
* After updating the in-memory value, bumps the Dexie `_scopeCursor`
* (lazy import to keep this module leaf-level) so every liveQuery
* subscribed via `touchScopeCursor` re-evaluates with the new
* `getInScopeSpaceIds()`. Fire-and-forget a missing bump only
* delays re-evaluation until the next Dexie write elsewhere.
* Subscribe to user-id changes. Returns an unsubscribe function. The
* handler is replayed once with the current value so consumers don't
* need a separate "read initial" call.
*/
export function onCurrentUserChanged(handler: CurrentUserChangedHandler): () => void {
handlers.push(handler);
try {
handler(currentUserId);
} catch (err) {
console.error('[current-user] handler replay failed:', err);
}
return () => {
const i = handlers.indexOf(handler);
if (i >= 0) handlers.splice(i, 1);
};
}
function notifyHandlers(id: string | null): void {
for (const h of handlers) {
try {
h(id);
} catch (err) {
console.error('[current-user] handler failed:', err);
}
}
}
/**
* Updates the active user. Pass `null` for sign-out / guest. Notifies
* every `onCurrentUserChanged` subscriber when the value flips.
*/
export function setCurrentUserId(id: string | null): void {
const prev = currentUserId;
currentUserId = id;
if (id !== prev) {
void import('./scope/cursor').then(({ bumpScopeCursor }) => {
bumpScopeCursor();
});
}
if (id !== prev) notifyHandlers(id);
}
/** Returns the active user id, or `null` if unauthenticated. */
@ -42,8 +73,9 @@ export function getCurrentUserId(): string | null {
}
/**
* Returns the user id to stamp on local records: real user when signed in,
* `GUEST_USER_ID` otherwise. Always non-null so it can be used as a key.
* Returns the user id to stamp on local records: real user when signed
* in, `GUEST_USER_ID` otherwise. Always non-null so it can be used as
* a key.
*/
export function getEffectiveUserId(): string {
return currentUserId ?? GUEST_USER_ID;

View file

@ -1052,16 +1052,32 @@ db.version(44).stores({
comicStories: 'id, createdAt, style, isFavorite, isArchived',
});
// v45 — Infra table `_scopeCursor` (see data/scope/cursor.ts for the
// full rationale). Single-row beacon that every scoped query touches
// so Dexie's liveQuery subscribes to it; bumped on every
// setActiveSpace. Without this, scope changes were invisible to
// liveQueries and modules rendered empty on first mount until an
// unrelated write re-triggered the querier. NOT in SYNC_APP_MAP —
// it's a client-side liveness signal, not user data.
// v45 → v46: scope-cursor bridge superseded by `useScopedLiveQuery`.
//
// v45 introduced a `_scopeCursor` infra table as a Dexie-side beacon
// for active-space / current-user changes — every scoped query touched
// it so Dexie's liveQuery subscribed to it, every setActiveSpace
// bumped it. Worked, but smelled: hidden side-effect inside scopedTable,
// scope state pretending to be a Dexie row, +1 roundtrip per query.
//
// The clean replacement: `data/scope/use-scoped-live-query.svelte.ts`
// wraps liveQuery in a Svelte 5 `$effect` that reads the scope state
// directly. Runes auto-track those reads, so a scope change tears
// down the previous Dexie subscription and creates a fresh one — no
// bridge table needed. Both `active-space.svelte.ts#active` and
// `current-user.svelte.ts#currentUserId` are `$state` so the tracking
// just works.
//
// v45 stays declared so existing Dexie databases keep their version
// chain intact; v46 drops the table by passing `null` (Dexie idiom
// for "delete this store"). Safe because `_scopeCursor` only ever
// held a transient `{id, bumpedAt}` row — no user data lost.
db.version(45).stores({
_scopeCursor: 'id',
});
db.version(46).stores({
_scopeCursor: null,
});
// ─── Sync Routing ──────────────────────────────────────────
// SYNC_APP_MAP, TABLE_TO_SYNC_NAME, TABLE_TO_APP, SYNC_NAME_TO_TABLE,

View file

@ -12,7 +12,6 @@
import type { SpaceType, SpaceTier } from '@mana/shared-types';
import { isSpaceType, isSpaceTier } from '@mana/shared-types';
import { authFetch } from './auth-fetch';
import { bumpScopeCursor } from './cursor';
export interface ActiveSpace {
id: string;
@ -92,14 +91,7 @@ export function setActiveSpace(space: ActiveSpace | null): void {
active = space;
status = space ? 'ready' : 'idle';
lastError = null;
if (space?.id !== prevId) {
notifyHandlers(space);
// Dexie-bridge: bump the _scopeCursor so every liveQuery that
// touchScopeCursor'd re-runs with the new getInScopeSpaceIds().
// Without this, modules mounted before the bootstrap resolved
// the active space sit on an empty first result forever.
bumpScopeCursor();
}
if (space?.id !== prevId) notifyHandlers(space);
}
/**
@ -172,10 +164,7 @@ export async function loadActiveSpace(opts: { force?: boolean } = {}): Promise<A
active = member;
status = 'ready';
writeActiveSpaceHint(member.id);
if (member.id !== prevId) {
notifyHandlers(member);
bumpScopeCursor();
}
if (member.id !== prevId) notifyHandlers(member);
return member;
}
@ -197,10 +186,7 @@ export async function loadActiveSpace(opts: { force?: boolean } = {}): Promise<A
active = { ...chosen, role: hinted ? hinted.role : 'owner' };
status = 'ready';
writeActiveSpaceHint(chosen.id);
if (active.id !== prevId) {
notifyHandlers(active);
bumpScopeCursor();
}
if (active.id !== prevId) notifyHandlers(active);
return active;
} catch (err) {
lastError = err instanceof Error ? err.message : String(err);

View file

@ -1,61 +0,0 @@
/**
* Dexie bridge for the scope-change signal.
*
* Problem this solves: `getInScopeSpaceIds()` reads from plain Svelte
* `$state` (`active-space.svelte.ts#active` + `current-user.ts#currentUserId`).
* Dexie's `liveQuery` only re-runs when a Dexie table it read changes;
* a `$state` assignment is invisible to it. The bootstrap sequence
* user opens `/wardrobe`, first querier runs before the active-space
* is resolved from Better Auth, returns empty, setActiveSpace fires,
* but the liveQuery never re-evaluates left modules stuck on an
* empty first result until an unrelated write woke them up.
*
* This module is the Dexie proxy. A single-row `_scopeCursor` table
* (schema: `_scopeCursor: 'id'`, registered in Dexie v45) acts as the
* change beacon. `bumpScopeCursor()` writes a new `{id:'active',
* bumpedAt}` row every time scope state changes; `touchScopeCursor()`
* reads the row on every scoped query so liveQuery subscribes to the
* table. Net effect: a scope change triggers one infra write, every
* dependent query re-runs with the fresh `getInScopeSpaceIds()`.
*
* Kept intentionally minimal (no encryption, no pending-change
* tracking, not in SYNC_APP_MAP) it's a liveness signal, not user
* data.
*/
import { db } from '../database';
const SCOPE_CURSOR_ID = 'active';
/**
* Write a new `_scopeCursor` row so any in-flight liveQuery that
* touched the table re-runs its querier. Swallows Dexie errors
* this is a best-effort signal, a missing bump only delays the
* re-evaluation until the next Dexie write to a tracked table.
* Safe to call before Dexie finishes opening; the write queues.
*/
export function bumpScopeCursor(): void {
void db
.table('_scopeCursor')
.put({ id: SCOPE_CURSOR_ID, bumpedAt: Date.now() })
.catch((err) => {
console.warn('[scope/cursor] bump failed', err);
});
}
/**
* Register a liveQuery-time subscription on `_scopeCursor`.
* Fire-and-forget: the Dexie read registers the subscription
* synchronously during the querier's execution, even though the
* returned Promise is not awaited. The async resolution itself is
* irrelevant liveQuery only cares that the table was read.
*
* Called from `scopedTable` / `scopedGet` so every scoped query
* automatically subscribes without the caller needing to know.
*/
export function touchScopeCursor(): void {
void db
.table('_scopeCursor')
.get(SCOPE_CURSOR_ID)
.catch(() => undefined);
}

View file

@ -24,7 +24,6 @@ import { getActiveSpaceId } from './active-space.svelte';
import { personalSpaceSentinel } from './bootstrap';
import { isModuleAllowedInSpace, type SpaceModuleId, type SpaceType } from '@mana/shared-types';
import { getActiveSpace } from './active-space.svelte';
import { touchScopeCursor } from './cursor';
export class ScopeNotReadyError extends Error {
constructor() {
@ -76,11 +75,6 @@ export function getInScopeSpaceIds(): string[] {
* first and the spaceId filter runs on the narrowed set.
*/
export function scopedTable<T, PK>(tableName: string): Collection<T, PK> {
// Register a liveQuery-time subscription on `_scopeCursor` so that
// setActiveSpace / setCurrentUserId changes trigger a re-run of this
// query. See data/scope/cursor.ts for the full rationale on the Dexie
// bridge for scope state.
touchScopeCursor();
const table = db.table(tableName) as Table<T, PK>;
const ids = getInScopeSpaceIds();
const check = (record: unknown) => {
@ -133,7 +127,6 @@ export function scopedForModule<T, PK>(
* compound queries with `.or()`, `.and()`, `.reverse()` first.
*/
export function scopedAnd<T, PK>(collection: Collection<T, PK>): Collection<T, PK> {
touchScopeCursor();
const ids = getInScopeSpaceIds();
return collection.and((record) => {
const r = record as { spaceId?: unknown };
@ -151,10 +144,6 @@ export function scopedAnd<T, PK>(collection: Collection<T, PK>): Collection<T, P
* is a single field comparison on the one row returned.
*/
export async function scopedGet<T>(tableName: string, id: string | number): Promise<T | undefined> {
// Register the liveQuery-time subscription same as scopedTable — a
// scopedGet inside a liveQuery (e.g. useGarment(id)) needs to re-run
// too when scope changes.
touchScopeCursor();
const record = (await db.table(tableName).get(id)) as T | undefined;
if (!record) return undefined;
const rec = record as { spaceId?: unknown };

View file

@ -9,29 +9,46 @@
* Why this exists:
* Dexie's `liveQuery` only re-runs when a Dexie table it read during
* evaluation is written to. The scope state `active` in
* `active-space.svelte.ts` and `currentUserId` in
* `current-user.svelte.ts` lives in Svelte 5 `$state` runes,
* which Dexie cannot observe. Without a bridge, a module mounted
* before the active-space bootstrap finished would render an empty
* first result and stay empty until some unrelated Dexie write
* `active-space.svelte.ts` and `currentUserId` in `current-user.ts`
* lives outside Dexie, so a scope change is invisible to the
* subscription. Without a bridge, a module mounted before the
* active-space bootstrap finished would render an empty first
* result and stay empty until some unrelated Dexie write
* accidentally re-triggered the querier.
*
* The clean fix: wrap the Dexie liveQuery subscription in an
* `$effect` that reads `getActiveSpaceId()` + `getEffectiveUserId()`
* at the top. Svelte 5 auto-tracks those reads as effect
* dependencies, and re-runs the effect tearing down the previous
* subscription and creating a fresh one whenever scope changes.
* Every call to the querier inside the new subscription evaluates
* `getInScopeSpaceIds()` fresh, so the visible result matches the
* active scope. No infra-tables, no hidden side-effects.
* The clean fix: wrap the Dexie `liveQuery` subscription in a
* Svelte 5 `$effect` whose dependency is a `scopeTick` `$state`
* counter. `setActiveSpace` (via `onActiveSpaceChanged`) and
* `setCurrentUserId` (via `onCurrentUserChanged`) bump the counter,
* which re-runs the effect tearing down the previous Dexie
* subscription and creating a fresh one. Each new subscription
* evaluates the querier with the up-to-date `getInScopeSpaceIds()`,
* so the visible result matches the active scope. No infra-tables,
* no hidden side-effects in `scopedTable`.
*
* Same return shape as `useLiveQueryWithDefault` for drop-in
* migration.
*/
import { liveQuery } from 'dexie';
import { getActiveSpaceId } from './active-space.svelte';
import { getEffectiveUserId } from '../current-user';
import { onActiveSpaceChanged } from './active-space.svelte';
import { onCurrentUserChanged } from '../current-user';
// Module-level scope-tick counter. A single counter is enough because
// every consumer just needs *some* reactive value to track — the
// liveQuery's querier itself reads the actual scope ids.
let scopeTick = $state(0);
// Wire both event buses to bump the tick once per real change. The
// handlers replay the current value on subscribe (so the tick fires
// once at module-init), then again on every flip. Subscriptions are
// permanent — there's only one of each per app session.
onActiveSpaceChanged(() => {
scopeTick++;
});
onCurrentUserChanged(() => {
scopeTick++;
});
export function useScopedLiveQuery<T>(
querier: () => T | Promise<T>,
@ -42,14 +59,15 @@ export function useScopedLiveQuery<T>(
let error = $state<unknown>(undefined);
$effect(() => {
// Tracking reads — these are the dependencies that trigger
// re-subscription. Reading the values (even discarding them)
// is enough; Svelte 5's $effect auto-registers the dep.
void getActiveSpaceId();
void getEffectiveUserId();
// Tracking read — Svelte 5 auto-registers this as a dependency.
// Whenever the tick changes (active space or user changed), the
// effect re-fires: the previous teardown unsubscribes the old
// Dexie observable, and a fresh one is created below with the
// up-to-date scope.
void scopeTick;
// Reset the loading flag on every re-subscribe so consumers
// can show a transient skeleton while the new querier runs.
// Reset the loading flag on every re-subscribe so consumers can
// show a transient skeleton while the new querier runs.
loading = true;
const subscription = liveQuery(querier).subscribe({
@ -65,7 +83,7 @@ export function useScopedLiveQuery<T>(
});
// Tear-down: Svelte runs this when the effect re-fires (scope
// changed) or the component unmounts. Replaces the old onDestroy
// changed) or the component unmounts. Replaces the onDestroy
// pattern from `useLiveQueryWithDefault`.
return () => subscription.unsubscribe();
});

View file

@ -6,7 +6,7 @@
* to know which space it's in.
*/
import { useLiveQueryWithDefault } from '@mana/local-store/svelte';
import { useScopedLiveQuery } from '$lib/data/scope/use-scoped-live-query.svelte';
import { decryptRecords } from '$lib/data/crypto';
import { scopedForModule, scopedGet } from '$lib/data/scope';
import { articleTagOps } from './stores/tags.svelte';
@ -61,7 +61,7 @@ export function toHighlight(local: LocalHighlight): Highlight {
// ─── Live Queries ─────────────────────────────────────────
export function useAllArticles() {
return useLiveQueryWithDefault(async () => {
return useScopedLiveQuery(async () => {
const locals = await scopedForModule<LocalArticle, string>('articles', 'articles').toArray();
const visible = locals.filter((a) => !a.deletedAt);
const decrypted = await decryptRecords('articles', visible);
@ -72,7 +72,7 @@ export function useAllArticles() {
}
export function useArticle(id: string) {
return useLiveQueryWithDefault(
return useScopedLiveQuery(
async () => {
// scopedGet returns undefined if the article belongs to another
// space — protects against URL-manipulated deep links.
@ -91,7 +91,7 @@ export function useArticle(id: string) {
* table, so the DetailView's TagField stays in sync with both sides.
*/
export function useArticleTagIds(articleId: string) {
return useLiveQueryWithDefault(async () => articleTagOps.getTagIds(articleId), [] as string[]);
return useScopedLiveQuery(async () => articleTagOps.getTagIds(articleId), [] as string[]);
}
/**
@ -100,7 +100,7 @@ export function useArticleTagIds(articleId: string) {
* Dexie query regardless of how many articles are shown.
*/
export function useArticleTagMap(articleIds: string[]) {
return useLiveQueryWithDefault(
return useScopedLiveQuery(
async () => articleTagOps.getTagIdsForMany(articleIds),
new Map<string, string[]>()
);
@ -132,7 +132,7 @@ const WEEK_MS = 7 * 24 * 60 * 60 * 1000;
* articles (needed for title-based top-sites grouping).
*/
export function useStats() {
return useLiveQueryWithDefault(
return useScopedLiveQuery(
async () => {
const [articleRows, highlightRows] = await Promise.all([
scopedForModule<LocalArticle, string>('articles', 'articles').toArray(),
@ -214,7 +214,7 @@ export interface HighlightWithArticle {
}
export function useAllHighlights() {
return useLiveQueryWithDefault(async () => {
return useScopedLiveQuery(async () => {
const [articleRows, highlightRows] = await Promise.all([
scopedForModule<LocalArticle, string>('articles', 'articles').toArray(),
scopedForModule<LocalHighlight, string>('articles', 'articleHighlights').toArray(),
@ -250,7 +250,7 @@ export function useAllHighlights() {
}
export function useArticleHighlights(articleId: string) {
return useLiveQueryWithDefault(async () => {
return useScopedLiveQuery(async () => {
// scopedForModule returns the scope-filtered Collection; we narrow
// to this article in a post-filter (O(highlights per space), tiny).
// Using scopedForModule instead of a direct indexed where() keeps the

View file

@ -4,7 +4,7 @@
* Read-side only mutations live in stores/body.svelte.ts.
*/
import { useLiveQueryWithDefault } from '@mana/local-store/svelte';
import { useScopedLiveQuery } from '$lib/data/scope/use-scoped-live-query.svelte';
import { decryptRecords } from '$lib/data/crypto';
import { db } from '$lib/data/database';
import { scopedForModule } from '$lib/data/scope';
@ -131,7 +131,7 @@ export function toBodyPhase(local: LocalBodyPhase): BodyPhase {
// ─── Live Queries ───────────────────────────────────────────
export function useAllBodyExercises() {
return useLiveQueryWithDefault(async () => {
return useScopedLiveQuery(async () => {
const locals = await scopedForModule<LocalBodyExercise, string>(
'body',
'bodyExercises'
@ -143,7 +143,7 @@ export function useAllBodyExercises() {
}
export function useAllBodyRoutines() {
return useLiveQueryWithDefault(async () => {
return useScopedLiveQuery(async () => {
const locals = await scopedForModule<LocalBodyRoutine, string>('body', 'bodyRoutines').sortBy(
'order'
);
@ -154,7 +154,7 @@ export function useAllBodyRoutines() {
}
export function useAllBodyWorkouts() {
return useLiveQueryWithDefault(async () => {
return useScopedLiveQuery(async () => {
const locals = await scopedForModule<LocalBodyWorkout, string>(
'body',
'bodyWorkouts'
@ -166,7 +166,7 @@ export function useAllBodyWorkouts() {
}
export function useAllBodySets() {
return useLiveQueryWithDefault(async () => {
return useScopedLiveQuery(async () => {
const locals = await scopedForModule<LocalBodySet, string>('body', 'bodySets').toArray();
const visible = locals.filter((s) => !s.deletedAt);
const decrypted = await decryptRecords('bodySets', visible);
@ -175,7 +175,7 @@ export function useAllBodySets() {
}
export function useSetsForWorkout(workoutId: string) {
return useLiveQueryWithDefault(async () => {
return useScopedLiveQuery(async () => {
const locals = await db
.table<LocalBodySet>('bodySets')
.where('workoutId')
@ -188,7 +188,7 @@ export function useSetsForWorkout(workoutId: string) {
}
export function useAllBodyMeasurements() {
return useLiveQueryWithDefault(async () => {
return useScopedLiveQuery(async () => {
const locals = await scopedForModule<LocalBodyMeasurement, string>(
'body',
'bodyMeasurements'
@ -200,7 +200,7 @@ export function useAllBodyMeasurements() {
}
export function useAllBodyChecks() {
return useLiveQueryWithDefault(async () => {
return useScopedLiveQuery(async () => {
const locals = await scopedForModule<LocalBodyCheck, string>('body', 'bodyChecks').toArray();
const visible = locals.filter((c) => !c.deletedAt);
const decrypted = await decryptRecords('bodyChecks', visible);
@ -209,7 +209,7 @@ export function useAllBodyChecks() {
}
export function useAllBodyPhases() {
return useLiveQueryWithDefault(async () => {
return useScopedLiveQuery(async () => {
const locals = await scopedForModule<LocalBodyPhase, string>('body', 'bodyPhases').toArray();
const visible = locals.filter((p) => !p.deletedAt);
const decrypted = await decryptRecords('bodyPhases', visible);
@ -231,7 +231,7 @@ export function useAllBodyPhases() {
* the helper is permissive so a future "year view" can extend it.
*/
export function useFoodMealsSince(since: string) {
return useLiveQueryWithDefault(async () => {
return useScopedLiveQuery(async () => {
const locals = await db.table<LocalMeal>('meals').where('date').aboveOrEqual(since).toArray();
const visible = locals.filter((m) => !m.deletedAt);
// Encrypted fields (description / portionSize / foods) get unwrapped

View file

@ -6,7 +6,7 @@
* matches the post-Spaces convention so lists respect the active space.
*/
import { useLiveQueryWithDefault } from '@mana/local-store/svelte';
import { useScopedLiveQuery } from '$lib/data/scope/use-scoped-live-query.svelte';
import { decryptRecords } from '$lib/data/crypto';
import { scopedForModule } from '$lib/data/scope';
import { campaignTable, templateTable, settingsTable } from './collections';
@ -77,7 +77,7 @@ export function toSettings(local: LocalBroadcastSettings): BroadcastSettings {
/** All campaigns in the active space, newest first by updatedAt. */
export function useAllCampaigns() {
return useLiveQueryWithDefault(async () => {
return useScopedLiveQuery(async () => {
const rows = await scopedForModule<LocalCampaign, string>(
'broadcast',
'broadcastCampaigns'
@ -89,7 +89,7 @@ export function useAllCampaigns() {
}
export function useAllTemplates() {
return useLiveQueryWithDefault(async () => {
return useScopedLiveQuery(async () => {
const rows = await scopedForModule<LocalBroadcastTemplate, string>(
'broadcast',
'broadcastTemplates'

View file

@ -2,7 +2,7 @@
* Reactive queries for Calc uses Dexie liveQuery on the unified DB.
*/
import { useLiveQueryWithDefault } from '@mana/local-store/svelte';
import { useScopedLiveQuery } from '$lib/data/scope/use-scoped-live-query.svelte';
import { db } from '$lib/data/database';
import { scopedForModule } from '$lib/data/scope';
import type { LocalCalculation, LocalSavedFormula } from './types';
@ -39,7 +39,7 @@ export function toSavedFormula(local: LocalSavedFormula): SavedFormula {
/** All calculations (history), newest first. */
export function useAllCalculations() {
return useLiveQueryWithDefault(async () => {
return useScopedLiveQuery(async () => {
const locals = await scopedForModule<LocalCalculation, string>(
'calc',
'calculations'
@ -53,7 +53,7 @@ export function useAllCalculations() {
/** All saved formulas. */
export function useAllSavedFormulas() {
return useLiveQueryWithDefault(async () => {
return useScopedLiveQuery(async () => {
const locals = await scopedForModule<LocalSavedFormula, string>(
'calc',
'savedFormulas'

View file

@ -10,7 +10,7 @@
* at init time; no manual fetch/refresh needed.
*/
import { useLiveQueryWithDefault } from '@mana/local-store/svelte';
import { useScopedLiveQuery } from '$lib/data/scope/use-scoped-live-query.svelte';
import { db } from '$lib/data/database';
import { scopedForModule, applyVisibility } from '$lib/data/scope';
import { decryptRecords } from '$lib/data/crypto';
@ -41,7 +41,7 @@ export function toCalendar(local: LocalCalendar): Calendar {
/** All calendars, auto-updates on any change. */
export function useAllCalendars() {
return useLiveQueryWithDefault(async () => {
return useScopedLiveQuery(async () => {
const locals = await scopedForModule<LocalCalendar, string>('calendar', 'calendars').toArray();
const visible = applyVisibility(locals).filter((c) => !c.deletedAt);
return visible.map(toCalendar);
@ -53,7 +53,7 @@ export function useAllCalendars() {
* with LocalEvent for native calendar events. Auto-updates on change.
*/
export function useAllCalendarItems() {
return useLiveQueryWithDefault(async () => {
return useScopedLiveQuery(async () => {
// Fetch all non-deleted timeBlocks (filter on plaintext deletedAt
// before paying the per-row decrypt cost). Scope filter narrows to
// the active space + visibility filter drops records the user isn't

View file

@ -7,7 +7,7 @@
* to the public types so consumers see plaintext.
*/
import { useLiveQueryWithDefault } from '@mana/local-store/svelte';
import { useScopedLiveQuery } from '$lib/data/scope/use-scoped-live-query.svelte';
import { db } from '$lib/data/database';
import { scopedForModule } from '$lib/data/scope';
import { decryptRecords } from '$lib/data/crypto';
@ -69,7 +69,7 @@ export function toMessage(local: LocalMessage): Message {
/** All non-archived conversations, sorted by pinned first then updatedAt desc. */
export function useAllConversations() {
return useLiveQueryWithDefault(async () => {
return useScopedLiveQuery(async () => {
const visible = (
await scopedForModule<LocalConversation, string>('chat', 'conversations').toArray()
).filter((c) => !c.deletedAt && !c.isArchived);
@ -80,7 +80,7 @@ export function useAllConversations() {
/** All archived conversations, sorted by updatedAt desc. */
export function useArchivedConversations() {
return useLiveQueryWithDefault(async () => {
return useScopedLiveQuery(async () => {
const visible = (
await scopedForModule<LocalConversation, string>('chat', 'conversations').toArray()
).filter((c) => !c.deletedAt && c.isArchived);
@ -93,7 +93,7 @@ export function useArchivedConversations() {
/** All templates, sorted by name. */
export function useAllTemplates() {
return useLiveQueryWithDefault(async () => {
return useScopedLiveQuery(async () => {
const visible = (
await scopedForModule<LocalTemplate, string>('chat', 'chatTemplates').toArray()
).filter((t) => !t.deletedAt);
@ -104,7 +104,7 @@ export function useAllTemplates() {
/** Messages for a specific conversation, sorted by createdAt asc. */
export function useConversationMessages(conversationId: string) {
return useLiveQueryWithDefault(async () => {
return useScopedLiveQuery(async () => {
const visible = (
await db
.table<LocalMessage>('messages')

View file

@ -5,7 +5,7 @@
* at init time; no manual fetch/refresh needed.
*/
import { useLiveQueryWithDefault } from '@mana/local-store/svelte';
import { useScopedLiveQuery } from '$lib/data/scope/use-scoped-live-query.svelte';
import { db } from '$lib/data/database';
import { scopedForModule } from '$lib/data/scope';
import type { LocalCity, LocalLocation, LocalFavorite } from './types';
@ -19,7 +19,7 @@ import type { LocalCity, LocalLocation, LocalFavorite } from './types';
/** All cities, sorted by name. Auto-updates on any change. */
export function useAllCities() {
return useLiveQueryWithDefault(async () => {
return useScopedLiveQuery(async () => {
const all = await scopedForModule<LocalCity, string>('citycorners', 'cities').toArray();
return all.filter((c) => !c.deletedAt).sort((a, b) => a.name.localeCompare(b.name));
}, [] as LocalCity[]);
@ -27,7 +27,7 @@ export function useAllCities() {
/** All locations, sorted by name. Auto-updates on any change. */
export function useAllLocations() {
return useLiveQueryWithDefault(async () => {
return useScopedLiveQuery(async () => {
const all = await scopedForModule<LocalLocation, string>(
'citycorners',
'ccLocations'
@ -38,7 +38,7 @@ export function useAllLocations() {
/** All favorites. Auto-updates on any change. */
export function useAllFavorites() {
return useLiveQueryWithDefault(async () => {
return useScopedLiveQuery(async () => {
const all = await scopedForModule<LocalFavorite, string>(
'citycorners',
'ccFavorites'

View file

@ -8,7 +8,7 @@
* in this module, panels are picture rows).
*/
import { useLiveQueryWithDefault } from '@mana/local-store/svelte';
import { useScopedLiveQuery } from '$lib/data/scope/use-scoped-live-query.svelte';
import { scopedForModule } from '$lib/data/scope';
import { decryptRecords } from '$lib/data/crypto';
import type { LocalImage, Image } from '$lib/modules/picture/types';
@ -17,7 +17,7 @@ import { toStory, type ComicStory, type ComicStyle, type LocalComicStory } from
/** All non-archived, non-deleted stories in the active space, newest first. */
export function useAllStories() {
return useLiveQueryWithDefault<ComicStory[]>(async () => {
return useScopedLiveQuery<ComicStory[]>(async () => {
const locals = await scopedForModule<LocalComicStory, string>(
'comic',
'comicStories'
@ -32,7 +32,7 @@ export function useAllStories() {
/** Stories filtered by style — used by the style-tabs view in M5 list tool. */
export function useStoriesByStyle(style: ComicStyle) {
return useLiveQueryWithDefault<ComicStory[]>(async () => {
return useScopedLiveQuery<ComicStory[]>(async () => {
const locals = await scopedForModule<LocalComicStory, string>('comic', 'comicStories')
.and((row) => row.style === style)
.toArray();
@ -52,7 +52,7 @@ export function useStoriesByStyle(style: ComicStyle) {
* single-image hook today.
*/
export function usePanelImage(imageId: string | null) {
return useLiveQueryWithDefault<Image | null>(async () => {
return useScopedLiveQuery<Image | null>(async () => {
if (!imageId) return null;
const locals = await scopedForModule<LocalImage, string>('picture', 'images')
.and((row) => row.id === imageId)
@ -66,7 +66,7 @@ export function usePanelImage(imageId: string | null) {
/** A single story by id, live-updating. Null while loading / missing. */
export function useStory(id: string | null) {
return useLiveQueryWithDefault<ComicStory | null>(async () => {
return useScopedLiveQuery<ComicStory | null>(async () => {
if (!id) return null;
const locals = await scopedForModule<LocalComicStory, string>('comic', 'comicStories')
.and((row) => row.id === id)
@ -87,7 +87,7 @@ export function useStory(id: string | null) {
* ordered list but not deleted.
*/
export function useStoryPanels(storyId: string | null) {
return useLiveQueryWithDefault<Image[]>(async () => {
return useScopedLiveQuery<Image[]>(async () => {
if (!storyId) return [];
const locals = await scopedForModule<LocalImage, string>('picture', 'images')
.and((row) => row.comicStoryId === storyId)
@ -111,7 +111,7 @@ export function useStoriesByInput(
module: 'journal' | 'notes' | 'library' | 'writing' | 'calendar' | null,
entryId: string | null
) {
return useLiveQueryWithDefault<ComicStory[]>(async () => {
return useScopedLiveQuery<ComicStory[]>(async () => {
if (!module || !entryId) return [];
const locals = await scopedForModule<LocalComicStory, string>(
'comic',

View file

@ -2,13 +2,13 @@
* Companion Queries Reactive reads for conversations and messages.
*/
import { useLiveQueryWithDefault } from '@mana/local-store/svelte';
import { useScopedLiveQuery } from '$lib/data/scope/use-scoped-live-query.svelte';
import { db } from '$lib/data/database';
import { scopedForModule } from '$lib/data/scope';
import type { LocalConversation, LocalMessage } from './types';
export function useConversations() {
return useLiveQueryWithDefault<LocalConversation[]>(async () => {
return useScopedLiveQuery<LocalConversation[]>(async () => {
try {
const all = await scopedForModule<LocalConversation, string>(
'companion',
@ -22,7 +22,7 @@ export function useConversations() {
}
export function useMessages(conversationId: string) {
return useLiveQueryWithDefault<LocalMessage[]>(async () => {
return useScopedLiveQuery<LocalMessage[]>(async () => {
if (!conversationId) return [];
try {
return await db

View file

@ -2,7 +2,7 @@
* Reactive queries & pure helpers for Contacts uses Dexie liveQuery on the unified DB.
*/
import { useLiveQueryWithDefault } from '@mana/local-store/svelte';
import { useScopedLiveQuery } from '$lib/data/scope/use-scoped-live-query.svelte';
import { db } from '$lib/data/database';
import { scopedForModule, applyVisibility } from '$lib/data/scope';
import { decryptRecords } from '$lib/data/crypto';
@ -53,7 +53,7 @@ export function toContact(local: LocalContact): Contact {
// ─── Live Queries ──────────────────────────────────────────
export function useAllContacts() {
return useLiveQueryWithDefault(async () => {
return useScopedLiveQuery(async () => {
const raw = await scopedForModule<LocalContact, string>('contacts', 'contacts').toArray();
const visible = applyVisibility(raw).filter((c) => !c.deletedAt);
const decrypted = await decryptRecords('contacts', visible);

View file

@ -6,7 +6,7 @@
* at init time; no manual fetch/refresh needed.
*/
import { useLiveQueryWithDefault } from '@mana/local-store/svelte';
import { useScopedLiveQuery } from '$lib/data/scope/use-scoped-live-query.svelte';
import { db } from '$lib/data/database';
import { scopedForModule } from '$lib/data/scope';
import { decryptRecords } from '$lib/data/crypto';
@ -49,7 +49,7 @@ export function toDocument(local: LocalDocument): Document {
/** All spaces, sorted by name. Auto-updates on any change. */
export function useAllSpaces() {
return useLiveQueryWithDefault(async () => {
return useScopedLiveQuery(async () => {
const locals = await scopedForModule<LocalContextSpace, string>(
'context',
'contextSpaces'
@ -63,7 +63,7 @@ export function useAllSpaces() {
/** All documents, sorted by updated_at desc. Auto-updates on any change. */
export function useAllDocuments() {
return useLiveQueryWithDefault(async () => {
return useScopedLiveQuery(async () => {
const locals = await scopedForModule<LocalDocument, string>('context', 'documents').toArray();
const visible = locals.filter((d) => !d.deletedAt);
const decrypted = await decryptRecords('documents', visible);
@ -75,7 +75,7 @@ export function useAllDocuments() {
/** Documents for a specific context-space. Auto-updates on any change. */
export function useSpaceDocuments(contextSpaceId: string) {
return useLiveQueryWithDefault(async () => {
return useScopedLiveQuery(async () => {
const locals = await db
.table<LocalDocument>('documents')
.where('contextSpaceId')

View file

@ -7,7 +7,7 @@ import { formatDate } from '$lib/i18n/format';
* then decryptRecords the visible set before mapping to public types.
*/
import { useLiveQueryWithDefault } from '@mana/local-store/svelte';
import { useScopedLiveQuery } from '$lib/data/scope/use-scoped-live-query.svelte';
import { db } from '$lib/data/database';
import { scopedForModule } from '$lib/data/scope';
import { decryptRecords } from '$lib/data/crypto';
@ -63,7 +63,7 @@ export function toDreamSymbol(local: LocalDreamSymbol): DreamSymbol {
// ─── Live Queries ──────────────────────────────────────────
export function useAllDreams() {
return useLiveQueryWithDefault(async () => {
return useScopedLiveQuery(async () => {
const visible = (
await scopedForModule<LocalDream, string>('dreams', 'dreams').toArray()
).filter((d) => !d.deletedAt && !d.isArchived);
@ -76,7 +76,7 @@ export function useAllDreams() {
}
export function useAllDreamSymbols() {
return useLiveQueryWithDefault(async () => {
return useScopedLiveQuery(async () => {
const visible = (
await scopedForModule<LocalDreamSymbol, string>('dreams', 'dreamSymbols').toArray()
).filter((s) => !s.deletedAt);

View file

@ -2,7 +2,7 @@
* Reactive Queries & Pure Helpers for Drink module.
*/
import { useLiveQueryWithDefault } from '@mana/local-store/svelte';
import { useScopedLiveQuery } from '$lib/data/scope/use-scoped-live-query.svelte';
import { decryptRecords } from '$lib/data/crypto';
import { db } from '$lib/data/database';
import { scopedForModule } from '$lib/data/scope';
@ -45,7 +45,7 @@ export function toDrinkPreset(local: LocalDrinkPreset): DrinkPreset {
// ─── Live Queries ─────────────────────────────────────────
export function useAllDrinkEntries() {
return useLiveQueryWithDefault(async () => {
return useScopedLiveQuery(async () => {
const locals = await db
.table<LocalDrinkEntry>('drinkEntries')
.orderBy('date')
@ -58,7 +58,7 @@ export function useAllDrinkEntries() {
}
export function useAllDrinkPresets() {
return useLiveQueryWithDefault(async () => {
return useScopedLiveQuery(async () => {
const locals = await scopedForModule<LocalDrinkPreset, string>('drink', 'drinkPresets').sortBy(
'order'
);

View file

@ -4,7 +4,7 @@
* Joins LocalSocialEvent with its TimeBlock to produce the UI-facing SocialEvent.
*/
import { useLiveQueryWithDefault } from '@mana/local-store/svelte';
import { useScopedLiveQuery } from '$lib/data/scope/use-scoped-live-query.svelte';
import { db } from '$lib/data/database';
import { scopedForModule } from '$lib/data/scope';
import { decryptRecords } from '$lib/data/crypto';
@ -87,7 +87,7 @@ export function toEventGuest(local: LocalEventGuest): EventGuest {
/** All non-deleted events, joined with their TimeBlock for time fields. */
export function useAllEvents() {
return useLiveQueryWithDefault(async () => {
return useScopedLiveQuery(async () => {
const locals = await scopedForModule<LocalSocialEvent, string>(
'events',
'socialEvents'
@ -101,7 +101,7 @@ export function useAllEvents() {
/** Upcoming events (startTime >= now), sorted ascending. */
export function useUpcomingEvents() {
return useLiveQueryWithDefault(async () => {
return useScopedLiveQuery(async () => {
const locals = await scopedForModule<LocalSocialEvent, string>(
'events',
'socialEvents'
@ -119,7 +119,7 @@ export function useUpcomingEvents() {
/** Past events. */
export function usePastEvents() {
return useLiveQueryWithDefault(async () => {
return useScopedLiveQuery(async () => {
const locals = await scopedForModule<LocalSocialEvent, string>(
'events',
'socialEvents'
@ -137,7 +137,7 @@ export function usePastEvents() {
/** Single event by ID. */
export function useEvent(eventId: () => string) {
return useLiveQueryWithDefault(
return useScopedLiveQuery(
async () => {
const id = eventId();
if (!id) return null;
@ -153,7 +153,7 @@ export function useEvent(eventId: () => string) {
/** All guests across all events, grouped by eventId. Useful for list views. */
export function useGuestsByEvent() {
return useLiveQueryWithDefault(
return useScopedLiveQuery(
async () => {
const all = await scopedForModule<LocalEventGuest, string>('events', 'eventGuests').toArray();
const visible = all.filter((g) => !g.deletedAt);
@ -173,7 +173,7 @@ export function useGuestsByEvent() {
/** Guests for a single event. */
export function useEventGuests(eventId: () => string) {
return useLiveQueryWithDefault(async () => {
return useScopedLiveQuery(async () => {
const id = eventId();
if (!id) return [];
const guests = await db
@ -189,7 +189,7 @@ export function useEventGuests(eventId: () => string) {
/** Bring-list items for a single event, sorted by order. */
export function useEventItems(eventId: () => string) {
return useLiveQueryWithDefault(async () => {
return useScopedLiveQuery(async () => {
const id = eventId();
if (!id) return [];
const items = await db

View file

@ -2,7 +2,7 @@
* Reactive Queries & Pure Helpers for Finance module.
*/
import { useLiveQueryWithDefault } from '@mana/local-store/svelte';
import { useScopedLiveQuery } from '$lib/data/scope/use-scoped-live-query.svelte';
import { db } from '$lib/data/database';
import { scopedForModule } from '$lib/data/scope';
import { decryptRecords } from '$lib/data/crypto';
@ -45,7 +45,7 @@ export function toCategory(local: LocalFinanceCategory): FinanceCategory {
// ─── Live Queries ──────────────────────────────────────────
export function useAllTransactions() {
return useLiveQueryWithDefault(async () => {
return useScopedLiveQuery(async () => {
const visible = (
await scopedForModule<LocalTransaction, string>('finance', 'transactions').toArray()
).filter((t) => !t.deletedAt);
@ -57,7 +57,7 @@ export function useAllTransactions() {
}
export function useAllCategories() {
return useLiveQueryWithDefault(async () => {
return useScopedLiveQuery(async () => {
const locals = await scopedForModule<LocalFinanceCategory, string>(
'finance',
'financeCategories'

View file

@ -1,4 +1,4 @@
import { useLiveQueryWithDefault } from '@mana/local-store/svelte';
import { useScopedLiveQuery } from '$lib/data/scope/use-scoped-live-query.svelte';
import { db } from '$lib/data/database';
import { scopedForModule } from '$lib/data/scope';
import { decryptRecords } from '$lib/data/crypto';
@ -35,7 +35,7 @@ export function toFirst(local: LocalFirst): First {
// ─── Live Queries ──────────────────────────────────────────
export function useAllFirsts() {
return useLiveQueryWithDefault(async () => {
return useScopedLiveQuery(async () => {
const visible = (
await scopedForModule<LocalFirst, string>('firsts', 'firsts').toArray()
).filter((f) => !f.deletedAt && !f.isArchived);
@ -58,7 +58,7 @@ export function useAllFirsts() {
}
export function useDreams() {
return useLiveQueryWithDefault(async () => {
return useScopedLiveQuery(async () => {
const visible = (
await scopedForModule<LocalFirst, string>('firsts', 'firsts').toArray()
).filter((f) => !f.deletedAt && !f.isArchived && f.status === 'dream');
@ -72,7 +72,7 @@ export function useDreams() {
}
export function useLivedFirsts() {
return useLiveQueryWithDefault(async () => {
return useScopedLiveQuery(async () => {
const visible = (
await scopedForModule<LocalFirst, string>('firsts', 'firsts').toArray()
).filter((f) => !f.deletedAt && !f.isArchived && f.status === 'lived');
@ -84,7 +84,7 @@ export function useLivedFirsts() {
}
export function useFirstsByPerson(personId: string) {
return useLiveQueryWithDefault(async () => {
return useScopedLiveQuery(async () => {
const visible = (
await scopedForModule<LocalFirst, string>('firsts', 'firsts').toArray()
).filter((f) => !f.deletedAt && !f.isArchived && f.personIds?.includes(personId));

View file

@ -4,7 +4,7 @@
* Uses table names: meals, goals, foodFavorites.
*/
import { useLiveQueryWithDefault } from '@mana/local-store/svelte';
import { useScopedLiveQuery } from '$lib/data/scope/use-scoped-live-query.svelte';
import { db } from '$lib/data/database';
import { scopedForModule } from '$lib/data/scope';
import { decryptRecords } from '$lib/data/crypto';
@ -43,7 +43,7 @@ export function toMealWithNutrition(local: LocalMeal): MealWithNutrition {
/** All meals, auto-updates on any change. */
export function useAllMeals() {
return useLiveQueryWithDefault(async () => {
return useScopedLiveQuery(async () => {
const locals = await scopedForModule<LocalMeal, string>('food', 'meals').toArray();
const visible = locals.filter((m) => !m.deletedAt);
const decrypted = await decryptRecords('meals', visible);
@ -53,7 +53,7 @@ export function useAllMeals() {
/**
* Look up a single meal by id and decrypt it. Used by the detail page,
* which inlines its own useLiveQueryWithDefault wrapper so the querier
* which inlines its own useScopedLiveQuery wrapper so the querier
* can capture the route param directly (matches plants DetailView pattern).
*/
export async function loadMealById(id: string): Promise<MealWithNutrition | null> {
@ -65,7 +65,7 @@ export async function loadMealById(id: string): Promise<MealWithNutrition | null
/** All goals, auto-updates on any change. */
export function useAllGoals() {
return useLiveQueryWithDefault(async () => {
return useScopedLiveQuery(async () => {
const locals = await scopedForModule<LocalGoal, string>('food', 'goals').toArray();
return locals.filter((g) => !g.deletedAt);
}, [] as LocalGoal[]);
@ -73,7 +73,7 @@ export function useAllGoals() {
/** All favorites, auto-updates on any change. */
export function useAllFavorites() {
return useLiveQueryWithDefault(async () => {
return useScopedLiveQuery(async () => {
const locals = await scopedForModule<LocalFavorite, string>('food', 'foodFavorites').toArray();
return locals.filter((f) => !f.deletedAt);
}, [] as LocalFavorite[]);

View file

@ -5,7 +5,7 @@
* content fields on the fly before handing to the UI.
*/
import { useLiveQueryWithDefault } from '@mana/local-store/svelte';
import { useScopedLiveQuery } from '$lib/data/scope/use-scoped-live-query.svelte';
import { db } from '$lib/data/database';
import { scopedForModule } from '$lib/data/scope';
import { decryptRecords } from '$lib/data/crypto';
@ -78,7 +78,7 @@ export function toRun(local: LocalRun): Run {
// ─── Live Queries ──────────────────────────────────────────
export function useAllGuides() {
return useLiveQueryWithDefault(async () => {
return useScopedLiveQuery(async () => {
const all = await scopedForModule<LocalGuide, string>('guides', 'guides').toArray();
const visible = all.filter((g) => !g.deletedAt);
const decrypted = await decryptRecords('guides', visible);
@ -87,7 +87,7 @@ export function useAllGuides() {
}
export function useGuide(id: () => string) {
return useLiveQueryWithDefault(
return useScopedLiveQuery(
async () => {
const guideId = id();
if (!guideId) return null;
@ -101,7 +101,7 @@ export function useGuide(id: () => string) {
}
export function useSections(guideId: () => string) {
return useLiveQueryWithDefault(async () => {
return useScopedLiveQuery(async () => {
const gid = guideId();
if (!gid) return [];
const all = await db.table<LocalSection>('sections').where('guideId').equals(gid).toArray();
@ -112,7 +112,7 @@ export function useSections(guideId: () => string) {
}
export function useSteps(guideId: () => string) {
return useLiveQueryWithDefault(async () => {
return useScopedLiveQuery(async () => {
const gid = guideId();
if (!gid) return [];
const all = await db.table<LocalStep>('steps').where('guideId').equals(gid).toArray();
@ -123,7 +123,7 @@ export function useSteps(guideId: () => string) {
}
export function useLatestRun(guideId: () => string) {
return useLiveQueryWithDefault(
return useScopedLiveQuery(
async () => {
const gid = guideId();
if (!gid) return null;
@ -138,7 +138,7 @@ export function useLatestRun(guideId: () => string) {
}
export function useRunsByGuide() {
return useLiveQueryWithDefault(async () => {
return useScopedLiveQuery(async () => {
const all = await scopedForModule<LocalRun, string>('guides', 'runs').toArray();
const visible = all.filter((r) => !r.deletedAt);
const map = new Map<string, Run>();

View file

@ -1,10 +1,10 @@
/**
* Reactive Queries & Pure Helpers for Habits module.
*
* Uses useLiveQueryWithDefault on the unified database.
* Uses useScopedLiveQuery on the unified database.
*/
import { useLiveQueryWithDefault } from '@mana/local-store/svelte';
import { useScopedLiveQuery } from '$lib/data/scope/use-scoped-live-query.svelte';
import { db } from '$lib/data/database';
import { scopedForModule } from '$lib/data/scope';
import type { LocalHabit, LocalHabitLog, Habit, HabitLog } from './types';
@ -44,14 +44,14 @@ export function toHabitLog(local: LocalHabitLog, block?: LocalTimeBlock | null):
// ─── Live Queries ──────────────────────────────────────────
export function useAllHabits() {
return useLiveQueryWithDefault(async () => {
return useScopedLiveQuery(async () => {
const locals = await scopedForModule<LocalHabit, string>('habits', 'habits').sortBy('order');
return locals.filter((h) => !h.deletedAt).map(toHabit);
}, [] as Habit[]);
}
export function useAllHabitLogs() {
return useLiveQueryWithDefault(async () => {
return useScopedLiveQuery(async () => {
const locals = await scopedForModule<LocalHabitLog, string>('habits', 'habitLogs').toArray();
const active = locals.filter((l) => !l.deletedAt);
@ -68,7 +68,7 @@ export function useAllHabitLogs() {
}
export function useHabitLogsForHabit(habitId: string) {
return useLiveQueryWithDefault(async () => {
return useScopedLiveQuery(async () => {
const locals = await db
.table<LocalHabitLog>('habitLogs')
.where('habitId')

View file

@ -4,7 +4,7 @@
* Uses prefixed table names: invCollections, invItems, invLocations, invCategories.
*/
import { useLiveQueryWithDefault } from '@mana/local-store/svelte';
import { useScopedLiveQuery } from '$lib/data/scope/use-scoped-live-query.svelte';
import { db } from '$lib/data/database';
import { scopedForModule } from '$lib/data/scope';
import { decryptRecords } from '$lib/data/crypto';
@ -161,7 +161,7 @@ export function toCategory(local: LocalCategory): Category {
// ─── Live Queries ──────────────────────────────────────────
export function useAllCollections() {
return useLiveQueryWithDefault(async () => {
return useScopedLiveQuery(async () => {
const locals = await scopedForModule<LocalCollection, string>(
'inventory',
'invCollections'
@ -171,7 +171,7 @@ export function useAllCollections() {
}
export function useAllItems() {
return useLiveQueryWithDefault(async () => {
return useScopedLiveQuery(async () => {
const visible = (
await scopedForModule<LocalItem, string>('inventory', 'invItems').toArray()
).filter((i) => !i.deletedAt);
@ -181,7 +181,7 @@ export function useAllItems() {
}
export function useAllLocations() {
return useLiveQueryWithDefault(async () => {
return useScopedLiveQuery(async () => {
const locals = await scopedForModule<LocalLocation, string>(
'inventory',
'invLocations'
@ -191,7 +191,7 @@ export function useAllLocations() {
}
export function useAllCategories() {
return useLiveQueryWithDefault(async () => {
return useScopedLiveQuery(async () => {
const locals = await scopedForModule<LocalCategory, string>(
'inventory',
'invCategories'

View file

@ -2,7 +2,7 @@
* Reactive queries and pure helpers for the Invoices module.
*/
import { useLiveQueryWithDefault } from '@mana/local-store/svelte';
import { useScopedLiveQuery } from '$lib/data/scope/use-scoped-live-query.svelte';
import { decryptRecords } from '$lib/data/crypto';
import { scopedForModule } from '$lib/data/scope';
import type {
@ -77,7 +77,7 @@ export function toInvoiceClient(local: LocalInvoiceClient): InvoiceClient {
* and persisting would require a cron to flip it, creating sync churn.
*/
export function useAllInvoices() {
return useLiveQueryWithDefault(async () => {
return useScopedLiveQuery(async () => {
const locals = await scopedForModule<LocalInvoice, string>('invoices', 'invoices').toArray();
const visible = locals.filter((i) => !i.deletedAt);
const decrypted = await decryptRecords('invoices', visible);
@ -95,7 +95,7 @@ export function useAllInvoices() {
}
export function useInvoiceClients() {
return useLiveQueryWithDefault(async () => {
return useScopedLiveQuery(async () => {
const locals = await scopedForModule<LocalInvoiceClient, string>(
'invoices',
'invoiceClients'

View file

@ -7,7 +7,7 @@ import { formatDate } from '$lib/i18n/format';
* then decryptRecords the visible set before mapping to public types.
*/
import { useLiveQueryWithDefault } from '@mana/local-store/svelte';
import { useScopedLiveQuery } from '$lib/data/scope/use-scoped-live-query.svelte';
import { db } from '$lib/data/database';
import { scopedForModule } from '$lib/data/scope';
import { decryptRecords } from '$lib/data/crypto';
@ -36,7 +36,7 @@ export function toJournalEntry(local: LocalJournalEntry): JournalEntry {
// ─── Live Queries ──────────────────────────────────────────
export function useAllJournalEntries() {
return useLiveQueryWithDefault(async () => {
return useScopedLiveQuery(async () => {
const visible = (
await scopedForModule<LocalJournalEntry, string>('journal', 'journalEntries').toArray()
).filter((e) => !e.deletedAt && !e.isArchived);
@ -49,7 +49,7 @@ export function useAllJournalEntries() {
}
export function useJournalEntry(id: string) {
return useLiveQueryWithDefault(
return useScopedLiveQuery(
async () => {
const local = await db.table<LocalJournalEntry>('journalEntries').get(id);
if (!local || local.deletedAt) return null;

View file

@ -2,7 +2,7 @@
* Mail module queries drafts (local-first) + API data helpers.
*/
import { useLiveQueryWithDefault } from '@mana/local-store/svelte';
import { useScopedLiveQuery } from '$lib/data/scope/use-scoped-live-query.svelte';
import { decryptRecords } from '$lib/data/crypto';
import { db } from '$lib/data/database';
import { scopedForModule } from '$lib/data/scope';
@ -29,7 +29,7 @@ export function toMailDraft(local: LocalMailDraft): MailDraft {
// ─── Live Queries (local drafts) ────────────────────────────
export function useAllDrafts() {
return useLiveQueryWithDefault(async () => {
return useScopedLiveQuery(async () => {
const locals = await scopedForModule<LocalMailDraft, string>('mail', 'mailDrafts').toArray();
const visible = locals.filter((d) => !d.deletedAt);
const decrypted = await decryptRecords('mailDrafts', visible);

View file

@ -4,7 +4,7 @@
* Read-side only mutations live in stores/meditate.svelte.ts.
*/
import { useLiveQueryWithDefault } from '@mana/local-store/svelte';
import { useScopedLiveQuery } from '$lib/data/scope/use-scoped-live-query.svelte';
import { decryptRecords } from '$lib/data/crypto';
import { db } from '$lib/data/database';
import { scopedForModule } from '$lib/data/scope';
@ -67,7 +67,7 @@ export function toMeditateSettings(local: LocalMeditateSettings): MeditateSettin
// ─── Live Queries ───────────────────────────────────────────
export function useAllPresets() {
return useLiveQueryWithDefault(async () => {
return useScopedLiveQuery(async () => {
const locals = await db
.table<LocalMeditatePreset>('meditatePresets')
.orderBy('order')
@ -79,7 +79,7 @@ export function useAllPresets() {
}
export function useAllSessions() {
return useLiveQueryWithDefault(async () => {
return useScopedLiveQuery(async () => {
const locals = await db
.table<LocalMeditateSession>('meditateSessions')
.orderBy('startedAt')
@ -92,7 +92,7 @@ export function useAllSessions() {
}
export function useSettings() {
return useLiveQueryWithDefault(
return useScopedLiveQuery(
async () => {
const locals = await scopedForModule<LocalMeditateSettings, string>(
'meditate',

View file

@ -2,7 +2,7 @@
* Reactive queries & pure helpers for Memoro uses Dexie liveQuery on the unified DB.
*/
import { useLiveQueryWithDefault } from '@mana/local-store/svelte';
import { useScopedLiveQuery } from '$lib/data/scope/use-scoped-live-query.svelte';
import { db } from '$lib/data/database';
import { scopedForModule } from '$lib/data/scope';
import { decryptRecords } from '$lib/data/crypto';
@ -68,7 +68,7 @@ export function toSpace(local: LocalSpace): Space {
/** All non-archived memos, sorted by pinned first then createdAt desc. */
export function useAllMemos() {
return useLiveQueryWithDefault(async () => {
return useScopedLiveQuery(async () => {
const visible = (await scopedForModule<LocalMemo, string>('memoro', 'memos').toArray()).filter(
(m) => !m.deletedAt && !m.isArchived
);
@ -79,7 +79,7 @@ export function useAllMemos() {
/** All archived memos, sorted by updatedAt desc. */
export function useArchivedMemos() {
return useLiveQueryWithDefault(async () => {
return useScopedLiveQuery(async () => {
const visible = (await scopedForModule<LocalMemo, string>('memoro', 'memos').toArray()).filter(
(m) => !m.deletedAt && m.isArchived
);
@ -92,7 +92,7 @@ export function useArchivedMemos() {
/** Memories for a specific memo. */
export function useMemoriesByMemo(memoId: string) {
return useLiveQueryWithDefault(async () => {
return useScopedLiveQuery(async () => {
const visible = (
await db.table<LocalMemory>('memories').where('memoId').equals(memoId).toArray()
).filter((m) => !m.deletedAt);
@ -106,7 +106,7 @@ export { useAllTags } from '@mana/shared-stores';
/** All memo-tag associations. */
export function useAllMemoTags() {
return useLiveQueryWithDefault(async () => {
return useScopedLiveQuery(async () => {
const locals = await scopedForModule<LocalMemoTag, string>('memoro', 'memoTags').toArray();
return locals.filter((mt) => !mt.deletedAt);
}, [] as LocalMemoTag[]);
@ -114,7 +114,7 @@ export function useAllMemoTags() {
/** All spaces. */
export function useAllSpaces() {
return useLiveQueryWithDefault(async () => {
return useScopedLiveQuery(async () => {
const locals = await scopedForModule<LocalSpace, string>('memoro', 'memoroSpaces').toArray();
return locals.filter((s) => !s.deletedAt).map(toSpace);
}, [] as Space[]);

View file

@ -2,7 +2,7 @@
* Reactive Queries & Pure Helpers for the Mood module.
*/
import { useLiveQueryWithDefault } from '@mana/local-store/svelte';
import { useScopedLiveQuery } from '$lib/data/scope/use-scoped-live-query.svelte';
import { decryptRecords } from '$lib/data/crypto';
import { db } from '$lib/data/database';
import { scopedForModule, applyVisibility } from '$lib/data/scope';
@ -46,7 +46,7 @@ export function toMoodSettings(local: LocalMoodSettings): MoodSettings {
// ─── Live Queries ───────────────────────────────────────────
export function useAllMoodEntries() {
return useLiveQueryWithDefault(async () => {
return useScopedLiveQuery(async () => {
const locals = await scopedForModule<LocalMoodEntry, string>('mood', 'moodEntries').toArray();
const visible = applyVisibility(locals).filter((e) => !e.deletedAt);
const decrypted = await decryptRecords('moodEntries', visible);
@ -58,7 +58,7 @@ export function useAllMoodEntries() {
}
export function useMoodSettings() {
return useLiveQueryWithDefault(
return useScopedLiveQuery(
async () => {
const locals = await scopedForModule<LocalMoodSettings, string>(
'mood',

View file

@ -2,7 +2,7 @@
* Reactive queries for Moodlit uses Dexie liveQuery on the unified DB.
*/
import { useLiveQueryWithDefault } from '@mana/local-store/svelte';
import { useScopedLiveQuery } from '$lib/data/scope/use-scoped-live-query.svelte';
import { db } from '$lib/data/database';
import { scopedForModule } from '$lib/data/scope';
import type { LocalMood, LocalSequence, Mood } from './types';
@ -26,7 +26,7 @@ export function getMoodById(moods: Mood[], id: string): Mood | undefined {
/** All moods, sorted by name. */
export function useAllMoods() {
return useLiveQueryWithDefault(async () => {
return useScopedLiveQuery(async () => {
const locals = await scopedForModule<LocalMood, string>('moodlit', 'moods').toArray();
return locals.filter((m) => !m.deletedAt);
}, []);
@ -34,7 +34,7 @@ export function useAllMoods() {
/** All sequences, sorted by name. */
export function useAllSequences() {
return useLiveQueryWithDefault(async () => {
return useScopedLiveQuery(async () => {
const locals = await scopedForModule<LocalSequence, string>('moodlit', 'sequences').toArray();
return locals.filter((s) => !s.deletedAt);
}, []);

View file

@ -2,7 +2,7 @@
* Reactive queries & pure helpers for Music uses Dexie liveQuery on the unified DB.
*/
import { useLiveQueryWithDefault } from '@mana/local-store/svelte';
import { useScopedLiveQuery } from '$lib/data/scope/use-scoped-live-query.svelte';
import { db } from '$lib/data/database';
import { scopedForModule } from '$lib/data/scope';
import { decryptRecords } from '$lib/data/crypto';
@ -71,7 +71,7 @@ export function toProject(local: LocalProject): Project {
/** All songs, sorted by title. */
export function useAllSongs() {
return useLiveQueryWithDefault(async () => {
return useScopedLiveQuery(async () => {
const locals = await scopedForModule<LocalSong, string>('music', 'songs').toArray();
const visible = locals.filter((s) => !s.deletedAt);
// title is encrypted on disk; sort needs the plaintext value.
@ -82,7 +82,7 @@ export function useAllSongs() {
/** All playlists, sorted by name. */
export function useAllPlaylists() {
return useLiveQueryWithDefault(async () => {
return useScopedLiveQuery(async () => {
const locals = await scopedForModule<LocalPlaylist, string>(
'music',
'mukkePlaylists'
@ -95,7 +95,7 @@ export function useAllPlaylists() {
/** All playlist-song associations. */
export function useAllPlaylistSongs() {
return useLiveQueryWithDefault(async () => {
return useScopedLiveQuery(async () => {
const locals = await scopedForModule<LocalPlaylistSong, string>(
'music',
'playlistSongs'
@ -106,7 +106,7 @@ export function useAllPlaylistSongs() {
/** All projects, sorted by title. */
export function useAllProjects() {
return useLiveQueryWithDefault(async () => {
return useScopedLiveQuery(async () => {
const locals = await scopedForModule<LocalProject, string>('music', 'mukkeProjects').toArray();
return locals
.filter((p) => !p.deletedAt)
@ -117,7 +117,7 @@ export function useAllProjects() {
/** All markers for a given beat ID. */
export function useMarkersByBeat(beatId: string) {
return useLiveQueryWithDefault(async () => {
return useScopedLiveQuery(async () => {
const locals = await db.table<LocalMarker>('markers').where('beatId').equals(beatId).toArray();
return locals.filter((m) => !m.deletedAt).sort((a, b) => a.startTime - b.startTime);
}, []);

View file

@ -15,7 +15,7 @@ import { formatDate } from '$lib/i18n/format';
* a future concern only if note volume per user grows past that.
*/
import { useLiveQueryWithDefault } from '@mana/local-store/svelte';
import { useScopedLiveQuery } from '$lib/data/scope/use-scoped-live-query.svelte';
import { db } from '$lib/data/database';
import { scopedForModule, scopedGet, applyVisibility } from '$lib/data/scope';
import { decryptRecords } from '$lib/data/crypto';
@ -42,7 +42,7 @@ export function toNote(local: LocalNote): Note {
// ─── Live Queries ──────────────────────────────────────────
export function useAllNotes() {
return useLiveQueryWithDefault(async () => {
return useScopedLiveQuery(async () => {
// Filter on plaintext metadata first — none of these fields are
// in the encryption registry, so they stay readable even with
// the vault locked. Cuts the decrypt workload to only what the
@ -64,7 +64,7 @@ export function useAllNotes() {
/** Single note by id, decrypted. Used by detail views. */
export function useNote(id: string) {
return useLiveQueryWithDefault(
return useScopedLiveQuery(
async () => {
// scopedGet returns undefined if the note belongs to another
// space — protects against URL-manipulated deep links.

View file

@ -2,7 +2,7 @@
* Reactive Queries & Pure Helpers for Periods module.
*/
import { useLiveQueryWithDefault } from '@mana/local-store/svelte';
import { useScopedLiveQuery } from '$lib/data/scope/use-scoped-live-query.svelte';
import { db } from '$lib/data/database';
import { scopedForModule } from '$lib/data/scope';
import { decryptRecord, decryptRecords } from '$lib/data/crypto';
@ -65,7 +65,7 @@ export function toPeriodSymptom(local: LocalPeriodSymptom): PeriodSymptom {
// ─── Live Queries ──────────────────────────────────────────
export function useAllPeriods() {
return useLiveQueryWithDefault(async () => {
return useScopedLiveQuery(async () => {
const visible = (
await scopedForModule<LocalPeriod, string>('period', 'periods').toArray()
).filter((c) => !c.deletedAt && !c.isArchived);
@ -75,7 +75,7 @@ export function useAllPeriods() {
}
export function useCurrentPeriod() {
return useLiveQueryWithDefault(
return useScopedLiveQuery(
async () => {
const locals = await scopedForModule<LocalPeriod, string>('period', 'periods').toArray();
const real = locals.filter((c) => !c.deletedAt && !c.isArchived && !c.isPredicted);
@ -89,7 +89,7 @@ export function useCurrentPeriod() {
}
export function useAllDayLogs() {
return useLiveQueryWithDefault(async () => {
return useScopedLiveQuery(async () => {
const visible = (
await scopedForModule<LocalPeriodDayLog, string>('period', 'periodDayLogs').toArray()
).filter((l) => !l.deletedAt);
@ -99,7 +99,7 @@ export function useAllDayLogs() {
}
export function useDayLog(date: string) {
return useLiveQueryWithDefault(
return useScopedLiveQuery(
async () => {
const locals = await db
.table<LocalPeriodDayLog>('periodDayLogs')
@ -116,7 +116,7 @@ export function useDayLog(date: string) {
}
export function useAllSymptoms() {
return useLiveQueryWithDefault(async () => {
return useScopedLiveQuery(async () => {
const locals = await scopedForModule<LocalPeriodSymptom, string>(
'period',
'periodSymptoms'

View file

@ -4,7 +4,7 @@
* Uses Dexie liveQuery on the unified DB.
*/
import { useLiveQueryWithDefault } from '@mana/local-store/svelte';
import { useScopedLiveQuery } from '$lib/data/scope/use-scoped-live-query.svelte';
import { db } from '$lib/data/database';
import { scopedForModule } from '$lib/data/scope';
import type { LocalAlbum, LocalAlbumItem, LocalFavorite, Album, AlbumItem } from './types';
@ -42,7 +42,7 @@ export function toAlbumItem(local: LocalAlbumItem): AlbumItem {
/** All albums. Auto-updates on any change. */
export function useAllAlbums() {
return useLiveQueryWithDefault(async () => {
return useScopedLiveQuery(async () => {
const locals = await scopedForModule<LocalAlbum, string>('photos', 'albums').toArray();
return locals.filter((a) => !a.deletedAt).map(toAlbum);
}, []);
@ -50,7 +50,7 @@ export function useAllAlbums() {
/** All album items. Auto-updates on any change. */
export function useAllAlbumItems() {
return useLiveQueryWithDefault(async () => {
return useScopedLiveQuery(async () => {
const locals = await scopedForModule<LocalAlbumItem, string>('photos', 'albumItems').toArray();
return locals.filter((i) => !i.deletedAt).map(toAlbumItem);
}, []);
@ -58,7 +58,7 @@ export function useAllAlbumItems() {
/** All favorites. Auto-updates on any change. */
export function useAllFavorites() {
return useLiveQueryWithDefault(async () => {
return useScopedLiveQuery(async () => {
const locals = await scopedForModule<LocalFavorite, string>(
'photos',
'photoFavorites'

View file

@ -7,7 +7,7 @@
*/
import { liveQuery } from 'dexie';
import { useLiveQueryWithDefault } from '@mana/local-store/svelte';
import { useScopedLiveQuery } from '$lib/data/scope/use-scoped-live-query.svelte';
import { db } from '$lib/data/database';
import { scopedForModule } from '$lib/data/scope';
import { decryptRecords } from '$lib/data/crypto';
@ -77,7 +77,7 @@ export function toBoard(local: LocalBoard): Board {
/** All non-archived images, sorted by createdAt desc. */
export function useAllImages() {
return useLiveQueryWithDefault(async () => {
return useScopedLiveQuery(async () => {
const locals = await scopedForModule<LocalImage, string>('picture', 'images').toArray();
const visible = locals.filter((img) => !img.isArchived && !img.deletedAt);
const decrypted = await decryptRecords('images', visible);
@ -89,7 +89,7 @@ export function useAllImages() {
/** All archived images, sorted by createdAt desc. */
export function useArchivedImages() {
return useLiveQueryWithDefault(async () => {
return useScopedLiveQuery(async () => {
const locals = await scopedForModule<LocalImage, string>('picture', 'images').toArray();
const visible = locals.filter((img) => !!img.isArchived && !img.deletedAt);
const decrypted = await decryptRecords('images', visible);
@ -101,7 +101,7 @@ export function useArchivedImages() {
/** All boards with item counts, sorted by updatedAt desc. */
export function useAllBoards() {
return useLiveQueryWithDefault(async () => {
return useScopedLiveQuery(async () => {
const locals = await scopedForModule<LocalBoard, string>('picture', 'boards').toArray();
const allItems = await scopedForModule<LocalBoardItem, string>(
'picture',
@ -139,7 +139,7 @@ export { useAllTags as useAllPictureTags } from '@mana/shared-stores';
/** All image-tag associations. */
export function useAllImageTags() {
return useLiveQueryWithDefault(async () => {
return useScopedLiveQuery(async () => {
return await scopedForModule<LocalImageTag, string>('picture', 'imageTags').toArray();
}, [] as LocalImageTag[]);
}

View file

@ -6,7 +6,7 @@
* at init time; no manual fetch/refresh needed.
*/
import { useLiveQueryWithDefault } from '@mana/local-store/svelte';
import { useScopedLiveQuery } from '$lib/data/scope/use-scoped-live-query.svelte';
import { db } from '$lib/data/database';
import { scopedForModule } from '$lib/data/scope';
import { decryptRecords } from '$lib/data/crypto';
@ -99,7 +99,7 @@ export function toWateringLog(local: LocalWateringLog): WateringLog {
/** All plants. Auto-updates on any change. */
export function useAllPlants() {
return useLiveQueryWithDefault(async () => {
return useScopedLiveQuery(async () => {
const visible = (
await scopedForModule<LocalPlant, string>('plants', 'plants').toArray()
).filter((p) => !p.deletedAt);
@ -110,7 +110,7 @@ export function useAllPlants() {
/** All plant photos. Auto-updates on any change. */
export function useAllPlantPhotos() {
return useLiveQueryWithDefault(async () => {
return useScopedLiveQuery(async () => {
const locals = await scopedForModule<LocalPlantPhoto, string>(
'plants',
'plantPhotos'
@ -121,7 +121,7 @@ export function useAllPlantPhotos() {
/** All watering schedules. Auto-updates on any change. */
export function useAllWateringSchedules() {
return useLiveQueryWithDefault(async () => {
return useScopedLiveQuery(async () => {
const locals = await scopedForModule<LocalWateringSchedule, string>(
'plants',
'wateringSchedules'
@ -132,7 +132,7 @@ export function useAllWateringSchedules() {
/** All watering logs. Auto-updates on any change. */
export function useAllWateringLogs() {
return useLiveQueryWithDefault(async () => {
return useScopedLiveQuery(async () => {
const locals = await scopedForModule<LocalWateringLog, string>(
'plants',
'wateringLogs'
@ -143,7 +143,7 @@ export function useAllWateringLogs() {
/** All plant↔tag junctions (active only). */
export function useAllPlantTags() {
return useLiveQueryWithDefault(async () => {
return useScopedLiveQuery(async () => {
const locals = await scopedForModule<LocalPlantTag, string>('plants', 'plantTags').toArray();
return locals.filter((t) => !t.deletedAt);
}, []);

View file

@ -6,7 +6,7 @@
* pattern as notes / dreams / places.
*/
import { useLiveQueryWithDefault } from '@mana/local-store/svelte';
import { useScopedLiveQuery } from '$lib/data/scope/use-scoped-live-query.svelte';
import { db } from '$lib/data/database';
import { scopedForModule } from '$lib/data/scope';
import { decryptRecords } from '$lib/data/crypto';
@ -34,7 +34,7 @@ export function toSnippet(local: LocalPlaygroundSnippet): PlaygroundSnippet {
}
export function useAllSnippets() {
return useLiveQueryWithDefault(async () => {
return useScopedLiveQuery(async () => {
const locals = await db
.table<LocalPlaygroundSnippet>('playgroundSnippets')
.orderBy('order')
@ -85,7 +85,7 @@ export function toMessage(local: LocalPlaygroundMessage): PlaygroundConversation
}
export function useAllConversations() {
return useLiveQueryWithDefault(async () => {
return useScopedLiveQuery(async () => {
const locals = await scopedForModule<LocalPlaygroundConversation, string>(
'playground',
'playgroundConversations'
@ -100,7 +100,7 @@ export function useAllConversations() {
}
export function useConversationMessages(conversationId: () => string | null) {
return useLiveQueryWithDefault(async () => {
return useScopedLiveQuery(async () => {
const cid = conversationId();
if (!cid) return [];
const locals = await db

View file

@ -4,7 +4,7 @@
* Uses prefixed table names: presiDecks, slides.
*/
import { useLiveQueryWithDefault } from '@mana/local-store/svelte';
import { useScopedLiveQuery } from '$lib/data/scope/use-scoped-live-query.svelte';
import { db } from '$lib/data/database';
import { scopedForModule } from '$lib/data/scope';
import { decryptRecord, decryptRecords } from '$lib/data/crypto';
@ -40,7 +40,7 @@ export function toSlide(local: LocalSlide): Slide {
/** All decks, sorted by updatedAt descending. Auto-updates on any change. */
export function useAllDecks() {
return useLiveQueryWithDefault(async () => {
return useScopedLiveQuery(async () => {
const visible = (
await scopedForModule<LocalDeck, string>('presi', 'presiDecks').toArray()
).filter((d) => !d.deletedAt);
@ -53,7 +53,7 @@ export function useAllDecks() {
/** Slides for a specific deck, sorted by order. Auto-updates on any change. */
export function useDeckSlides(deckId: string) {
return useLiveQueryWithDefault(async () => {
return useScopedLiveQuery(async () => {
const visible = (
await db.table<LocalSlide>('slides').where('deckId').equals(deckId).toArray()
).filter((s) => !s.deletedAt);
@ -64,7 +64,7 @@ export function useDeckSlides(deckId: string) {
/** Single deck by ID. Auto-updates on any change. */
export function useDeck(id: string) {
return useLiveQueryWithDefault(
return useScopedLiveQuery(
async () => {
const local = await db.table<LocalDeck>('presiDecks').get(id);
if (!local || local.deletedAt) return null;

View file

@ -7,7 +7,7 @@
* visible pool without any query re-write.
*/
import { useLiveQueryWithDefault } from '@mana/local-store/svelte';
import { useScopedLiveQuery } from '$lib/data/scope/use-scoped-live-query.svelte';
import { decryptRecords } from '$lib/data/crypto';
import { scopedForModule } from '$lib/data/scope';
import { userContextTable } from './collections';
@ -24,7 +24,7 @@ import {
/** Reactive live-query for the user context singleton. */
export function useUserContext() {
return useLiveQueryWithDefault<UserContext | null>(async () => {
return useScopedLiveQuery<UserContext | null>(async () => {
const local = await userContextTable.get(USER_CONTEXT_SINGLETON_ID);
if (!local || local.deletedAt) return null;
const [decrypted] = await decryptRecords('userContext', [local]);
@ -38,7 +38,7 @@ export function useUserContext() {
* query re-runs automatically when the active space changes.
*/
export function useAllMeImages() {
return useLiveQueryWithDefault<MeImage[]>(async () => {
return useScopedLiveQuery<MeImage[]>(async () => {
const locals = await scopedForModule<LocalMeImage, string>('profile', 'meImages').toArray();
const visible = locals
.filter((row) => !row.deletedAt)
@ -52,7 +52,7 @@ export function useAllMeImages() {
* Me-images in the active space filtered by `kind`.
*/
export function useMeImagesByKind(kind: MeImageKind) {
return useLiveQueryWithDefault<MeImage[]>(async () => {
return useScopedLiveQuery<MeImage[]>(async () => {
const locals = await scopedForModule<LocalMeImage, string>('profile', 'meImages')
.and((row) => row.kind === kind)
.toArray();
@ -71,7 +71,7 @@ export function useMeImagesByKind(kind: MeImageKind) {
* isn't here, it must not be sent to OpenAI.
*/
export function useReferenceImages() {
return useLiveQueryWithDefault<MeImage[]>(async () => {
return useScopedLiveQuery<MeImage[]>(async () => {
const locals = await scopedForModule<LocalMeImage, string>('profile', 'meImages').toArray();
const visible = locals
.filter((row) => !row.deletedAt && row.usage?.aiReference === true)
@ -89,7 +89,7 @@ export function useReferenceImages() {
* local to that space.
*/
export function useImageByPrimary(slot: MeImagePrimarySlot) {
return useLiveQueryWithDefault<MeImage | null>(async () => {
return useScopedLiveQuery<MeImage | null>(async () => {
const locals = await scopedForModule<LocalMeImage, string>('profile', 'meImages')
.and((row) => row.primaryFor === slot)
.toArray();

View file

@ -4,7 +4,7 @@
* Uses table names: qCollections, questions, answers.
*/
import { useLiveQueryWithDefault } from '@mana/local-store/svelte';
import { useScopedLiveQuery } from '$lib/data/scope/use-scoped-live-query.svelte';
import { db } from '$lib/data/database';
import { scopedForModule } from '$lib/data/scope';
import { decryptRecords } from '$lib/data/crypto';
@ -103,7 +103,7 @@ export function toAnswer(local: LocalAnswer): Answer {
/** All collections, sorted by sortOrder. Auto-updates on any change. */
export function useAllCollections() {
return useLiveQueryWithDefault(async () => {
return useScopedLiveQuery(async () => {
const locals = await scopedForModule<LocalCollection, string>(
'questions',
'qCollections'
@ -117,7 +117,7 @@ export function useAllCollections() {
/** All questions. Auto-updates on any change. */
export function useAllQuestions() {
return useLiveQueryWithDefault(async () => {
return useScopedLiveQuery(async () => {
const locals = await scopedForModule<LocalQuestion, string>('questions', 'questions').toArray();
const visible = locals.filter((q) => !q.deletedAt);
const decrypted = await decryptRecords('questions', visible);
@ -127,7 +127,7 @@ export function useAllQuestions() {
/** All answers for a given question. */
export function useAnswersByQuestion(questionId: string) {
return useLiveQueryWithDefault(async () => {
return useScopedLiveQuery(async () => {
const locals = await scopedForModule<LocalAnswer, string>('questions', 'answers').toArray();
const visible = locals.filter((a) => !a.deletedAt && a.questionId === questionId);
const decrypted = await decryptRecords('answers', visible);

View file

@ -7,7 +7,7 @@
* AttemptAnswer payloads, which hold no user-typed content).
*/
import { useLiveQueryWithDefault } from '@mana/local-store/svelte';
import { useScopedLiveQuery } from '$lib/data/scope/use-scoped-live-query.svelte';
import { db } from '$lib/data/database';
import { scopedForModule } from '$lib/data/scope';
import { decryptRecords } from '$lib/data/crypto';
@ -69,7 +69,7 @@ export function toAttempt(local: LocalQuizAttempt): QuizAttempt {
// ─── Live Queries ──────────────────────────────────────────
export function useAllQuizzes() {
return useLiveQueryWithDefault(async () => {
return useScopedLiveQuery(async () => {
const visible = (await scopedForModule<LocalQuiz, string>('quiz', 'quizzes').toArray()).filter(
(q) => !q.deletedAt && !q.isArchived
);
@ -82,7 +82,7 @@ export function useAllQuizzes() {
}
export function useQuiz(id: string) {
return useLiveQueryWithDefault(
return useScopedLiveQuery(
async () => {
const local = await db.table<LocalQuiz>('quizzes').get(id);
if (!local || local.deletedAt) return null;
@ -94,7 +94,7 @@ export function useQuiz(id: string) {
}
export function useQuestions(quizId: string) {
return useLiveQueryWithDefault(async () => {
return useScopedLiveQuery(async () => {
const visible = (
await db.table<LocalQuizQuestion>('quizQuestions').where('quizId').equals(quizId).toArray()
).filter((q) => !q.deletedAt);
@ -104,7 +104,7 @@ export function useQuestions(quizId: string) {
}
export function useAttempts(quizId: string) {
return useLiveQueryWithDefault(async () => {
return useScopedLiveQuery(async () => {
const visible = (
await db.table<LocalQuizAttempt>('quizAttempts').where('quizId').equals(quizId).toArray()
).filter((a) => !a.deletedAt);

View file

@ -2,7 +2,7 @@
* Reactive Queries & Pure Helpers for Recipes module.
*/
import { useLiveQueryWithDefault } from '@mana/local-store/svelte';
import { useScopedLiveQuery } from '$lib/data/scope/use-scoped-live-query.svelte';
import { decryptRecords } from '$lib/data/crypto';
import { db } from '$lib/data/database';
import { scopedForModule } from '$lib/data/scope';
@ -36,7 +36,7 @@ export function toRecipe(local: LocalRecipe): Recipe {
// ─── Live Queries ─────────────────────────────────────────
export function useAllRecipes() {
return useLiveQueryWithDefault(async () => {
return useScopedLiveQuery(async () => {
const locals = await scopedForModule<LocalRecipe, string>('recipes', 'recipes').toArray();
const visible = locals.filter((r) => !r.deletedAt);
const decrypted = await decryptRecords('recipes', visible);

View file

@ -6,7 +6,7 @@
* at init time; no manual fetch/refresh needed.
*/
import { useLiveQueryWithDefault } from '@mana/local-store/svelte';
import { useScopedLiveQuery } from '$lib/data/scope/use-scoped-live-query.svelte';
import { db } from '$lib/data/database';
import { scopedForModule } from '$lib/data/scope';
import type { LocalSkill, LocalActivity, LocalAchievement } from './types';
@ -47,7 +47,7 @@ export function toActivity(local: LocalActivity): Activity {
/** All skills, auto-updates on any change. */
export function useAllSkills() {
return useLiveQueryWithDefault(async () => {
return useScopedLiveQuery(async () => {
const locals = await scopedForModule<LocalSkill, string>('skilltree', 'skills').toArray();
return locals.filter((s) => !s.deletedAt).map(toSkill);
}, [] as Skill[]);
@ -55,7 +55,7 @@ export function useAllSkills() {
/** All activities, auto-updates on any change. */
export function useAllActivities() {
return useLiveQueryWithDefault(async () => {
return useScopedLiveQuery(async () => {
const locals = await scopedForModule<LocalActivity, string>(
'skilltree',
'activities'
@ -66,7 +66,7 @@ export function useAllActivities() {
/** All achievements (raw local records), auto-updates on any change. */
export function useAllAchievements() {
return useLiveQueryWithDefault(async () => {
return useScopedLiveQuery(async () => {
const locals = await scopedForModule<LocalAchievement, string>(
'skilltree',
'achievements'

View file

@ -4,7 +4,7 @@
* Read-side only mutations live in stores/sleep.svelte.ts.
*/
import { useLiveQueryWithDefault } from '@mana/local-store/svelte';
import { useScopedLiveQuery } from '$lib/data/scope/use-scoped-live-query.svelte';
import { decryptRecords } from '$lib/data/crypto';
import { db } from '$lib/data/database';
import { scopedForModule } from '$lib/data/scope';
@ -84,7 +84,7 @@ export function toSleepSettings(local: LocalSleepSettings): SleepSettings {
// ─── Live Queries ───────────────────────────────────────────
export function useAllSleepEntries() {
return useLiveQueryWithDefault(async () => {
return useScopedLiveQuery(async () => {
const locals = await scopedForModule<LocalSleepEntry, string>(
'sleep',
'sleepEntries'
@ -96,7 +96,7 @@ export function useAllSleepEntries() {
}
export function useAllSleepHygieneLogs() {
return useLiveQueryWithDefault(async () => {
return useScopedLiveQuery(async () => {
const locals = await scopedForModule<LocalSleepHygieneLog, string>(
'sleep',
'sleepHygieneLogs'
@ -107,7 +107,7 @@ export function useAllSleepHygieneLogs() {
}
export function useAllSleepHygieneChecks() {
return useLiveQueryWithDefault(async () => {
return useScopedLiveQuery(async () => {
const locals = await scopedForModule<LocalSleepHygieneCheck, string>(
'sleep',
'sleepHygieneChecks'
@ -119,7 +119,7 @@ export function useAllSleepHygieneChecks() {
}
export function useSleepSettings() {
return useLiveQueryWithDefault(
return useScopedLiveQuery(
async () => {
const locals = await scopedForModule<LocalSleepSettings, string>(
'sleep',

View file

@ -4,7 +4,7 @@
* Uses table names: files, storageFolders, storageTags, fileTags.
*/
import { useLiveQueryWithDefault } from '@mana/local-store/svelte';
import { useScopedLiveQuery } from '$lib/data/scope/use-scoped-live-query.svelte';
import { db } from '$lib/data/database';
import { scopedForModule } from '$lib/data/scope';
import { decryptRecords } from '$lib/data/crypto';
@ -107,7 +107,7 @@ export function toTag(local: {
/** All non-deleted files, sorted by name. Auto-updates on any change. */
export function useAllFiles() {
return useLiveQueryWithDefault(async () => {
return useScopedLiveQuery(async () => {
const locals = await scopedForModule<LocalFile, string>('storage', 'files').toArray();
const visible = locals.filter((f) => !f.isDeleted && !f.deletedAt);
// name + originalName are encrypted on disk; sort needs plaintext.
@ -118,7 +118,7 @@ export function useAllFiles() {
/** All non-deleted folders, sorted by name. Auto-updates on any change. */
export function useAllFolders() {
return useLiveQueryWithDefault(async () => {
return useScopedLiveQuery(async () => {
const locals = await scopedForModule<LocalFolder, string>(
'storage',
'storageFolders'

View file

@ -4,7 +4,7 @@
* Read-side only mutations live in stores/stretch.svelte.ts.
*/
import { useLiveQueryWithDefault } from '@mana/local-store/svelte';
import { useScopedLiveQuery } from '$lib/data/scope/use-scoped-live-query.svelte';
import { decryptRecords } from '$lib/data/crypto';
import { db } from '$lib/data/database';
import { scopedForModule } from '$lib/data/scope';
@ -109,7 +109,7 @@ export function toStretchReminder(local: LocalStretchReminder): StretchReminder
// ─── Live Queries ───────────────────────────────────────────
export function useAllStretchExercises() {
return useLiveQueryWithDefault(async () => {
return useScopedLiveQuery(async () => {
const locals = await scopedForModule<LocalStretchExercise, string>(
'stretch',
'stretchExercises'
@ -121,7 +121,7 @@ export function useAllStretchExercises() {
}
export function useAllStretchRoutines() {
return useLiveQueryWithDefault(async () => {
return useScopedLiveQuery(async () => {
const locals = await scopedForModule<LocalStretchRoutine, string>(
'stretch',
'stretchRoutines'
@ -133,7 +133,7 @@ export function useAllStretchRoutines() {
}
export function useAllStretchSessions() {
return useLiveQueryWithDefault(async () => {
return useScopedLiveQuery(async () => {
const locals = await scopedForModule<LocalStretchSession, string>(
'stretch',
'stretchSessions'
@ -145,7 +145,7 @@ export function useAllStretchSessions() {
}
export function useAllStretchAssessments() {
return useLiveQueryWithDefault(async () => {
return useScopedLiveQuery(async () => {
const locals = await scopedForModule<LocalStretchAssessment, string>(
'stretch',
'stretchAssessments'
@ -159,7 +159,7 @@ export function useAllStretchAssessments() {
}
export function useAllStretchReminders() {
return useLiveQueryWithDefault(async () => {
return useScopedLiveQuery(async () => {
const locals = await scopedForModule<LocalStretchReminder, string>(
'stretch',
'stretchReminders'

View file

@ -7,7 +7,7 @@
import { liveQuery } from 'dexie';
import { db } from '$lib/data/database';
import { scopedForModule } from '$lib/data/scope';
import { useLiveQueryWithDefault } from '@mana/local-store/svelte';
import { useScopedLiveQuery } from '$lib/data/scope/use-scoped-live-query.svelte';
import type {
LocalClient,
LocalProject,
@ -255,7 +255,7 @@ export function allWorldClocks$() {
/** All alarms, auto-updates on any change. Returns { value, loading, error }. */
export function useAllAlarms() {
return useLiveQueryWithDefault(async () => {
return useScopedLiveQuery(async () => {
const locals = await scopedForModule<LocalAlarm, string>('times', 'timeAlarms').toArray();
return locals.filter((a) => !a.deletedAt).map(toAlarm);
}, [] as Alarm[]);
@ -263,7 +263,7 @@ export function useAllAlarms() {
/** All countdown timers, auto-updates on any change. Returns { value, loading, error }. */
export function useAllCountdownTimers() {
return useLiveQueryWithDefault(async () => {
return useScopedLiveQuery(async () => {
const locals = await scopedForModule<LocalCountdownTimer, string>(
'times',
'timeCountdownTimers'
@ -274,7 +274,7 @@ export function useAllCountdownTimers() {
/** All world clocks, sorted by sortOrder. Returns { value, loading, error }. */
export function useAllWorldClocks() {
return useLiveQueryWithDefault(async () => {
return useScopedLiveQuery(async () => {
const locals = await db
.table<LocalWorldClock>('timeWorldClocks')
.orderBy('sortOrder')

View file

@ -2,7 +2,7 @@
* Reactive queries & pure helpers for Todo uses Dexie liveQuery on the unified DB.
*/
import { useLiveQueryWithDefault } from '@mana/local-store/svelte';
import { useScopedLiveQuery } from '$lib/data/scope/use-scoped-live-query.svelte';
import { db } from '$lib/data/database';
import { scopedForModule, applyVisibility } from '$lib/data/scope';
import { decryptRecords } from '$lib/data/crypto';
@ -45,7 +45,7 @@ export function toTask(local: LocalTask): Task {
// ─── Live Queries ──────────────────────────────────────────
export function useAllTasks() {
return useLiveQueryWithDefault(async () => {
return useScopedLiveQuery(async () => {
// Scope-first, then in-memory sort by `order`. sortBy is O(n) — fine
// for a user's own task list; if it ever becomes hot, add a
// [spaceId+order] compound index in a follow-up Dexie version.
@ -72,7 +72,7 @@ export function useAllTasks() {
export { useAllTags as useAllLabels } from '@mana/shared-stores';
export function useAllBoardViews() {
return useLiveQueryWithDefault(async () => {
return useScopedLiveQuery(async () => {
const locals = await scopedForModule<LocalBoardView, string>('todo', 'boardViews').sortBy(
'order'
);
@ -81,14 +81,14 @@ export function useAllBoardViews() {
}
export function useAllReminders() {
return useLiveQueryWithDefault(async () => {
return useScopedLiveQuery(async () => {
const locals = await scopedForModule<LocalReminder, string>('todo', 'reminders').toArray();
return applyVisibility(locals).filter((r) => !r.deletedAt);
}, [] as LocalReminder[]);
}
export function useAllProjects() {
return useLiveQueryWithDefault(async () => {
return useScopedLiveQuery(async () => {
const locals = await scopedForModule<LocalTodoProject, string>('todo', 'todoProjects').sortBy(
'order'
);

View file

@ -6,7 +6,7 @@
*/
import { liveQuery } from 'dexie';
import { useLiveQueryWithDefault } from '@mana/local-store/svelte';
import { useScopedLiveQuery } from '$lib/data/scope/use-scoped-live-query.svelte';
import { db } from '$lib/data/database';
import { scopedForModule } from '$lib/data/scope';
import { decryptRecord, decryptRecords } from '$lib/data/crypto';
@ -165,7 +165,7 @@ export function allLinkTags$() {
// ─── Svelte 5 Reactive Hooks ──────────────────────────────
export function useAllLinks() {
return useLiveQueryWithDefault(async () => {
return useScopedLiveQuery(async () => {
const locals = await scopedForModule<LocalLink, string>('uload', 'links').toArray();
const visible = locals.filter((l) => !l.deletedAt);
const decrypted = await decryptRecords('links', visible);
@ -174,14 +174,14 @@ export function useAllLinks() {
}
export function useAllTags() {
return useLiveQueryWithDefault(async () => {
return useScopedLiveQuery(async () => {
const locals = await scopedForModule<LocalTag, string>('uload', 'uloadTags').toArray();
return locals.filter((t) => !t.deletedAt).map(toTag);
}, [] as Tag[]);
}
export function useAllFolders() {
return useLiveQueryWithDefault(async () => {
return useScopedLiveQuery(async () => {
const locals = await scopedForModule<LocalFolder, string>('uload', 'uloadFolders').sortBy(
'order'
);
@ -190,14 +190,14 @@ export function useAllFolders() {
}
export function useAllLinkTags() {
return useLiveQueryWithDefault(async () => {
return useScopedLiveQuery(async () => {
const locals = await scopedForModule<LocalLinkTag, string>('uload', 'linkTags').toArray();
return locals.filter((lt) => !lt.deletedAt).map(toLinkTag);
}, [] as LinkTag[]);
}
export function useLinkById(id: string) {
return useLiveQueryWithDefault(
return useScopedLiveQuery(
async () => {
if (!id) return null;
const local = await db.table<LocalLink>('links').get(id);

View file

@ -7,7 +7,7 @@
* filtered by `wardrobeOutfitId` see useOutfitTryOns below.
*/
import { useLiveQueryWithDefault } from '@mana/local-store/svelte';
import { useScopedLiveQuery } from '$lib/data/scope/use-scoped-live-query.svelte';
import { scopedForModule } from '$lib/data/scope';
import { decryptRecords } from '$lib/data/crypto';
import type { LocalImage, Image } from '$lib/modules/picture/types';
@ -27,7 +27,7 @@ import {
/** All non-archived, non-deleted garments in the active space. */
export function useAllGarments() {
return useLiveQueryWithDefault<Garment[]>(async () => {
return useScopedLiveQuery<Garment[]>(async () => {
const locals = await scopedForModule<LocalWardrobeGarment, string>(
'wardrobe',
'wardrobeGarments'
@ -42,7 +42,7 @@ export function useAllGarments() {
/** Garments filtered by category — used by the Category-Tabs view. */
export function useGarmentsByCategory(category: GarmentCategory) {
return useLiveQueryWithDefault<Garment[]>(async () => {
return useScopedLiveQuery<Garment[]>(async () => {
const locals = await scopedForModule<LocalWardrobeGarment, string>(
'wardrobe',
'wardrobeGarments'
@ -59,7 +59,7 @@ export function useGarmentsByCategory(category: GarmentCategory) {
/** A single garment by id, live-updating. Null while loading / missing. */
export function useGarment(id: string | null) {
return useLiveQueryWithDefault<Garment | null>(async () => {
return useScopedLiveQuery<Garment | null>(async () => {
if (!id) return null;
const locals = await scopedForModule<LocalWardrobeGarment, string>(
'wardrobe',
@ -78,7 +78,7 @@ export function useGarment(id: string | null) {
/** All non-archived outfits in the active space. */
export function useAllOutfits() {
return useLiveQueryWithDefault<Outfit[]>(async () => {
return useScopedLiveQuery<Outfit[]>(async () => {
const locals = await scopedForModule<LocalWardrobeOutfit, string>(
'wardrobe',
'wardrobeOutfits'
@ -92,7 +92,7 @@ export function useAllOutfits() {
}
export function useOutfitsByOccasion(occasion: OutfitOccasion) {
return useLiveQueryWithDefault<Outfit[]>(async () => {
return useScopedLiveQuery<Outfit[]>(async () => {
const locals = await scopedForModule<LocalWardrobeOutfit, string>('wardrobe', 'wardrobeOutfits')
.and((row) => row.occasion === occasion)
.toArray();
@ -105,7 +105,7 @@ export function useOutfitsByOccasion(occasion: OutfitOccasion) {
}
export function useOutfit(id: string | null) {
return useLiveQueryWithDefault<Outfit | null>(async () => {
return useScopedLiveQuery<Outfit | null>(async () => {
if (!id) return null;
const locals = await scopedForModule<LocalWardrobeOutfit, string>('wardrobe', 'wardrobeOutfits')
.and((row) => row.id === id)
@ -125,7 +125,7 @@ export function useOutfit(id: string | null) {
* strip under the current composition.
*/
export function useOutfitTryOns(outfitId: string | null) {
return useLiveQueryWithDefault<Image[]>(async () => {
return useScopedLiveQuery<Image[]>(async () => {
if (!outfitId) return [];
const locals = await scopedForModule<LocalImage, string>('picture', 'images')
.and((row) => row.wardrobeOutfitId === outfitId)
@ -146,7 +146,7 @@ export function useOutfitTryOns(outfitId: string | null) {
* `useOutfitsContainingGarment` for the cross-outfit view.
*/
export function useGarmentSoloTryOns(garmentId: string | null) {
return useLiveQueryWithDefault<Image[]>(async () => {
return useScopedLiveQuery<Image[]>(async () => {
if (!garmentId) return [];
const locals = await scopedForModule<LocalImage, string>('picture', 'images')
.and((row) => row.wardrobeGarmentId === garmentId)
@ -167,7 +167,7 @@ export function useGarmentSoloTryOns(garmentId: string | null) {
* snapshot provides the thumbnail without another image lookup.
*/
export function useOutfitsContainingGarment(garmentId: string | null) {
return useLiveQueryWithDefault<Outfit[]>(async () => {
return useScopedLiveQuery<Outfit[]>(async () => {
if (!garmentId) return [];
const locals = await scopedForModule<LocalWardrobeOutfit, string>(
'wardrobe',

View file

@ -7,7 +7,7 @@
* parameters change, which is why we don't accept id params here.
*/
import { useLiveQueryWithDefault } from '@mana/local-store/svelte';
import { useScopedLiveQuery } from '$lib/data/scope/use-scoped-live-query.svelte';
import { db } from '$lib/data/database';
import { scopedForModule } from '$lib/data/scope';
import type {
@ -72,7 +72,7 @@ export function toWebsiteBlock(local: LocalWebsiteBlock): WebsiteBlock {
// ─── Live Queries ─────────────────────────────────────────
export function useAllSites() {
return useLiveQueryWithDefault(async () => {
return useScopedLiveQuery(async () => {
const locals = await scopedForModule<LocalWebsite, string>('website', 'websites').toArray();
const visible = locals.filter((s) => !s.deletedAt);
return visible.map(toWebsite).sort((a, b) => b.updatedAt.localeCompare(a.updatedAt));
@ -80,7 +80,7 @@ export function useAllSites() {
}
export function useAllPages() {
return useLiveQueryWithDefault(async () => {
return useScopedLiveQuery(async () => {
const locals = await db.table<LocalWebsitePage>('websitePages').toArray();
const visible = locals.filter((p) => !p.deletedAt);
return visible.map(toWebsitePage).sort((a, b) => a.order - b.order);
@ -88,7 +88,7 @@ export function useAllPages() {
}
export function useAllBlocks() {
return useLiveQueryWithDefault(async () => {
return useScopedLiveQuery(async () => {
const locals = await db.table<LocalWebsiteBlock>('websiteBlocks').toArray();
const visible = locals.filter((b) => !b.deletedAt);
return visible.map(toWebsiteBlock).sort((a, b) => a.order - b.order);

View file

@ -2,27 +2,27 @@
* Wetter module read-side Dexie liveQuery hooks for locations.
*/
import { useLiveQueryWithDefault } from '@mana/local-store/svelte';
import { useScopedLiveQuery } from '$lib/data/scope/use-scoped-live-query.svelte';
import { db } from '$lib/data/database';
import { scopedForModule } from '$lib/data/scope';
import type { WeatherLocation, WeatherSettings } from './types';
export function useLocations() {
return useLiveQueryWithDefault(
return useScopedLiveQuery(
() => scopedForModule<WeatherLocation, string>('wetter', 'wetterLocations').sortBy('order'),
[] as WeatherLocation[]
);
}
export function useDefaultLocation() {
return useLiveQueryWithDefault(
return useScopedLiveQuery(
() => db.table<WeatherLocation>('wetterLocations').where('isDefault').equals(1).first(),
undefined as WeatherLocation | undefined
);
}
export function useSettings() {
return useLiveQueryWithDefault(
return useScopedLiveQuery(
async () => {
const settings = await db.table<WeatherSettings>('wetterSettings').get('default');
return (

View file

@ -2,7 +2,7 @@
* Reactive queries & pure helpers for Wishes uses Dexie liveQuery on the unified DB.
*/
import { useLiveQueryWithDefault } from '@mana/local-store/svelte';
import { useScopedLiveQuery } from '$lib/data/scope/use-scoped-live-query.svelte';
import { db } from '$lib/data/database';
import { scopedForModule } from '$lib/data/scope';
import { decryptRecords } from '$lib/data/crypto';
@ -68,7 +68,7 @@ export function toPriceCheck(local: LocalPriceCheck): PriceCheck {
// ─── Live Queries ──────────────────────────────────────────
export function useAllWishes() {
return useLiveQueryWithDefault(async () => {
return useScopedLiveQuery(async () => {
const locals = await scopedForModule<LocalWish, string>('wishes', 'wishesItems').sortBy(
'order'
);
@ -79,7 +79,7 @@ export function useAllWishes() {
}
export function useAllLists() {
return useLiveQueryWithDefault(async () => {
return useScopedLiveQuery(async () => {
const locals = await scopedForModule<LocalWishList, string>('wishes', 'wishesLists').sortBy(
'order'
);
@ -89,7 +89,7 @@ export function useAllLists() {
}
export function usePriceChecks(wishId: string) {
return useLiveQueryWithDefault(async () => {
return useScopedLiveQuery(async () => {
const locals = await db
.table<LocalPriceCheck>('wishesPriceChecks')
.where('wishId')

View file

@ -2,7 +2,7 @@
* Reactive queries + pure helpers for the Writing module.
*/
import { useLiveQueryWithDefault } from '@mana/local-store/svelte';
import { useScopedLiveQuery } from '$lib/data/scope/use-scoped-live-query.svelte';
import { decryptRecords } from '$lib/data/crypto';
import { db } from '$lib/data/database';
import { scopedForModule } from '$lib/data/scope';
@ -102,7 +102,7 @@ export function toWritingStyle(local: LocalWritingStyle): WritingStyle {
// ─── Live Queries ─────────────────────────────────────────
export function useAllDrafts() {
return useLiveQueryWithDefault(async () => {
return useScopedLiveQuery(async () => {
const locals = await scopedForModule<LocalDraft, string>('writing', 'writingDrafts').toArray();
const visible = locals.filter((d) => !d.deletedAt);
const decrypted = await decryptRecords('writingDrafts', visible);
@ -111,7 +111,7 @@ export function useAllDrafts() {
}
export function useDraft(id: string) {
return useLiveQueryWithDefault(
return useScopedLiveQuery(
async () => {
if (!id) return null;
const row = await db.table<LocalDraft>('writingDrafts').get(id);
@ -124,7 +124,7 @@ export function useDraft(id: string) {
}
export function useVersionsForDraft(draftId: string) {
return useLiveQueryWithDefault(async () => {
return useScopedLiveQuery(async () => {
if (!draftId) return [] as DraftVersion[];
const rows = await db
.table<LocalDraftVersion>('writingDraftVersions')
@ -138,7 +138,7 @@ export function useVersionsForDraft(draftId: string) {
}
export function useVersion(versionId: string) {
return useLiveQueryWithDefault(
return useScopedLiveQuery(
async () => {
if (!versionId) return null;
const row = await db.table<LocalDraftVersion>('writingDraftVersions').get(versionId);
@ -157,7 +157,7 @@ export function useVersion(versionId: string) {
* automatically in the editor.
*/
export function useCurrentVersionForDraft(draftId: string) {
return useLiveQueryWithDefault(
return useScopedLiveQuery(
async () => {
if (!draftId) return null;
const draftRow = await db.table<LocalDraft>('writingDrafts').get(draftId);
@ -174,7 +174,7 @@ export function useCurrentVersionForDraft(draftId: string) {
}
export function useGenerationsForDraft(draftId: string) {
return useLiveQueryWithDefault(async () => {
return useScopedLiveQuery(async () => {
if (!draftId) return [] as Generation[];
const rows = await db
.table<LocalGeneration>('writingGenerations')
@ -188,7 +188,7 @@ export function useGenerationsForDraft(draftId: string) {
}
export function useAllStyles() {
return useLiveQueryWithDefault(async () => {
return useScopedLiveQuery(async () => {
const rows = await scopedForModule<LocalWritingStyle, string>(
'writing',
'writingStyles'