sh3-core 0.8.2 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/api.d.ts +3 -6
- package/dist/api.js +1 -3
- package/dist/apps/types.d.ts +3 -5
- package/dist/documents/backends.d.ts +2 -0
- package/dist/documents/backends.js +6 -0
- package/dist/documents/handle.js +13 -5
- package/dist/documents/handle.test.js +55 -0
- package/dist/documents/http-backend.d.ts +11 -4
- package/dist/documents/http-backend.js +37 -11
- package/dist/documents/index.d.ts +2 -1
- package/dist/documents/index.js +1 -1
- package/dist/documents/sync-types.d.ts +45 -0
- package/dist/documents/sync-types.js +11 -0
- package/dist/documents/types.d.ts +40 -2
- package/dist/documents/types.js +3 -2
- package/dist/keys/ConsentDialog.svelte +4 -4
- package/dist/keys/consent.test.js +4 -3
- package/dist/keys/types.d.ts +4 -2
- package/dist/server-shard/types.d.ts +55 -8
- package/dist/shards/activate.svelte.js +4 -29
- package/dist/shards/types.d.ts +0 -15
- package/dist/shell/views/KeysAndPeers.svelte +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +2 -10
- package/dist/documents/journal-hook.d.ts +0 -6
- package/dist/documents/journal-hook.js +0 -16
- package/dist/documents/sync/activate-integration.test.d.ts +0 -1
- package/dist/documents/sync/activate-integration.test.js +0 -37
- package/dist/documents/sync/components/DocumentSyncExplorer.svelte +0 -99
- package/dist/documents/sync/components/DocumentSyncExplorer.svelte.d.ts +0 -15
- package/dist/documents/sync/components/SyncGrantPicker.svelte +0 -70
- package/dist/documents/sync/components/SyncGrantPicker.svelte.d.ts +0 -12
- package/dist/documents/sync/conflicts.d.ts +0 -30
- package/dist/documents/sync/conflicts.js +0 -77
- package/dist/documents/sync/conflicts.test.d.ts +0 -1
- package/dist/documents/sync/conflicts.test.js +0 -71
- package/dist/documents/sync/engine.d.ts +0 -19
- package/dist/documents/sync/engine.js +0 -188
- package/dist/documents/sync/engine.test.d.ts +0 -1
- package/dist/documents/sync/engine.test.js +0 -169
- package/dist/documents/sync/handle.d.ts +0 -11
- package/dist/documents/sync/handle.js +0 -79
- package/dist/documents/sync/handle.test.js +0 -56
- package/dist/documents/sync/hash.d.ts +0 -1
- package/dist/documents/sync/hash.js +0 -13
- package/dist/documents/sync/hash.test.d.ts +0 -1
- package/dist/documents/sync/hash.test.js +0 -20
- package/dist/documents/sync/index.d.ts +0 -5
- package/dist/documents/sync/index.js +0 -10
- package/dist/documents/sync/journal.d.ts +0 -30
- package/dist/documents/sync/journal.js +0 -179
- package/dist/documents/sync/journal.test.d.ts +0 -1
- package/dist/documents/sync/journal.test.js +0 -87
- package/dist/documents/sync/observer.d.ts +0 -3
- package/dist/documents/sync/observer.js +0 -45
- package/dist/documents/sync/registry.d.ts +0 -13
- package/dist/documents/sync/registry.js +0 -73
- package/dist/documents/sync/registry.test.d.ts +0 -1
- package/dist/documents/sync/registry.test.js +0 -53
- package/dist/documents/sync/serialization.d.ts +0 -5
- package/dist/documents/sync/serialization.js +0 -24
- package/dist/documents/sync/serialization.test.d.ts +0 -1
- package/dist/documents/sync/serialization.test.js +0 -26
- package/dist/documents/sync/singleton.d.ts +0 -11
- package/dist/documents/sync/singleton.js +0 -26
- package/dist/documents/sync/tombstones.d.ts +0 -19
- package/dist/documents/sync/tombstones.js +0 -58
- package/dist/documents/sync/tombstones.test.d.ts +0 -1
- package/dist/documents/sync/tombstones.test.js +0 -37
- package/dist/documents/sync/types.d.ts +0 -116
- package/dist/documents/sync/types.js +0 -27
- package/dist/documents/sync/write-hook.test.d.ts +0 -1
- package/dist/documents/sync/write-hook.test.js +0 -36
- package/dist/server-sync.d.ts +0 -6
- package/dist/server-sync.js +0 -634
- package/dist/server-sync.js.map +0 -7
- package/dist/shards/activate-sync-registry.test.d.ts +0 -1
- package/dist/shards/activate-sync-registry.test.js +0 -42
- package/dist/testing.d.ts +0 -3
- package/dist/testing.js +0 -77
- package/dist/testing.js.map +0 -7
- /package/dist/documents/{sync/handle.test.d.ts → handle.test.d.ts} +0 -0
package/dist/server-sync.js.map
DELETED
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"version": 3,
|
|
3
|
-
"sources": ["../src/documents/journal-hook.ts", "../src/documents/notifications.ts", "../src/documents/sync/hash.ts", "../src/documents/sync/types.ts", "../src/documents/sync/serialization.ts", "../src/documents/sync/journal.ts", "../src/documents/sync/tombstones.ts", "../src/documents/sync/conflicts.ts", "../src/documents/sync/engine.ts", "../src/documents/sync/registry.ts", "../src/documents/sync/singleton.ts", "../src/documents/sync/handle.ts"],
|
|
4
|
-
"sourcesContent": ["/*\n * Journal appender hook \u2014 lets the sync engine subscribe to regular\n * shard writes/deletes without creating an import cycle between the\n * document handle and the sync subsystem.\n */\n\nimport type { JournalEntry } from './sync/types';\n\ntype Appender = (entry: Omit<JournalEntry, 'seq' | 'ts'>) => Promise<void>;\n\nlet appender: Appender | null = null;\n\nexport function setJournalAppender(fn: Appender): void {\n appender = fn;\n}\n\nexport function clearJournalAppender(): void {\n appender = null;\n}\n\nexport async function notifyJournal(entry: Omit<JournalEntry, 'seq' | 'ts'>): Promise<void> {\n if (appender) await appender(entry);\n}\n", "/*\n * Document change notification emitter \u2014 in-process pub/sub.\n *\n * A single global emitter connects document handles so that a write\n * from one handle (e.g. an editor shard saving a file) is visible to\n * another handle watching the same scope (e.g. a preview shard).\n *\n * Cross-tab sync (BroadcastChannel) is deferred \u2014 this emitter is\n * in-process only.\n */\n\nimport type { DocumentChange } from './types';\n\ntype Listener = (change: DocumentChange) => void;\n\nclass DocumentChangeEmitter {\n #listeners = new Set<Listener>();\n\n subscribe(fn: Listener): () => void {\n this.#listeners.add(fn);\n return () => {\n this.#listeners.delete(fn);\n };\n }\n\n emit(change: DocumentChange): void {\n for (const fn of this.#listeners) {\n try {\n fn(change);\n } catch (e) {\n console.error('SH3: document change listener threw', e);\n }\n }\n }\n}\n\nexport const documentChanges = new DocumentChangeEmitter();\n", "/*\n * Content hashing for the sync subsystem. sha-256 via SubtleCrypto,\n * truncated to 16 hex chars \u2014 enough for collision resistance at\n * per-user document scale while keeping manifest entries small.\n */\n\nexport async function hashContent(content: string | ArrayBuffer): Promise<string> {\n const buf =\n typeof content === 'string' ? new TextEncoder().encode(content) : new Uint8Array(content);\n const digest = await crypto.subtle.digest('SHA-256', buf);\n const hex = Array.from(new Uint8Array(digest))\n .map((b) => b.toString(16).padStart(2, '0'))\n .join('');\n return hex.slice(0, 16);\n}\n", "/*\n * Document Sync API types. See docs/superpowers/specs/2026-04-14-document-sync-api-design.md.\n */\n\n/** Permission string required in a shard manifest to obtain ctx.sync(). */\nexport const PERMISSION_DOCUMENTS_SYNC = 'documents:sync';\n\n/** Reserved shardId used to persist sync metadata (journal, tombstones, cursors, grants). */\nexport const SYNC_RESERVED_SHARD_ID = '__sync__';\n\nexport type SyncScope =\n | { kind: 'shard'; shardId: string }\n | { kind: 'tenant' }\n | { kind: 'path'; shardId: string; prefix: string };\n\nexport interface ManifestEntry {\n path: string;\n shardId: string;\n hash: string;\n size: number;\n lastModified: number;\n tombstone?: { deletedAt: number };\n}\n\nexport interface ApplyEntry {\n path: string;\n shardId: string;\n op: 'upsert' | 'delete';\n content?: string | ArrayBuffer;\n remoteHash: string;\n remoteMtime: number;\n}\n\nexport interface ApplyOpts {\n onConflict?: ConflictPolicy;\n expectedLocalHash?: string;\n}\n\nexport type ApplyOutcome =\n | { status: 'applied'; newHash: string }\n | { status: 'skipped-identical' }\n | { status: 'conflict'; resolution: ConflictResolution };\n\nexport interface ApplyBatchResult {\n applied: Array<{ path: string; shardId: string; newHash: string }>;\n skipped: Array<{ path: string; shardId: string; reason: 'identical' }>;\n conflicts: ConflictResolution[];\n}\n\nexport interface ConflictResolution {\n path: string;\n shardId: string;\n localHash: string;\n remoteHash: string;\n conflictArtifactPath: string;\n base?: { hash: string };\n}\n\nexport interface ConflictContext {\n path: string;\n shardId: string;\n localHash: string;\n remoteHash: string;\n baseHash?: string;\n}\n\nexport type ConflictPolicy =\n | 'default'\n | 'remote-wins'\n | 'local-wins'\n | 'keep-both'\n | ((ctx: ConflictContext) => Promise<'remote-wins' | 'local-wins' | 'keep-both'>);\n\nexport interface JournalEntry {\n seq: number;\n ts: number;\n shardId: string;\n path: string;\n op: 'upsert' | 'delete';\n hash: string | null;\n}\n\nexport interface ChangePage {\n entries: JournalEntry[];\n nextCursor: string;\n hasMore: boolean;\n truncated?: boolean;\n}\n\nexport interface GrantRecord {\n connectorId: string;\n scope: SyncScope;\n grantedAt: number;\n}\n\nexport interface SyncHandle {\n readonly connectorId: string;\n grantedScopes(): Promise<SyncScope[]>;\n getManifest(scope: SyncScope): Promise<ManifestEntry[]>;\n changesSince(scope: SyncScope, cursor?: string): Promise<ChangePage>;\n ack(scope: SyncScope, cursor: string): Promise<void>;\n apply(scope: SyncScope, entry: ApplyEntry, opts?: ApplyOpts): Promise<ApplyOutcome>;\n applyBatch(scope: SyncScope, manifest: ApplyEntry[], opts?: ApplyOpts): Promise<ApplyBatchResult>;\n forget(scope: SyncScope, path: string): Promise<void>;\n}\n\nexport class ScopeNotGrantedError extends Error {\n constructor(public readonly scope: SyncScope) {\n super(`Scope not granted: ${JSON.stringify(scope)}`);\n this.name = 'ScopeNotGrantedError';\n }\n}\n\nexport class ScopeRevokedError extends Error {\n constructor(public readonly scope: SyncScope) {\n super(`Scope revoked during operation: ${JSON.stringify(scope)}`);\n this.name = 'ScopeRevokedError';\n }\n}\n\nexport class TenantMismatchError extends Error {\n constructor() {\n super('Sync handle tenantId does not match current session');\n this.name = 'TenantMismatchError';\n }\n}\n", "/*\n * JSON storage helpers on top of DocumentBackend. All sync metadata\n * lives under the reserved shardId SYNC_RESERVED_SHARD_ID, scoped per\n * tenant by the backend. Callers pass tenantId explicitly; tenant\n * scoping is enforced at the SyncHandle/engine layer.\n */\n\nimport type { DocumentBackend } from '../types';\nimport { SYNC_RESERVED_SHARD_ID } from './types';\n\nexport async function readJson<T>(\n backend: DocumentBackend,\n tenantId: string,\n path: string,\n): Promise<T | null> {\n const raw = await backend.read(tenantId, SYNC_RESERVED_SHARD_ID, path);\n if (raw === null) return null;\n const str = typeof raw === 'string' ? raw : new TextDecoder().decode(raw);\n return JSON.parse(str) as T;\n}\n\nexport async function writeJson(\n backend: DocumentBackend,\n tenantId: string,\n path: string,\n value: unknown,\n): Promise<void> {\n await backend.write(tenantId, SYNC_RESERVED_SHARD_ID, path, JSON.stringify(value));\n}\n\nexport async function deletePath(\n backend: DocumentBackend,\n tenantId: string,\n path: string,\n): Promise<void> {\n await backend.delete(tenantId, SYNC_RESERVED_SHARD_ID, path);\n}\n\nexport async function listJsonPaths(\n backend: DocumentBackend,\n tenantId: string,\n prefix: string,\n): Promise<string[]> {\n const all = await backend.list(tenantId, SYNC_RESERVED_SHARD_ID);\n return all.map((m) => m.path).filter((p) => p.startsWith(prefix));\n}\n", "/*\n * Change journal for the sync subsystem.\n *\n * Append-only per-tenant sequence of JournalEntry, stored as JSON\n * segments under journal/<n>.json. Cursors are opaque \"<seq>:<version>\"\n * strings; version matches the journal's truncation epoch. A cursor with\n * an older version whose seq < oldestSeq means the connector has fallen\n * behind retained history.\n */\n\nimport type { DocumentBackend } from '../types';\nimport type { ChangePage, JournalEntry, SyncScope } from './types';\nimport { readJson, writeJson, deletePath, listJsonPaths } from './serialization';\n\ninterface JournalMeta {\n currentSeq: number;\n currentSegment: number;\n oldestSegment: number;\n version: number; // bumped on truncation\n}\n\nconst META_PATH = 'journal/meta.json';\nconst SEGMENT_PREFIX = 'journal/seg-';\nconst CURSOR_PREFIX = 'cursors/';\nconst DEFAULT_SEGMENT_SIZE = 500;\nconst DEFAULT_PAGE_SIZE = 100;\n\nfunction segmentPath(i: number): string {\n return `${SEGMENT_PREFIX}${i}.json`;\n}\n\nfunction cursorPath(connectorId: string): string {\n return `${CURSOR_PREFIX}${encodeURIComponent(connectorId)}.json`;\n}\n\nfunction matchesScope(entry: JournalEntry, scope: SyncScope): boolean {\n switch (scope.kind) {\n case 'tenant': return true;\n case 'shard': return entry.shardId === scope.shardId;\n case 'path': return entry.shardId === scope.shardId && entry.path.startsWith(scope.prefix);\n }\n}\n\nexport class Journal {\n #meta: JournalMeta = { currentSeq: 0, currentSegment: 0, oldestSegment: 0, version: 0 };\n #segmentSize: number;\n #pageSize: number;\n\n constructor(\n private backend: DocumentBackend,\n private tenantId: string,\n opts: { segmentSize?: number; pageSize?: number } = {},\n ) {\n this.#segmentSize = opts.segmentSize ?? DEFAULT_SEGMENT_SIZE;\n this.#pageSize = opts.pageSize ?? DEFAULT_PAGE_SIZE;\n }\n\n async init(): Promise<void> {\n const meta = await readJson<JournalMeta>(this.backend, this.tenantId, META_PATH);\n if (meta) this.#meta = meta;\n }\n\n static encodeCursor(seq: number, version: number): string {\n return `${seq}:${version}`;\n }\n\n static decodeCursor(cursor: string): { seq: number; version: number } | null {\n const m = /^(\\d+):(\\d+)$/.exec(cursor);\n if (!m) return null;\n return { seq: Number(m[1]), version: Number(m[2]) };\n }\n\n async append(\n entry: Omit<JournalEntry, 'seq' | 'ts'>,\n ): Promise<JournalEntry> {\n const seq = this.#meta.currentSeq + 1;\n const full: JournalEntry = { ...entry, seq, ts: Date.now() };\n const segIdx = this.#meta.currentSegment;\n const current = (await readJson<JournalEntry[]>(this.backend, this.tenantId, segmentPath(segIdx))) ?? [];\n current.push(full);\n await writeJson(this.backend, this.tenantId, segmentPath(segIdx), current);\n this.#meta.currentSeq = seq;\n if (current.length >= this.#segmentSize) {\n this.#meta.currentSegment = segIdx + 1;\n }\n await writeJson(this.backend, this.tenantId, META_PATH, this.#meta);\n return full;\n }\n\n async oldestRetainedSeq(): Promise<number> {\n const first = await readJson<JournalEntry[]>(this.backend, this.tenantId, segmentPath(this.#meta.oldestSegment));\n if (!first || first.length === 0) return 0;\n return first[0].seq;\n }\n\n async changesSince(scope: SyncScope, cursor?: string): Promise<ChangePage> {\n let startSeq = 0;\n if (cursor) {\n const decoded = Journal.decodeCursor(cursor);\n if (!decoded) return { entries: [], nextCursor: cursor, hasMore: false };\n const oldest = await this.oldestRetainedSeq();\n if (decoded.version < this.#meta.version && decoded.seq < oldest) {\n return { entries: [], nextCursor: cursor, hasMore: false, truncated: true };\n }\n startSeq = decoded.seq;\n }\n\n const out: JournalEntry[] = [];\n let lastSeq = startSeq;\n let hasMore = false;\n\n for (let seg = this.#meta.oldestSegment; seg <= this.#meta.currentSegment; seg++) {\n const entries = (await readJson<JournalEntry[]>(this.backend, this.tenantId, segmentPath(seg))) ?? [];\n for (const e of entries) {\n if (e.seq <= startSeq) continue;\n if (!matchesScope(e, scope)) { lastSeq = e.seq; continue; }\n if (out.length >= this.#pageSize) { hasMore = true; break; }\n out.push(e);\n lastSeq = e.seq;\n }\n if (hasMore) break;\n }\n\n return {\n entries: out,\n nextCursor: Journal.encodeCursor(lastSeq, this.#meta.version),\n hasMore,\n };\n }\n\n async getCursor(connectorId: string): Promise<string | null> {\n return readJson<string>(this.backend, this.tenantId, cursorPath(connectorId));\n }\n\n async ackCursor(connectorId: string, cursor: string): Promise<void> {\n await writeJson(this.backend, this.tenantId, cursorPath(connectorId), cursor);\n }\n\n async dropCursor(connectorId: string): Promise<void> {\n await deletePath(this.backend, this.tenantId, cursorPath(connectorId));\n }\n\n async listCursors(): Promise<Array<{ connectorId: string; cursor: string }>> {\n const paths = await listJsonPaths(this.backend, this.tenantId, CURSOR_PREFIX);\n const out: Array<{ connectorId: string; cursor: string }> = [];\n for (const p of paths) {\n const cursor = await readJson<string>(this.backend, this.tenantId, p);\n if (cursor === null) continue;\n const id = decodeURIComponent(p.slice(CURSOR_PREFIX.length, -'.json'.length));\n out.push({ connectorId: id, cursor });\n }\n return out;\n }\n\n async minSeqAckedByAll(connectorIds: string[]): Promise<number> {\n if (connectorIds.length === 0) return 0;\n let min = Infinity;\n for (const id of connectorIds) {\n const c = await this.getCursor(id);\n const decoded = c ? Journal.decodeCursor(c) : null;\n const seq = decoded ? decoded.seq : 0;\n if (seq < min) min = seq;\n }\n return min === Infinity ? 0 : min;\n }\n\n /** Test-only: simulate truncating all segments whose entries are <= uptoSeq. */\n async __truncateForTest(uptoSeq: number): Promise<void> {\n for (let seg = this.#meta.oldestSegment; seg <= this.#meta.currentSegment; seg++) {\n const entries = (await readJson<JournalEntry[]>(this.backend, this.tenantId, segmentPath(seg))) ?? [];\n const last = entries[entries.length - 1];\n if (last && last.seq <= uptoSeq) {\n await deletePath(this.backend, this.tenantId, segmentPath(seg));\n this.#meta.oldestSegment = seg + 1;\n } else break;\n }\n this.#meta.version += 1;\n await writeJson(this.backend, this.tenantId, META_PATH, this.#meta);\n }\n}\n", "/*\n * Tombstone store \u2014 records deletion metadata so sync connectors can\n * distinguish \"never existed\" from \"was deleted.\" Backed by JSON docs\n * under the reserved sync shardId. GC is driven by the engine, not here.\n */\n\nimport type { DocumentBackend } from '../types';\nimport { readJson, writeJson, deletePath, listJsonPaths } from './serialization';\n\nconst PREFIX = 'tombstones/';\n\nfunction key(shardId: string, path: string): string {\n return `${PREFIX}${shardId}__${encodeURIComponent(path)}.json`;\n}\n\nexport interface TombstoneRecord {\n deletedAt: number;\n lastHash: string;\n}\n\nexport interface TombstoneEntry extends TombstoneRecord {\n shardId: string;\n path: string;\n}\n\nexport class TombstoneStore {\n constructor(\n private backend: DocumentBackend,\n private tenantId: string,\n ) {}\n\n async record(shardId: string, path: string, lastHash: string, deletedAt: number): Promise<void> {\n await writeJson(this.backend, this.tenantId, key(shardId, path), {\n deletedAt,\n lastHash,\n } satisfies TombstoneRecord);\n }\n\n async get(shardId: string, path: string): Promise<TombstoneRecord | null> {\n return readJson<TombstoneRecord>(this.backend, this.tenantId, key(shardId, path));\n }\n\n async clear(shardId: string, path: string): Promise<void> {\n await deletePath(this.backend, this.tenantId, key(shardId, path));\n }\n\n async listByShard(shardId: string): Promise<TombstoneEntry[]> {\n const prefix = `${PREFIX}${shardId}__`;\n const paths = await listJsonPaths(this.backend, this.tenantId, prefix);\n const out: TombstoneEntry[] = [];\n for (const p of paths) {\n const rec = await readJson<TombstoneRecord>(this.backend, this.tenantId, p);\n if (!rec) continue;\n const originalPath = decodeURIComponent(p.slice(prefix.length, -'.json'.length));\n out.push({ shardId, path: originalPath, ...rec });\n }\n return out;\n }\n\n async listAll(): Promise<TombstoneEntry[]> {\n const paths = await listJsonPaths(this.backend, this.tenantId, PREFIX);\n const out: TombstoneEntry[] = [];\n for (const p of paths) {\n const rec = await readJson<TombstoneRecord>(this.backend, this.tenantId, p);\n if (!rec) continue;\n const rest = p.slice(PREFIX.length, -'.json'.length);\n const sep = rest.indexOf('__');\n if (sep < 0) continue;\n const shardId = rest.slice(0, sep);\n const path = decodeURIComponent(rest.slice(sep + 2));\n out.push({ shardId, path, ...rec });\n }\n return out;\n }\n}\n", "/*\n * Conflict resolution \u2014 dispatches the caller-supplied (or default)\n * policy, writes .sync-conflict-* artifacts when required, and tracks\n * per-(connectorId, path) base hashes for three-way comparisons.\n */\n\nimport type { DocumentBackend } from '../types';\nimport type { ConflictPolicy, ConflictResolution } from './types';\nimport { readJson, writeJson } from './serialization';\n\ninterface ConflictInput {\n connectorId: string;\n shardId: string;\n path: string;\n localHash: string;\n remoteHash: string;\n remoteContent?: string | ArrayBuffer;\n baseHash?: string;\n}\n\nexport type ConflictAction =\n | { action: 'apply-remote'; asPath?: string }\n | { action: 'skip' }\n | { action: 'conflict'; resolution: ConflictResolution };\n\nconst BASES_PREFIX = 'bases/';\n\nfunction baseKey(connectorId: string, shardId: string, path: string): string {\n return `${BASES_PREFIX}${encodeURIComponent(connectorId)}__${shardId}__${encodeURIComponent(path)}.json`;\n}\n\nfunction isArtifactName(name: string): boolean {\n return /\\.sync-conflict-[^.]+-\\d+$/.test(name);\n}\n\nexport class ConflictManager {\n constructor(\n private backend: DocumentBackend,\n private tenantId: string,\n ) {}\n\n async resolve(policy: ConflictPolicy, input: ConflictInput): Promise<ConflictAction> {\n const p = typeof policy === 'function' ? await policy({\n path: input.path, shardId: input.shardId,\n localHash: input.localHash, remoteHash: input.remoteHash, baseHash: input.baseHash,\n }) : policy;\n\n switch (p) {\n case 'remote-wins': return { action: 'apply-remote' };\n case 'local-wins': return { action: 'skip' };\n case 'keep-both': {\n const asPath = `${input.path}.incoming-${input.connectorId}-${Date.now()}`;\n return { action: 'apply-remote', asPath };\n }\n case 'default':\n default: {\n const ts = Date.now();\n const artifact = `${input.path}.sync-conflict-${input.connectorId}-${ts}`;\n if (input.remoteContent !== undefined) {\n await this.backend.write(this.tenantId, input.shardId, artifact, input.remoteContent);\n }\n const resolution: ConflictResolution = {\n path: input.path,\n shardId: input.shardId,\n localHash: input.localHash,\n remoteHash: input.remoteHash,\n conflictArtifactPath: artifact,\n base: input.baseHash ? { hash: input.baseHash } : undefined,\n };\n return { action: 'conflict', resolution };\n }\n }\n }\n\n async getBaseHash(connectorId: string, shardId: string, path: string): Promise<string | null> {\n return readJson<string>(this.backend, this.tenantId, baseKey(connectorId, shardId, path));\n }\n\n async setBaseHash(connectorId: string, shardId: string, path: string, hash: string): Promise<void> {\n await writeJson(this.backend, this.tenantId, baseKey(connectorId, shardId, path), hash);\n }\n\n async listConflicts(shardId: string): Promise<ConflictResolution[]> {\n const metas = await this.backend.list(this.tenantId, shardId);\n const out: ConflictResolution[] = [];\n for (const m of metas) {\n const name = m.path;\n if (!isArtifactName(name)) continue;\n const m2 = /^(.*)\\.sync-conflict-([^-]+)-(\\d+)$/.exec(name);\n if (!m2) continue;\n const originalPath = m2[1];\n out.push({\n path: originalPath,\n shardId,\n localHash: '',\n remoteHash: '',\n conflictArtifactPath: name,\n });\n }\n return out;\n }\n}\n", "/*\n * SyncEngine \u2014 orchestrates manifest generation, apply pipeline, and\n * change notifications. Composes Journal, TombstoneStore, and\n * ConflictManager over a DocumentBackend. One instance per tenant.\n */\n\nimport type { DocumentBackend, DocumentChange } from '../types';\nimport { documentChanges } from '../notifications';\nimport type {\n ApplyBatchResult, ApplyEntry, ApplyOpts, ApplyOutcome,\n ChangePage, ConflictResolution, ManifestEntry, SyncScope,\n} from './types';\nimport { hashContent } from './hash';\nimport { Journal } from './journal';\nimport { TombstoneStore } from './tombstones';\nimport { ConflictManager } from './conflicts';\n\nfunction scopeCovers(scope: SyncScope, shardId: string, path: string): boolean {\n switch (scope.kind) {\n case 'tenant': return true;\n case 'shard': return scope.shardId === shardId;\n case 'path': return scope.shardId === shardId && path.startsWith(scope.prefix);\n }\n}\n\nexport class SyncEngine {\n #journal: Journal;\n #tombstones: TombstoneStore;\n #conflicts: ConflictManager;\n\n constructor(\n private backend: DocumentBackend,\n private tenantId: string,\n opts: { segmentSize?: number } = {},\n ) {\n this.#journal = new Journal(backend, tenantId, { segmentSize: opts.segmentSize });\n this.#tombstones = new TombstoneStore(backend, tenantId);\n this.#conflicts = new ConflictManager(backend, tenantId);\n }\n\n async init(): Promise<void> {\n await this.#journal.init();\n }\n\n get journal(): Journal { return this.#journal; }\n\n async getManifest(_connectorId: string, scope: SyncScope): Promise<ManifestEntry[]> {\n const shardIds = await this.#shardsInScope(scope);\n const entries: ManifestEntry[] = [];\n for (const shardId of shardIds) {\n const metas = await this.backend.list(this.tenantId, shardId);\n for (const m of metas) {\n if (!scopeCovers(scope, shardId, m.path)) continue;\n if (m.path.startsWith('.') || /\\.sync-conflict-/.test(m.path)) continue;\n const raw = await this.backend.read(this.tenantId, shardId, m.path);\n if (raw === null) continue;\n const hash = await hashContent(raw);\n entries.push({ path: m.path, shardId, hash, size: m.size, lastModified: m.lastModified });\n }\n const tombs = await this.#tombstones.listByShard(shardId);\n for (const t of tombs) {\n if (!scopeCovers(scope, shardId, t.path)) continue;\n entries.push({\n path: t.path, shardId, hash: t.lastHash, size: 0, lastModified: t.deletedAt,\n tombstone: { deletedAt: t.deletedAt },\n });\n }\n }\n return entries;\n }\n\n async changesSince(scope: SyncScope, cursor?: string): Promise<ChangePage> {\n return this.#journal.changesSince(scope, cursor);\n }\n\n async ack(connectorId: string, _scope: SyncScope, cursor: string): Promise<void> {\n // Scope is informational \u2014 cursors are connector-wide, not scope-specific.\n await this.#journal.ackCursor(connectorId, cursor);\n }\n\n async apply(\n connectorId: string,\n scope: SyncScope,\n entry: ApplyEntry,\n opts: ApplyOpts = {},\n ): Promise<ApplyOutcome> {\n if (!scopeCovers(scope, entry.shardId, entry.path)) {\n throw new Error(`ApplyEntry {${entry.shardId}:${entry.path}} falls outside scope`);\n }\n if (entry.op === 'upsert') return this.#applyUpsert(connectorId, entry, opts);\n return this.#applyDelete(connectorId, entry);\n }\n\n async applyBatch(\n connectorId: string,\n scope: SyncScope,\n manifest: ApplyEntry[],\n opts: ApplyOpts = {},\n ): Promise<ApplyBatchResult> {\n const applied: ApplyBatchResult['applied'] = [];\n const skipped: ApplyBatchResult['skipped'] = [];\n const conflicts: ConflictResolution[] = [];\n for (const e of manifest) {\n const out = await this.apply(connectorId, scope, e, opts);\n if (out.status === 'applied') applied.push({ path: e.path, shardId: e.shardId, newHash: out.newHash });\n else if (out.status === 'skipped-identical') skipped.push({ path: e.path, shardId: e.shardId, reason: 'identical' });\n else conflicts.push(out.resolution);\n }\n return { applied, skipped, conflicts };\n }\n\n async forget(scope: SyncScope, path: string): Promise<void> {\n const shardIds = await this.#shardsInScope(scope);\n for (const shardId of shardIds) {\n if (!scopeCovers(scope, shardId, path)) continue;\n await this.#tombstones.clear(shardId, path);\n }\n }\n\n // ----- internals -----\n\n async #applyUpsert(\n connectorId: string,\n entry: ApplyEntry,\n opts: ApplyOpts,\n ): Promise<ApplyOutcome> {\n const existing = await this.backend.read(this.tenantId, entry.shardId, entry.path);\n const existed = existing !== null;\n const localHash = existed ? await hashContent(existing) : null;\n\n if (localHash !== null && localHash === entry.remoteHash) {\n return { status: 'skipped-identical' };\n }\n\n const conflictTriggered =\n existed && (\n (opts.expectedLocalHash !== undefined && opts.expectedLocalHash !== localHash) ||\n (opts.expectedLocalHash === undefined && await this.#hasDivergedBase(connectorId, entry, localHash!))\n );\n\n if (conflictTriggered) {\n const baseHash = (await this.#conflicts.getBaseHash(connectorId, entry.shardId, entry.path)) ?? undefined;\n const action = await this.#conflicts.resolve(opts.onConflict ?? 'default', {\n connectorId,\n shardId: entry.shardId,\n path: entry.path,\n localHash: localHash!,\n remoteHash: entry.remoteHash,\n remoteContent: entry.content,\n baseHash,\n });\n if (action.action === 'skip') return { status: 'skipped-identical' };\n if (action.action === 'conflict') return { status: 'conflict', resolution: action.resolution };\n // apply-remote \u2014 fall through to write (possibly under asPath)\n const writePath = action.asPath ?? entry.path;\n return this.#writeAndRecord(connectorId, entry, writePath, existed);\n }\n\n return this.#writeAndRecord(connectorId, entry, entry.path, existed);\n }\n\n async #writeAndRecord(\n connectorId: string,\n entry: ApplyEntry,\n writePath: string,\n existed: boolean,\n ): Promise<ApplyOutcome> {\n if (entry.content === undefined) {\n throw new Error(`Upsert without content for ${entry.shardId}:${entry.path}`);\n }\n await this.backend.write(this.tenantId, entry.shardId, writePath, entry.content);\n await this.#tombstones.clear(entry.shardId, writePath);\n const newHash = await hashContent(entry.content);\n await this.#conflicts.setBaseHash(connectorId, entry.shardId, writePath, newHash);\n await this.#journal.append({ shardId: entry.shardId, path: writePath, op: 'upsert', hash: newHash });\n this.#emit({ type: existed && writePath === entry.path ? 'update' : 'create', path: writePath, tenantId: this.tenantId, shardId: entry.shardId });\n return { status: 'applied', newHash };\n }\n\n async #applyDelete(connectorId: string, entry: ApplyEntry): Promise<ApplyOutcome> {\n const existing = await this.backend.read(this.tenantId, entry.shardId, entry.path);\n if (existing === null) {\n // Already gone. Record a tombstone anyway so manifests reflect the delete.\n await this.#tombstones.record(entry.shardId, entry.path, entry.remoteHash, Date.now());\n await this.#journal.append({ shardId: entry.shardId, path: entry.path, op: 'delete', hash: null });\n return { status: 'applied', newHash: '' };\n }\n const lastHash = await hashContent(existing);\n await this.backend.delete(this.tenantId, entry.shardId, entry.path);\n await this.#tombstones.record(entry.shardId, entry.path, lastHash, Date.now());\n await this.#conflicts.setBaseHash(connectorId, entry.shardId, entry.path, ''); // clear base\n await this.#journal.append({ shardId: entry.shardId, path: entry.path, op: 'delete', hash: null });\n this.#emit({ type: 'delete', path: entry.path, tenantId: this.tenantId, shardId: entry.shardId });\n return { status: 'applied', newHash: '' };\n }\n\n async #hasDivergedBase(connectorId: string, entry: ApplyEntry, localHash: string): Promise<boolean> {\n const base = await this.#conflicts.getBaseHash(connectorId, entry.shardId, entry.path);\n if (!base) return false; // no prior base \u2192 treat as first-seen, apply\n return base !== localHash; // local moved since we last wrote it\n }\n\n async #shardsInScope(scope: SyncScope): Promise<string[]> {\n if (scope.kind === 'tenant') {\n // Listing all shards under a tenant isn't part of DocumentBackend's\n // contract. For now, we rely on callers passing {kind:'shard'} or\n // {kind:'path'}; {kind:'tenant'} traversal is driven by registry\n // listing at the handle layer. Return empty here as a placeholder\n // \u2014 the handle expands tenant-scope into per-shard calls.\n return [];\n }\n return [scope.shardId];\n }\n\n #emit(change: DocumentChange): void {\n documentChanges.emit(change);\n }\n}\n", "/*\n * Grant registry \u2014 public surface (revoke/list/listConflicts) for\n * connectors, plus __grantInternal used only by the core-owned\n * SyncGrantPicker component. Connectors do not import __grantInternal.\n */\n\nimport type { DocumentBackend } from '../types';\nimport type { ConflictResolution, GrantRecord, SyncScope } from './types';\nimport { readJson, writeJson, deletePath, listJsonPaths } from './serialization';\nimport { ConflictManager } from './conflicts';\n\nconst GRANTS_PREFIX = 'grants/';\n\nfunction grantPath(connectorId: string): string {\n return `${GRANTS_PREFIX}${encodeURIComponent(connectorId)}.json`;\n}\n\nfunction scopesEqual(a: SyncScope, b: SyncScope): boolean {\n if (a.kind !== b.kind) return false;\n if (a.kind === 'tenant') return true;\n if (a.kind === 'shard' && b.kind === 'shard') return a.shardId === b.shardId;\n if (a.kind === 'path' && b.kind === 'path')\n return a.shardId === b.shardId && a.prefix === b.prefix;\n return false;\n}\n\nexport async function __grantInternal(\n backend: DocumentBackend,\n tenantId: string,\n connectorId: string,\n scope: SyncScope,\n): Promise<void> {\n const existing = (await readJson<GrantRecord[]>(backend, tenantId, grantPath(connectorId))) ?? [];\n if (existing.some((g) => scopesEqual(g.scope, scope))) return;\n existing.push({ connectorId, scope, grantedAt: Date.now() });\n await writeJson(backend, tenantId, grantPath(connectorId), existing);\n}\n\nexport interface SyncRegistry {\n list(connectorId?: string): Promise<GrantRecord[]>;\n revoke(connectorId: string, scope: SyncScope): Promise<void>;\n /** Per-shard conflict enumeration. */\n listConflicts(shardId: string): Promise<ConflictResolution[]>;\n /** Tenant-wide conflict enumeration (fans out over every shard). */\n listConflicts(): Promise<ConflictResolution[]>;\n listAllConnectorIds(): Promise<string[]>;\n}\n\nexport function createSyncRegistry(backend: DocumentBackend, tenantId: string): SyncRegistry {\n const conflicts = new ConflictManager(backend, tenantId);\n\n async function readGrants(connectorId: string): Promise<GrantRecord[]> {\n return (await readJson<GrantRecord[]>(backend, tenantId, grantPath(connectorId))) ?? [];\n }\n\n return {\n async list(connectorId) {\n if (connectorId) return readGrants(connectorId);\n const paths = await listJsonPaths(backend, tenantId, GRANTS_PREFIX);\n const out: GrantRecord[] = [];\n for (const p of paths) {\n const arr = await readJson<GrantRecord[]>(backend, tenantId, p);\n if (arr) out.push(...arr);\n }\n return out;\n },\n\n async revoke(connectorId, scope) {\n const grants = await readGrants(connectorId);\n const next = grants.filter((g) => !scopesEqual(g.scope, scope));\n if (next.length === 0) await deletePath(backend, tenantId, grantPath(connectorId));\n else await writeJson(backend, tenantId, grantPath(connectorId), next);\n },\n\n async listConflicts(shardId?: string): Promise<ConflictResolution[]> {\n if (shardId !== undefined) return conflicts.listConflicts(shardId);\n const shards = await backend.listAllShards(tenantId);\n const out: ConflictResolution[] = [];\n for (const s of shards) {\n out.push(...(await conflicts.listConflicts(s)));\n }\n return out;\n },\n\n async listAllConnectorIds() {\n const paths = await listJsonPaths(backend, tenantId, GRANTS_PREFIX);\n return paths.map((p) =>\n decodeURIComponent(p.slice(GRANTS_PREFIX.length, -'.json'.length)),\n );\n },\n };\n}\n", "/*\n * Per-tenant SyncEngine singleton. Lazily initialised on first\n * ctx.sync() call. The journal appender hook is registered once\n * when the engine is first built so regular shard writes feed\n * the journal for all future connectors.\n */\n\nimport type { DocumentBackend } from '../types';\nimport { setJournalAppender } from '../journal-hook';\nimport { SyncEngine } from './engine';\nimport { createSyncRegistry, type SyncRegistry } from './registry';\n\ninterface Bundle {\n engine: SyncEngine;\n registry: SyncRegistry;\n}\n\nconst bundles = new Map<string, Bundle>();\n\nexport async function getSyncBundle(backend: DocumentBackend, tenantId: string): Promise<Bundle> {\n const existing = bundles.get(tenantId);\n if (existing) return existing;\n const engine = new SyncEngine(backend, tenantId);\n await engine.init();\n const registry = createSyncRegistry(backend, tenantId);\n setJournalAppender(async (e) => { await engine.journal.append(e); });\n const bundle = { engine, registry };\n bundles.set(tenantId, bundle);\n return bundle;\n}\n\n/** Test-only reset. */\nexport function __resetSyncBundlesForTest(): void {\n bundles.clear();\n}\n", "/*\n * SyncHandle factory \u2014 binds tenantId + connectorId, validates scope\n * grants per call, and fans {kind:'tenant'} scopes across the granted\n * shard/path scopes before delegating to the engine.\n */\n\nimport type { SyncEngine } from './engine';\nimport type { SyncRegistry } from './registry';\nimport {\n ScopeNotGrantedError,\n type ApplyBatchResult,\n type ManifestEntry, type SyncHandle, type SyncScope,\n} from './types';\n\nfunction scopeContains(parent: SyncScope, child: SyncScope): boolean {\n if (parent.kind === 'tenant') return true;\n if (parent.kind === 'shard') {\n if (child.kind === 'shard') return parent.shardId === child.shardId;\n if (child.kind === 'path') return parent.shardId === child.shardId;\n return false;\n }\n // parent.kind === 'path'\n if (child.kind === 'path')\n return child.shardId === parent.shardId && child.prefix.startsWith(parent.prefix);\n return false;\n}\n\ninterface SyncHandleDeps {\n tenantId: string;\n connectorId: string;\n engine: SyncEngine;\n registry: SyncRegistry;\n}\n\nexport function createSyncHandle(deps: SyncHandleDeps): SyncHandle {\n const { connectorId, engine, registry } = deps;\n\n async function currentGrants(): Promise<SyncScope[]> {\n const records = await registry.list(connectorId);\n return records.map((r) => r.scope);\n }\n\n async function requireScope(requested: SyncScope): Promise<SyncScope[]> {\n const grants = await currentGrants();\n const matching = grants.filter((g) => scopeContains(g, requested));\n if (matching.length === 0) throw new ScopeNotGrantedError(requested);\n if (requested.kind === 'tenant') {\n // Expand into the set of sub-scopes the connector is granted.\n const concrete = grants.filter((g) => g.kind === 'shard' || g.kind === 'path');\n return concrete.length > 0 ? concrete : [requested];\n }\n return [requested];\n }\n\n return {\n connectorId,\n\n async grantedScopes() {\n return currentGrants();\n },\n\n async getManifest(scope) {\n const concreteScopes = await requireScope(scope);\n const out: ManifestEntry[] = [];\n if (scope.kind === 'tenant' && concreteScopes[0]?.kind !== 'tenant') {\n for (const s of concreteScopes) out.push(...await engine.getManifest(connectorId, s));\n } else {\n out.push(...await engine.getManifest(connectorId, scope));\n }\n return out;\n },\n\n async changesSince(scope, cursor) {\n await requireScope(scope);\n return engine.changesSince(scope, cursor);\n },\n\n async ack(scope, cursor) {\n await requireScope(scope);\n await engine.ack(connectorId, scope, cursor);\n },\n\n async apply(scope, entry, opts) {\n await requireScope(scope);\n return engine.apply(connectorId, scope, entry, opts);\n },\n\n async applyBatch(scope, manifest, opts): Promise<ApplyBatchResult> {\n await requireScope(scope);\n return engine.applyBatch(connectorId, scope, manifest, opts);\n },\n\n async forget(scope, path) {\n await requireScope(scope);\n await engine.forget(scope, path);\n },\n };\n}\n"],
|
|
5
|
-
"mappings": "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",
|
|
6
|
-
"names": []
|
|
7
|
-
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, beforeEach } from 'vitest';
|
|
2
|
-
import { MemoryDocumentBackend } from '../documents/backends';
|
|
3
|
-
import { __setDocumentBackend, __setTenantId } from '../documents/config';
|
|
4
|
-
import { __resetSyncBundlesForTest } from '../documents/sync/singleton';
|
|
5
|
-
import { registerShard, activateShard, __resetShardRegistryForTest } from './activate.svelte';
|
|
6
|
-
import { PERMISSION_DOCUMENTS_BROWSE } from '../documents/types';
|
|
7
|
-
describe('ctx.syncRegistry', () => {
|
|
8
|
-
beforeEach(() => {
|
|
9
|
-
__resetShardRegistryForTest();
|
|
10
|
-
__resetSyncBundlesForTest();
|
|
11
|
-
__setDocumentBackend(new MemoryDocumentBackend());
|
|
12
|
-
__setTenantId('tenant-a');
|
|
13
|
-
});
|
|
14
|
-
it('is undefined without documents:browse', async () => {
|
|
15
|
-
let captured = null;
|
|
16
|
-
registerShard({
|
|
17
|
-
manifest: { id: 'no-obs', label: 'n', version: '0.0.0', views: [] },
|
|
18
|
-
activate(ctx) { captured = ctx; },
|
|
19
|
-
});
|
|
20
|
-
await activateShard('no-obs');
|
|
21
|
-
expect(captured.syncRegistry).toBeUndefined();
|
|
22
|
-
});
|
|
23
|
-
it('is available under documents:browse', async () => {
|
|
24
|
-
let captured = null;
|
|
25
|
-
registerShard({
|
|
26
|
-
manifest: {
|
|
27
|
-
id: 'obs', label: 'o', version: '0.0.0', views: [],
|
|
28
|
-
permissions: [PERMISSION_DOCUMENTS_BROWSE],
|
|
29
|
-
},
|
|
30
|
-
activate(ctx) { captured = ctx; },
|
|
31
|
-
});
|
|
32
|
-
await activateShard('obs');
|
|
33
|
-
const reg = captured.syncRegistry();
|
|
34
|
-
expect(typeof reg.list).toBe('function');
|
|
35
|
-
expect(typeof reg.listConflicts).toBe('function');
|
|
36
|
-
expect(typeof reg.listAllConnectorIds).toBe('function');
|
|
37
|
-
expect(typeof reg.revoke).toBe('function');
|
|
38
|
-
// Functional smoke: empty registry should return []
|
|
39
|
-
expect(await reg.list()).toEqual([]);
|
|
40
|
-
expect(await reg.listConflicts()).toEqual([]);
|
|
41
|
-
});
|
|
42
|
-
});
|
package/dist/testing.d.ts
DELETED
package/dist/testing.js
DELETED
|
@@ -1,77 +0,0 @@
|
|
|
1
|
-
// src/documents/backends.ts
|
|
2
|
-
function compositeKey(tenantId, shardId, path) {
|
|
3
|
-
return `${tenantId}/${shardId}/${path}`;
|
|
4
|
-
}
|
|
5
|
-
function keyPrefix(tenantId, shardId) {
|
|
6
|
-
return `${tenantId}/${shardId}/`;
|
|
7
|
-
}
|
|
8
|
-
var MemoryDocumentBackend = class {
|
|
9
|
-
#store = /* @__PURE__ */ new Map();
|
|
10
|
-
async read(tenantId, shardId, path) {
|
|
11
|
-
const entry = this.#store.get(compositeKey(tenantId, shardId, path));
|
|
12
|
-
return entry ? entry.content : null;
|
|
13
|
-
}
|
|
14
|
-
async write(tenantId, shardId, path, content) {
|
|
15
|
-
const size = typeof content === "string" ? new Blob([content]).size : content.byteLength;
|
|
16
|
-
this.#store.set(compositeKey(tenantId, shardId, path), {
|
|
17
|
-
content,
|
|
18
|
-
size,
|
|
19
|
-
lastModified: Date.now()
|
|
20
|
-
});
|
|
21
|
-
}
|
|
22
|
-
async delete(tenantId, shardId, path) {
|
|
23
|
-
this.#store.delete(compositeKey(tenantId, shardId, path));
|
|
24
|
-
}
|
|
25
|
-
async list(tenantId, shardId) {
|
|
26
|
-
const prefix = keyPrefix(tenantId, shardId);
|
|
27
|
-
const out = [];
|
|
28
|
-
for (const [key, entry] of this.#store) {
|
|
29
|
-
if (key.startsWith(prefix)) {
|
|
30
|
-
out.push({
|
|
31
|
-
path: key.slice(prefix.length),
|
|
32
|
-
size: entry.size,
|
|
33
|
-
lastModified: entry.lastModified
|
|
34
|
-
});
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
return out;
|
|
38
|
-
}
|
|
39
|
-
async exists(tenantId, shardId, path) {
|
|
40
|
-
return this.#store.has(compositeKey(tenantId, shardId, path));
|
|
41
|
-
}
|
|
42
|
-
async listAllShards(tenantId) {
|
|
43
|
-
const prefix = `${tenantId}/`;
|
|
44
|
-
const shards = /* @__PURE__ */ new Set();
|
|
45
|
-
for (const key of this.#store.keys()) {
|
|
46
|
-
if (!key.startsWith(prefix)) continue;
|
|
47
|
-
const rest = key.slice(prefix.length);
|
|
48
|
-
const slash = rest.indexOf("/");
|
|
49
|
-
if (slash < 0) continue;
|
|
50
|
-
shards.add(rest.slice(0, slash));
|
|
51
|
-
}
|
|
52
|
-
return [...shards];
|
|
53
|
-
}
|
|
54
|
-
async listAllDocuments(tenantId) {
|
|
55
|
-
const prefix = `${tenantId}/`;
|
|
56
|
-
const out = [];
|
|
57
|
-
for (const [key, entry] of this.#store) {
|
|
58
|
-
if (!key.startsWith(prefix)) continue;
|
|
59
|
-
const rest = key.slice(prefix.length);
|
|
60
|
-
const slash = rest.indexOf("/");
|
|
61
|
-
if (slash < 0) continue;
|
|
62
|
-
const shardId = rest.slice(0, slash);
|
|
63
|
-
const path = rest.slice(slash + 1);
|
|
64
|
-
out.push({ shardId, path, size: entry.size, lastModified: entry.lastModified });
|
|
65
|
-
}
|
|
66
|
-
return out;
|
|
67
|
-
}
|
|
68
|
-
};
|
|
69
|
-
|
|
70
|
-
// src/testing.ts
|
|
71
|
-
function createMemoryDocumentBackend() {
|
|
72
|
-
return new MemoryDocumentBackend();
|
|
73
|
-
}
|
|
74
|
-
export {
|
|
75
|
-
createMemoryDocumentBackend
|
|
76
|
-
};
|
|
77
|
-
//# sourceMappingURL=testing.js.map
|
package/dist/testing.js.map
DELETED
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"version": 3,
|
|
3
|
-
"sources": ["../src/documents/backends.ts", "../src/testing.ts"],
|
|
4
|
-
"sourcesContent": ["/*\n * Document zone backends \u2014 concrete storage implementations.\n *\n * MemoryDocumentBackend: Map-based, for tests and ephemeral use.\n * IndexedDBDocumentBackend: The web default. Lazy-inits on first\n * operation to avoid blocking bootstrap.\n */\n\nimport type { DocumentBackend, DocumentMeta } from './types';\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\nfunction compositeKey(tenantId: string, shardId: string, path: string): string {\n return `${tenantId}/${shardId}/${path}`;\n}\n\nfunction keyPrefix(tenantId: string, shardId: string): string {\n return `${tenantId}/${shardId}/`;\n}\n\n// ---------------------------------------------------------------------------\n// MemoryDocumentBackend\n// ---------------------------------------------------------------------------\n\ninterface MemoryEntry {\n content: string | ArrayBuffer;\n size: number;\n lastModified: number;\n}\n\nexport class MemoryDocumentBackend implements DocumentBackend {\n #store = new Map<string, MemoryEntry>();\n\n async read(\n tenantId: string,\n shardId: string,\n path: string,\n ): Promise<string | ArrayBuffer | null> {\n const entry = this.#store.get(compositeKey(tenantId, shardId, path));\n return entry ? entry.content : null;\n }\n\n async write(\n tenantId: string,\n shardId: string,\n path: string,\n content: string | ArrayBuffer,\n ): Promise<void> {\n const size =\n typeof content === 'string' ? new Blob([content]).size : content.byteLength;\n this.#store.set(compositeKey(tenantId, shardId, path), {\n content,\n size,\n lastModified: Date.now(),\n });\n }\n\n async delete(tenantId: string, shardId: string, path: string): Promise<void> {\n this.#store.delete(compositeKey(tenantId, shardId, path));\n }\n\n async list(tenantId: string, shardId: string): Promise<DocumentMeta[]> {\n const prefix = keyPrefix(tenantId, shardId);\n const out: DocumentMeta[] = [];\n for (const [key, entry] of this.#store) {\n if (key.startsWith(prefix)) {\n out.push({\n path: key.slice(prefix.length),\n size: entry.size,\n lastModified: entry.lastModified,\n });\n }\n }\n return out;\n }\n\n async exists(tenantId: string, shardId: string, path: string): Promise<boolean> {\n return this.#store.has(compositeKey(tenantId, shardId, path));\n }\n\n async listAllShards(tenantId: string): Promise<string[]> {\n const prefix = `${tenantId}/`;\n const shards = new Set<string>();\n for (const key of this.#store.keys()) {\n if (!key.startsWith(prefix)) continue;\n const rest = key.slice(prefix.length);\n const slash = rest.indexOf('/');\n if (slash < 0) continue;\n shards.add(rest.slice(0, slash));\n }\n return [...shards];\n }\n\n async listAllDocuments(\n tenantId: string,\n ): Promise<Array<DocumentMeta & { shardId: string }>> {\n const prefix = `${tenantId}/`;\n const out: Array<DocumentMeta & { shardId: string }> = [];\n for (const [key, entry] of this.#store) {\n if (!key.startsWith(prefix)) continue;\n const rest = key.slice(prefix.length);\n const slash = rest.indexOf('/');\n if (slash < 0) continue;\n const shardId = rest.slice(0, slash);\n const path = rest.slice(slash + 1);\n out.push({ shardId, path, size: entry.size, lastModified: entry.lastModified });\n }\n return out;\n }\n}\n\n// ---------------------------------------------------------------------------\n// IndexedDBDocumentBackend\n// ---------------------------------------------------------------------------\n\nconst IDB_NAME = 'sh3-documents';\nconst IDB_STORE = 'docs';\nconst IDB_VERSION = 2;\n\ninterface IDBEntry {\n content: string | ArrayBuffer;\n size: number;\n lastModified: number;\n}\n\nexport class IndexedDBDocumentBackend implements DocumentBackend {\n #dbPromise: Promise<IDBDatabase> | null = null;\n\n /**\n * Lazy-open the database on first use. The promise is cached so\n * subsequent calls await the same open.\n */\n #db(): Promise<IDBDatabase> {\n if (!this.#dbPromise) {\n this.#dbPromise = new Promise<IDBDatabase>((resolve, reject) => {\n const req = indexedDB.open(IDB_NAME, IDB_VERSION);\n req.onupgradeneeded = () => {\n const db = req.result;\n if (!db.objectStoreNames.contains(IDB_STORE)) {\n db.createObjectStore(IDB_STORE);\n }\n };\n req.onsuccess = () => resolve(req.result);\n req.onerror = () => reject(req.error);\n });\n }\n return this.#dbPromise;\n }\n\n /** Run a single-store transaction and return the request result. */\n async #tx<T>(\n mode: IDBTransactionMode,\n fn: (store: IDBObjectStore) => IDBRequest<T>,\n ): Promise<T> {\n const db = await this.#db();\n return new Promise<T>((resolve, reject) => {\n const tx = db.transaction(IDB_STORE, mode);\n const store = tx.objectStore(IDB_STORE);\n const req = fn(store);\n req.onsuccess = () => resolve(req.result);\n req.onerror = () => reject(req.error);\n });\n }\n\n async read(\n tenantId: string,\n shardId: string,\n path: string,\n ): Promise<string | ArrayBuffer | null> {\n const key = compositeKey(tenantId, shardId, path);\n const entry = await this.#tx<IDBEntry | undefined>('readonly', (s) => s.get(key));\n return entry ? entry.content : null;\n }\n\n async write(\n tenantId: string,\n shardId: string,\n path: string,\n content: string | ArrayBuffer,\n ): Promise<void> {\n const key = compositeKey(tenantId, shardId, path);\n const size =\n typeof content === 'string' ? new Blob([content]).size : content.byteLength;\n const entry: IDBEntry = { content, size, lastModified: Date.now() };\n await this.#tx('readwrite', (s) => s.put(entry, key));\n }\n\n async delete(tenantId: string, shardId: string, path: string): Promise<void> {\n const key = compositeKey(tenantId, shardId, path);\n await this.#tx('readwrite', (s) => s.delete(key));\n }\n\n async list(tenantId: string, shardId: string): Promise<DocumentMeta[]> {\n const prefix = keyPrefix(tenantId, shardId);\n const db = await this.#db();\n return new Promise<DocumentMeta[]>((resolve, reject) => {\n const tx = db.transaction(IDB_STORE, 'readonly');\n const store = tx.objectStore(IDB_STORE);\n // IDBKeyRange.bound selects all keys that start with the prefix.\n // The upper bound appends a character beyond '/' to capture all\n // sub-paths without over-selecting.\n const range = IDBKeyRange.bound(prefix, prefix + '\\uffff', false, false);\n const req = store.openCursor(range);\n const out: DocumentMeta[] = [];\n req.onsuccess = () => {\n const cursor = req.result;\n if (cursor) {\n const entry = cursor.value as IDBEntry;\n out.push({\n path: (cursor.key as string).slice(prefix.length),\n size: entry.size,\n lastModified: entry.lastModified,\n });\n cursor.continue();\n } else {\n resolve(out);\n }\n };\n req.onerror = () => reject(req.error);\n });\n }\n\n async exists(tenantId: string, shardId: string, path: string): Promise<boolean> {\n const key = compositeKey(tenantId, shardId, path);\n // getKey is cheaper than get \u2014 avoids deserializing the value.\n const result = await this.#tx<IDBValidKey | undefined>(\n 'readonly',\n (s) => s.getKey(key),\n );\n return result !== undefined;\n }\n\n async listAllShards(tenantId: string): Promise<string[]> {\n const prefix = `${tenantId}/`;\n const db = await this.#db();\n return new Promise<string[]>((resolve, reject) => {\n const tx = db.transaction(IDB_STORE, 'readonly');\n const store = tx.objectStore(IDB_STORE);\n const range = IDBKeyRange.bound(prefix, prefix + '\\uffff', false, false);\n const req = store.openKeyCursor(range);\n const shards = new Set<string>();\n req.onsuccess = () => {\n const cursor = req.result;\n if (cursor) {\n const rest = (cursor.key as string).slice(prefix.length);\n const slash = rest.indexOf('/');\n if (slash >= 0) shards.add(rest.slice(0, slash));\n cursor.continue();\n } else {\n resolve([...shards]);\n }\n };\n req.onerror = () => reject(req.error);\n });\n }\n\n async listAllDocuments(\n tenantId: string,\n ): Promise<Array<DocumentMeta & { shardId: string }>> {\n const prefix = `${tenantId}/`;\n const db = await this.#db();\n return new Promise<Array<DocumentMeta & { shardId: string }>>((resolve, reject) => {\n const tx = db.transaction(IDB_STORE, 'readonly');\n const store = tx.objectStore(IDB_STORE);\n const range = IDBKeyRange.bound(prefix, prefix + '\\uffff', false, false);\n const req = store.openCursor(range);\n const out: Array<DocumentMeta & { shardId: string }> = [];\n req.onsuccess = () => {\n const cursor = req.result;\n if (cursor) {\n const rest = (cursor.key as string).slice(prefix.length);\n const slash = rest.indexOf('/');\n if (slash >= 0) {\n const entry = cursor.value as IDBEntry;\n out.push({\n shardId: rest.slice(0, slash),\n path: rest.slice(slash + 1),\n size: entry.size,\n lastModified: entry.lastModified,\n });\n }\n cursor.continue();\n } else {\n resolve(out);\n }\n };\n req.onerror = () => reject(req.error);\n });\n }\n}\n", "/*\n * Testing utilities subpath entry \u2014 exports in-memory helpers for use\n * in sh3-server and shard tests without pulling in browser-only code.\n */\n\nimport { MemoryDocumentBackend } from './documents/backends';\nimport type { DocumentBackend } from './documents/types';\n\n/** Factory wrapper so test code can call createMemoryDocumentBackend() without new. */\nexport function createMemoryDocumentBackend(): DocumentBackend {\n return new MemoryDocumentBackend();\n}\n"],
|
|
5
|
-
"mappings": ";AAcA,SAAS,aAAa,UAAkB,SAAiB,MAAsB;AAC7E,SAAO,GAAG,QAAQ,IAAI,OAAO,IAAI,IAAI;AACvC;AAEA,SAAS,UAAU,UAAkB,SAAyB;AAC5D,SAAO,GAAG,QAAQ,IAAI,OAAO;AAC/B;AAYO,IAAM,wBAAN,MAAuD;AAAA,EAC5D,SAAS,oBAAI,IAAyB;AAAA,EAEtC,MAAM,KACJ,UACA,SACA,MACsC;AACtC,UAAM,QAAQ,KAAK,OAAO,IAAI,aAAa,UAAU,SAAS,IAAI,CAAC;AACnE,WAAO,QAAQ,MAAM,UAAU;AAAA,EACjC;AAAA,EAEA,MAAM,MACJ,UACA,SACA,MACA,SACe;AACf,UAAM,OACJ,OAAO,YAAY,WAAW,IAAI,KAAK,CAAC,OAAO,CAAC,EAAE,OAAO,QAAQ;AACnE,SAAK,OAAO,IAAI,aAAa,UAAU,SAAS,IAAI,GAAG;AAAA,MACrD;AAAA,MACA;AAAA,MACA,cAAc,KAAK,IAAI;AAAA,IACzB,CAAC;AAAA,EACH;AAAA,EAEA,MAAM,OAAO,UAAkB,SAAiB,MAA6B;AAC3E,SAAK,OAAO,OAAO,aAAa,UAAU,SAAS,IAAI,CAAC;AAAA,EAC1D;AAAA,EAEA,MAAM,KAAK,UAAkB,SAA0C;AACrE,UAAM,SAAS,UAAU,UAAU,OAAO;AAC1C,UAAM,MAAsB,CAAC;AAC7B,eAAW,CAAC,KAAK,KAAK,KAAK,KAAK,QAAQ;AACtC,UAAI,IAAI,WAAW,MAAM,GAAG;AAC1B,YAAI,KAAK;AAAA,UACP,MAAM,IAAI,MAAM,OAAO,MAAM;AAAA,UAC7B,MAAM,MAAM;AAAA,UACZ,cAAc,MAAM;AAAA,QACtB,CAAC;AAAA,MACH;AAAA,IACF;AACA,WAAO;AAAA,EACT;AAAA,EAEA,MAAM,OAAO,UAAkB,SAAiB,MAAgC;AAC9E,WAAO,KAAK,OAAO,IAAI,aAAa,UAAU,SAAS,IAAI,CAAC;AAAA,EAC9D;AAAA,EAEA,MAAM,cAAc,UAAqC;AACvD,UAAM,SAAS,GAAG,QAAQ;AAC1B,UAAM,SAAS,oBAAI,IAAY;AAC/B,eAAW,OAAO,KAAK,OAAO,KAAK,GAAG;AACpC,UAAI,CAAC,IAAI,WAAW,MAAM,EAAG;AAC7B,YAAM,OAAO,IAAI,MAAM,OAAO,MAAM;AACpC,YAAM,QAAQ,KAAK,QAAQ,GAAG;AAC9B,UAAI,QAAQ,EAAG;AACf,aAAO,IAAI,KAAK,MAAM,GAAG,KAAK,CAAC;AAAA,IACjC;AACA,WAAO,CAAC,GAAG,MAAM;AAAA,EACnB;AAAA,EAEA,MAAM,iBACJ,UACoD;AACpD,UAAM,SAAS,GAAG,QAAQ;AAC1B,UAAM,MAAiD,CAAC;AACxD,eAAW,CAAC,KAAK,KAAK,KAAK,KAAK,QAAQ;AACtC,UAAI,CAAC,IAAI,WAAW,MAAM,EAAG;AAC7B,YAAM,OAAO,IAAI,MAAM,OAAO,MAAM;AACpC,YAAM,QAAQ,KAAK,QAAQ,GAAG;AAC9B,UAAI,QAAQ,EAAG;AACf,YAAM,UAAU,KAAK,MAAM,GAAG,KAAK;AACnC,YAAM,OAAO,KAAK,MAAM,QAAQ,CAAC;AACjC,UAAI,KAAK,EAAE,SAAS,MAAM,MAAM,MAAM,MAAM,cAAc,MAAM,aAAa,CAAC;AAAA,IAChF;AACA,WAAO;AAAA,EACT;AACF;;;ACtGO,SAAS,8BAA+C;AAC7D,SAAO,IAAI,sBAAsB;AACnC;",
|
|
6
|
-
"names": []
|
|
7
|
-
}
|
|
File without changes
|