mirror of
https://github.com/Memo-2023/mana-monorepo.git
synced 2026-05-14 18:41:08 +02:00
refactor(scope): smart hook stamps active-Space id, revert explicit stamps
Replaces the silent `_personal:<userId>` literal in the creating-hook
with `getEffectiveSpaceId()`, which returns the currently-active Space
when one is loaded and falls back to the personal sentinel for
guests / pre-bootstrap windows. Side-effect: writes during a Brand /
Family / Team session now land under that Space's UUID instead of
silently routing to Personal once `reconcileSentinels` runs — the
underlying tenancy bug Schicht A was supposed to catch.
With the hook doing the right thing automatically, the 16 explicit
`spaceId: getEffectiveSpaceId()` stamps from Etappe 1 are redundant
boilerplate. Reverted across:
picture/stores/boards (boards + boardItems)
events/stores/{guests,items}
companion/stores/chat (conversations + messages)
calc/stores/{calculations,saved-formulas}
quotes/stores/{favorites,custom-quotes}
skilltree/stores/{skills,achievements}
moodlit/stores/moods
plants/mutations
questions/stores/answers (manual + research draft)
data/ai/agents/{bootstrap,kontext}
Helper plumbing:
- `getEffectiveSpaceId()` lives in `scope/active-space.svelte.ts` (no
db dependency) so the creating-hook in `database.ts` can import it
without an ESM cycle. Inlined the `_personal:<userId>` literal there
instead of pulling `personalSpaceSentinel` from `bootstrap.ts`,
which would otherwise tangle the import graph.
- Re-exported via `scope/index.ts` for callers outside the hook.
- `setActiveSpace` and `loadActiveSpace` already funnel through the
shared `applyActiveSpace` helper, so the hook's view of the active
Space stays in sync with the rest of the scope layer.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
9e04385930
commit
a6c5397d10
19 changed files with 80 additions and 71 deletions
|
|
@ -27,7 +27,6 @@ import { encryptRecord } from '../../crypto';
|
|||
import type { Mission } from '../missions/types';
|
||||
import { MISSIONS_TABLE } from '../missions/types';
|
||||
import { getActiveSpace } from '../../scope/active-space.svelte';
|
||||
import { getEffectiveSpaceId } from '../../scope/scoped-db';
|
||||
import { DEFAULT_AI_POLICY } from '../policy';
|
||||
import { getAgent } from './store';
|
||||
import type { Agent } from './types';
|
||||
|
|
@ -106,7 +105,7 @@ export async function ensureDefaultAgent(): Promise<Agent> {
|
|||
const toWrite: Agent = { ...agent };
|
||||
await encryptRecord(AGENTS_TABLE, toWrite);
|
||||
try {
|
||||
await db.table(AGENTS_TABLE).add({ ...toWrite, spaceId: getEffectiveSpaceId() });
|
||||
await db.table(AGENTS_TABLE).add(toWrite);
|
||||
} catch (err) {
|
||||
// Race: another tab just wrote the same id. Re-fetch and return
|
||||
// that tab's record.
|
||||
|
|
|
|||
|
|
@ -9,7 +9,6 @@
|
|||
|
||||
import { db } from '../../database';
|
||||
import { encryptRecord, decryptRecords } from '../../crypto';
|
||||
import { getEffectiveSpaceId } from '../../scope/scoped-db';
|
||||
|
||||
const TABLE = 'agentKontextDocs';
|
||||
|
||||
|
|
@ -66,6 +65,6 @@ export async function saveAgentKontext(agentId: string, content: string): Promis
|
|||
content,
|
||||
};
|
||||
await encryptRecord(TABLE, doc);
|
||||
await db.table(TABLE).add({ ...doc, spaceId: getEffectiveSpaceId() });
|
||||
await db.table<LocalAgentKontextDoc>(TABLE).add(doc);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ import { trackFirstContent } from '$lib/stores/funnel-tracking';
|
|||
import { fire as fireTrigger } from '$lib/triggers/registry';
|
||||
import { checkInlineSuggestion } from '$lib/triggers/inline-suggest';
|
||||
import { getEffectiveUserId, GUEST_USER_ID } from './current-user';
|
||||
import { getEffectiveSpaceId } from './scope/active-space.svelte';
|
||||
import { getCurrentActor } from './events/actor';
|
||||
import type { Actor } from './events/actor';
|
||||
import { isQuotaError, notifyQuotaExceeded } from './quota-detect';
|
||||
|
|
@ -1119,6 +1120,30 @@ db.version(48).upgrade(async (tx) => {
|
|||
}
|
||||
});
|
||||
|
||||
// v49 — Comic-Character sub-system (docs/plans/comic-module.md §11).
|
||||
// Space-scoped sibling table to comicStories: a `LocalComicCharacter`
|
||||
// row groups N variant renderings of "the user as a comic-style
|
||||
// character" generated via gpt-image-2 / Nano Banana edits over the
|
||||
// raw face/body meImages with a comic-style prefix. One pinned variant
|
||||
// is the canonical look; stories snapshot that variant's mediaId at
|
||||
// story-create time so re-pinning later doesn't rewrite history.
|
||||
//
|
||||
// Why space-scoped (not user-scoped): the source meImages this builds
|
||||
// on are themselves space-scoped after v40. A character generated in
|
||||
// the personal space references face/body refs that don't exist in
|
||||
// the brand space, so making the character user-global would orphan
|
||||
// references on space-switch. Same scoping rationale as wardrobe-
|
||||
// garments — derived assets travel with their source.
|
||||
//
|
||||
// Indices:
|
||||
// - createdAt for "newest first" grid ordering
|
||||
// - style for style-tab filtering on /comic/character (M5 list-tool)
|
||||
// - isFavorite for the favorites filter
|
||||
// - isArchived for the standard archive-hide filter
|
||||
db.version(49).stores({
|
||||
comicCharacters: 'id, createdAt, style, isFavorite, isArchived',
|
||||
});
|
||||
|
||||
// ─── Sync Routing ──────────────────────────────────────────
|
||||
// SYNC_APP_MAP, TABLE_TO_SYNC_NAME, TABLE_TO_APP, SYNC_NAME_TO_TABLE,
|
||||
// toSyncName() and fromSyncName() are now derived from per-module
|
||||
|
|
@ -1340,17 +1365,21 @@ for (const [appId, tables] of Object.entries(SYNC_APP_MAP)) {
|
|||
// (see v36 below). Skipping the stamps here keeps future
|
||||
// rows clean.
|
||||
} else {
|
||||
// Auto-stamp the Space-scope fields on data tables. Until
|
||||
// the scope bootstrap (see `./scope/active-space.svelte.ts`)
|
||||
// resolves the user's personal-space id from Better Auth,
|
||||
// new records carry a deterministic sentinel
|
||||
// `_personal:<userId>` that the bootstrap rewrites in a
|
||||
// single pass. Module stores set spaceId explicitly once
|
||||
// they start writing into non-personal spaces — this stamp
|
||||
// only fills the gap. Sentinel uses `effectiveUserId`
|
||||
// directly since `userId` isn't on data records anymore.
|
||||
// Auto-stamp Space-scope fields on data tables. The hook
|
||||
// resolves `getEffectiveSpaceId()` which returns the
|
||||
// currently-active Space's id — so a calendar event
|
||||
// created during a Brand-Space session lands under that
|
||||
// Brand UUID, not under Personal. During the bootstrap
|
||||
// window before `loadActiveSpace` resolves, the helper
|
||||
// falls back to the sentinel `_personal:<userId>` which
|
||||
// `reconcileSentinels` rewrites once the real id is known.
|
||||
//
|
||||
// Stores can set `spaceId` explicitly when writing to a
|
||||
// space they're not currently active in (e.g. workbench-
|
||||
// home seeder writes to a target Space's id, not the
|
||||
// active one) — the hook only fills in the gap.
|
||||
if (objRecord.spaceId === undefined || objRecord.spaceId === null) {
|
||||
objRecord.spaceId = `_personal:${effectiveUserId}`;
|
||||
objRecord.spaceId = getEffectiveSpaceId();
|
||||
}
|
||||
if (objRecord.authorId === undefined || objRecord.authorId === null) {
|
||||
objRecord.authorId = effectiveUserId;
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ import type { SpaceType, SpaceTier } from '@mana/shared-types';
|
|||
import { isSpaceType, isSpaceTier } from '@mana/shared-types';
|
||||
import { authFetch } from './auth-fetch';
|
||||
import { runSpaceSeeds } from './per-space-seeds';
|
||||
import { getEffectiveUserId } from '../current-user';
|
||||
|
||||
export interface ActiveSpace {
|
||||
id: string;
|
||||
|
|
@ -79,6 +80,29 @@ export function getActiveSpaceId(): string | null {
|
|||
return active?.id ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* The spaceId every new write to a space-scoped table should carry.
|
||||
* Used by the Dexie creating-hook to auto-stamp tenancy on records the
|
||||
* caller hasn't explicitly assigned. Returns:
|
||||
*
|
||||
* - the active Space's id when one is loaded (e.g. `<personal-uuid>`,
|
||||
* `<brand-uuid>`, …) — every write goes to the right tenant
|
||||
* - the personal sentinel `_personal:<userId>` during the bootstrap
|
||||
* window before `loadActiveSpace` resolves, so writes never block
|
||||
* and `reconcileSentinels` rewrites the placeholder once the real
|
||||
* id is known.
|
||||
*
|
||||
* Inlined sentinel helper instead of importing `personalSpaceSentinel`
|
||||
* from `./bootstrap` because bootstrap.ts pulls in `db` and the
|
||||
* creating-hook in `database.ts` imports this function — the indirect
|
||||
* cycle would tangle ESM resolution.
|
||||
*/
|
||||
export function getEffectiveSpaceId(): string {
|
||||
const id = active?.id;
|
||||
if (id) return id;
|
||||
return `_personal:${getEffectiveUserId()}`;
|
||||
}
|
||||
|
||||
export function getActiveSpaceStatus(): ActiveSpaceStatus {
|
||||
return status;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@
|
|||
export {
|
||||
getActiveSpace,
|
||||
getActiveSpaceId,
|
||||
getEffectiveSpaceId,
|
||||
getActiveSpaceStatus,
|
||||
getActiveSpaceError,
|
||||
setActiveSpace,
|
||||
|
|
@ -29,7 +30,6 @@ export {
|
|||
scopedGet,
|
||||
assertModuleAllowed,
|
||||
getInScopeSpaceIds,
|
||||
getEffectiveSpaceId,
|
||||
ScopeNotReadyError,
|
||||
ModuleNotInSpaceError,
|
||||
} from './scoped-db';
|
||||
|
|
|
|||
|
|
@ -66,25 +66,6 @@ export function getInScopeSpaceIds(): string[] {
|
|||
return [sentinel];
|
||||
}
|
||||
|
||||
/**
|
||||
* The spaceId every new write to a space-scoped table should carry.
|
||||
* Returns the active Space's id when one is loaded, falling back to the
|
||||
* personal sentinel `_personal:<userId>` for guests / pre-bootstrap
|
||||
* windows. The sentinel value matches what `reconcileSentinels` rewrites
|
||||
* to the real personal-space id once `loadActiveSpace` resolves, so no
|
||||
* row gets stranded.
|
||||
*
|
||||
* Module stores call this and stamp `spaceId` on the record explicitly
|
||||
* before `.add()` / `.put()`. Once Schicht A flips the creating-hook to
|
||||
* throw on missing spaceId (see docs/plans/workbench-seeding-cleanup.md),
|
||||
* forgetting this call is a hard error instead of silent corruption.
|
||||
*/
|
||||
export function getEffectiveSpaceId(): string {
|
||||
const active = getActiveSpaceId();
|
||||
if (active) return active;
|
||||
return personalSpaceSentinel(getEffectiveUserId());
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a Collection that applies the space filter — chainable with any
|
||||
* further `.where()`, `.filter()`, `.toArray()`, `.modify()`.
|
||||
|
|
|
|||
|
|
@ -4,19 +4,17 @@
|
|||
|
||||
import { db } from '$lib/data/database';
|
||||
import { CalcEvents } from '@mana/shared-utils/analytics';
|
||||
import { getEffectiveSpaceId } from '$lib/data/scope';
|
||||
import type { LocalCalculation } from '../types';
|
||||
import type { CreateCalculationInput } from '@calc/shared';
|
||||
|
||||
export const calculationsStore = {
|
||||
async addCalculation(input: CreateCalculationInput) {
|
||||
await db.table('calculations').add({
|
||||
await db.table<LocalCalculation>('calculations').add({
|
||||
id: crypto.randomUUID(),
|
||||
mode: input.mode,
|
||||
expression: input.expression,
|
||||
result: input.result,
|
||||
skin: input.skin,
|
||||
spaceId: getEffectiveSpaceId(),
|
||||
createdAt: new Date().toISOString(),
|
||||
updatedAt: new Date().toISOString(),
|
||||
});
|
||||
|
|
|
|||
|
|
@ -4,19 +4,17 @@
|
|||
|
||||
import { db } from '$lib/data/database';
|
||||
import { CalcEvents } from '@mana/shared-utils/analytics';
|
||||
import { getEffectiveSpaceId } from '$lib/data/scope';
|
||||
import type { LocalSavedFormula } from '../types';
|
||||
import type { CreateFormulaInput, UpdateFormulaInput } from '@calc/shared';
|
||||
|
||||
export const savedFormulasStore = {
|
||||
async saveFormula(input: CreateFormulaInput) {
|
||||
await db.table('savedFormulas').add({
|
||||
await db.table<LocalSavedFormula>('savedFormulas').add({
|
||||
id: crypto.randomUUID(),
|
||||
name: input.name,
|
||||
expression: input.expression,
|
||||
description: input.description ?? null,
|
||||
mode: input.mode,
|
||||
spaceId: getEffectiveSpaceId(),
|
||||
createdAt: new Date().toISOString(),
|
||||
updatedAt: new Date().toISOString(),
|
||||
});
|
||||
|
|
|
|||
|
|
@ -8,7 +8,6 @@
|
|||
|
||||
import { db } from '$lib/data/database';
|
||||
import { emitDomainEvent } from '$lib/data/events';
|
||||
import { getEffectiveSpaceId } from '$lib/data/scope';
|
||||
import type { LocalConversation, LocalMessage } from '../types';
|
||||
|
||||
const CONV_TABLE = 'companionConversations';
|
||||
|
|
@ -25,7 +24,7 @@ export const chatStore = {
|
|||
createdAt: now,
|
||||
updatedAt: now,
|
||||
};
|
||||
await db.table(CONV_TABLE).add({ ...conv, spaceId: getEffectiveSpaceId() });
|
||||
await db.table<LocalConversation>(CONV_TABLE).add(conv);
|
||||
emitDomainEvent('CompanionConversationStarted', 'companion', CONV_TABLE, conv.id, {
|
||||
conversationId: conv.id,
|
||||
title: conv.title,
|
||||
|
|
@ -67,7 +66,7 @@ export const chatStore = {
|
|||
toolResult: extra?.toolResult,
|
||||
createdAt: new Date().toISOString(),
|
||||
};
|
||||
await db.table(MSG_TABLE).add({ ...msg, spaceId: getEffectiveSpaceId() });
|
||||
await db.table<LocalMessage>(MSG_TABLE).add(msg);
|
||||
|
||||
// Touch conversation updatedAt
|
||||
await db.table<LocalConversation>(CONV_TABLE).update(conversationId, {
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@
|
|||
|
||||
import { db } from '$lib/data/database';
|
||||
import { encryptRecord } from '$lib/data/crypto';
|
||||
import { getEffectiveSpaceId } from '$lib/data/scope';
|
||||
import type { LocalEventGuest, RsvpStatus } from '../types';
|
||||
|
||||
let error = $state<string | null>(null);
|
||||
|
|
@ -45,7 +44,7 @@ export const eventGuestsStore = {
|
|||
// records stay local-only — they're never pushed to the
|
||||
// public RSVP snapshot, so no decrypt-before-publish here.
|
||||
await encryptRecord('eventGuests', newGuest);
|
||||
await db.table('eventGuests').add({ ...newGuest, spaceId: getEffectiveSpaceId() });
|
||||
await db.table<LocalEventGuest>('eventGuests').add(newGuest);
|
||||
return { success: true as const, id };
|
||||
} catch (e) {
|
||||
error = e instanceof Error ? e.message : 'Failed to add guest';
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@
|
|||
*/
|
||||
|
||||
import { db } from '$lib/data/database';
|
||||
import { getEffectiveSpaceId } from '$lib/data/scope';
|
||||
import type { LocalEventItem } from '../types';
|
||||
import { eventsStore } from './events.svelte';
|
||||
|
||||
|
|
@ -48,7 +47,7 @@ export const eventItemsStore = {
|
|||
createdAt: new Date().toISOString(),
|
||||
updatedAt: new Date().toISOString(),
|
||||
};
|
||||
await db.table('eventItems').add({ ...newItem, spaceId: getEffectiveSpaceId() });
|
||||
await db.table<LocalEventItem>('eventItems').add(newItem);
|
||||
void eventsStore.syncItems(input.eventId);
|
||||
return { success: true as const, id };
|
||||
} catch (e) {
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@
|
|||
import { db } from '$lib/data/database';
|
||||
import { MoodlitEvents } from '@mana/shared-utils/analytics';
|
||||
import { createBlock, updateBlock } from '$lib/data/time-blocks/service';
|
||||
import { getEffectiveSpaceId } from '$lib/data/scope';
|
||||
import type { LocalMood } from '../types';
|
||||
import type { Mood, MoodSettings } from '../types';
|
||||
|
||||
|
|
@ -129,13 +128,12 @@ function createMoodsStore() {
|
|||
|
||||
// IndexedDB mutation methods
|
||||
async createMood(data: { name: string; colors: string[]; animation: string }) {
|
||||
await db.table('moods').add({
|
||||
await db.table<LocalMood>('moods').add({
|
||||
id: crypto.randomUUID(),
|
||||
name: data.name,
|
||||
colors: data.colors,
|
||||
animation: data.animation,
|
||||
isDefault: false,
|
||||
spaceId: getEffectiveSpaceId(),
|
||||
createdAt: new Date().toISOString(),
|
||||
updatedAt: new Date().toISOString(),
|
||||
});
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@
|
|||
import { db } from '$lib/data/database';
|
||||
import { encryptRecord, decryptRecord } from '$lib/data/crypto';
|
||||
import { emitDomainEvent } from '$lib/data/events';
|
||||
import { getActiveSpace, getEffectiveSpaceId } from '$lib/data/scope';
|
||||
import { getActiveSpace } from '$lib/data/scope';
|
||||
import { getEffectiveUserId } from '$lib/data/current-user';
|
||||
import {
|
||||
defaultVisibilityFor,
|
||||
|
|
@ -56,7 +56,7 @@ export const boardsStore = {
|
|||
// mutates `newLocal` in place — UI consumers expect plaintext.
|
||||
const plaintextSnapshot = toBoard({ ...newLocal });
|
||||
await encryptRecord('boards', newLocal);
|
||||
await db.table('boards').add({ ...newLocal, spaceId: getEffectiveSpaceId() });
|
||||
await db.table<LocalBoard>('boards').add(newLocal);
|
||||
return { success: true, data: plaintextSnapshot };
|
||||
} catch (e) {
|
||||
error = e instanceof Error ? e.message : 'Failed to create board';
|
||||
|
|
@ -170,7 +170,7 @@ export const boardsStore = {
|
|||
updatedAt: now,
|
||||
};
|
||||
await encryptRecord('boardItems', newItem);
|
||||
await db.table('boardItems').add({ ...newItem, spaceId: getEffectiveSpaceId() });
|
||||
await db.table<LocalBoardItem>('boardItems').add(newItem);
|
||||
}
|
||||
|
||||
return { success: true, data: plaintextSnapshot };
|
||||
|
|
|
|||
|
|
@ -11,7 +11,6 @@ import { PlantsEvents } from '@mana/shared-utils/analytics';
|
|||
import { encryptRecord, decryptRecord } from '$lib/data/crypto';
|
||||
import { emitDomainEvent } from '$lib/data/events';
|
||||
import { createBlock } from '$lib/data/time-blocks/service';
|
||||
import { getEffectiveSpaceId } from '$lib/data/scope';
|
||||
import { uploadPlantPhoto, identifyPlant, type IdentifyResult } from './api';
|
||||
import type {
|
||||
LocalPlant,
|
||||
|
|
@ -195,7 +194,6 @@ export const wateringMutations = {
|
|||
nextWateringAt: nextDate.toISOString(),
|
||||
reminderEnabled: false,
|
||||
reminderHoursBefore: 0,
|
||||
spaceId: getEffectiveSpaceId(),
|
||||
createdAt: now,
|
||||
updatedAt: now,
|
||||
});
|
||||
|
|
|
|||
|
|
@ -22,7 +22,6 @@
|
|||
import { db } from '$lib/data/database';
|
||||
import { encryptRecord, decryptRecord } from '$lib/data/crypto';
|
||||
import { emitDomainEvent } from '$lib/data/events';
|
||||
import { getEffectiveSpaceId } from '$lib/data/scope';
|
||||
import { researchApi, type ResearchEvent, type ResearchSource } from '$lib/api/research';
|
||||
import type { LocalAnswer, LocalQuestion } from '../types';
|
||||
|
||||
|
|
@ -44,7 +43,6 @@ async function createManual(input: CreateManualAnswerInput): Promise<string> {
|
|||
citations: [],
|
||||
rating: null,
|
||||
isAccepted: false,
|
||||
spaceId: getEffectiveSpaceId(),
|
||||
createdAt: now,
|
||||
updatedAt: now,
|
||||
};
|
||||
|
|
@ -109,8 +107,6 @@ async function startResearch(opts: StartResearchOptions): Promise<ResearchHandle
|
|||
content: '',
|
||||
citations: [],
|
||||
rating: null,
|
||||
isAccepted: false,
|
||||
spaceId: getEffectiveSpaceId(),
|
||||
createdAt: now,
|
||||
updatedAt: now,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@
|
|||
*/
|
||||
|
||||
import { db } from '$lib/data/database';
|
||||
import { getEffectiveSpaceId } from '$lib/data/scope';
|
||||
import type { LocalCustomQuote } from '../types';
|
||||
|
||||
export interface CustomQuoteInput {
|
||||
|
|
@ -19,14 +18,13 @@ export const customQuotesStore = {
|
|||
async create(input: CustomQuoteInput): Promise<string> {
|
||||
const now = new Date().toISOString();
|
||||
const id = `custom-${crypto.randomUUID()}`;
|
||||
await db.table('customQuotes').add({
|
||||
await db.table<LocalCustomQuote>('customQuotes').add({
|
||||
id,
|
||||
text: input.text,
|
||||
author: input.author,
|
||||
category: input.category ?? null,
|
||||
source: input.source ?? null,
|
||||
year: input.year ?? null,
|
||||
spaceId: getEffectiveSpaceId(),
|
||||
createdAt: now,
|
||||
updatedAt: now,
|
||||
});
|
||||
|
|
|
|||
|
|
@ -5,17 +5,15 @@
|
|||
*/
|
||||
|
||||
import { db } from '$lib/data/database';
|
||||
import { getEffectiveSpaceId } from '$lib/data/scope';
|
||||
import type { LocalFavorite } from '../types';
|
||||
import type { Favorite } from '../queries';
|
||||
|
||||
export const favoritesStore = {
|
||||
async add(quoteId: string) {
|
||||
const now = new Date().toISOString();
|
||||
await db.table('quotesFavorites').add({
|
||||
await db.table<LocalFavorite>('quotesFavorites').add({
|
||||
id: crypto.randomUUID(),
|
||||
quoteId,
|
||||
spaceId: getEffectiveSpaceId(),
|
||||
createdAt: now,
|
||||
updatedAt: now,
|
||||
});
|
||||
|
|
|
|||
|
|
@ -6,7 +6,6 @@
|
|||
*/
|
||||
|
||||
import { db } from '$lib/data/database';
|
||||
import { getEffectiveSpaceId } from '$lib/data/scope';
|
||||
import type {
|
||||
AchievementWithStatus,
|
||||
AchievementUnlockResult,
|
||||
|
|
@ -96,14 +95,13 @@ async function seedIfEmpty() {
|
|||
const active = stored.filter((a) => !a.deletedAt);
|
||||
if (active.length === 0) {
|
||||
for (const def of ACHIEVEMENT_DEFINITIONS) {
|
||||
await db.table('achievements').add({
|
||||
await db.table<LocalAchievement>('achievements').add({
|
||||
id: def.id,
|
||||
key: def.id,
|
||||
name: def.name,
|
||||
description: def.description,
|
||||
icon: def.icon,
|
||||
unlockedAt: '',
|
||||
spaceId: getEffectiveSpaceId(),
|
||||
createdAt: new Date().toISOString(),
|
||||
updatedAt: new Date().toISOString(),
|
||||
});
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@
|
|||
|
||||
import { db } from '$lib/data/database';
|
||||
import { emitDomainEvent } from '$lib/data/events';
|
||||
import { getEffectiveSpaceId } from '$lib/data/scope';
|
||||
import type { Skill } from '../types';
|
||||
import { calculateLevel, createDefaultSkill, createActivity } from '../types';
|
||||
import type { LocalSkill, LocalActivity } from '../types';
|
||||
|
|
@ -30,9 +29,8 @@ async function addSkill(data: Partial<Skill>): Promise<Skill> {
|
|||
totalXp: skill.totalXp,
|
||||
level: skill.level,
|
||||
};
|
||||
await db.table('skills').add({
|
||||
await db.table<LocalSkill>('skills').add({
|
||||
...localSkill,
|
||||
spaceId: getEffectiveSpaceId(),
|
||||
createdAt: new Date().toISOString(),
|
||||
updatedAt: new Date().toISOString(),
|
||||
});
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue