mirror of
https://github.com/Memo-2023/mana-monorepo.git
synced 2026-05-14 18:41:08 +02:00
refactor(stores): replace Record<string,unknown> declarations with Partial<LocalX>
Final follow-up to drop the type-bypass patterns from F3's codemod.
Mit \`Partial<LocalX>\` als Deklaration akzeptiert Dexie's UpdateSpec
ohne weiteren Cast — die kombinierte \`as Record<string,unknown>\` +
\`as never\` Konstruktion wird durch eine einzige saubere
Typ-Annotation ersetzt.
Touched stores (12 Files):
wardrobe/stores/{garments,outfits}, invoices/stores/invoices,
sleep/stores/sleep, library/stores/entries,
comic/stores/{characters,stories},
profile/stores/me-images, recipes/stores/recipes,
broadcast/stores/campaigns, writing/stores/{styles,drafts}
Plus inline literal-object patterns (\`{ lines, totals } as Record\`,
\`{ content } as Record\`, \`{ audience } as Record\`,
\`{ ...spread } as Record\` im comic appendPanel).
Verbleibende \`as Record<string, unknown>\` Vorkommen sind legitime
Reads von typed-data und nicht das F3-Pattern.
7670 svelte-check Files, 0 Errors, 0 Warnings.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
b4589a7249
commit
515de79c8b
12 changed files with 52 additions and 37 deletions
|
|
@ -96,7 +96,7 @@ export const broadcastCampaignsStore = {
|
|||
if (existing.status !== 'draft') {
|
||||
throw new Error('[broadcast] only drafts can be edited; duplicate to revise a sent campaign');
|
||||
}
|
||||
const wrapped = { ...patch } as Record<string, unknown>;
|
||||
const wrapped: Partial<LocalCampaign> = { ...patch };
|
||||
await encryptRecord('broadcastCampaigns', wrapped);
|
||||
await campaignTable.update(id, wrapped);
|
||||
},
|
||||
|
|
@ -113,7 +113,7 @@ export const broadcastCampaignsStore = {
|
|||
if (existing.status !== 'draft') {
|
||||
throw new Error('[broadcast] only drafts can be edited');
|
||||
}
|
||||
const patch = { content } as Record<string, unknown>;
|
||||
const patch: Partial<LocalCampaign> = { content };
|
||||
await encryptRecord('broadcastCampaigns', patch);
|
||||
await campaignTable.update(id, patch);
|
||||
},
|
||||
|
|
@ -124,7 +124,7 @@ export const broadcastCampaignsStore = {
|
|||
if (existing.status !== 'draft') {
|
||||
throw new Error('[broadcast] only drafts can be edited');
|
||||
}
|
||||
const patch = { audience } as Record<string, unknown>;
|
||||
const patch: Partial<LocalCampaign> = { audience };
|
||||
await encryptRecord('broadcastCampaigns', patch);
|
||||
await campaignTable.update(id, patch);
|
||||
},
|
||||
|
|
|
|||
|
|
@ -161,7 +161,7 @@ export const comicStoriesStore = {
|
|||
const patch = {
|
||||
panelImageIds: nextIds,
|
||||
panelMeta: nextMeta,
|
||||
} as Record<string, unknown>;
|
||||
} as Partial<LocalComicStory>;
|
||||
await encryptRecord('comicStories', patch);
|
||||
await comicStoriesTable.update(storyId, patch);
|
||||
emitDomainEvent('ComicPanelAppended', 'comic', 'comicStories', storyId, {
|
||||
|
|
|
|||
|
|
@ -135,7 +135,7 @@ export const invoicesStore = {
|
|||
'[invoices] only drafts can be edited; void and duplicate to revise a sent invoice'
|
||||
);
|
||||
}
|
||||
const wrapped = { ...patch } as Record<string, unknown>;
|
||||
const wrapped: Partial<LocalInvoice> = { ...patch };
|
||||
await encryptRecord('invoices', wrapped);
|
||||
await invoiceTable.update(id, wrapped);
|
||||
},
|
||||
|
|
@ -152,7 +152,7 @@ export const invoicesStore = {
|
|||
throw new Error('[invoices] only drafts can be edited');
|
||||
}
|
||||
const totals = computeInvoiceTotals(lines);
|
||||
const patch = { lines, totals } as Record<string, unknown>;
|
||||
const patch: Partial<LocalInvoice> = { lines, totals };
|
||||
// `lines` is in the encryption allowlist; `totals` is not. encryptRecord
|
||||
// only touches allowlisted keys, so a single call is correct for both.
|
||||
await encryptRecord('invoices', patch);
|
||||
|
|
|
|||
|
|
@ -130,7 +130,7 @@ export const libraryEntriesStore = {
|
|||
>
|
||||
>
|
||||
) {
|
||||
const wrapped = { ...patch } as Record<string, unknown>;
|
||||
const wrapped: Partial<LocalLibraryEntry> = { ...patch };
|
||||
await encryptRecord('libraryEntries', wrapped);
|
||||
await libraryEntryTable.update(id, wrapped);
|
||||
// Keep the share-link snapshot in sync if this entry is unlisted.
|
||||
|
|
|
|||
|
|
@ -165,7 +165,7 @@ export const meImagesStore = {
|
|||
id: string,
|
||||
patch: Partial<Pick<LocalMeImage, 'label' | 'tags' | 'kind' | 'usage'>>
|
||||
): Promise<void> {
|
||||
const wrapped = { ...patch } as Record<string, unknown>;
|
||||
const wrapped: Partial<LocalMeImage> = { ...patch };
|
||||
await encryptRecord('meImages', wrapped);
|
||||
await meImagesTable.update(id, wrapped);
|
||||
},
|
||||
|
|
|
|||
|
|
@ -76,7 +76,7 @@ export const recipesStore = {
|
|||
>
|
||||
>
|
||||
) {
|
||||
const wrapped = { ...patch } as Record<string, unknown>;
|
||||
const wrapped: Partial<LocalRecipe> = { ...patch };
|
||||
await encryptRecord('recipes', wrapped);
|
||||
await recipeTable.update(id, wrapped);
|
||||
},
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
Short links list with click counts and quick link creation.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { _ } from 'svelte-i18n';
|
||||
import { useLiveQueryWithDefault } from '@mana/local-store/svelte';
|
||||
import { db } from '$lib/data/database';
|
||||
import { decryptRecords } from '$lib/data/crypto';
|
||||
|
|
@ -59,11 +60,11 @@
|
|||
try {
|
||||
const parsed = new URL(fullUrl);
|
||||
if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:') {
|
||||
error = 'Ungültige URL';
|
||||
error = $_('uload.list_view.err_invalid_url');
|
||||
return;
|
||||
}
|
||||
} catch {
|
||||
error = 'Ungültige URL';
|
||||
error = $_('uload.list_view.err_invalid_url');
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -95,11 +96,11 @@
|
|||
}
|
||||
</script>
|
||||
|
||||
<BaseListView items={sorted} getKey={(l) => l.id} emptyTitle="Keine Links">
|
||||
<BaseListView items={sorted} getKey={(l) => l.id} emptyTitle={$_('uload.list_view.empty_title')}>
|
||||
{#snippet header()}
|
||||
<span>{links.length} Links</span>
|
||||
<span>{totalClicks} Klicks</span>
|
||||
<span>{folders.length} Ordner</span>
|
||||
<span>{$_('uload.list_view.header_links', { values: { count: links.length } })}</span>
|
||||
<span>{$_('uload.list_view.header_clicks', { values: { count: totalClicks } })}</span>
|
||||
<span>{$_('uload.list_view.header_folders', { values: { count: folders.length } })}</span>
|
||||
{/snippet}
|
||||
|
||||
{#snippet listHeader()}
|
||||
|
|
@ -113,7 +114,12 @@
|
|||
>
|
||||
<LinkIcon size={14} class="icon" />
|
||||
<!-- svelte-ignore a11y_autofocus -->
|
||||
<input class="add-input" bind:value={newUrl} placeholder="URL einfügen..." autofocus />
|
||||
<input
|
||||
class="add-input"
|
||||
bind:value={newUrl}
|
||||
placeholder={$_('uload.list_view.add_placeholder')}
|
||||
autofocus
|
||||
/>
|
||||
<button type="submit" class="submit-btn" disabled={!newUrl.trim()}>
|
||||
<Plus size={14} />
|
||||
</button>
|
||||
|
|
@ -124,7 +130,7 @@
|
|||
{:else}
|
||||
<button class="add-toggle" onclick={() => (showAdd = true)}>
|
||||
<Plus size={14} />
|
||||
<span>Neuer Link</span>
|
||||
<span>{$_('uload.list_view.add_button')}</span>
|
||||
</button>
|
||||
{/if}
|
||||
{/snippet}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
All fields are always editable. Changes auto-save on blur.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { _ } from 'svelte-i18n';
|
||||
import { formatDate } from '$lib/i18n/format';
|
||||
import { db } from '$lib/data/database';
|
||||
import { encryptRecord } from '$lib/data/crypto';
|
||||
|
|
@ -65,14 +66,14 @@
|
|||
<DetailViewShell
|
||||
entity={detail.entity}
|
||||
loading={detail.loading}
|
||||
notFoundLabel="Link nicht gefunden"
|
||||
notFoundLabel={$_('uload.detail_view.not_found')}
|
||||
confirmDelete={detail.confirmDelete}
|
||||
onAskDelete={detail.askDelete}
|
||||
onCancelDelete={detail.cancelDelete}
|
||||
confirmDeleteLabel="Link wirklich löschen?"
|
||||
confirmDeleteLabel={$_('uload.detail_view.confirm_delete')}
|
||||
onConfirmDelete={() =>
|
||||
detail.deleteWithUndo({
|
||||
label: 'Link gelöscht',
|
||||
label: $_('uload.detail_view.deleted_toast'),
|
||||
delete: deleteLink,
|
||||
goBack,
|
||||
})}
|
||||
|
|
@ -83,12 +84,12 @@
|
|||
bind:value={editTitle}
|
||||
onfocus={detail.focus}
|
||||
onblur={saveField}
|
||||
placeholder="Titel..."
|
||||
placeholder={$_('uload.detail_view.placeholder_title')}
|
||||
/>
|
||||
|
||||
<div class="properties">
|
||||
<div class="prop-row">
|
||||
<span class="prop-label">URL</span>
|
||||
<span class="prop-label">{$_('uload.detail_view.label_url')}</span>
|
||||
<input
|
||||
class="prop-input"
|
||||
bind:value={editOriginalUrl}
|
||||
|
|
@ -99,25 +100,25 @@
|
|||
</div>
|
||||
|
||||
<div class="prop-row">
|
||||
<span class="prop-label">Kurzcode</span>
|
||||
<span class="prop-label">{$_('uload.detail_view.label_short_code')}</span>
|
||||
<input
|
||||
class="prop-input"
|
||||
bind:value={editCustomCode}
|
||||
onfocus={detail.focus}
|
||||
onblur={saveField}
|
||||
placeholder="custom-code"
|
||||
placeholder={$_('uload.detail_view.placeholder_short_code')}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{#if link.shortCode}
|
||||
<div class="prop-row">
|
||||
<span class="prop-label">Short Code</span>
|
||||
<span class="prop-label">{$_('uload.detail_view.label_short_code_legacy')}</span>
|
||||
<span class="prop-value">{link.shortCode}</span>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<div class="prop-row">
|
||||
<span class="prop-label">Aktiv</span>
|
||||
<span class="prop-label">{$_('uload.detail_view.label_active')}</span>
|
||||
<button
|
||||
class="toggle-btn"
|
||||
class:active={editIsActive}
|
||||
|
|
@ -126,17 +127,17 @@
|
|||
handleActiveToggle();
|
||||
}}
|
||||
>
|
||||
{editIsActive ? 'Ja' : 'Nein'}
|
||||
{editIsActive ? $_('uload.detail_view.yes') : $_('uload.detail_view.no')}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="prop-row">
|
||||
<span class="prop-label">Klicks</span>
|
||||
<span class="prop-label">{$_('uload.detail_view.label_clicks')}</span>
|
||||
<span class="prop-value">{link.clickCount}</span>
|
||||
</div>
|
||||
|
||||
<div class="prop-row">
|
||||
<span class="prop-label">Ablaufdatum</span>
|
||||
<span class="prop-label">{$_('uload.detail_view.label_expires_at')}</span>
|
||||
<input
|
||||
type="date"
|
||||
class="prop-input"
|
||||
|
|
@ -148,21 +149,29 @@
|
|||
</div>
|
||||
|
||||
<div class="section">
|
||||
<span class="section-label">Beschreibung</span>
|
||||
<span class="section-label">{$_('uload.detail_view.section_description')}</span>
|
||||
<textarea
|
||||
class="description-input"
|
||||
bind:value={editDescription}
|
||||
onfocus={detail.focus}
|
||||
onblur={saveField}
|
||||
placeholder="Beschreibung hinzufügen..."
|
||||
placeholder={$_('uload.detail_view.placeholder_description')}
|
||||
rows={3}
|
||||
></textarea>
|
||||
</div>
|
||||
|
||||
<div class="meta">
|
||||
<span>Erstellt: {formatDate(new Date(link.createdAt ?? ''))}</span>
|
||||
<span
|
||||
>{$_('uload.detail_view.meta_created', {
|
||||
values: { date: formatDate(new Date(link.createdAt ?? '')) },
|
||||
})}</span
|
||||
>
|
||||
{#if link.updatedAt}
|
||||
<span>Bearbeitet: {formatDate(new Date(link.updatedAt))}</span>
|
||||
<span
|
||||
>{$_('uload.detail_view.meta_updated', {
|
||||
values: { date: formatDate(new Date(link.updatedAt)) },
|
||||
})}</span
|
||||
>
|
||||
{/if}
|
||||
</div>
|
||||
{/snippet}
|
||||
|
|
|
|||
|
|
@ -80,7 +80,7 @@ export const wardrobeGarmentsStore = {
|
|||
>
|
||||
>
|
||||
): Promise<void> {
|
||||
const wrapped = { ...patch } as Record<string, unknown>;
|
||||
const wrapped: Partial<LocalWardrobeGarment> = { ...patch };
|
||||
await encryptRecord('wardrobeGarments', wrapped);
|
||||
await wardrobeGarmentsTable.update(id, wrapped);
|
||||
},
|
||||
|
|
|
|||
|
|
@ -71,7 +71,7 @@ export const wardrobeOutfitsStore = {
|
|||
>
|
||||
>
|
||||
): Promise<void> {
|
||||
const wrapped = { ...patch } as Record<string, unknown>;
|
||||
const wrapped: Partial<LocalWardrobeOutfit> = { ...patch };
|
||||
await encryptRecord('wardrobeOutfits', wrapped);
|
||||
await wardrobeOutfitsTable.update(id, wrapped);
|
||||
},
|
||||
|
|
|
|||
|
|
@ -139,7 +139,7 @@ export const draftsStore = {
|
|||
},
|
||||
|
||||
async updateDraft(id: string, patch: UpdateDraftPatch) {
|
||||
const wrapped = { ...patch } as Record<string, unknown>;
|
||||
const wrapped: Partial<LocalDraft> = { ...patch };
|
||||
await encryptRecord('writingDrafts', wrapped);
|
||||
await draftTable.update(id, wrapped);
|
||||
},
|
||||
|
|
|
|||
|
|
@ -64,7 +64,7 @@ export const stylesStore = {
|
|||
},
|
||||
|
||||
async updateStyle(id: string, patch: UpdateStylePatch) {
|
||||
const wrapped = { ...patch } as Record<string, unknown>;
|
||||
const wrapped: Partial<LocalWritingStyle> = { ...patch };
|
||||
await encryptRecord('writingStyles', wrapped);
|
||||
await writingStyleTable.update(id, wrapped);
|
||||
},
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue