mirror of
https://github.com/Memo-2023/mana-monorepo.git
synced 2026-05-14 19:01:08 +02:00
feat: add @manacore/shared-links for cross-app record linking
New shared package enabling bidirectional links between records across apps (e.g. todo→calendar, task→file). Each link creates a forward+reverse pair sharing a pairId for efficient queries from both sides. Stored in dedicated IndexedDB (manacore-links), synced via mana-sync. Includes: types, store, mutations, reactive queries, cached display data resolvers, ManaLinkBadge and ManaLinkList UI components. Integrates into Todo app as first consumer — link store initialized in layout, ManaLinkList rendered in TaskEditModal. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
928d88aabd
commit
2222ce25e5
14 changed files with 614 additions and 1 deletions
23
packages/shared-links/package.json
Normal file
23
packages/shared-links/package.json
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
{
|
||||
"name": "@manacore/shared-links",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"main": "./src/index.ts",
|
||||
"types": "./src/index.ts",
|
||||
"exports": {
|
||||
".": "./src/index.ts",
|
||||
"./ui": "./src/ui/index.ts"
|
||||
},
|
||||
"dependencies": {
|
||||
"@manacore/local-store": "workspace:*",
|
||||
"@manacore/shared-branding": "workspace:*"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"svelte": "^5.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"svelte": "^5.0.0",
|
||||
"typescript": "^5.9.3"
|
||||
}
|
||||
}
|
||||
26
packages/shared-links/src/index.ts
Normal file
26
packages/shared-links/src/index.ts
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
// Store
|
||||
export { linkLocalStore, linkCollection } from './store.js';
|
||||
|
||||
// Types
|
||||
export type {
|
||||
LocalManaLink,
|
||||
CreateManaLinkInput,
|
||||
LinkCachedData,
|
||||
ManaRecordRef,
|
||||
ManaLinkType,
|
||||
} from './types.js';
|
||||
export { LINK_TYPE_INVERSIONS } from './types.js';
|
||||
|
||||
// Mutations
|
||||
export { linkMutations } from './mutations.svelte.js';
|
||||
|
||||
// Queries
|
||||
export {
|
||||
useLinksForRecord,
|
||||
useLinksForApp,
|
||||
useLinksOfType,
|
||||
useLinkCount,
|
||||
} from './queries.svelte.js';
|
||||
|
||||
// Resolvers
|
||||
export { buildCachedData, isCacheStale } from './resolvers.js';
|
||||
143
packages/shared-links/src/mutations.svelte.ts
Normal file
143
packages/shared-links/src/mutations.svelte.ts
Normal file
|
|
@ -0,0 +1,143 @@
|
|||
/**
|
||||
* ManaLink — Mutation Service
|
||||
*
|
||||
* Handles creating, deleting, and updating cross-app links.
|
||||
* Each link creates TWO records (forward + reverse) sharing a pairId
|
||||
* for efficient bidirectional queries.
|
||||
*/
|
||||
|
||||
import { linkLocalStore, linkCollection } from './store.js';
|
||||
import type { LocalManaLink, CreateManaLinkInput, LinkCachedData } from './types.js';
|
||||
import { LINK_TYPE_INVERSIONS } from './types.js';
|
||||
|
||||
/** Invert a directional link type. Symmetric types return unchanged. */
|
||||
function invertLinkType(type: string): string {
|
||||
return LINK_TYPE_INVERSIONS[type] ?? type;
|
||||
}
|
||||
|
||||
let error = $state<string | null>(null);
|
||||
|
||||
export const linkMutations = {
|
||||
get error() {
|
||||
return error;
|
||||
},
|
||||
|
||||
// === Store Lifecycle ===
|
||||
|
||||
async initialize() {
|
||||
await linkLocalStore.initialize();
|
||||
},
|
||||
|
||||
startSync(getToken: () => Promise<string | null>) {
|
||||
linkLocalStore.startSync(getToken);
|
||||
},
|
||||
|
||||
stopSync() {
|
||||
linkLocalStore.stopSync();
|
||||
},
|
||||
|
||||
// === Links ===
|
||||
|
||||
/**
|
||||
* Create a bidirectional link. Inserts TWO records sharing a pairId:
|
||||
* - Forward: source → target
|
||||
* - Reverse: target → source (swapped)
|
||||
*/
|
||||
async createLink(
|
||||
input: CreateManaLinkInput
|
||||
): Promise<{ forward: LocalManaLink; reverse: LocalManaLink }> {
|
||||
error = null;
|
||||
try {
|
||||
const pairId = crypto.randomUUID();
|
||||
const linkType = input.linkType ?? 'related';
|
||||
|
||||
const forward: LocalManaLink = {
|
||||
id: crypto.randomUUID(),
|
||||
pairId,
|
||||
direction: 'forward',
|
||||
sourceApp: input.sourceApp,
|
||||
sourceCollection: input.sourceCollection,
|
||||
sourceId: input.sourceId,
|
||||
targetApp: input.targetApp,
|
||||
targetCollection: input.targetCollection,
|
||||
targetId: input.targetId,
|
||||
linkType,
|
||||
cachedSource: input.cachedSource,
|
||||
cachedTarget: input.cachedTarget,
|
||||
};
|
||||
|
||||
const reverse: LocalManaLink = {
|
||||
id: crypto.randomUUID(),
|
||||
pairId,
|
||||
direction: 'reverse',
|
||||
sourceApp: input.targetApp,
|
||||
sourceCollection: input.targetCollection,
|
||||
sourceId: input.targetId,
|
||||
targetApp: input.sourceApp,
|
||||
targetCollection: input.sourceCollection,
|
||||
targetId: input.sourceId,
|
||||
linkType: invertLinkType(linkType),
|
||||
cachedSource: input.cachedTarget,
|
||||
cachedTarget: input.cachedSource,
|
||||
};
|
||||
|
||||
const insertedForward = await linkCollection.insert(forward);
|
||||
const insertedReverse = await linkCollection.insert(reverse);
|
||||
|
||||
return { forward: insertedForward, reverse: insertedReverse };
|
||||
} catch (e) {
|
||||
error = e instanceof Error ? e.message : 'Failed to create link';
|
||||
throw e;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Delete a link pair by pairId. Soft-deletes both forward and reverse records.
|
||||
*/
|
||||
async deleteLinkPair(pairId: string): Promise<void> {
|
||||
error = null;
|
||||
try {
|
||||
const links = await linkCollection.getAll({
|
||||
pairId,
|
||||
} as Partial<LocalManaLink>);
|
||||
for (const link of links) {
|
||||
await linkCollection.delete(link.id);
|
||||
}
|
||||
} catch (e) {
|
||||
error = e instanceof Error ? e.message : 'Failed to delete link pair';
|
||||
throw e;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Delete a link by its record ID (and its pair partner).
|
||||
*/
|
||||
async deleteLink(linkId: string): Promise<void> {
|
||||
error = null;
|
||||
try {
|
||||
const link = await linkCollection.get(linkId);
|
||||
if (link) {
|
||||
await this.deleteLinkPair(link.pairId);
|
||||
}
|
||||
} catch (e) {
|
||||
error = e instanceof Error ? e.message : 'Failed to delete link';
|
||||
throw e;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Update cached display data on a link record.
|
||||
*/
|
||||
async updateCache(
|
||||
linkId: string,
|
||||
cached: Partial<{ cachedSource: LinkCachedData; cachedTarget: LinkCachedData }>
|
||||
): Promise<void> {
|
||||
error = null;
|
||||
try {
|
||||
await linkCollection.update(linkId, cached as Partial<LocalManaLink>);
|
||||
} catch (e) {
|
||||
error = e instanceof Error ? e.message : 'Failed to update link cache';
|
||||
throw e;
|
||||
}
|
||||
},
|
||||
};
|
||||
64
packages/shared-links/src/queries.svelte.ts
Normal file
64
packages/shared-links/src/queries.svelte.ts
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
/**
|
||||
* ManaLink — Reactive Live Queries
|
||||
*
|
||||
* Svelte 5 reactive queries for cross-app links.
|
||||
* Auto-update when IndexedDB changes (local writes, sync, other tabs).
|
||||
*/
|
||||
|
||||
import { useLiveQueryWithDefault } from '@manacore/local-store/svelte';
|
||||
import { linkCollection } from './store.js';
|
||||
import type { LocalManaLink, ManaRecordRef } from './types.js';
|
||||
|
||||
/**
|
||||
* Get all links where this record is the source.
|
||||
* Because we store reverse records, this returns links in BOTH directions.
|
||||
*/
|
||||
export function useLinksForRecord(ref: ManaRecordRef) {
|
||||
return useLiveQueryWithDefault(
|
||||
() =>
|
||||
linkCollection.getAll({
|
||||
sourceApp: ref.app,
|
||||
sourceId: ref.id,
|
||||
} as Partial<LocalManaLink>),
|
||||
[] as LocalManaLink[]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all links for an entire app.
|
||||
*/
|
||||
export function useLinksForApp(appId: string) {
|
||||
return useLiveQueryWithDefault(
|
||||
() =>
|
||||
linkCollection.getAll({
|
||||
sourceApp: appId,
|
||||
} as Partial<LocalManaLink>),
|
||||
[] as LocalManaLink[]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get links of a specific type for a record.
|
||||
*/
|
||||
export function useLinksOfType(ref: ManaRecordRef, linkType: string) {
|
||||
return useLiveQueryWithDefault(async () => {
|
||||
const all = await linkCollection.getAll({
|
||||
sourceApp: ref.app,
|
||||
sourceId: ref.id,
|
||||
} as Partial<LocalManaLink>);
|
||||
return all.filter((l) => l.linkType === linkType);
|
||||
}, [] as LocalManaLink[]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Count links for a record.
|
||||
*/
|
||||
export function useLinkCount(ref: ManaRecordRef) {
|
||||
return useLiveQueryWithDefault(async () => {
|
||||
const all = await linkCollection.getAll({
|
||||
sourceApp: ref.app,
|
||||
sourceId: ref.id,
|
||||
} as Partial<LocalManaLink>);
|
||||
return all.length;
|
||||
}, 0);
|
||||
}
|
||||
34
packages/shared-links/src/resolvers.ts
Normal file
34
packages/shared-links/src/resolvers.ts
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
/**
|
||||
* ManaLink — Display Data Resolvers
|
||||
*
|
||||
* Helpers to build cached display data from app metadata.
|
||||
*/
|
||||
|
||||
import { getManaApp } from '@manacore/shared-branding';
|
||||
import type { AppIconId } from '@manacore/shared-branding';
|
||||
import type { LinkCachedData } from './types.js';
|
||||
|
||||
/**
|
||||
* Build LinkCachedData from app branding + a title.
|
||||
* Called by the app creating the link to populate offline-friendly display data.
|
||||
*/
|
||||
export function buildCachedData(appId: string, title: string, subtitle?: string): LinkCachedData {
|
||||
const app = getManaApp(appId as AppIconId);
|
||||
return {
|
||||
title,
|
||||
subtitle,
|
||||
icon: app?.icon,
|
||||
color: app?.color,
|
||||
appName: app?.name,
|
||||
fetchedAt: new Date().toISOString(),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if cached data is stale (older than threshold).
|
||||
* Default: 24 hours.
|
||||
*/
|
||||
export function isCacheStale(cached: LinkCachedData, maxAgeMs = 24 * 60 * 60 * 1000): boolean {
|
||||
const age = Date.now() - new Date(cached.fetchedAt).getTime();
|
||||
return age > maxAgeMs;
|
||||
}
|
||||
40
packages/shared-links/src/store.ts
Normal file
40
packages/shared-links/src/store.ts
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
/**
|
||||
* ManaLink — Local-First Store
|
||||
*
|
||||
* Creates a shared IndexedDB database ('manacore-links') that all apps
|
||||
* can read from. Links are synced to the server via mana-sync.
|
||||
*/
|
||||
|
||||
import { createLocalStore } from '@manacore/local-store';
|
||||
import type { LocalManaLink } from './types.js';
|
||||
|
||||
const SYNC_SERVER_URL =
|
||||
(typeof window !== 'undefined' &&
|
||||
(window as unknown as { __PUBLIC_SYNC_SERVER_URL__?: string }).__PUBLIC_SYNC_SERVER_URL__) ||
|
||||
(typeof import.meta !== 'undefined' && import.meta.env?.PUBLIC_SYNC_SERVER_URL) ||
|
||||
'http://localhost:3050';
|
||||
|
||||
export const linkLocalStore = createLocalStore({
|
||||
appId: 'links',
|
||||
collections: [
|
||||
{
|
||||
name: 'links',
|
||||
indexes: [
|
||||
'pairId',
|
||||
'direction',
|
||||
'sourceApp',
|
||||
'sourceId',
|
||||
'targetApp',
|
||||
'targetId',
|
||||
'linkType',
|
||||
'[sourceApp+sourceId]',
|
||||
'[sourceApp+sourceCollection+sourceId]',
|
||||
],
|
||||
},
|
||||
],
|
||||
sync: {
|
||||
serverUrl: SYNC_SERVER_URL,
|
||||
},
|
||||
});
|
||||
|
||||
export const linkCollection = linkLocalStore.collection<LocalManaLink>('links');
|
||||
92
packages/shared-links/src/types.ts
Normal file
92
packages/shared-links/src/types.ts
Normal file
|
|
@ -0,0 +1,92 @@
|
|||
/**
|
||||
* ManaLink — Cross-App Linking Types
|
||||
*
|
||||
* Defines the data structures for bidirectional links between records
|
||||
* across different apps in the Mana ecosystem.
|
||||
*/
|
||||
|
||||
import type { BaseRecord } from '@manacore/local-store';
|
||||
|
||||
/** Cached display data for a linked record (offline-friendly). */
|
||||
export interface LinkCachedData {
|
||||
/** Display title of the linked record. */
|
||||
title: string;
|
||||
/** Optional subtitle (e.g. date, status). */
|
||||
subtitle?: string;
|
||||
/** App icon (data URL from shared-branding). */
|
||||
icon?: string;
|
||||
/** App color hex (from shared-branding). */
|
||||
color?: string;
|
||||
/** Human-readable app name. */
|
||||
appName?: string;
|
||||
/** ISO timestamp when this cache was built. */
|
||||
fetchedAt: string;
|
||||
}
|
||||
|
||||
/** A single link record stored in IndexedDB (manacore-links). */
|
||||
export interface LocalManaLink extends BaseRecord {
|
||||
/** UUID shared by the forward and reverse link records. */
|
||||
pairId: string;
|
||||
/** Direction marker for this half of the pair. */
|
||||
direction: 'forward' | 'reverse';
|
||||
/** App ID of the source record (e.g. 'todo'). */
|
||||
sourceApp: string;
|
||||
/** Collection name in the source app (e.g. 'tasks'). */
|
||||
sourceCollection: string;
|
||||
/** Record ID in the source app. */
|
||||
sourceId: string;
|
||||
/** App ID of the target record (e.g. 'calendar'). */
|
||||
targetApp: string;
|
||||
/** Collection name in the target app (e.g. 'events'). */
|
||||
targetCollection: string;
|
||||
/** Record ID in the target app. */
|
||||
targetId: string;
|
||||
/** Semantic link type. */
|
||||
linkType: string;
|
||||
/** Cached display data for the source record. */
|
||||
cachedSource?: LinkCachedData;
|
||||
/** Cached display data for the target record. */
|
||||
cachedTarget?: LinkCachedData;
|
||||
/** User ID (for sync scoping). */
|
||||
userId?: string;
|
||||
}
|
||||
|
||||
/** Input for creating a link (user-facing). */
|
||||
export interface CreateManaLinkInput {
|
||||
sourceApp: string;
|
||||
sourceCollection: string;
|
||||
sourceId: string;
|
||||
targetApp: string;
|
||||
targetCollection: string;
|
||||
targetId: string;
|
||||
/** Link type. Default: 'related'. */
|
||||
linkType?: string;
|
||||
cachedSource?: LinkCachedData;
|
||||
cachedTarget?: LinkCachedData;
|
||||
}
|
||||
|
||||
/** Identifies a record across apps. */
|
||||
export interface ManaRecordRef {
|
||||
app: string;
|
||||
collection: string;
|
||||
id: string;
|
||||
}
|
||||
|
||||
/** Built-in link types. */
|
||||
export type ManaLinkType =
|
||||
| 'related'
|
||||
| 'blocks'
|
||||
| 'blocked-by'
|
||||
| 'time-block'
|
||||
| 'attachment'
|
||||
| 'reference'
|
||||
| 'parent'
|
||||
| 'child';
|
||||
|
||||
/** Map of directional link type inversions. */
|
||||
export const LINK_TYPE_INVERSIONS: Record<string, string> = {
|
||||
blocks: 'blocked-by',
|
||||
'blocked-by': 'blocks',
|
||||
parent: 'child',
|
||||
child: 'parent',
|
||||
};
|
||||
119
packages/shared-links/src/ui/ManaLinkBadge.svelte
Normal file
119
packages/shared-links/src/ui/ManaLinkBadge.svelte
Normal file
|
|
@ -0,0 +1,119 @@
|
|||
<script lang="ts">
|
||||
import type { LocalManaLink } from '../types.js';
|
||||
import { getManaApp } from '@manacore/shared-branding';
|
||||
import type { AppIconId } from '@manacore/shared-branding';
|
||||
|
||||
interface Props {
|
||||
link: LocalManaLink;
|
||||
onRemove?: (pairId: string) => void;
|
||||
onclick?: (link: LocalManaLink) => void;
|
||||
}
|
||||
|
||||
let { link, onRemove, onclick }: Props = $props();
|
||||
|
||||
let targetApp = $derived(getManaApp(link.targetApp as AppIconId));
|
||||
let displayTitle = $derived(
|
||||
link.cachedTarget?.title ?? `${link.targetApp}/${link.targetId.slice(0, 8)}`
|
||||
);
|
||||
let displayColor = $derived(link.cachedTarget?.color ?? targetApp?.color ?? '#6b7280');
|
||||
let displayAppName = $derived(link.cachedTarget?.appName ?? targetApp?.name ?? link.targetApp);
|
||||
</script>
|
||||
|
||||
<span class="manalink-badge" role="group">
|
||||
<button
|
||||
class="manalink-main"
|
||||
onclick={() => onclick?.(link)}
|
||||
title="{displayAppName}: {displayTitle}"
|
||||
>
|
||||
<span class="manalink-dot" style:background-color={displayColor}></span>
|
||||
<span class="manalink-title">{displayTitle}</span>
|
||||
{#if link.cachedTarget?.subtitle}
|
||||
<span class="manalink-subtitle">{link.cachedTarget.subtitle}</span>
|
||||
{/if}
|
||||
</button>
|
||||
{#if onRemove}
|
||||
<button class="manalink-remove" onclick={() => onRemove?.(link.pairId)} title="Link entfernen">
|
||||
<svg width="12" height="12" viewBox="0 0 12 12" fill="none">
|
||||
<path
|
||||
d="M3 3l6 6M9 3l-6 6"
|
||||
stroke="currentColor"
|
||||
stroke-width="1.5"
|
||||
stroke-linecap="round"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
{/if}
|
||||
</span>
|
||||
|
||||
<style>
|
||||
.manalink-badge {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
border-radius: 9999px;
|
||||
background: var(--color-surface, #f3f4f6);
|
||||
border: 1px solid var(--color-border, #e5e7eb);
|
||||
font-size: 0.75rem;
|
||||
line-height: 1;
|
||||
transition: all 0.15s ease;
|
||||
max-width: 220px;
|
||||
}
|
||||
|
||||
.manalink-badge:hover {
|
||||
background: var(--color-surface-hover, #e5e7eb);
|
||||
border-color: var(--color-border-hover, #d1d5db);
|
||||
}
|
||||
|
||||
.manalink-main {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.375rem;
|
||||
padding: 0.25rem 0.625rem;
|
||||
border: none;
|
||||
background: transparent;
|
||||
cursor: pointer;
|
||||
font-size: inherit;
|
||||
line-height: inherit;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.manalink-dot {
|
||||
width: 0.5rem;
|
||||
height: 0.5rem;
|
||||
border-radius: 50%;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.manalink-title {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
color: var(--color-text, #374151);
|
||||
}
|
||||
|
||||
.manalink-subtitle {
|
||||
color: var(--color-text-muted, #9ca3af);
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.manalink-remove {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 1rem;
|
||||
height: 1rem;
|
||||
border-radius: 50%;
|
||||
border: none;
|
||||
background: transparent;
|
||||
color: var(--color-text-muted, #9ca3af);
|
||||
cursor: pointer;
|
||||
padding: 0;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.manalink-remove:hover {
|
||||
background: var(--color-danger, #ef4444);
|
||||
color: white;
|
||||
}
|
||||
</style>
|
||||
36
packages/shared-links/src/ui/ManaLinkList.svelte
Normal file
36
packages/shared-links/src/ui/ManaLinkList.svelte
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
<script lang="ts">
|
||||
import type { LocalManaLink, ManaRecordRef } from '../types.js';
|
||||
import { useLinksForRecord } from '../queries.svelte.js';
|
||||
import { linkMutations } from '../mutations.svelte.js';
|
||||
import ManaLinkBadge from './ManaLinkBadge.svelte';
|
||||
|
||||
interface Props {
|
||||
recordRef: ManaRecordRef;
|
||||
onclick?: (link: LocalManaLink) => void;
|
||||
editable?: boolean;
|
||||
}
|
||||
|
||||
let { recordRef, onclick, editable = false }: Props = $props();
|
||||
|
||||
const links = useLinksForRecord(recordRef);
|
||||
|
||||
function handleRemove(pairId: string) {
|
||||
linkMutations.deleteLinkPair(pairId);
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if (links.value ?? []).length > 0}
|
||||
<div class="manalink-list">
|
||||
{#each links.value ?? [] as link (link.id)}
|
||||
<ManaLinkBadge {link} {onclick} onRemove={editable ? handleRemove : undefined} />
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<style>
|
||||
.manalink-list {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.375rem;
|
||||
}
|
||||
</style>
|
||||
2
packages/shared-links/src/ui/index.ts
Normal file
2
packages/shared-links/src/ui/index.ts
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
export { default as ManaLinkBadge } from './ManaLinkBadge.svelte';
|
||||
export { default as ManaLinkList } from './ManaLinkList.svelte';
|
||||
19
packages/shared-links/tsconfig.json
Normal file
19
packages/shared-links/tsconfig.json
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2022",
|
||||
"module": "ES2022",
|
||||
"moduleResolution": "bundler",
|
||||
"strict": true,
|
||||
"esModuleInterop": true,
|
||||
"skipLibCheck": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"declaration": true,
|
||||
"declarationMap": true,
|
||||
"sourceMap": true,
|
||||
"outDir": "dist",
|
||||
"types": ["svelte"]
|
||||
},
|
||||
"include": ["src/**/*"]
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue