@happyvertical/smrt-assets 0.30.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/AGENTS.md +78 -0
- package/CLAUDE.md +1 -0
- package/LICENSE +7 -0
- package/README.md +136 -0
- package/dist/__smrt-register__.d.ts +2 -0
- package/dist/__smrt-register__.d.ts.map +1 -0
- package/dist/asset-association.d.ts +16 -0
- package/dist/asset-association.d.ts.map +1 -0
- package/dist/asset-associations.d.ts +27 -0
- package/dist/asset-associations.d.ts.map +1 -0
- package/dist/asset-capabilities.d.ts +137 -0
- package/dist/asset-capabilities.d.ts.map +1 -0
- package/dist/asset-conventions.d.ts +76 -0
- package/dist/asset-conventions.d.ts.map +1 -0
- package/dist/asset-metafield.d.ts +27 -0
- package/dist/asset-metafield.d.ts.map +1 -0
- package/dist/asset-metafields.d.ts +27 -0
- package/dist/asset-metafields.d.ts.map +1 -0
- package/dist/asset-runtime.d.ts +218 -0
- package/dist/asset-runtime.d.ts.map +1 -0
- package/dist/asset-serving.d.ts +146 -0
- package/dist/asset-serving.d.ts.map +1 -0
- package/dist/asset-status.d.ts +15 -0
- package/dist/asset-status.d.ts.map +1 -0
- package/dist/asset-statuses.d.ts +25 -0
- package/dist/asset-statuses.d.ts.map +1 -0
- package/dist/asset-store.d.ts +200 -0
- package/dist/asset-store.d.ts.map +1 -0
- package/dist/asset-type.d.ts +15 -0
- package/dist/asset-type.d.ts.map +1 -0
- package/dist/asset-types.d.ts +28 -0
- package/dist/asset-types.d.ts.map +1 -0
- package/dist/asset.d.ts +158 -0
- package/dist/asset.d.ts.map +1 -0
- package/dist/assets.d.ts +125 -0
- package/dist/assets.d.ts.map +1 -0
- package/dist/folder.d.ts +16 -0
- package/dist/folder.d.ts.map +1 -0
- package/dist/folders.d.ts +45 -0
- package/dist/folders.d.ts.map +1 -0
- package/dist/index.d.ts +21 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +2285 -0
- package/dist/index.js.map +1 -0
- package/dist/manifest.json +4079 -0
- package/dist/media-bundle-persistence.d.ts +99 -0
- package/dist/media-bundle-persistence.d.ts.map +1 -0
- package/dist/owned-asset-helpers.d.ts +20 -0
- package/dist/owned-asset-helpers.d.ts.map +1 -0
- package/dist/playground.d.ts +2 -0
- package/dist/playground.d.ts.map +1 -0
- package/dist/playground.js +127 -0
- package/dist/playground.js.map +1 -0
- package/dist/smrt-knowledge.json +1922 -0
- package/dist/svelte/ActionBar.svelte +203 -0
- package/dist/svelte/ActionBar.svelte.d.ts +5 -0
- package/dist/svelte/ActionBar.svelte.d.ts.map +1 -0
- package/dist/svelte/AssetDetail.svelte +521 -0
- package/dist/svelte/AssetDetail.svelte.d.ts +35 -0
- package/dist/svelte/AssetDetail.svelte.d.ts.map +1 -0
- package/dist/svelte/AssetGrid.svelte +351 -0
- package/dist/svelte/AssetGrid.svelte.d.ts +5 -0
- package/dist/svelte/AssetGrid.svelte.d.ts.map +1 -0
- package/dist/svelte/AssetList.svelte +436 -0
- package/dist/svelte/AssetList.svelte.d.ts +5 -0
- package/dist/svelte/AssetList.svelte.d.ts.map +1 -0
- package/dist/svelte/AssetManager.svelte +381 -0
- package/dist/svelte/AssetManager.svelte.d.ts +5 -0
- package/dist/svelte/AssetManager.svelte.d.ts.map +1 -0
- package/dist/svelte/AssetToolbar.svelte +388 -0
- package/dist/svelte/AssetToolbar.svelte.d.ts +5 -0
- package/dist/svelte/AssetToolbar.svelte.d.ts.map +1 -0
- package/dist/svelte/CreateAssetModal.svelte +373 -0
- package/dist/svelte/CreateAssetModal.svelte.d.ts +19 -0
- package/dist/svelte/CreateAssetModal.svelte.d.ts.map +1 -0
- package/dist/svelte/__tests__/ActionBar.test.js +72 -0
- package/dist/svelte/__tests__/AssetDetail.test.js +57 -0
- package/dist/svelte/__tests__/AssetGrid.test.js +69 -0
- package/dist/svelte/__tests__/AssetList.test.js +72 -0
- package/dist/svelte/__tests__/AssetManager.test.js +21 -0
- package/dist/svelte/__tests__/AssetManagerRoute.test.js +16 -0
- package/dist/svelte/__tests__/AssetToolbar.test.js +39 -0
- package/dist/svelte/__tests__/CreateAssetModal.test.js +42 -0
- package/dist/svelte/i18n.d.ts +76 -0
- package/dist/svelte/i18n.d.ts.map +1 -0
- package/dist/svelte/i18n.js +87 -0
- package/dist/svelte/index.d.ts +19 -0
- package/dist/svelte/index.d.ts.map +1 -0
- package/dist/svelte/index.js +30 -0
- package/dist/svelte/playground/AssetDetailPreview.svelte +131 -0
- package/dist/svelte/playground/AssetDetailPreview.svelte.d.ts +8 -0
- package/dist/svelte/playground/AssetDetailPreview.svelte.d.ts.map +1 -0
- package/dist/svelte/playground/CreateAssetModalPreview.svelte +151 -0
- package/dist/svelte/playground/CreateAssetModalPreview.svelte.d.ts +4 -0
- package/dist/svelte/playground/CreateAssetModalPreview.svelte.d.ts.map +1 -0
- package/dist/svelte/playground.d.ts +60 -0
- package/dist/svelte/playground.d.ts.map +1 -0
- package/dist/svelte/playground.js +93 -0
- package/dist/svelte/routes/AssetManagerRoute.svelte +209 -0
- package/dist/svelte/routes/AssetManagerRoute.svelte.d.ts +4 -0
- package/dist/svelte/routes/AssetManagerRoute.svelte.d.ts.map +1 -0
- package/dist/svelte/routes/index.d.ts +2 -0
- package/dist/svelte/routes/index.d.ts.map +1 -0
- package/dist/svelte/routes/index.js +1 -0
- package/dist/svelte/routes/shared.d.ts +25 -0
- package/dist/svelte/routes/shared.d.ts.map +1 -0
- package/dist/svelte/routes/shared.js +31 -0
- package/dist/svelte/types.d.ts +179 -0
- package/dist/svelte/types.d.ts.map +1 -0
- package/dist/svelte/types.js +6 -0
- package/dist/types.d.ts +80 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/dist/ui.d.ts +10 -0
- package/dist/ui.d.ts.map +1 -0
- package/dist/ui.js +85 -0
- package/dist/ui.js.map +1 -0
- package/package.json +102 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sources":["../src/__smrt-register__.ts","../src/asset-association.ts","../src/asset-associations.ts","../src/asset-status.ts","../src/asset-type.ts","../src/asset.ts","../src/asset-capabilities.ts","../src/asset-conventions.ts","../src/asset-metafield.ts","../src/asset-metafields.ts","../src/asset-store.ts","../src/assets.ts","../src/asset-runtime.ts","../src/asset-serving.ts","../src/asset-statuses.ts","../src/asset-types.ts","../src/folder.ts","../src/folders.ts","../src/media-bundle-persistence.ts","../src/owned-asset-helpers.ts"],"sourcesContent":["/**\n * Self-registers this package's build-time manifest before any @smrt() decorator\n * in the package fires. Fixes issue #1132: in consumer runtimes (tsx, SvelteKit\n * SSR, plain `vite dev`) the decorator's synchronous manifest lookup previously\n * missed because no step populated the global manifest cache — classes got\n * registered with zero fields and `save()` / `toJSON()` silently dropped every\n * declared property.\n *\n * Import this module as the first statement in `src/index.ts` so its top-level\n * side effect runs ahead of any class module's @smrt() decorator.\n *\n * Silent no-op in dev/test, where the vitest plugin already populates manifests\n * via a different path. Only needs to succeed in the published dist output.\n *\n * @see https://github.com/happyvertical/smrt/issues/1132\n */\nimport { ObjectRegistry } from '@happyvertical/smrt-core';\n\n// `new URL('./manifest.json', import.meta.url)` resolves at runtime to the\n// manifest sitting next to this module's compiled output. Vite warns at build\n// time that it cannot pre-resolve the URL; that is the intended behavior —\n// the URL must resolve to dist/manifest.json at runtime, not be inlined.\nObjectRegistry.registerPackageManifest(\n new URL('./manifest.json', import.meta.url),\n);\n","/**\n * AssetAssociation model - Links assets to any SmrtObject via polymorphic join\n *\n * Enables many-to-many relationships between assets and arbitrary objects\n * (e.g., Article, Profile, Event) with role-based categorization.\n *\n * The polymorphic right side (`metaType` + `metaId` + `role` + `sortOrder`)\n * and the registry-aware `hydrate()` helper come from\n * `SmrtPolymorphicAssociation` in core; this class only adds the `assetId`\n * left/owner FK.\n */\n\nimport {\n foreignKey,\n SmrtPolymorphicAssociation,\n smrt,\n} from '@happyvertical/smrt-core';\nimport { TenantScoped, tenantId } from '@happyvertical/smrt-tenancy';\nimport type { AssetAssociationOptions } from './types';\n\n@TenantScoped({ mode: 'optional' })\n@smrt({\n conflictColumns: ['asset_id', 'meta_type', 'meta_id', 'role'],\n api: { include: ['list', 'get', 'create', 'delete'] },\n mcp: { include: ['list', 'get', 'create'] },\n cli: true,\n})\nexport class AssetAssociation extends SmrtPolymorphicAssociation {\n /**\n * Optional tenant scope. Without this, the generated `api`/`mcp` `create`\n * routes let any caller forge a polymorphic link between an asset and an\n * arbitrary object across tenant boundaries — the #1540 generated-route\n * tenant-context fix only filters models that ARE `@TenantScoped`, so this\n * model must opt in to be covered (S5 #1396).\n */\n @tenantId({ nullable: true })\n tenantId: string | null = null;\n\n /** FK to Asset.id */\n @foreignKey('Asset', { required: true })\n assetId = '';\n\n constructor(options: AssetAssociationOptions = {}) {\n super(options);\n if (options.assetId) this.assetId = options.assetId;\n if (options.tenantId !== undefined) this.tenantId = options.tenantId;\n }\n}\n","/**\n * AssetAssociationCollection - Polymorphic junction collection.\n *\n * Extends `SmrtJunction` with one critical difference: the \"left\" side of the\n * join is a composite key (`metaType` + `metaId`), not a single column. As a\n * result, `byLeft` / `attach` / `detach` / `setLinks` diverge from the base\n * signatures by taking both halves of the polymorphic owner key.\n *\n * - `leftField` is set to `'metaId'` as a placeholder to satisfy the\n * `SmrtJunction` contract; the overridden methods never use it.\n * - `byRight(assetId)` is inherited unchanged from the base — the right side\n * is a single column (`assetId`) and behaves like any other junction.\n *\n * For non-polymorphic links between two domain models, prefer a dedicated\n * noun-table junction (e.g. `content_assets`, `place_assets`) extending\n * `SmrtJunction` directly. Use `AssetAssociation` only for generic/provenance\n * relationships that aren't worth a dedicated table.\n */\n\nimport {\n type JunctionAttachOptions,\n type JunctionFilterOptions,\n SmrtJunction,\n smrt,\n} from '@happyvertical/smrt-core';\nimport { AssetAssociation } from './asset-association';\n\n// Decorator with empty config — only needed so the scanner detects the\n// class. See FactContentCollection for the full rationale; the short\n// version is: api/mcp/cli on a collection decorator clobber the item\n// class's own config, so leave them off here.\n@smrt()\nexport class AssetAssociationCollection extends SmrtJunction<AssetAssociation> {\n static readonly _itemClass = AssetAssociation;\n\n // Composite left = (metaType, metaId); right = assetId.\n // `leftField` placeholder satisfies the abstract base — the overrides below\n // always handle the composite key explicitly.\n protected leftField = 'metaId';\n protected rightField = 'assetId';\n\n /**\n * List associations for a polymorphic owner.\n *\n * Composite left key — pass both halves.\n */\n // @ts-expect-error — diverges from SmrtJunction.byLeft(leftId) by arity; see class docstring.\n async byLeft(\n metaType: string,\n metaId: string,\n opts: JunctionFilterOptions = {},\n ): Promise<AssetAssociation[]> {\n return (await this.list({\n // Spread opts first so the fixed polymorphic owner keys always win.\n where: { ...opts, metaType, metaId },\n orderBy: 'sort_order ASC',\n })) as AssetAssociation[];\n }\n\n /**\n * Create an association. Composite left (metaType, metaId) precedes right (assetId).\n */\n // @ts-expect-error — diverges from SmrtJunction.attach(leftId, rightId) by arity.\n async attach(\n metaType: string,\n metaId: string,\n assetId: string,\n opts: JunctionAttachOptions = {},\n ): Promise<AssetAssociation> {\n return (await this.create({\n // Spread opts first so the fixed key fields always win.\n ...opts,\n assetId,\n metaType,\n metaId,\n } as any)) as AssetAssociation;\n }\n\n /**\n * Delete matching associations. Composite left (metaType, metaId) precedes right (assetId).\n */\n // @ts-expect-error — diverges from SmrtJunction.detach(leftId, rightId) by arity.\n async detach(\n metaType: string,\n metaId: string,\n assetId: string,\n opts: JunctionFilterOptions = {},\n ): Promise<void> {\n const links = (await this.list({\n where: { ...opts, metaType, metaId, assetId },\n })) as AssetAssociation[];\n for (const link of links) {\n await link.delete();\n }\n }\n\n /**\n * Replace all associations for a polymorphic owner with the given asset IDs.\n * Not transactional — see `SmrtJunction.setLinks` for caveats.\n */\n // @ts-expect-error — diverges from SmrtJunction.setLinks(leftId, rightIds) by arity.\n async setLinks(\n metaType: string,\n metaId: string,\n assetIds: string[],\n opts: JunctionAttachOptions = {},\n ): Promise<void> {\n // Strip the right-side key (assetId) from snapshot opts — see the\n // base class docstring for the rationale; the same contract applies\n // here.\n const snapshotOpts: JunctionAttachOptions = { ...opts };\n delete snapshotOpts.assetId;\n\n const existing = (await this.list({\n where: { ...snapshotOpts, metaType, metaId },\n })) as AssetAssociation[];\n for (const link of existing) {\n await link.delete();\n }\n\n const positionKey = this.positionField;\n for (let i = 0; i < assetIds.length; i++) {\n const rowOpts: JunctionAttachOptions = { ...opts };\n if (positionKey && rowOpts[positionKey] === undefined) {\n rowOpts[positionKey] = i;\n }\n await this.attach(metaType, metaId, assetIds[i], rowOpts);\n }\n }\n}\n","/**\n * AssetStatus model - Defines the lifecycle status of an asset\n *\n * Lookup table for asset status classification (e.g., 'draft', 'published', 'archived')\n */\n\nimport { SmrtObject, smrt } from '@happyvertical/smrt-core';\nimport type { AssetStatusOptions } from './types';\n\n@smrt({\n tableStrategy: 'sti',\n api: { include: ['list', 'get', 'create', 'update', 'delete'] },\n mcp: { include: ['list', 'get', 'create'] },\n cli: true,\n})\nexport class AssetStatus extends SmrtObject {\n // slug is inherited as an accessor from SmrtObject\n name = ''; // Display name (e.g., 'Draft', 'Published')\n description = ''; // Optional description\n\n constructor(options: AssetStatusOptions = {}) {\n super(options);\n if (options.slug) this.slug = options.slug;\n if (options.name) this.name = options.name;\n if (options.description) this.description = options.description;\n }\n\n /**\n * Get asset status by slug\n *\n * @param slug - The slug to search for\n * @returns AssetStatus instance or null\n */\n static async getBySlug(_slug: string): Promise<AssetStatus | null> {\n // Will be auto-implemented by SMRT\n return null;\n }\n}\n","/**\n * AssetType model - Defines the high-level type of an asset\n *\n * Lookup table for asset type classification (e.g., 'image', 'video', 'document')\n */\n\nimport { SmrtObject, smrt } from '@happyvertical/smrt-core';\nimport type { AssetTypeOptions } from './types';\n\n@smrt({\n tableStrategy: 'sti',\n api: { include: ['list', 'get', 'create', 'update', 'delete'] },\n mcp: { include: ['list', 'get', 'create'] },\n cli: true,\n})\nexport class AssetType extends SmrtObject {\n // slug is inherited as an accessor from SmrtObject\n name = ''; // Display name (e.g., 'Image', 'Video')\n description = ''; // Optional description\n\n constructor(options: AssetTypeOptions = {}) {\n super(options);\n if (options.slug) this.slug = options.slug;\n if (options.name) this.name = options.name;\n if (options.description) this.description = options.description;\n }\n\n /**\n * Get asset type by slug\n *\n * @param slug - The slug to search for\n * @returns AssetType instance or null\n */\n static async getBySlug(_slug: string): Promise<AssetType | null> {\n // Will be auto-implemented by SMRT\n return null;\n }\n}\n","/**\n * Asset model - Core entity for asset management\n *\n * Represents a digital asset with versioning, metadata, and tag support.\n * Supports multi-tenancy with optional tenant scoping.\n *\n * Post R3-D: `parentId` was renamed to `sourceAssetId` to reflect what\n * the column actually means — a derivation pointer (\"this asset was\n * produced from that source asset\"), not a structural-hierarchy edge.\n * Folder, which used `parent_id` for actual hierarchy via STI on this\n * table, has moved to its own `folders` table. See `folder.ts` and the\n * `R3-D` changeset for the migration.\n */\n\nimport {\n crossPackageRef,\n foreignKey,\n ObjectRegistry,\n type SmrtCollection,\n SmrtObject,\n smrt,\n} from '@happyvertical/smrt-core';\nimport { Tag } from '@happyvertical/smrt-tags';\nimport { TenantScoped, tenantId } from '@happyvertical/smrt-tenancy';\nimport type { AssetAssociation } from './asset-association';\nimport { AssetAssociationCollection } from './asset-associations';\nimport { AssetStatus } from './asset-status';\nimport { AssetType } from './asset-type';\nimport type { AssetOptions } from './types';\n\nexport interface AssetExternalReference {\n provider: string;\n assetId?: string | null;\n externalId?: string | null;\n sourceRef?: Record<string, unknown> | null;\n status?: string | null;\n metadata?: Record<string, unknown> | null;\n syncedAt?: string | null;\n [key: string]: unknown;\n}\n\nfunction parseAssetRecord(\n value: string | Record<string, unknown> | null | undefined,\n): Record<string, unknown> {\n if (!value) return {};\n if (typeof value !== 'string') return value;\n if (!value.trim()) return {};\n try {\n const parsed = JSON.parse(value) as unknown;\n return parsed && typeof parsed === 'object' && !Array.isArray(parsed)\n ? (parsed as Record<string, unknown>)\n : {};\n } catch {\n return {};\n }\n}\n\nfunction stringifyAssetRecord(\n value: Record<string, unknown> | null | undefined,\n): string {\n if (!value || Object.keys(value).length === 0) return '';\n return JSON.stringify(value);\n}\n\n@TenantScoped({ mode: 'optional' })\n@smrt({\n tableStrategy: 'sti',\n api: { include: ['list', 'get', 'create', 'update', 'delete'] },\n mcp: { include: ['list', 'get', 'create', 'update'] },\n cli: true,\n})\nexport class Asset extends SmrtObject {\n // Tenant isolation (optional - assets can be global or tenant-specific)\n @tenantId({ nullable: true })\n tenantId: string | null = null;\n\n // Core fields\n name = ''; // User-friendly name\n // slug is inherited as an accessor from SmrtObject\n sourceUri = ''; // URI to the actual file (e.g., 's3://bucket/key', 'file:///path')\n mimeType = ''; // MIME type (e.g., 'image/jpeg', 'video/mp4')\n description = ''; // Optional description\n metadata = ''; // JSON metadata owned by SMRT asset processors\n version = 1; // Version number\n\n // Foreign key references (stored as IDs/slugs)\n // Self-referential: the version chain points at the first version's\n // Asset record (createNewVersion() chains via primaryVersionId).\n @foreignKey('Asset')\n primaryVersionId: string | null = null; // Points to first version's ID\n typeSlug = ''; // FK to AssetType.slug\n statusSlug = ''; // FK to AssetStatus.slug\n @crossPackageRef('@happyvertical/smrt-profiles:Profile')\n ownerProfileId: string | null = null; // FK to Profile.id (nullable)\n /**\n * FK to the source Asset this one was derived from (e.g. thumbnail\n * derived from an original image, transcoded video, AI variation).\n *\n * Renamed from `parentId` in R3-D to make the derivation semantics\n * explicit and free `parentId` to mean exactly structural hierarchy\n * (SmrtHierarchical) across the framework. The column on the assets\n * table is `source_asset_id`.\n */\n @foreignKey('Asset')\n sourceAssetId: string | null = null;\n @foreignKey('Folder')\n folderId: string | null = null; // FK to Folder.id\n\n // Provenance fields\n sourceType = ''; // 'local', 'shutterstock', 'google-photos', 'upstream-smrt'\n externalId = ''; // Original ID in upstream source\n externalRefs = ''; // JSON map of provider references, keyed by provider slug\n\n // Timestamps\n createdAt = new Date();\n updatedAt = new Date();\n\n constructor(options: AssetOptions = {}) {\n super(options);\n if (options.name) this.name = options.name;\n if (options.slug) this.slug = options.slug;\n if (options.sourceUri) this.sourceUri = options.sourceUri;\n if (options.mimeType) this.mimeType = options.mimeType;\n if (options.description) this.description = options.description;\n if (options.metadata !== undefined)\n this.metadata =\n typeof options.metadata === 'string'\n ? options.metadata\n : stringifyAssetRecord(options.metadata);\n if (options.version !== undefined) this.version = options.version;\n if (options.primaryVersionId !== undefined)\n this.primaryVersionId = options.primaryVersionId;\n if (options.typeSlug) this.typeSlug = options.typeSlug;\n if (options.statusSlug) this.statusSlug = options.statusSlug;\n if (options.ownerProfileId !== undefined)\n this.ownerProfileId = options.ownerProfileId;\n if (options.sourceAssetId !== undefined)\n this.sourceAssetId = options.sourceAssetId;\n if (options.folderId !== undefined) this.folderId = options.folderId;\n if (options.sourceType) this.sourceType = options.sourceType;\n if (options.externalId) this.externalId = options.externalId;\n if (options.externalRefs !== undefined)\n this.externalRefs =\n typeof options.externalRefs === 'string'\n ? options.externalRefs\n : stringifyAssetRecord(options.externalRefs);\n if (options.tenantId !== undefined) this.tenantId = options.tenantId as any;\n if (options.createdAt) this.createdAt = options.createdAt;\n if (options.updatedAt) this.updatedAt = options.updatedAt;\n }\n\n getMetadata(): Record<string, unknown> {\n return parseAssetRecord(this.metadata);\n }\n\n setMetadata(metadata: Record<string, unknown>): void {\n this.metadata = stringifyAssetRecord(metadata);\n }\n\n mergeMetadata(metadata: Record<string, unknown>): void {\n this.setMetadata({\n ...this.getMetadata(),\n ...metadata,\n });\n }\n\n getExternalRefs(): Record<string, AssetExternalReference> {\n const refs = parseAssetRecord(this.externalRefs);\n const normalized: Record<string, AssetExternalReference> = {};\n for (const [provider, value] of Object.entries(refs)) {\n if (value && typeof value === 'object' && !Array.isArray(value)) {\n normalized[provider] = {\n ...(value as Record<string, unknown>),\n provider,\n } as AssetExternalReference;\n }\n }\n return normalized;\n }\n\n getExternalRef(provider: string): AssetExternalReference | null {\n return this.getExternalRefs()[provider] ?? null;\n }\n\n setExternalRef(\n provider: string,\n reference: Omit<AssetExternalReference, 'provider'> & { provider?: string },\n ): void {\n this.externalRefs = stringifyAssetRecord({\n ...this.getExternalRefs(),\n [provider]: {\n ...(this.getExternalRef(provider) ?? {}),\n ...reference,\n provider: reference.provider ?? provider,\n },\n });\n }\n\n /**\n * Get all tags for this asset from @happyvertical/smrt-tags\n *\n * @returns Array of Tag instances from @happyvertical/smrt-tags package\n */\n async getTags(): Promise<Tag[]> {\n // Query asset_tags join table and retrieve Tag instances\n const db = this.db;\n const rows = await db.list('asset_tags', {\n where: { asset_id: this.id },\n });\n\n const tags: Tag[] = [];\n\n for (const row of rows as { tag_slug: string }[]) {\n const tag = await Tag.getBySlug(row.tag_slug);\n if (tag) tags.push(tag);\n }\n\n return tags;\n }\n\n /**\n * Check if this asset has a specific tag\n *\n * @param tagSlug - The slug of the tag to check\n * @returns True if the asset has this tag\n */\n async hasTag(tagSlug: string): Promise<boolean> {\n const db = this.db;\n const rows = await db.list('asset_tags', {\n where: { asset_id: this.id, tag_slug: tagSlug },\n });\n\n return rows.length > 0;\n }\n\n /**\n * Resolve the AssetCollection lazily. Going through `ObjectRegistry`\n * mirrors the pattern used by `SmrtHierarchical._hierarchyCollection`\n * so source/derivative lookups inherit tenant scoping and ORM\n * hydration without hard-coding an import of `./assets` (which would\n * create a module-import cycle).\n *\n * The return type is `SmrtCollection<Asset>` (the framework base, type-\n * only import from core) rather than the concrete `AssetCollection` —\n * importing the concrete class is what would create the cycle, but the\n * base-class shape gives callers full `.get()` / `.list()` type\n * safety here.\n *\n * R5-canon: hardcode the base Asset's qualified key so a different\n * package also registering a class called `Asset` can't be picked\n * by `findClass`'s multi-strategy fallback. Crucially we DON'T\n * resolve via `this.constructor._smrtQualifiedName` — for an STI\n * subclass like `Image`, that would yield the Image collection\n * (which auto-filters `_meta_type = '...:Image'` on `get`/`list`),\n * and `getSource()` / `getDerivatives()` would miss cross-type\n * derivation links (Image derived from a plain Asset, etc.).\n * `sourceAssetId` is a base-table derivation link, so it always\n * resolves through the base Asset collection.\n */\n private async _assetCollection(): Promise<SmrtCollection<Asset>> {\n return await ObjectRegistry.getCollection<Asset>(\n '@happyvertical/smrt-assets:Asset',\n this.options,\n );\n }\n\n /**\n * Get the source asset this one was derived from, if any.\n *\n * Renamed from `getParent` in R3-D. The relationship is \"I was produced\n * from that asset\" (e.g. a thumbnail's source is its original image),\n * not a structural-hierarchy parent.\n *\n * Goes through the AssetCollection so tenant interceptors and ORM\n * hydration apply — important because a tenant-scoped consumer with\n * cross-tenant derivative chains would otherwise return assets from\n * tenants the caller cannot see, and a raw `db.get` returns\n * snake_case rows that leave camelCase props (e.g. `sourceUri`) at\n * their constructor defaults.\n *\n * @returns Source Asset instance, or null if this asset has no source\n */\n async getSource(): Promise<Asset | null> {\n if (!this.sourceAssetId) return null;\n const collection = await this._assetCollection();\n return (await collection.get({ id: this.sourceAssetId })) as Asset | null;\n }\n\n /**\n * Get all assets derived from this one (e.g. thumbnails, variants,\n * transcodes, AI edits).\n *\n * Renamed from `getChildren` in R3-D to match the derivation\n * semantics. Goes through the AssetCollection so tenant interceptors\n * and ORM hydration apply (see `getSource` for why this matters —\n * the pre-R3-D `getChildren` used raw `db.list`, which both bypassed\n * tenant scoping and dropped camelCase property hydration; that\n * latent breakage is fixed here).\n *\n * @returns Array of derivative Asset instances\n */\n async getDerivatives(): Promise<Asset[]> {\n if (!this.id) return [];\n const collection = await this._assetCollection();\n return (await collection.list({\n where: { sourceAssetId: this.id },\n })) as Asset[];\n }\n\n /**\n * Get the type of this asset\n *\n * @returns AssetType instance or null\n */\n async getType(): Promise<AssetType | null> {\n if (!this.typeSlug) return null;\n\n return await AssetType.getBySlug(this.typeSlug);\n }\n\n /**\n * Get the status of this asset\n *\n * @returns AssetStatus instance or null\n */\n async getStatus(): Promise<AssetStatus | null> {\n if (!this.statusSlug) return null;\n\n return await AssetStatus.getBySlug(this.statusSlug);\n }\n\n /**\n * Get all associations for this asset\n *\n * @returns Array of AssetAssociation instances\n */\n async getAssociations(): Promise<AssetAssociation[]> {\n const associations = await AssetAssociationCollection.create({\n db: this.db,\n });\n return await associations.byRight(this.id!);\n }\n\n /**\n * Associate this asset with a target object\n *\n * @param metaType - Target class name or qualified name (e.g., 'Article' or '@pkg:Article')\n * @param metaId - Target object ID\n * @param role - Association role (default: 'default')\n * @returns The created AssetAssociation\n */\n async associateWith(\n metaType: string,\n metaId: string,\n role = 'default',\n ): Promise<AssetAssociation> {\n const associations = await AssetAssociationCollection.create({\n db: this.db,\n });\n return await associations.attach(metaType, metaId, this.id!, { role });\n }\n\n /**\n * Get asset by slug\n *\n * @param slug - The slug to search for\n * @returns Asset instance or null\n */\n static async getBySlug(_slug: string): Promise<Asset | null> {\n // Will be auto-implemented by SMRT\n return null;\n }\n}\n","import type { Asset } from './asset';\nimport type { AssetAssociationCollection } from './asset-associations';\nimport type { AssetStore, StoreOptions } from './asset-store';\nimport type { AssetCollection } from './assets';\n\nexport type AssetVariantFit = 'cover' | 'contain' | 'inside';\nexport type AssetCapabilitySource = 'cached' | 'generated' | 'external';\n\nexport interface AssetCapabilityRuntime {\n readonly collection: AssetCollection;\n readonly associations: AssetAssociationCollection;\n readonly store: AssetStore;\n storeDerivedAsset(\n source: Asset,\n name: string,\n data: Buffer,\n opts: Omit<StoreOptions, 'sourceAssetId'> & {\n role?: string;\n derivativeMetaType?: string;\n linkAssociation?: boolean;\n },\n ): Promise<Asset>;\n}\n\nexport interface AssetVariantRequest {\n variant: string;\n width?: number;\n height?: number;\n fit?: AssetVariantFit;\n mimeType?: string;\n metadata?: Record<string, unknown>;\n}\n\nexport interface AssetVariantResult {\n asset: Asset;\n variant: string;\n source: AssetCapabilitySource;\n url?: string | null;\n metadata?: Record<string, unknown> | null;\n}\n\nexport interface AssetProcessInput {\n runtime: AssetCapabilityRuntime;\n asset: Asset;\n variants?: AssetVariantRequest[];\n metadata?: Record<string, unknown>;\n}\n\nexport interface AssetProcessResult {\n asset: Asset;\n metadata?: Record<string, unknown> | null;\n variants?: AssetVariantResult[];\n warnings?: string[];\n}\n\nexport interface AssetEnsureVariantInput {\n runtime: AssetCapabilityRuntime;\n asset: Asset;\n request: AssetVariantRequest;\n}\n\nexport interface AssetNearbySearchInput {\n runtime: AssetCapabilityRuntime;\n tenantId?: string | null;\n latitude: number;\n longitude: number;\n radiusMeters?: number;\n limit?: number;\n source?: string;\n metadata?: Record<string, unknown>;\n}\n\nexport interface AssetSearchItem {\n asset?: Asset | null;\n assetId: string;\n origin: string;\n title?: string | null;\n previewUrl?: string | null;\n deliveryUrl?: string | null;\n mimeType?: string | null;\n width?: number | null;\n height?: number | null;\n distanceMeters?: number | null;\n metadata?: Record<string, unknown> | null;\n}\n\nexport interface AssetSearchResult {\n items: AssetSearchItem[];\n nextCursor?: string | null;\n}\n\nexport interface AssetExternalSyncInput {\n runtime: AssetCapabilityRuntime;\n asset: Asset;\n externalId?: string | null;\n sourceRef?: AssetExternalSourceRef | null;\n metadata?: Record<string, unknown>;\n}\n\nexport interface AssetExternalSourceRef {\n system: string;\n id: string;\n kind?: string | null;\n}\n\nexport interface AssetExternalSyncResult {\n asset: Asset;\n provider: string;\n status: 'ready' | 'pending' | 'skipped';\n externalAssetId?: string | null;\n externalId?: string | null;\n metadata?: Record<string, unknown> | null;\n}\n\nexport interface AssetWorkflowInputSelection {\n slotKey: string;\n assetIds: string[];\n}\n\nexport interface AssetWorkflowInput {\n runtime: AssetCapabilityRuntime;\n asset: Asset;\n workflowSlug: string;\n clientRequestId?: string | null;\n name?: string;\n inputSelections?: AssetWorkflowInputSelection[];\n sourceAssetIds?: string[];\n parameters?: Record<string, unknown>;\n requestContext?: Record<string, unknown>;\n requestedPlacement?: string | null;\n priority?: number | null;\n}\n\nexport interface AssetWorkflowResult {\n provider: string;\n jobId: string;\n status: string;\n idempotent?: boolean;\n metadata?: Record<string, unknown> | null;\n}\n\nexport interface AssetCapabilityProvider {\n readonly name: string;\n processAsset?(input: AssetProcessInput): Promise<AssetProcessResult>;\n ensureVariant?(input: AssetEnsureVariantInput): Promise<AssetVariantResult>;\n searchNearbyAssets?(\n input: AssetNearbySearchInput,\n ): Promise<AssetSearchResult>;\n syncExternalAsset?(\n input: AssetExternalSyncInput,\n ): Promise<AssetExternalSyncResult>;\n submitAssetWorkflow?(input: AssetWorkflowInput): Promise<AssetWorkflowResult>;\n}\n\nexport type AssetCapabilityName =\n | 'processAsset'\n | 'ensureVariant'\n | 'searchNearbyAssets'\n | 'syncExternalAsset'\n | 'submitAssetWorkflow';\n\nexport class AssetCapabilityUnavailableError extends Error {\n constructor(\n public readonly capability: AssetCapabilityName,\n message = `No asset capability provider is registered for ${capability}.`,\n ) {\n super(message);\n this.name = 'AssetCapabilityUnavailableError';\n }\n}\n\nexport class AssetCapabilitySkippedError extends Error {\n constructor(\n public readonly capability: AssetCapabilityName,\n message: string,\n ) {\n super(message);\n this.name = 'AssetCapabilitySkippedError';\n }\n}\n","/**\n * Asset conventions\n *\n * Shared constants and metadata-key conventions for how apps and agents\n * describe asset relationships and derivation state on top of `smrt-assets`.\n *\n * These are advisory: `AssetAssociation.role` and `Asset` metadata fields\n * accept any string, but standardising on these values makes cross-agent\n * serving, filtering, and UI work possible without everybody inventing\n * their own vocabulary.\n */\n\n/**\n * Canonical association roles used with `AssetAssociation` and noun join\n * tables like `content_assets`.\n *\n * - `source_document`: original upstream document (e.g. agenda PDF) that\n * later derivatives are extracted from.\n * - `document_image`: page image or other visual derived from a source\n * document.\n * - `thumbnail`: small preview rendition of another asset.\n * - `asset_variant`: deterministic sized rendition of another asset.\n * - `proof`: non-canonical evidence asset (e.g. a screenshot used to\n * back a fact).\n * - `derivation_source`: source side of a derivation link (used when a\n * derivative points back to the asset it was produced from).\n * - `attachment`: generic \"this asset belongs to that object\" link —\n * the default for noun join tables.\n * - `hero`: primary/featured asset for an owner.\n */\nexport const ASSET_ROLES = {\n SOURCE_DOCUMENT: 'source_document',\n DOCUMENT_IMAGE: 'document_image',\n THUMBNAIL: 'thumbnail',\n ASSET_VARIANT: 'asset_variant',\n PROOF: 'proof',\n DERIVATION_SOURCE: 'derivation_source',\n ATTACHMENT: 'attachment',\n HERO: 'hero',\n} as const;\n\nexport type AssetRole = (typeof ASSET_ROLES)[keyof typeof ASSET_ROLES];\n\n/**\n * Canonical metadata keys used on `Asset.description`-adjacent metadata\n * surfaces or stored via `AssetAssociation` sidecars. These keys are the\n * recommended names for cross-package extraction, provenance, and\n * content-addressing state.\n *\n * Apps are free to add their own keys; using these for the common cases\n * keeps derived-asset tooling portable.\n *\n * - `extractionStatus`: `'pending' | 'running' | 'succeeded' | 'failed'`\n * - `extractionError`: error string from the last extraction attempt\n * - `extractedAt`: ISO timestamp of the last successful extraction\n * - `sourceUrl`: upstream URL the asset was fetched from\n * - `sourceHash`: content-address hash of the source bytes (e.g. sha256)\n * - `pageNumber`: 1-based page number within a multi-page source document\n */\nexport const ASSET_METADATA_KEYS = {\n EXTRACTION_STATUS: 'extractionStatus',\n EXTRACTION_ERROR: 'extractionError',\n EXTRACTED_AT: 'extractedAt',\n SOURCE_URL: 'sourceUrl',\n SOURCE_HASH: 'sourceHash',\n PAGE_NUMBER: 'pageNumber',\n} as const;\n\nexport type AssetMetadataKey =\n (typeof ASSET_METADATA_KEYS)[keyof typeof ASSET_METADATA_KEYS];\n\n/**\n * Known extraction status values for `ASSET_METADATA_KEYS.EXTRACTION_STATUS`.\n */\nexport const ASSET_EXTRACTION_STATUS = {\n PENDING: 'pending',\n RUNNING: 'running',\n SUCCEEDED: 'succeeded',\n FAILED: 'failed',\n} as const;\n\nexport type AssetExtractionStatus =\n (typeof ASSET_EXTRACTION_STATUS)[keyof typeof ASSET_EXTRACTION_STATUS];\n","/**\n * AssetMetafield model - Defines the controlled vocabulary for metadata keys\n *\n * Lookup table for metadata field definitions with validation rules\n */\n\nimport { SmrtObject, smrt } from '@happyvertical/smrt-core';\nimport type { AssetMetafieldOptions } from './types';\n\n@smrt({\n tableStrategy: 'sti',\n api: { include: ['list', 'get', 'create', 'update', 'delete'] },\n mcp: { include: ['list', 'get', 'create'] },\n cli: true,\n})\nexport class AssetMetafield extends SmrtObject {\n // slug is inherited as an accessor from SmrtObject\n name = ''; // Display name (e.g., 'Width', 'Height')\n validation = ''; // JSON validation rules stored as text\n\n constructor(options: AssetMetafieldOptions = {}) {\n super(options);\n if (options.slug) this.slug = options.slug;\n if (options.name) this.name = options.name;\n if (options.validation) this.validation = options.validation;\n }\n\n /**\n * Get validation rules as parsed object\n *\n * @returns Parsed validation object or empty object if no validation\n */\n getValidation(): Record<string, unknown> {\n if (!this.validation) return {};\n try {\n return JSON.parse(this.validation);\n } catch {\n return {};\n }\n }\n\n /**\n * Set validation rules from object\n *\n * @param rules - Validation rules object\n */\n setValidation(rules: Record<string, unknown>): void {\n this.validation = JSON.stringify(rules);\n }\n\n /**\n * Get asset metafield by slug\n *\n * @param slug - The slug to search for\n * @returns AssetMetafield instance or null\n */\n static async getBySlug(_slug: string): Promise<AssetMetafield | null> {\n // Will be auto-implemented by SMRT\n return null;\n }\n}\n","/**\n * AssetMetafieldCollection - Collection manager for AssetMetafield instances\n *\n * Manages asset metafield definitions with validation rules\n */\n\nimport { SmrtCollection } from '@happyvertical/smrt-core';\nimport { AssetMetafield } from './asset-metafield';\n\nexport class AssetMetafieldCollection extends SmrtCollection<AssetMetafield> {\n static readonly _itemClass = AssetMetafield;\n\n /**\n * Get or create an asset metafield by slug\n *\n * @param slug - The metafield slug\n * @param name - The display name (defaults to slug)\n * @param validation - Optional validation rules (JSON string or object)\n * @returns The existing or newly created AssetMetafield\n */\n async getOrCreate(\n slug: string,\n name?: string,\n validation?: string | Record<string, unknown>,\n ): Promise<AssetMetafield> {\n const existing = await this.list({ where: { slug }, limit: 1 });\n if (existing.length > 0) {\n return existing[0];\n }\n\n const validationString =\n typeof validation === 'string'\n ? validation\n : validation\n ? JSON.stringify(validation)\n : '';\n\n return await this.create({\n slug,\n name: name || slug,\n validation: validationString,\n });\n }\n\n /**\n * Initialize common asset metafields\n *\n * Creates standard metafields with validation rules:\n * - width (integer, min: 0)\n * - height (integer, min: 0)\n * - duration (number, min: 0)\n * - size (integer, min: 0)\n * - author (string)\n * - copyright (string)\n */\n async initializeCommonMetafields(): Promise<void> {\n await this.getOrCreate('width', 'Width', {\n type: 'integer',\n min: 0,\n description: 'Width in pixels',\n });\n await this.getOrCreate('height', 'Height', {\n type: 'integer',\n min: 0,\n description: 'Height in pixels',\n });\n await this.getOrCreate('duration', 'Duration', {\n type: 'number',\n min: 0,\n description: 'Duration in seconds',\n });\n await this.getOrCreate('size', 'File Size', {\n type: 'integer',\n min: 0,\n description: 'File size in bytes',\n });\n await this.getOrCreate('author', 'Author', {\n type: 'string',\n description: 'Content creator',\n });\n await this.getOrCreate('copyright', 'Copyright', {\n type: 'string',\n description: 'Copyright notice',\n });\n }\n}\n","/**\n * AssetStore - File I/O + Asset record management\n *\n * Writes buffers to disk via @happyvertical/files and creates\n * corresponding Asset records in the database via AssetCollection.\n *\n * Supports provider-agnostic storage (local, S3, etc.) via @happyvertical/files.\n */\n\nimport {\n FileNotFoundError,\n type FilesystemInterface,\n type GetFilesystemOptions,\n getFilesystem,\n} from '@happyvertical/files';\nimport type { Asset } from './asset';\nimport type { AssetCollection } from './assets';\n\n/**\n * MIME type to file extension mapping\n */\nconst MIME_TO_EXT: Record<string, string> = {\n 'image/png': 'png',\n 'image/jpeg': 'jpg',\n 'image/webp': 'webp',\n 'image/gif': 'gif',\n 'image/svg+xml': 'svg',\n 'video/mp4': 'mp4',\n 'video/webm': 'webm',\n 'audio/wav': 'wav',\n 'audio/mpeg': 'mp3',\n 'audio/ogg': 'ogg',\n 'audio/flac': 'flac',\n 'application/pdf': 'pdf',\n 'application/json': 'json',\n 'text/plain': 'txt',\n};\n\n/**\n * Options for storing an asset\n */\nexport interface StoreOptions {\n /** MIME type of the data */\n mimeType: string;\n\n /** Asset type slug (e.g., 'video', 'audio', 'image', 'reference-image') */\n typeSlug?: string;\n\n /**\n * Source asset ID (for derivatives like thumbnails, variants, AI edits).\n *\n * Renamed from `parentId` in R3-D — see Asset.sourceAssetId.\n */\n sourceAssetId?: string;\n\n /** Asset status slug */\n statusSlug?: string;\n\n /** Description */\n description?: string;\n\n /** Original source system for imported/derived assets */\n sourceType?: string;\n\n /** External id in the source system */\n externalId?: string;\n\n /** JSON metadata stored on the asset record */\n metadata?: string | Record<string, unknown> | null;\n}\n\nfunction serializeStoreMetadata(\n metadata: StoreOptions['metadata'],\n): string | undefined {\n if (metadata === undefined) return undefined;\n if (metadata === null) return '';\n if (typeof metadata === 'string') return metadata;\n return JSON.stringify(metadata);\n}\n\n/**\n * Provider options for configuring the filesystem backend.\n * Accepts any @happyvertical/files options or a simple basePath string for local storage.\n */\nexport type ProviderOptions = GetFilesystemOptions | string;\n\n/**\n * Asset storage operation currently being resolved.\n */\nexport type AssetStorageOperation = 'read' | 'write' | 'delete';\n\n/**\n * Context passed to an AssetStore resolver.\n *\n * Downstream apps can use this hook to route a logical Asset to whichever\n * storage backend is appropriate for the current operation.\n */\nexport interface AssetStorageResolveRequest {\n operation: AssetStorageOperation;\n asset: Asset;\n path: string;\n sourceUri: string;\n mimeType?: string;\n typeSlug?: string;\n defaultProviderOptions: GetFilesystemOptions;\n defaultFilesystem: FilesystemInterface;\n}\n\n/**\n * Resolved storage target for an AssetStore operation.\n */\nexport interface AssetStorageResolution {\n filesystem?: FilesystemInterface;\n providerOptions?: ProviderOptions;\n path?: string;\n sourceUri?: string;\n}\n\nexport type AssetStorageResolver = (\n request: AssetStorageResolveRequest,\n) =>\n | AssetStorageResolution\n | null\n | undefined\n | Promise<AssetStorageResolution | null | undefined>;\n\nexport interface AssetStoreOptions {\n resolver?: AssetStorageResolver;\n}\n\ninterface ResolvedAssetStorageTarget {\n filesystem: FilesystemInterface;\n providerOptions: GetFilesystemOptions;\n path: string;\n sourceUri: string;\n}\n\nfunction normalizeProviderOptions(\n providerOrBasePath: ProviderOptions,\n): GetFilesystemOptions {\n if (typeof providerOrBasePath === 'string') {\n return { type: 'local', basePath: providerOrBasePath };\n }\n return providerOrBasePath;\n}\n\n/**\n * AssetStore manages file storage and Asset record creation.\n *\n * Files are stored using @happyvertical/files with the convention:\n * `{typeSlug}/{assetId}.{ext}`\n *\n * @example\n * ```typescript\n * import { AssetStore } from '@happyvertical/smrt-assets';\n *\n * // Local storage (backward-compatible)\n * const store = new AssetStore('dev/data/assets', assetCollection);\n * await store.initialize();\n *\n * // Provider-agnostic storage\n * const s3Store = new AssetStore({ type: 's3', bucket: 'my-bucket' }, assetCollection);\n * await s3Store.initialize();\n *\n * // Store a video buffer\n * const asset = await store.store('my-video', videoBuffer, {\n * mimeType: 'video/mp4',\n * typeSlug: 'video',\n * });\n *\n * // Read it back\n * const data = await store.read(asset);\n *\n * // Delete it\n * await store.remove(asset);\n * ```\n */\nexport class AssetStore {\n private fs: FilesystemInterface | null = null;\n private readonly fsOptions: GetFilesystemOptions;\n private readonly fsCache = new Map<string, FilesystemInterface>();\n private readonly resolver?: AssetStorageResolver;\n\n constructor(\n providerOrBasePath: ProviderOptions,\n private readonly collection: AssetCollection,\n options: AssetStoreOptions = {},\n ) {\n this.fsOptions = normalizeProviderOptions(providerOrBasePath);\n this.resolver = options.resolver;\n }\n\n /** The base path for local storage, or empty string for non-local providers */\n get basePath(): string {\n return this.fsOptions.basePath ?? '';\n }\n\n /**\n * Initialize the filesystem adapter.\n * Must be called before any file operations.\n */\n async initialize(): Promise<this> {\n this.fs = await getFilesystem(this.fsOptions);\n this.fsCache.set(this.providerCacheKey(this.fsOptions), this.fs);\n return this;\n }\n\n /**\n * Get the initialized filesystem (throws if not initialized)\n */\n private getFs(): FilesystemInterface {\n if (!this.fs) {\n throw new Error('AssetStore not initialized. Call initialize() first.');\n }\n return this.fs;\n }\n\n private providerCacheKey(providerOptions: GetFilesystemOptions): string {\n return JSON.stringify(providerOptions);\n }\n\n private async getFilesystemForOptions(\n providerOptions: GetFilesystemOptions,\n ): Promise<FilesystemInterface> {\n const key = this.providerCacheKey(providerOptions);\n const cached = this.fsCache.get(key);\n if (cached) return cached;\n\n const filesystem = await getFilesystem(providerOptions);\n this.fsCache.set(key, filesystem);\n return filesystem;\n }\n\n /**\n * Build a sourceUri for the given file path based on the provider type.\n */\n private buildSourceUri(filePath: string): string {\n return AssetStore.buildSourceUriForProvider(filePath, this.fsOptions);\n }\n\n private static buildSourceUriForProvider(\n filePath: string,\n providerOptions: GetFilesystemOptions,\n ): string {\n const type = providerOptions.type;\n if (type === 's3') {\n const bucket = providerOptions.bucket ?? '';\n return `s3://${bucket}/${filePath}`;\n }\n // Default to file:// for local and other types\n const base = providerOptions.basePath ?? '';\n return base ? `file://${base}/${filePath}` : `file://${filePath}`;\n }\n\n private static providerRelativePath(\n filePath: string,\n providerOptions: GetFilesystemOptions,\n ): string {\n const base = providerOptions.basePath ?? '';\n return base && filePath.startsWith(base)\n ? filePath.slice(base.length + 1)\n : filePath;\n }\n\n private static requireAssetId(asset: Asset): string {\n if (!asset.id) {\n throw new Error('Asset must be saved before storing file data.');\n }\n return asset.id;\n }\n\n private async resolveStorage(\n request: Omit<\n AssetStorageResolveRequest,\n 'defaultProviderOptions' | 'defaultFilesystem'\n >,\n ): Promise<ResolvedAssetStorageTarget> {\n const defaultFilesystem = this.getFs();\n const resolution = this.resolver\n ? await this.resolver({\n ...request,\n defaultProviderOptions: this.fsOptions,\n defaultFilesystem,\n })\n : undefined;\n if (\n request.operation === 'write' &&\n resolution?.filesystem &&\n !resolution.providerOptions &&\n !resolution.sourceUri\n ) {\n throw new Error(\n 'Asset storage resolver must return providerOptions or sourceUri when overriding filesystem for write operations.',\n );\n }\n const providerOptions = resolution?.providerOptions\n ? normalizeProviderOptions(resolution.providerOptions)\n : this.fsOptions;\n const path = AssetStore.providerRelativePath(\n resolution?.path ?? request.path,\n providerOptions,\n );\n const sourceUri =\n resolution?.sourceUri ??\n (resolution?.path || resolution?.providerOptions\n ? AssetStore.buildSourceUriForProvider(path, providerOptions)\n : request.sourceUri);\n const filesystem =\n resolution?.filesystem ??\n (providerOptions === this.fsOptions\n ? defaultFilesystem\n : await this.getFilesystemForOptions(providerOptions));\n\n return {\n filesystem,\n providerOptions,\n path,\n sourceUri,\n };\n }\n\n private async writeAssetData(\n asset: Asset,\n data: Buffer,\n opts: { mimeType: string; typeSlug?: string },\n ): Promise<string> {\n const ext = MIME_TO_EXT[opts.mimeType] ?? 'bin';\n const typeSlug = opts.typeSlug ?? 'file';\n const assetId = AssetStore.requireAssetId(asset);\n const filePath = `${typeSlug}/${assetId}.${ext}`;\n const sourceUri = this.buildSourceUri(filePath);\n const target = await this.resolveStorage({\n operation: 'write',\n asset,\n path: filePath,\n sourceUri,\n mimeType: opts.mimeType,\n typeSlug,\n });\n\n await target.filesystem.write(target.path, data, { createParents: true });\n return target.sourceUri;\n }\n\n /**\n * Write file data for an existing Asset record (no DB record created).\n *\n * Use this when you've already created the record (e.g., via a\n * collection.create()) and only need to persist the file data.\n *\n * @param asset - The existing asset to write data for\n * @param data - File data as a Buffer\n * @param opts - Storage options (mimeType required)\n * @returns The sourceUri for the written file\n */\n async storeFile(\n asset: Asset,\n data: Buffer,\n opts: { mimeType: string; typeSlug?: string },\n ): Promise<string> {\n return this.writeAssetData(asset, data, opts);\n }\n\n /**\n * Write buffer to disk and create an Asset record.\n *\n * @param name - Human-readable name for the asset\n * @param data - File data as a Buffer\n * @param opts - Storage options (mimeType required)\n * @returns Created Asset instance\n */\n async store(name: string, data: Buffer, opts: StoreOptions): Promise<Asset> {\n const typeSlug = opts.typeSlug ?? 'file';\n\n // Create the Asset record first to get an ID\n const asset = (await this.collection.create({\n name,\n mimeType: opts.mimeType,\n typeSlug,\n statusSlug: opts.statusSlug ?? 'active',\n sourceAssetId: opts.sourceAssetId ?? null,\n description: opts.description ?? '',\n sourceType: opts.sourceType ?? '',\n externalId: opts.externalId ?? '',\n metadata: serializeStoreMetadata(opts.metadata) ?? '',\n sourceUri: '', // Will be updated after file write\n })) as Asset;\n\n try {\n // Write file to storage\n asset.sourceUri = await this.writeAssetData(asset, data, {\n mimeType: opts.mimeType,\n typeSlug,\n });\n } catch (err) {\n // Clean up orphaned Asset record on file write failure\n await asset.delete();\n throw err;\n }\n\n // Update the Asset with the file URI\n await asset.save();\n\n return asset;\n }\n\n /**\n * Store a new version of an existing asset.\n *\n * @param asset - The existing asset to version\n * @param data - File data for the new version\n * @param opts - Optional overrides for store options\n * @returns The newly created version Asset\n */\n async storeVersion(\n asset: Asset,\n data: Buffer,\n opts: Partial<StoreOptions> = {},\n ): Promise<Asset> {\n const primaryVersionId =\n asset.primaryVersionId ?? AssetStore.requireAssetId(asset);\n const { metadata, ...versionOptions } = opts;\n const versionUpdates: Partial<Asset> = { ...versionOptions };\n const serializedMetadata = serializeStoreMetadata(metadata);\n if (serializedMetadata !== undefined) {\n versionUpdates.metadata = serializedMetadata;\n } else {\n delete versionUpdates.metadata;\n }\n const newVersion = await this.collection.createNewVersion(\n primaryVersionId,\n '', // sourceUri will be set by store\n versionUpdates,\n );\n\n const mimeType = opts.mimeType ?? asset.mimeType;\n const typeSlug = opts.typeSlug ?? asset.typeSlug ?? 'file';\n\n try {\n newVersion.sourceUri = await this.writeAssetData(newVersion, data, {\n mimeType,\n typeSlug,\n });\n } catch (err) {\n await newVersion.delete();\n throw err;\n }\n\n if (opts.mimeType) newVersion.mimeType = opts.mimeType;\n await newVersion.save();\n\n return newVersion;\n }\n\n /**\n * Read file data for a specific version of an asset.\n *\n * @param asset - The asset (any version in the chain)\n * @param version - The version number to read\n * @returns File data as a Buffer\n */\n async readVersion(asset: Asset, version: number): Promise<Buffer> {\n const primaryVersionId =\n asset.primaryVersionId ?? AssetStore.requireAssetId(asset);\n const versions = await this.collection.listVersions(primaryVersionId);\n const targetVersion = versions.find((v) => v.version === version);\n\n if (!targetVersion) {\n throw new Error(\n `Version ${version} not found for asset ${primaryVersionId}`,\n );\n }\n\n return this.read(targetVersion);\n }\n\n /**\n * Read file data from an Asset's sourceUri.\n *\n * @param asset - Asset to read data for\n * @returns File data as a Buffer\n */\n async read(asset: Asset): Promise<Buffer> {\n const filePath = AssetStore.pathFromUri(asset.sourceUri);\n const target = await this.resolveStorage({\n operation: 'read',\n asset,\n path: AssetStore.providerRelativePath(filePath, this.fsOptions),\n sourceUri: asset.sourceUri,\n });\n return (await target.filesystem.read(target.path, { raw: true })) as Buffer;\n }\n\n /**\n * Read file by asset ID.\n *\n * @param id - Asset ID to look up\n * @returns Object with data Buffer and Asset, or null if not found\n */\n async readById(id: string): Promise<{ data: Buffer; asset: Asset } | null> {\n const asset = (await this.collection.get({ id })) as Asset | null;\n if (!asset) return null;\n\n try {\n const data = await this.read(asset);\n return { data, asset };\n } catch {\n return null;\n }\n }\n\n /**\n * Delete file from disk and remove the Asset record.\n *\n * @param asset - Asset to remove\n */\n async remove(asset: Asset): Promise<void> {\n // Delete the file if it exists\n if (asset.sourceUri) {\n const filePath = AssetStore.pathFromUri(asset.sourceUri);\n const target = await this.resolveStorage({\n operation: 'delete',\n asset,\n path: AssetStore.providerRelativePath(filePath, this.fsOptions),\n sourceUri: asset.sourceUri,\n });\n try {\n await target.filesystem.delete(target.path);\n } catch (err) {\n if (!(err instanceof FileNotFoundError)) {\n throw err;\n }\n // File may not exist on disk — still delete the record\n }\n }\n\n // Delete the asset record\n await asset.delete();\n }\n\n /**\n * Extract filesystem path from a sourceUri.\n * Handles file://, s3://, and plain paths.\n *\n * @param sourceUri - Asset sourceUri (e.g., 'file:///path/to/file.mp4', 's3://bucket/key')\n * @returns Filesystem path\n */\n static pathFromUri(sourceUri: string): string {\n if (sourceUri.startsWith('file://')) {\n return sourceUri.slice(7);\n }\n if (sourceUri.startsWith('s3://')) {\n // Return everything after s3://bucket/\n const withoutScheme = sourceUri.slice(5);\n const slashIdx = withoutScheme.indexOf('/');\n return slashIdx >= 0 ? withoutScheme.slice(slashIdx + 1) : withoutScheme;\n }\n return sourceUri;\n }\n}\n","/**\n * AssetCollection - Collection manager for Asset instances\n *\n * Provides tag management, versioning, tenant-aware queries, and operations for assets\n */\n\nimport { SmrtCollection } from '@happyvertical/smrt-core';\nimport { Asset } from './asset';\n\nexport class AssetCollection extends SmrtCollection<Asset> {\n static readonly _itemClass = Asset;\n\n // ─────────────────────────────────────────────────────────────────────────────\n // Tenant-Aware Query Methods\n // ─────────────────────────────────────────────────────────────────────────────\n\n /**\n * Find all assets belonging to a specific tenant\n *\n * @param tenantId - The tenant ID to filter by\n * @returns Array of assets belonging to this tenant\n */\n async findByTenant(tenantId: string): Promise<Asset[]> {\n return (await this.list({ where: { tenantId } })) as Asset[];\n }\n\n /**\n * Find all global assets (assets without a tenant)\n *\n * @returns Array of global assets\n */\n async findGlobal(): Promise<Asset[]> {\n return (await this.list({ where: { tenantId: null } })) as Asset[];\n }\n\n /**\n * Find assets belonging to a tenant plus all global assets\n *\n * @param tenantId - The tenant ID to include\n * @returns Array of tenant-specific and global assets\n */\n async findWithGlobals(tenantId: string): Promise<Asset[]> {\n return (await this.query(\n `SELECT * FROM ${this.tableName} WHERE tenant_id = ? OR tenant_id IS NULL`,\n [tenantId],\n )) as Asset[];\n }\n\n /**\n * Add a tag to an asset (uses @smrt/tags)\n *\n * @param assetId - The asset ID to tag\n * @param tagSlug - The tag slug from @smrt/tags\n */\n async addTag(assetId: string, tagSlug: string): Promise<void> {\n const db = this.db;\n await db.upsert('asset_tags', ['asset_id', 'tag_slug'], {\n asset_id: assetId,\n tag_slug: tagSlug,\n created_at: new Date().toISOString(),\n });\n }\n\n /**\n * Remove a tag from an asset\n *\n * @param assetId - The asset ID\n * @param tagSlug - The tag slug to remove\n */\n async removeTag(assetId: string, tagSlug: string): Promise<void> {\n const db = this.db;\n await db.delete('asset_tags', {\n asset_id: assetId,\n tag_slug: tagSlug,\n });\n }\n\n /**\n * Get all assets with a specific tag\n *\n * @param tagSlug - The tag slug to filter by\n * @returns Array of assets with this tag\n */\n async getByTag(tagSlug: string): Promise<Asset[]> {\n const db = this.db;\n // db.list(table, where) takes the WHERE object directly — not a\n // `{ where }` wrapper (which would filter on a column literally named\n // \"where\" and throw).\n const rows = await db.list('asset_tags', { tag_slug: tagSlug });\n\n const assets: Asset[] = [];\n for (const row of rows as { asset_id: string }[]) {\n const asset = await this.get({ id: row.asset_id });\n if (asset) assets.push(asset as Asset);\n }\n\n return assets;\n }\n\n /**\n * Get assets by type\n *\n * @param typeSlug - The asset type slug (e.g., 'image', 'video')\n * @returns Array of assets matching the type\n */\n async getByType(typeSlug: string): Promise<Asset[]> {\n return (await this.list({ where: { typeSlug } })) as Asset[];\n }\n\n /**\n * Get assets by status\n *\n * @param statusSlug - The asset status slug (e.g., 'published', 'draft')\n * @returns Array of assets matching the status\n */\n async getByStatus(statusSlug: string): Promise<Asset[]> {\n return (await this.list({ where: { statusSlug } })) as Asset[];\n }\n\n /**\n * Get assets by owner\n *\n * @param ownerProfileId - The profile ID of the owner\n * @returns Array of assets owned by this profile\n */\n async getByOwner(ownerProfileId: string): Promise<Asset[]> {\n return (await this.list({ where: { ownerProfileId } })) as Asset[];\n }\n\n /**\n * Create a new version of an existing asset\n *\n * @param primaryVersionId - The primary version ID (first version's ID)\n * @param newSourceUri - The new source URI for this version\n * @param updates - Optional additional updates\n * @returns The newly created asset version\n */\n async createNewVersion(\n primaryVersionId: string,\n newSourceUri: string,\n updates: Partial<Asset> = {},\n ): Promise<Asset> {\n // Get the current latest version\n const versions = await this.listVersions(primaryVersionId);\n if (versions.length === 0) {\n throw new Error(\n `No asset found with primary version ID: ${primaryVersionId}`,\n );\n }\n\n // Sort by version number to find the latest\n versions.sort((a, b) => b.version - a.version);\n const latestVersion = versions[0];\n const newVersionNumber = latestVersion.version + 1;\n\n // Give the new version a distinct, collision-free slug. The assets table is\n // unique on (slug, context, meta_type), so a reused slug would\n // upsert-overwrite an existing row instead of appending a version. Build\n // `<primary-slug>-v<n>` and verify it's not already taken by another version\n // in the chain OR by an unrelated asset — appending a disambiguator until\n // free. (The chain itself is tracked by `primaryVersionId`, not the slug.)\n const primary =\n versions.find((v) => v.id === primaryVersionId) ??\n versions[versions.length - 1];\n const baseSlug = String(primary?.slug ?? '') || 'asset';\n const takenInChain = new Set(\n versions.map((v) => v.slug).filter(Boolean) as string[],\n );\n let candidate = `${baseSlug}-v${newVersionNumber}`;\n for (let attempt = 1; ; attempt += 1) {\n const collides =\n takenInChain.has(candidate) ||\n (await this.get({ slug: candidate })) !== null;\n if (!collides) break;\n candidate = `${baseSlug}-v${newVersionNumber}-${attempt}`;\n }\n\n // Create new version\n return (await this.create({\n ...latestVersion,\n id: undefined, // Generate new ID\n slug: candidate,\n sourceUri: newSourceUri,\n version: newVersionNumber,\n primaryVersionId,\n createdAt: new Date(),\n updatedAt: new Date(),\n ...updates,\n })) as Asset;\n }\n\n /**\n * Get the latest version of an asset\n *\n * @param primaryVersionId - The primary version ID\n * @returns The latest version or null\n */\n async getLatestVersion(primaryVersionId: string): Promise<Asset | null> {\n const versions = await this.listVersions(primaryVersionId);\n if (versions.length === 0) return null;\n\n // Sort by version number descending\n versions.sort((a, b) => b.version - a.version);\n return versions[0];\n }\n\n /**\n * List all versions of an asset\n *\n * @param primaryVersionId - The primary version ID\n * @returns Array of all asset versions, ordered by version number\n */\n async listVersions(primaryVersionId: string): Promise<Asset[]> {\n // Use the collection's `list`/`get` (not raw `db.list`) so rows are properly\n // hydrated into `Asset` instances — `Object.assign(new Asset(), rawRow)`\n // would leave snake_case columns (`source_uri`, `primary_version_id`) on the\n // instance and never populate the camelCase fields. The chain is the rows\n // pointing at this primary plus the primary itself; the adapter has no OR, so\n // fetch both and dedupe by id.\n const [chained, primary] = await Promise.all([\n this.list({ where: { primaryVersionId } }) as Promise<Asset[]>,\n this.get({ id: primaryVersionId }) as Promise<Asset | null>,\n ]);\n\n const byId = new Map<string, Asset>();\n for (const asset of [...(primary ? [primary] : []), ...chained]) {\n if (asset?.id) byId.set(asset.id, asset);\n }\n\n const assets = Array.from(byId.values());\n assets.sort((a, b) => a.version - b.version);\n return assets;\n }\n\n /**\n * Get derivative assets of a source asset.\n *\n * Renamed from `getChildren(parentId)` in R3-D to match the rename of\n * the underlying column (`parent_id` → `source_asset_id`) and method\n * (`Asset.getChildren` → `Asset.getDerivatives`).\n *\n * @param sourceAssetId - The source asset ID\n * @returns Array of derivative assets\n */\n async getDerivatives(sourceAssetId: string): Promise<Asset[]> {\n return (await this.list({ where: { sourceAssetId } })) as Asset[];\n }\n\n /**\n * Get assets by MIME type pattern\n *\n * @param mimePattern - MIME type pattern (e.g., 'image/*', 'video/mp4')\n * @returns Array of matching assets\n */\n async getByMimeType(mimePattern: string): Promise<Asset[]> {\n const pattern = mimePattern.replace('*', '%');\n // Collection `list` (not raw `db.list`) so the rows hydrate into proper\n // `Asset` instances; the `<field> like` operator key is mapped to the column.\n return (await this.list({\n where: { 'mimeType like': pattern },\n })) as Asset[];\n }\n\n /**\n * Rollback to a previous version by creating a new version with the target's content.\n * Does NOT delete intermediate versions (safe rollback).\n *\n * @param primaryVersionId - The primary version ID of the version chain\n * @param targetVersion - The version number to rollback to\n * @returns The newly created asset version with content copied from target\n */\n async rollbackToVersion(\n primaryVersionId: string,\n targetVersion: number,\n ): Promise<Asset> {\n const versions = await this.listVersions(primaryVersionId);\n const target = versions.find((v) => v.version === targetVersion);\n\n if (!target) {\n throw new Error(\n `Version ${targetVersion} not found for asset ${primaryVersionId}`,\n );\n }\n\n // Create a new version that copies the target's sourceUri\n return await this.createNewVersion(primaryVersionId, target.sourceUri, {\n description: `Rollback to version ${targetVersion}`,\n } as Partial<Asset>);\n }\n\n /**\n * Get assets in a specific folder\n *\n * @param folderId - The folder ID to list contents for\n * @returns Array of assets in this folder\n */\n async getByFolder(folderId: string): Promise<Asset[]> {\n return (await this.list({ where: { folderId } })) as Asset[];\n }\n}\n","/**\n * AssetRuntime - shared asset runtime surface\n *\n * Bundles the pieces an app or agent needs to work with `smrt-assets`\n * consistently:\n *\n * - `AssetCollection` for records\n * - `AssetAssociationCollection` for generic/provenance links\n * - `AssetStore` for provider-agnostic byte storage\n * - a single `ProviderOptions` that wires storage in one place\n *\n * Agents that want to accept a shared asset runtime should take an\n * `AssetRuntime` (or `AssetRuntimeLike`) in their options, rather than\n * asking callers to hand-wire a collection + store.\n */\n\nimport type { SmrtClassOptions } from '@happyvertical/smrt-core';\nimport type { Asset } from './asset';\nimport type { AssetAssociation } from './asset-association';\nimport { AssetAssociationCollection } from './asset-associations';\nimport {\n type AssetCapabilityName,\n type AssetCapabilityProvider,\n AssetCapabilitySkippedError,\n AssetCapabilityUnavailableError,\n type AssetExternalSourceRef,\n type AssetExternalSyncResult,\n type AssetNearbySearchInput,\n type AssetProcessResult,\n type AssetSearchResult,\n type AssetVariantRequest,\n type AssetVariantResult,\n type AssetWorkflowInput,\n type AssetWorkflowResult,\n} from './asset-capabilities';\nimport {\n ASSET_ROLES,\n type AssetExtractionStatus,\n type AssetRole,\n} from './asset-conventions';\nimport {\n AssetStore,\n type AssetStoreOptions,\n type ProviderOptions,\n type StoreOptions,\n} from './asset-store';\nimport { AssetCollection } from './assets';\n\n/**\n * DB configuration accepted by the runtime — mirrors the shape\n * `SmrtCollection.create({ db })` already accepts.\n */\nexport type AssetRuntimeDb = NonNullable<SmrtClassOptions['db']>;\n\n/**\n * Options for constructing an `AssetRuntime` via `createAssetRuntime()`.\n */\nexport interface AssetRuntimeOptions {\n /**\n * Database used for `AssetCollection` and `AssetAssociationCollection`.\n *\n * Accepts anything `SmrtCollection.create({ db })` accepts — a string\n * URL, a config object, or a live `DatabaseInterface`.\n */\n db: AssetRuntimeDb;\n\n /**\n * Storage provider for `AssetStore`.\n *\n * A string is treated as a local filesystem `basePath`; otherwise\n * forwarded to `@happyvertical/files`.\n */\n storage: ProviderOptions;\n\n /**\n * Optional behavior for the underlying `AssetStore`.\n *\n * Use this to provide a storage resolver while keeping the convenience\n * runtime factory.\n */\n storeOptions?: AssetStoreOptions;\n\n /**\n * Optional collection instance. If omitted, one is created from `db`.\n * Useful in tests or when the caller already has a configured\n * collection (e.g. from `ObjectRegistry`).\n */\n collection?: AssetCollection;\n\n /**\n * Optional associations collection. If omitted, one is created from `db`.\n */\n associations?: AssetAssociationCollection;\n\n /**\n * Optional asset capability providers.\n *\n * Providers let callers keep one app-facing asset runtime while\n * delegating processing, variant generation, search, external sync, or\n * workflow submission to local processors or external systems.\n */\n capabilityProviders?: AssetCapabilityProvider[];\n}\n\n/**\n * Structural shape of the runtime. Agents and serving helpers should\n * depend on `AssetRuntimeLike` rather than the concrete class so tests\n * can pass in a minimal object.\n */\nexport interface AssetRuntimeLike {\n readonly collection: AssetCollection;\n readonly associations: AssetAssociationCollection;\n readonly store: AssetStore;\n readonly capabilityProviders?: readonly AssetCapabilityProvider[];\n}\n\n/**\n * Options for `AssetRuntime.storeDerivedAsset()`.\n */\nexport interface StoreDerivedAssetOptions\n extends Omit<StoreOptions, 'sourceAssetId'> {\n /**\n * Relationship role for the derivation link.\n *\n * Defaults to `ASSET_ROLES.DERIVATION_SOURCE` when `linkAssociation`\n * is true (the default). Set explicitly for roles like\n * `document_image` or `thumbnail` when a derivative has a more\n * specific semantic than \"came from\".\n */\n role?: AssetRole | string;\n\n /**\n * If true (default), also create an `AssetAssociation` record with\n * `assetId=source.id`, `metaType=<derivativeMetaType>`,\n * `metaId=<newAssetId>`, `role=<role>` so the provenance link is\n * queryable independent of `sourceAssetId`.\n *\n * Set to `false` if you only want the column-level `sourceAssetId`\n * derivation link.\n */\n linkAssociation?: boolean;\n\n /**\n * `metaType` string stored on the `AssetAssociation`. This describes\n * the *derivative* object (the target of `metaId`), not the source.\n *\n * Defaults to `'Asset'`. Callers whose derivatives are STI subclasses\n * — e.g. an `Image` produced from a PDF — should pass the subclass\n * name here so consumers can distinguish subtype derivatives from\n * plain assets. This matches the convention used by\n * `smrt-images`' `ImageDeriver.deriveWithAssociations()`.\n */\n derivativeMetaType?: string;\n}\n\n/**\n * Options for `AssetRuntime.linkDerivation()`.\n */\nexport interface LinkDerivationOptions {\n role?: AssetRole | string;\n /**\n * `metaType` describing the derivative object (target of `metaId`).\n * See `StoreDerivedAssetOptions.derivativeMetaType`.\n */\n derivativeMetaType?: string;\n}\n\n/**\n * Convenience runtime for `smrt-assets` callers. See the package\n * `CLAUDE.md` for the full \"source vs derived\" vocabulary.\n */\nexport class AssetRuntime implements AssetRuntimeLike {\n readonly capabilityProviders: AssetCapabilityProvider[];\n\n constructor(\n public readonly collection: AssetCollection,\n public readonly associations: AssetAssociationCollection,\n public readonly store: AssetStore,\n capabilityProviders: AssetCapabilityProvider[] = [],\n ) {\n this.capabilityProviders = [...capabilityProviders];\n }\n\n registerCapabilityProvider(provider: AssetCapabilityProvider): this {\n this.capabilityProviders.push(provider);\n return this;\n }\n\n private providersFor(\n capability: AssetCapabilityName,\n ): AssetCapabilityProvider[] {\n const providers = this.capabilityProviders.filter(\n (candidate) => typeof candidate[capability] === 'function',\n );\n if (providers.length === 0) {\n throw new AssetCapabilityUnavailableError(capability);\n }\n return providers;\n }\n\n async processAsset(\n asset: Asset,\n input: {\n variants?: AssetVariantRequest[];\n metadata?: Record<string, unknown>;\n } = {},\n ): Promise<AssetProcessResult> {\n let skipped: Error | null = null;\n for (const provider of this.providersFor('processAsset')) {\n const processAsset = provider.processAsset;\n if (!processAsset) continue;\n try {\n return await processAsset({\n runtime: this,\n asset,\n ...input,\n });\n } catch (cause) {\n if (cause instanceof AssetCapabilitySkippedError) {\n skipped = cause;\n continue;\n }\n throw cause;\n }\n }\n throw new AssetCapabilityUnavailableError('processAsset', skipped?.message);\n }\n\n async ensureVariant(\n asset: Asset,\n request: AssetVariantRequest,\n ): Promise<AssetVariantResult> {\n let skipped: Error | null = null;\n for (const provider of this.providersFor('ensureVariant')) {\n const ensureVariant = provider.ensureVariant;\n if (!ensureVariant) continue;\n try {\n return await ensureVariant({\n runtime: this,\n asset,\n request,\n });\n } catch (cause) {\n if (cause instanceof AssetCapabilitySkippedError) {\n skipped = cause;\n continue;\n }\n throw cause;\n }\n }\n throw new AssetCapabilityUnavailableError(\n 'ensureVariant',\n skipped?.message,\n );\n }\n\n async searchNearbyAssets(\n input: Omit<AssetNearbySearchInput, 'runtime'>,\n ): Promise<AssetSearchResult> {\n let skipped: Error | null = null;\n for (const provider of this.providersFor('searchNearbyAssets')) {\n const searchNearbyAssets = provider.searchNearbyAssets;\n if (!searchNearbyAssets) continue;\n try {\n return await searchNearbyAssets({\n runtime: this,\n ...input,\n });\n } catch (cause) {\n if (cause instanceof AssetCapabilitySkippedError) {\n skipped = cause;\n continue;\n }\n throw cause;\n }\n }\n throw new AssetCapabilityUnavailableError(\n 'searchNearbyAssets',\n skipped?.message,\n );\n }\n\n async syncExternalAsset(\n asset: Asset,\n input: {\n externalId?: string | null;\n sourceRef?: AssetExternalSourceRef | null;\n metadata?: Record<string, unknown>;\n } = {},\n ): Promise<AssetExternalSyncResult> {\n let skipped: Error | null = null;\n for (const provider of this.providersFor('syncExternalAsset')) {\n const syncExternalAsset = provider.syncExternalAsset;\n if (!syncExternalAsset) continue;\n try {\n return await syncExternalAsset({\n runtime: this,\n asset,\n ...input,\n });\n } catch (cause) {\n if (cause instanceof AssetCapabilitySkippedError) {\n skipped = cause;\n continue;\n }\n throw cause;\n }\n }\n throw new AssetCapabilityUnavailableError(\n 'syncExternalAsset',\n skipped?.message,\n );\n }\n\n async submitAssetWorkflow(\n asset: Asset,\n input: Omit<AssetWorkflowInput, 'runtime' | 'asset'>,\n ): Promise<AssetWorkflowResult> {\n let skipped: Error | null = null;\n for (const provider of this.providersFor('submitAssetWorkflow')) {\n const submitAssetWorkflow = provider.submitAssetWorkflow;\n if (!submitAssetWorkflow) continue;\n try {\n return await submitAssetWorkflow({\n runtime: this,\n asset,\n ...input,\n });\n } catch (cause) {\n if (cause instanceof AssetCapabilitySkippedError) {\n skipped = cause;\n continue;\n }\n throw cause;\n }\n }\n throw new AssetCapabilityUnavailableError(\n 'submitAssetWorkflow',\n skipped?.message,\n );\n }\n\n /**\n * Create a new source asset with both a record and bytes on disk.\n *\n * This is the same as `AssetStore.store()`, but exposed on the runtime\n * so callers only need one handle.\n */\n storeSourceAsset(\n name: string,\n data: Buffer,\n opts: StoreOptions,\n ): Promise<Asset> {\n return this.store.store(name, data, opts);\n }\n\n /**\n * Create a derivative of `source`, persist its bytes, and optionally\n * record a provenance association.\n *\n * The new asset's `sourceAssetId` always points at `source.id`. When\n * `linkAssociation` is true (the default), the runtime also writes\n * an `AssetAssociation` so queries by role (e.g. \"all `document_image`\n * derivatives for this `source_document`\") work without scanning\n * `source_asset_id` chains.\n */\n async storeDerivedAsset(\n source: Asset,\n name: string,\n data: Buffer,\n opts: StoreDerivedAssetOptions,\n ): Promise<Asset> {\n if (!source.id) {\n throw new Error(\n 'storeDerivedAsset: source asset must be persisted (missing id)',\n );\n }\n\n const {\n role: rawRole,\n linkAssociation = true,\n derivativeMetaType = 'Asset',\n ...storeOpts\n } = opts;\n const role = rawRole ?? ASSET_ROLES.DERIVATION_SOURCE;\n\n const derived = await this.store.store(name, data, {\n ...storeOpts,\n sourceAssetId: source.id,\n });\n\n if (linkAssociation && derived.id) {\n await this.associations.attach(\n derivativeMetaType,\n derived.id,\n source.id,\n { role },\n );\n }\n\n return derived;\n }\n\n /**\n * Record a provenance association between an existing source asset\n * and an existing derivative asset without touching bytes.\n */\n async linkDerivation(\n source: Asset,\n derivative: Asset,\n opts: LinkDerivationOptions = {},\n ): Promise<AssetAssociation> {\n if (!source.id || !derivative.id) {\n throw new Error(\n 'linkDerivation: both source and derivative must be persisted (missing id)',\n );\n }\n const role = opts.role ?? ASSET_ROLES.DERIVATION_SOURCE;\n const derivativeMetaType = opts.derivativeMetaType ?? 'Asset';\n return this.associations.attach(\n derivativeMetaType,\n derivative.id,\n source.id,\n { role },\n );\n }\n\n /**\n * Update the standard extraction-status metadata on an asset's\n * `description` JSON sidecar. This is a thin convenience over the\n * convention in `asset-conventions.ts` — callers that store\n * metadata elsewhere can ignore it.\n *\n * **How existing descriptions are handled**:\n * - Empty / unset → fresh JSON object.\n * - Valid JSON object → merged into; existing keys preserved.\n * - Free-form prose or non-object JSON → preserved under the\n * reserved `text` key of the resulting object (e.g.\n * `{ text: \"original prose\", extractionStatus: \"...\" }`). No prose\n * is discarded.\n *\n * Callers that already use `text` for something else, or that need\n * an entirely separate metadata surface, should either round-trip\n * the JSON themselves or skip this helper — its only job is the\n * `extractionStatus` / `extractionError` / `extractedAt` triple.\n *\n * Error handling: when `status` transitions away from `failed`\n * without a new `extra.error`, the stale `extractionError` is\n * cleared so downstream consumers don't misread the current state.\n * When `status === 'succeeded'`, `extractedAt` is stamped to now\n * unless the caller provides one.\n */\n async setExtractionStatus(\n asset: Asset,\n status: AssetExtractionStatus,\n extra: { error?: string; extractedAt?: Date } = {},\n ): Promise<void> {\n const existing = parseDescriptionSidecar(asset.description);\n const next: Record<string, unknown> = {\n ...existing,\n extractionStatus: status,\n };\n if (extra.error !== undefined) {\n next.extractionError = extra.error;\n } else if (status !== 'failed') {\n // Transitioning out of a prior failure — drop the stale error\n // so consumers don't misread it as still-failing.\n delete next.extractionError;\n }\n if (status === 'succeeded') {\n next.extractedAt = (extra.extractedAt ?? new Date()).toISOString();\n }\n asset.description = JSON.stringify(next);\n await asset.save();\n }\n}\n\n/**\n * Factory — lazily creates a shared asset runtime from DB + storage\n * config. Initializes the store's filesystem adapter before returning.\n *\n * @example\n * ```ts\n * import { createAssetRuntime, ASSET_ROLES } from '@happyvertical/smrt-assets';\n *\n * const runtime = await createAssetRuntime({\n * db: { type: 'sqlite', url: 'app.db' },\n * storage: { type: 's3', bucket: 'my-app' },\n * });\n *\n * const pdf = await runtime.storeSourceAsset('agenda.pdf', bytes, {\n * mimeType: 'application/pdf',\n * typeSlug: 'document',\n * });\n *\n * await runtime.storeDerivedAsset(pdf, 'agenda-p1.png', pageBytes, {\n * mimeType: 'image/png',\n * typeSlug: 'image',\n * role: ASSET_ROLES.DOCUMENT_IMAGE,\n * });\n * ```\n */\nexport async function createAssetRuntime(\n options: AssetRuntimeOptions,\n): Promise<AssetRuntime> {\n const collection =\n options.collection ?? (await AssetCollection.create({ db: options.db }));\n const associations =\n options.associations ??\n (await AssetAssociationCollection.create({ db: options.db }));\n const store = await new AssetStore(\n options.storage,\n collection,\n options.storeOptions,\n ).initialize();\n return new AssetRuntime(\n collection,\n associations,\n store,\n options.capabilityProviders,\n );\n}\n\n/**\n * Parse the `description` field as a JSON sidecar object used by\n * `setExtractionStatus()`. Free-form prose and non-object JSON values\n * are preserved under the reserved `text` key so the helper is\n * non-destructive on assets whose descriptions were authored as\n * human-readable text.\n */\nfunction parseDescriptionSidecar(description: string): Record<string, unknown> {\n if (!description) return {};\n try {\n const parsed = JSON.parse(description);\n if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {\n return parsed as Record<string, unknown>;\n }\n // Non-object JSON (number, string, array, null) — treat the whole\n // value as prose-equivalent and stash it under `text`.\n return { text: description };\n } catch {\n // Not JSON — preserve the original prose under `text`.\n return { text: description };\n }\n}\n","/**\n * Generic asset-serving contract\n *\n * Framework-agnostic helpers that turn a `smrt-assets` runtime plus\n * an asset id (or asset instance) into a standard `Response`. Apps\n * (SvelteKit, Hono, plain Node) can wire this once and stop inventing\n * agent-specific routes.\n *\n * `serveAsset()` returns a Web `Response` built against the global\n * `Response` available in Node 18+. If a runtime needs an older Node,\n * pass its own Response constructor via `responseCtor`.\n */\n\nimport type { Asset } from './asset';\nimport type { AssetRuntimeLike } from './asset-runtime';\n\n/**\n * Result of `resolveAssetForServing()` — lets advanced callers\n * stream bytes themselves while still reusing the access check.\n */\nexport interface ResolvedAssetBytes {\n asset: Asset;\n data: Buffer;\n contentType: string;\n filename: string;\n size: number;\n}\n\n/**\n * Options for `serveAsset()` and `resolveAssetForServing()`.\n */\nexport interface ServeAssetOptions {\n /** Shared asset runtime (collection + store). */\n runtime: AssetRuntimeLike;\n\n /**\n * Either an asset id to look up or a fully-loaded `Asset`\n * instance. Passing the instance skips the `collection.get()` call.\n */\n asset: string | Asset;\n\n /**\n * Tenant of the caller. When provided, the asset must either\n * belong to this tenant or be a global asset (`tenantId = null`)\n * or a 403 is returned.\n */\n tenantId?: string | null;\n\n /**\n * Optional `Content-Disposition` — `'inline'` (default) or\n * `'attachment'`. Use `'attachment'` for download links.\n */\n disposition?: 'inline' | 'attachment';\n\n /**\n * Extra headers merged into the 200 response. Does not affect\n * 403/404/500 responses.\n */\n headers?: Record<string, string>;\n\n /**\n * Optional override for the filename used in `Content-Disposition`.\n * Defaults to the basename of the asset's `sourceUri` or `name`.\n */\n filename?: string;\n\n /**\n * Optional access-check hook. Return `false` to short-circuit with a\n * 403 before bytes are loaded. Runs after the built-in tenant check.\n */\n canAccess?: (asset: Asset) => boolean | Promise<boolean>;\n\n /**\n * How to handle assets whose `sourceUri` is an `http(s)` URL rather\n * than a store-backed path.\n *\n * - `'error'` (default): treat a remote URI as an error (`500`). This\n * is the safe default — `sourceUri` is attacker-settable on many\n * asset-creation paths, so proxying it blindly is an SSRF vector\n * (fetching `169.254.169.254` cloud metadata, internal services,\n * etc.). Opt into `'proxy'` only for trusted sources.\n * - `'proxy'`: fetch the bytes via `globalThis.fetch` and return them\n * inline. Keeps the origin URL hidden from the client and preserves\n * the same tenant/access checks the local path gets. Guarded against\n * SSRF: the host must resolve to a public IP (private, loopback,\n * link-local, and cloud-metadata ranges are rejected), only\n * `http(s)` is allowed, and redirects are re-validated.\n * - `'redirect'`: return a `302` to `sourceUri` instead of fetching.\n * Skips the byte round-trip but exposes the origin URL.\n *\n * Anything that does not look like `http://` or `https://` is read\n * through the store as usual.\n */\n remoteMode?: 'proxy' | 'redirect' | 'error';\n\n /**\n * Custom `fetch` implementation for the `remoteMode: 'proxy'` path.\n * Defaults to `globalThis.fetch` (Node 18+).\n */\n fetchImpl?: typeof fetch;\n\n /**\n * Maximum number of bytes to buffer when `remoteMode: 'proxy'` fetches\n * a remote asset. Protects against memory-exhaustion DoS from a hostile\n * (or compromised) origin streaming an unbounded body. Defaults to\n * {@link DEFAULT_REMOTE_MAX_BYTES} (50 MiB).\n */\n remoteMaxBytes?: number;\n\n /**\n * Timeout in milliseconds for the `remoteMode: 'proxy'` fetch. Defaults\n * to {@link DEFAULT_REMOTE_TIMEOUT_MS} (10s). Prevents a slow/hung\n * origin from pinning the request indefinitely.\n */\n remoteTimeoutMs?: number;\n\n /**\n * Custom `Response`-like constructor, for runtimes that don't have\n * a global `Response` (pre-Node-18, workers with a custom shim).\n * Defaults to `globalThis.Response`.\n */\n responseCtor?: ResponseConstructor;\n}\n\ntype ResponseConstructor = typeof Response;\n\n/** Default cap (50 MiB) on bytes buffered from a proxied remote asset. */\nexport const DEFAULT_REMOTE_MAX_BYTES = 50 * 1024 * 1024;\n\n/** Default timeout (10s) for a proxied remote-asset fetch. */\nexport const DEFAULT_REMOTE_TIMEOUT_MS = 10_000;\n\n/** Maximum number of redirects to follow during a proxied fetch. */\nconst MAX_REMOTE_REDIRECTS = 5;\n\n/**\n * MIME types safe to serve `inline`. Anything else is forced to\n * `attachment` so a browser won't render attacker-controlled content\n * (HTML/SVG/XML) in the serving origin's context (stored XSS).\n */\nconst INLINE_SAFE_MIME_TYPES = new Set<string>([\n 'application/json',\n 'application/pdf',\n 'audio/aac',\n 'audio/mpeg',\n 'audio/ogg',\n 'audio/wav',\n 'audio/webm',\n 'image/avif',\n 'image/gif',\n 'image/jpeg',\n 'image/png',\n 'image/webp',\n 'text/csv',\n 'text/plain',\n 'video/mp4',\n 'video/ogg',\n 'video/webm',\n]);\n\n/** Parse the bare type (drop parameters like `; charset=...`) and lowercase. */\nfunction normalizeMimeType(raw: string): string {\n return (raw.split(';')[0] ?? '').trim().toLowerCase();\n}\n\n/** Whether a MIME type is on the inline-safe safelist. */\nfunction isInlineSafeMimeType(contentType: string): boolean {\n return INLINE_SAFE_MIME_TYPES.has(normalizeMimeType(contentType));\n}\n\nfunction isRemoteUri(uri: string): boolean {\n return /^https?:\\/\\//i.test(uri);\n}\n\n/**\n * Reject hostnames/IPs that point at private, loopback, link-local, or\n * cloud-metadata ranges — the core SSRF guard for proxied fetches.\n *\n * This is a literal-IP check; it does NOT perform DNS resolution, so a\n * hostname that resolves to a private IP is not caught here. Treat\n * `remoteMode: 'proxy'` as trusted-source-only and keep the default at\n * `'error'`; this guard is defense-in-depth for the literal-IP and obvious\n * cloud-metadata cases that make up the bulk of real SSRF payloads.\n */\nfunction isBlockedHost(hostname: string): boolean {\n let host = hostname.trim().toLowerCase();\n // Strip IPv6 brackets if present (URL.hostname keeps them off, but be safe).\n if (host.startsWith('[') && host.endsWith(']')) {\n host = host.slice(1, -1);\n }\n // Strip terminal dot(s): a FQDN like `localhost.` or\n // `metadata.google.internal.` is equivalent to the dotless form and would\n // otherwise sail past the exact-match checks below while still resolving to\n // loopback/metadata.\n host = host.replace(/\\.+$/, '');\n\n if (host === '' || host === 'localhost' || host.endsWith('.localhost')) {\n return true;\n }\n\n // Cloud metadata endpoints (AWS/GCP/Azure/OpenStack link-local + alibaba).\n if (\n host === '169.254.169.254' ||\n host === 'metadata.google.internal' ||\n host === 'metadata' ||\n host === '100.100.100.200'\n ) {\n return true;\n }\n\n // IPv4 literal?\n const ipv4 = host.match(/^(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})$/);\n if (ipv4) {\n const octets = ipv4.slice(1).map((o) => Number(o));\n if (octets.some((o) => o > 255)) return true; // malformed → block\n return isBlockedIpv4(octets as [number, number, number, number]);\n }\n\n // IPv6 literal?\n if (host.includes(':')) {\n return isBlockedIpv6(host);\n }\n\n return false;\n}\n\nfunction isBlockedIpv4(octets: [number, number, number, number]): boolean {\n const [a, b] = octets;\n if (a === 0) return true; // 0.0.0.0/8\n if (a === 10) return true; // 10.0.0.0/8 private\n if (a === 127) return true; // loopback\n if (a === 169 && b === 254) return true; // link-local + metadata\n if (a === 172 && b >= 16 && b <= 31) return true; // 172.16.0.0/12 private\n if (a === 192 && b === 168) return true; // 192.168.0.0/16 private\n if (a === 100 && b >= 64 && b <= 127) return true; // 100.64.0.0/10 CGNAT\n if (a >= 224) return true; // multicast + reserved\n return false;\n}\n\n/**\n * Expand an IPv6 literal (possibly with a `::` run and/or a trailing dotted\n * IPv4 tail) into its eight 16-bit hextets. Returns `null` when the input\n * isn't a parseable IPv6 address — callers treat unparseable as \"not a\n * recognised v6 literal\" and fall back to blocking via the malformed paths.\n */\nfunction expandIpv6(host: string): number[] | null {\n let h = host.toLowerCase();\n // Drop a zone id (e.g. `fe80::1%eth0`) — irrelevant for range checks.\n const pct = h.indexOf('%');\n if (pct !== -1) h = h.slice(0, pct);\n\n // A trailing dotted-quad IPv4 tail (`::ffff:127.0.0.1`) becomes two hextets.\n // Strip it off entirely (colon included) and remember the two hextets it\n // contributes to the explicit-tail side.\n let tailHextets: number[] = [];\n const lastColon = h.lastIndexOf(':');\n const tail = lastColon === -1 ? '' : h.slice(lastColon + 1);\n if (tail.includes('.')) {\n const m = tail.match(/^(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})$/);\n if (!m) return null;\n const octets = m.slice(1).map((o) => Number(o));\n if (octets.some((o) => o > 255)) return null;\n tailHextets = [(octets[0] << 8) | octets[1], (octets[2] << 8) | octets[3]];\n h = h.slice(0, lastColon); // drop ':a.b.c.d' so no dangling empty segment\n // `h` now ends just before the colon that preceded the dotted quad. If the\n // address was exactly `::a.b.c.d`, `h` is now `:` (the leading half of the\n // `::`); normalise that so the `::` detection below still fires.\n if (h === ':') h = '::';\n }\n\n const parseHextets = (parts: string[]): number[] | null => {\n const out: number[] = [];\n for (const p of parts) {\n if (p === '' || !/^[0-9a-f]{1,4}$/.test(p)) return null;\n out.push(Number.parseInt(p, 16));\n }\n return out;\n };\n\n const doubleColon = h.indexOf('::');\n let head: number[] | null;\n let midTail: number[] | null;\n if (doubleColon !== -1) {\n if (h.indexOf('::', doubleColon + 1) !== -1) return null; // only one `::`\n const before = h.slice(0, doubleColon);\n const after = h.slice(doubleColon + 2);\n head = parseHextets(before === '' ? [] : before.split(':'));\n midTail = parseHextets(after === '' ? [] : after.split(':'));\n } else {\n head = parseHextets(h === '' ? [] : h.split(':'));\n midTail = [];\n }\n if (head === null || midTail === null) return null;\n\n const explicit = [...head, ...midTail, ...tailHextets];\n if (doubleColon !== -1) {\n if (explicit.length > 7) return null; // `::` must stand for ≥1 hextet\n const fill = 8 - explicit.length;\n return [...head, ...new Array(fill).fill(0), ...midTail, ...tailHextets];\n }\n if (explicit.length !== 8) return null;\n return explicit;\n}\n\nfunction isBlockedIpv6(host: string): boolean {\n const hextets = expandIpv6(host);\n if (hextets === null) return true; // unparseable v6 literal → block\n\n const [h0, h1, h2, h3, h4, h5, h6, h7] = hextets;\n\n const embeddedV4 = (): [number, number, number, number] => [\n (h6 >> 8) & 0xff,\n h6 & 0xff,\n (h7 >> 8) & 0xff,\n h7 & 0xff,\n ];\n\n // ::/128 unspecified and ::1/128 loopback.\n if (h0 === 0 && h1 === 0 && h2 === 0 && h3 === 0 && h4 === 0 && h5 === 0) {\n if (h6 === 0 && h7 === 0) return true; // ::\n if (h6 === 0 && h7 === 1) return true; // ::1\n // ::/96 IPv4-compatible (deprecated). Node normalises `::127.0.0.1` to\n // `::7f00:1`, which lands here — decode and re-check the embedded v4.\n return isBlockedIpv4(embeddedV4());\n }\n\n // ::ffff:0:0/96 — IPv4-mapped. Decode the embedded v4 (covers both the\n // dotted `::ffff:127.0.0.1` and hex `::ffff:7f00:1` forms, since both expand\n // to the same hextets) and run it through the v4 blocklist.\n if (\n h0 === 0 &&\n h1 === 0 &&\n h2 === 0 &&\n h3 === 0 &&\n h4 === 0 &&\n h5 === 0xffff\n ) {\n return isBlockedIpv4(embeddedV4());\n }\n\n // fe80::/10 link-local (fe80–febf): top 10 bits == 1111111010.\n if ((h0 & 0xffc0) === 0xfe80) return true;\n // fec0::/10 site-local (deprecated, but block defensively).\n if ((h0 & 0xffc0) === 0xfec0) return true;\n // fc00::/7 unique-local (fc00–fdff): top 7 bits == 1111110.\n if ((h0 & 0xfe00) === 0xfc00) return true;\n // ff00::/8 multicast.\n if ((h0 & 0xff00) === 0xff00) return true;\n\n return false;\n}\n\n/**\n * Validate a remote URL for proxying: only `http(s)` and only public hosts.\n * Throws `AssetServeError(502)` on a rejected target. The distinct `message`s\n * (invalid URL vs unsupported scheme vs blocked host) are server-side only —\n * `serveAsset` returns a single generic 502 body for all of them so the client\n * can't probe internal reachability via status OR body differences (S5 #1396).\n */\nfunction assertSafeRemoteUrl(rawUrl: string): URL {\n let url: URL;\n try {\n url = new URL(rawUrl);\n } catch {\n throw new AssetServeError('Invalid remote asset URL', 502);\n }\n if (url.protocol !== 'http:' && url.protocol !== 'https:') {\n throw new AssetServeError('Unsupported remote asset scheme', 502);\n }\n if (isBlockedHost(url.hostname)) {\n throw new AssetServeError('Remote asset host not allowed', 502);\n }\n return url;\n}\n\n/**\n * Fetch a remote asset with SSRF protection, a byte cap, and a timeout.\n *\n * Redirects are followed manually (`redirect: 'manual'`) so each hop's\n * `Location` is re-validated through {@link assertSafeRemoteUrl} — otherwise\n * a public host could 302 to `169.254.169.254`.\n */\nasync function fetchRemoteAsset(\n startUrl: string,\n fetchImpl: typeof fetch,\n maxBytes: number,\n timeoutMs: number,\n): Promise<{ data: Buffer; contentType: string | undefined }> {\n let current = assertSafeRemoteUrl(startUrl);\n\n for (let hop = 0; hop <= MAX_REMOTE_REDIRECTS; hop++) {\n // Use AbortController + setTimeout rather than AbortSignal.timeout():\n // this helper deliberately supports pre-Node-18 runtimes via\n // `responseCtor`/`fetchImpl`, where AbortSignal.timeout() may be missing\n // (S5 #1396, Copilot review). Clear the timer in `finally` so it never\n // leaks past the fetch.\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), timeoutMs);\n let response: Response;\n try {\n response = await fetchImpl(current.toString(), {\n redirect: 'manual',\n signal: controller.signal,\n });\n } finally {\n clearTimeout(timer);\n }\n\n // Manual redirect handling — re-validate each Location hop.\n if (response.status >= 300 && response.status < 400) {\n const location = response.headers.get('location');\n if (!location) {\n throw new AssetServeError(\n 'Remote asset redirect missing Location',\n 502,\n );\n }\n current = assertSafeRemoteUrl(new URL(location, current).toString());\n continue;\n }\n\n if (!response.ok) {\n // Keep the upstream status in `message` for server-side logs only; the\n // client gets the generic opaque 502 body so the origin's status can't\n // leak through the response (S5 #1396, Copilot review).\n throw new AssetServeError(\n `Remote asset upstream returned ${response.status}`,\n 502,\n );\n }\n\n const data = await readBodyWithCap(response, maxBytes);\n return {\n data,\n contentType: response.headers.get('content-type') ?? undefined,\n };\n }\n\n throw new AssetServeError('Too many remote asset redirects', 502);\n}\n\n/**\n * Read a response body into a Buffer, aborting if it exceeds `maxBytes`.\n * Streams when possible so an oversized body is rejected without first\n * buffering the whole thing; falls back to a post-hoc length check when the\n * response exposes no readable stream (e.g. test doubles).\n */\nasync function readBodyWithCap(\n response: Response,\n maxBytes: number,\n): Promise<Buffer> {\n const declared = response.headers.get('content-length');\n if (declared && Number(declared) > maxBytes) {\n throw new AssetServeError('Remote asset exceeds size limit', 502);\n }\n\n const body = response.body;\n if (body && typeof body.getReader === 'function') {\n const reader = body.getReader();\n const chunks: Uint8Array[] = [];\n let total = 0;\n while (true) {\n const { done, value } = await reader.read();\n if (done) break;\n if (value) {\n total += value.byteLength;\n if (total > maxBytes) {\n await reader.cancel().catch(() => {});\n throw new AssetServeError('Remote asset exceeds size limit', 502);\n }\n chunks.push(value);\n }\n }\n return Buffer.concat(chunks.map((c) => Buffer.from(c)));\n }\n\n // No stream available — buffer then enforce the cap.\n const buf = Buffer.from(await response.arrayBuffer());\n if (buf.byteLength > maxBytes) {\n throw new AssetServeError('Remote asset exceeds size limit', 502);\n }\n return buf;\n}\n\n/**\n * Resolve an asset + bytes for serving, enforcing the same tenant\n * and access checks as `serveAsset()` but returning the parts so\n * callers can render their own framework response.\n *\n * Throws `AssetServeError` with a status on any error so callers can\n * map to their HTTP layer.\n */\nexport async function resolveAssetForServing(\n options: ServeAssetOptions,\n): Promise<ResolvedAssetBytes> {\n const {\n runtime,\n asset: assetOrId,\n tenantId,\n canAccess,\n disposition: _disposition,\n } = options;\n\n const asset =\n typeof assetOrId === 'string'\n ? ((await runtime.collection.get({ id: assetOrId })) as Asset | null)\n : assetOrId;\n\n if (!asset) {\n throw new AssetServeError('Asset not found', 404);\n }\n\n if (\n tenantId !== undefined &&\n asset.tenantId !== null &&\n asset.tenantId !== tenantId\n ) {\n throw new AssetServeError('Asset not visible to this tenant', 403);\n }\n\n if (canAccess) {\n const allowed = await canAccess(asset);\n if (!allowed) {\n throw new AssetServeError('Asset access denied', 403);\n }\n }\n\n // Default to 'error': `sourceUri` is attacker-settable on many\n // asset-creation paths, so proxying it blindly is an SSRF vector. Callers\n // serving trusted sources opt into 'proxy' explicitly (S5 #1396).\n const remoteMode = options.remoteMode ?? 'error';\n let data: Buffer;\n let remoteContentType: string | undefined;\n\n if (isRemoteUri(asset.sourceUri)) {\n if (remoteMode === 'error') {\n throw new AssetServeError('Remote asset not supported', 500);\n }\n if (remoteMode === 'redirect') {\n throw new RedirectToSourceUri(asset.sourceUri);\n }\n // Proxy — fetch the bytes from the origin URL with SSRF guards,\n // a byte cap, and a timeout.\n const fetchImpl = options.fetchImpl ?? globalThis.fetch;\n if (!fetchImpl) {\n throw new AssetServeError(\n 'No fetch implementation available for remote asset',\n 500,\n );\n }\n const maxBytes = options.remoteMaxBytes ?? DEFAULT_REMOTE_MAX_BYTES;\n const timeoutMs = options.remoteTimeoutMs ?? DEFAULT_REMOTE_TIMEOUT_MS;\n try {\n const result = await fetchRemoteAsset(\n asset.sourceUri,\n fetchImpl,\n maxBytes,\n timeoutMs,\n );\n data = result.data;\n remoteContentType = result.contentType;\n } catch (err) {\n if (err instanceof AssetServeError) throw err;\n // Log the underlying error so operators can debug without\n // leaking paths/bucket names to the HTTP client.\n console.error('serveAsset: remote fetch failed', err);\n throw new AssetServeError('Failed to fetch remote asset', 502);\n }\n } else {\n try {\n data = await runtime.store.read(asset);\n } catch (err) {\n console.error('serveAsset: store read failed', err);\n // Generic, non-leaking client body (no paths/bucket names).\n throw new AssetServeError(\n 'Failed to read asset bytes',\n 500,\n 'Failed to read asset bytes',\n );\n }\n }\n\n const contentType =\n asset.mimeType || remoteContentType || 'application/octet-stream';\n const filename = options.filename ?? deriveFilename(asset);\n\n return {\n asset,\n data,\n contentType,\n filename,\n size: data.byteLength,\n };\n}\n\n/**\n * Internal signal for `remoteMode: 'redirect'`. Caught by `serveAsset`\n * and turned into a 302; `resolveAssetForServing` re-throws so direct\n * callers can inspect it via `err.location`.\n */\nclass RedirectToSourceUri extends Error {\n constructor(public readonly location: string) {\n super('Asset stored at remote URL');\n this.name = 'RedirectToSourceUri';\n }\n}\n\n/**\n * Serve an asset as a standard Web `Response`.\n *\n * Returns:\n * - `404` when the asset id doesn't resolve\n * - `403` when the tenant mismatches or `canAccess` denies\n * - `302` when `remoteMode: 'redirect'` and the asset's `sourceUri`\n * is an `http(s)` URL\n * - `502` when `remoteMode: 'proxy'` and the origin fetch fails\n * - `500` when bytes fail to read (file missing, provider error)\n * - `200` with the raw bytes, `Content-Type`, `Content-Length`, and\n * `Content-Disposition` set otherwise.\n *\n * The resulting `Response` is framework-agnostic: SvelteKit `+server.ts`\n * endpoints, Hono, and plain Node HTTP all accept it via their Web\n * interop layer.\n */\nexport async function serveAsset(\n options: ServeAssetOptions,\n): Promise<Response> {\n const Ctor =\n options.responseCtor ?? (globalThis.Response as ResponseConstructor);\n if (!Ctor) {\n throw new Error(\n 'serveAsset: no Response constructor available. Pass options.responseCtor or run on Node 18+.',\n );\n }\n\n try {\n const { data, contentType, filename, size } =\n await resolveAssetForServing(options);\n\n // Force `attachment` for any type not on the inline-safe safelist so a\n // browser won't render attacker-controlled HTML/SVG/XML in the serving\n // origin's context (stored XSS). An explicit `disposition: 'attachment'`\n // is always honoured; a requested `'inline'` is downgraded for unsafe\n // types. Pair with `X-Content-Type-Options: nosniff` so the browser\n // can't MIME-sniff a safelisted type into something executable (S5 #1396).\n const requestedDisposition = options.disposition ?? 'inline';\n const disposition =\n requestedDisposition === 'attachment' || isInlineSafeMimeType(contentType)\n ? requestedDisposition\n : 'attachment';\n\n // Spread caller-supplied headers FIRST so the safety headers below always\n // win. Otherwise a caller could pass `Content-Disposition: inline` or\n // `X-Content-Type-Options: ` and defeat the forced-attachment + nosniff\n // protections for an XSS-prone type (S5 #1396 follow-up). HTTP header names\n // are case-insensitive, so drop any case-variant of a protected name from\n // the caller's set before applying the canonical ones.\n const PROTECTED_HEADERS = new Set([\n 'content-type',\n 'content-length',\n 'content-disposition',\n 'x-content-type-options',\n ]);\n const headers: Record<string, string> = {};\n for (const [name, value] of Object.entries(options.headers ?? {})) {\n if (!PROTECTED_HEADERS.has(name.toLowerCase())) {\n headers[name] = value;\n }\n }\n headers['Content-Type'] = contentType;\n headers['Content-Length'] = String(size);\n headers['Content-Disposition'] = buildContentDisposition(\n disposition,\n filename,\n );\n headers['X-Content-Type-Options'] = 'nosniff';\n\n // `new Response(body)` accepts Buffer at runtime (it's a Uint8Array\n // subclass), but the strongly-typed `BodyInit` name lives in\n // `lib.dom.d.ts`, which the package-build tsconfig doesn't pull in.\n // Cast through `unknown` instead of referencing a DOM type so `.d.ts`\n // generation works without needing DOM lib.\n return new Ctor(\n data as unknown as ConstructorParameters<ResponseConstructor>[0],\n {\n status: 200,\n headers,\n },\n );\n } catch (err) {\n if (err instanceof RedirectToSourceUri) {\n return new Ctor(null, {\n status: 302,\n headers: { Location: err.location },\n });\n }\n if (err instanceof AssetServeError) {\n // Return the opaque, status-keyed body — never `err.message`, which can\n // carry the specific rejection reason (SSRF cause, upstream status) and\n // would give clients an oracle about WHY a target was rejected\n // (S5 #1396, Copilot review). The specific reason is logged below.\n if (err.status >= 500) {\n console.error('serveAsset: request failed', err.status, err.message);\n }\n return new Ctor(err.clientMessage, {\n status: err.status,\n headers: { 'Content-Type': 'text/plain; charset=utf-8' },\n });\n }\n // Don't surface the underlying error to clients — it can leak\n // paths/bucket names/provider details. Log for operators.\n console.error('serveAsset: unexpected error', err);\n return new Ctor('Internal error serving asset', {\n status: 500,\n headers: { 'Content-Type': 'text/plain; charset=utf-8' },\n });\n }\n}\n\n/**\n * Generic, status-keyed client-facing bodies. The `message` of an\n * {@link AssetServeError} can carry a specific reason for server-side logs,\n * but the body returned to the HTTP client is one of these opaque strings so\n * a caller can't probe internal reachability or upstream state via the\n * response text — e.g. distinguishing \"blocked host\" from \"bad scheme\", or\n * reading the upstream origin's status (S5 #1396, Copilot review).\n */\nfunction defaultClientMessage(status: number): string {\n switch (status) {\n case 403:\n return 'Forbidden';\n case 404:\n return 'Not found';\n case 502:\n return 'Bad gateway';\n default:\n return 'Internal error serving asset';\n }\n}\n\n/**\n * Error raised by `resolveAssetForServing()` with the HTTP status\n * callers should use when rendering their own response.\n *\n * `message` is the specific, server-side reason — safe to log, never sent to\n * the client. `clientMessage` is the opaque body {@link serveAsset} returns to\n * the HTTP client; it defaults to a generic, status-keyed string so the\n * specific reason can't leak (SSRF rejection cause, upstream status, etc.).\n */\nexport class AssetServeError extends Error {\n /** Opaque body safe to return to the HTTP client. */\n public readonly clientMessage: string;\n\n constructor(\n message: string,\n public readonly status: number,\n clientMessage?: string,\n ) {\n super(message);\n this.name = 'AssetServeError';\n this.clientMessage = clientMessage ?? defaultClientMessage(status);\n }\n}\n\nfunction deriveFilename(asset: Asset): string {\n if (asset.name) return asset.name;\n const fromUri = extractBasename(asset.sourceUri);\n if (fromUri) return fromUri;\n return asset.id ?? 'asset';\n}\n\nfunction extractBasename(uri: string): string {\n if (!uri) return '';\n const noScheme = uri.replace(/^[a-z][a-z0-9+\\-.]*:\\/\\//i, '');\n const last = noScheme.split('/').pop() ?? '';\n return last;\n}\n\nfunction sanitizeDispositionFilename(filename: string): string {\n // Strip control characters (C0 + DEL) first — they make the header\n // invalid in Node's Response constructor and could enable CRLF\n // injection. Then replace quotes, backslashes, and path separators\n // so the quoted-string form is safe.\n let stripped = '';\n for (const ch of filename) {\n const code = ch.charCodeAt(0);\n if (code > 0x1f && code !== 0x7f) {\n stripped += ch;\n }\n }\n const safe = stripped.replace(/[\"\\\\/]/g, '_').trim();\n return safe || 'asset';\n}\n\nfunction buildContentDisposition(\n disposition: 'inline' | 'attachment',\n filename: string,\n): string {\n const sanitized = sanitizeDispositionFilename(filename);\n const encoded = encodeURIComponent(sanitized);\n return `${disposition}; filename=\"${sanitized}\"; filename*=UTF-8''${encoded}`;\n}\n","/**\n * AssetStatusCollection - Collection manager for AssetStatus instances\n *\n * Manages asset status lookup table with common status initialization\n */\n\nimport { SmrtCollection } from '@happyvertical/smrt-core';\nimport { AssetStatus } from './asset-status';\n\nexport class AssetStatusCollection extends SmrtCollection<AssetStatus> {\n static readonly _itemClass = AssetStatus;\n\n /**\n * Get or create an asset status by slug\n *\n * @param slug - The asset status slug\n * @param name - The display name (defaults to slug)\n * @param description - Optional description\n * @returns The existing or newly created AssetStatus\n */\n async getOrCreate(\n slug: string,\n name?: string,\n description?: string,\n ): Promise<AssetStatus> {\n const existing = await this.list({ where: { slug }, limit: 1 });\n if (existing.length > 0) {\n return existing[0];\n }\n\n return await this.create({\n slug,\n name: name || slug,\n description,\n });\n }\n\n /**\n * Initialize common asset statuses\n *\n * Creates standard asset statuses if they don't exist:\n * - draft\n * - published\n * - archived\n * - deleted\n */\n async initializeCommonStatuses(): Promise<void> {\n await this.getOrCreate('draft', 'Draft', 'Work in progress, not yet ready');\n await this.getOrCreate('published', 'Published', 'Live and available');\n await this.getOrCreate(\n 'archived',\n 'Archived',\n 'No longer active but preserved',\n );\n await this.getOrCreate('deleted', 'Deleted', 'Marked for deletion');\n }\n}\n","/**\n * AssetTypeCollection - Collection manager for AssetType instances\n *\n * Manages asset type lookup table with common type initialization\n */\n\nimport { SmrtCollection } from '@happyvertical/smrt-core';\nimport { AssetType } from './asset-type';\n\nexport class AssetTypeCollection extends SmrtCollection<AssetType> {\n static readonly _itemClass = AssetType;\n\n /**\n * Get or create an asset type by slug\n *\n * @param slug - The asset type slug\n * @param name - The display name (defaults to slug)\n * @param description - Optional description\n * @returns The existing or newly created AssetType\n */\n async getOrCreate(\n slug: string,\n name?: string,\n description?: string,\n ): Promise<AssetType> {\n const existing = await this.list({ where: { slug }, limit: 1 });\n if (existing.length > 0) {\n return existing[0];\n }\n\n return await this.create({\n slug,\n name: name || slug,\n description,\n });\n }\n\n /**\n * Initialize common asset types\n *\n * Creates standard asset types if they don't exist:\n * - image\n * - video\n * - document\n * - audio\n *\n * Note: `folder` was removed in R3-D. Folders are no longer an asset\n * subtype — they live on their own `folders` table. See `folder.ts`.\n */\n async initializeCommonTypes(): Promise<void> {\n await this.getOrCreate(\n 'image',\n 'Image',\n 'Image files (JPEG, PNG, GIF, etc.)',\n );\n await this.getOrCreate(\n 'video',\n 'Video',\n 'Video files (MP4, AVI, MOV, etc.)',\n );\n await this.getOrCreate(\n 'document',\n 'Document',\n 'Document files (PDF, DOCX, TXT, etc.)',\n );\n await this.getOrCreate(\n 'audio',\n 'Audio',\n 'Audio files (MP3, WAV, AAC, etc.)',\n );\n }\n}\n","/**\n * Folder model — hierarchical container for organizing assets.\n *\n * Folders are now their own SmrtHierarchical model with their own `folders`\n * table (R3-D). Prior to R3-D, Folder was an STI subclass of Asset and\n * folder rows lived in the `assets` table with `type_slug='folder'`. The\n * column `assets.parent_id` did double duty: structural hierarchy for\n * folder rows, derivation source for asset rows.\n *\n * Splitting Folder onto its own table eliminates the overload and lets\n * Folder participate in the framework-wide `parentId` convention as a\n * SmrtHierarchical subclass (Place, Event, Tag, Account, Zone).\n *\n * Migration path (4 steps; see changeset):\n *\n * 1. Create `folders` table.\n * 2. Copy folder rows from `assets` (`type_slug='folder'`) into `folders`.\n * 3. On `assets`, rename `parent_id` → `source_asset_id` (handled in\n * asset.ts schema generator output) and copy values for non-folder\n * rows.\n * 4. Delete folder rows from `assets`.\n *\n * @see Asset.sourceAssetId for the renamed derivation pointer.\n */\n\nimport {\n crossPackageRef,\n SmrtHierarchical,\n smrt,\n} from '@happyvertical/smrt-core';\nimport { TenantScoped, tenantId } from '@happyvertical/smrt-tenancy';\nimport type { FolderOptions } from './types';\n\n@TenantScoped({ mode: 'optional' })\n@smrt({\n api: { include: ['list', 'get', 'create', 'update', 'delete'] },\n mcp: { include: ['list', 'get', 'create', 'update'] },\n cli: true,\n})\nexport class Folder extends SmrtHierarchical {\n // Tenant isolation (optional — folders can be global or tenant-specific,\n // mirroring Asset's tenancy mode).\n @tenantId({ nullable: true })\n tenantId: string | null = null;\n\n // Core fields. `parentId` is inherited from SmrtHierarchical.\n name = '';\n // `slug` is inherited as an accessor from SmrtObject.\n description = '';\n @crossPackageRef('@happyvertical/smrt-profiles:Profile')\n ownerProfileId: string | null = null;\n\n // Timestamps\n createdAt = new Date();\n updatedAt = new Date();\n\n constructor(options: FolderOptions = {}) {\n super(options);\n if (options.name !== undefined) this.name = options.name;\n if (options.slug !== undefined) this.slug = options.slug;\n if (options.description !== undefined)\n this.description = options.description;\n if (options.parentId !== undefined)\n this.parentId = options.parentId ?? null;\n if (options.ownerProfileId !== undefined)\n this.ownerProfileId = options.ownerProfileId;\n if (options.tenantId !== undefined) this.tenantId = options.tenantId;\n if (options.createdAt) this.createdAt = options.createdAt;\n if (options.updatedAt) this.updatedAt = options.updatedAt;\n }\n\n // Hierarchy traversal (getParent / getChildren / getAncestors /\n // getDescendants / getHierarchy / moveTo) provided by SmrtHierarchical.\n\n /**\n * Look up a folder by slug.\n */\n static async getBySlug(_slug: string): Promise<Folder | null> {\n // Will be auto-implemented by SMRT\n return null;\n }\n}\n","/**\n * FolderCollection — collection manager for Folder instances.\n *\n * Provides tree-oriented helpers that wrap the inherited SmrtHierarchical\n * traversal methods, plus folder-content helpers (`getContents`,\n * `moveAsset`) for working with the assets in a folder.\n *\n * Post R3-D, Folder lives on its own `folders` table; tree queries no\n * longer have to filter by `type_slug='folder'`.\n */\n\nimport { SmrtCollection } from '@happyvertical/smrt-core';\nimport type { Asset } from './asset';\nimport type { AssetCollection } from './assets';\nimport { Folder } from './folder';\n\nexport class FolderCollection extends SmrtCollection<Folder> {\n static readonly _itemClass = Folder;\n\n /**\n * Get the folder tree starting from an optional root.\n *\n * When `rootId` is omitted, returns top-level folders (those with no\n * parent). When `rootId` is provided, returns all descendants of that\n * folder via the inherited SmrtHierarchical BFS traversal — same\n * cycle-safe, one-query-per-depth behaviour as Place/Event.\n *\n * @param rootId - Optional root folder ID; if omitted, returns top-level folders\n * @returns Array of folders (flat list; use parentId to reconstruct tree)\n */\n async getTree(rootId?: string): Promise<Folder[]> {\n if (!rootId) {\n return (await this.list({\n where: { parentId: null },\n orderBy: 'name ASC',\n })) as Folder[];\n }\n\n // Walk the tree BFS-by-level, sorting siblings by name within each\n // batch. This preserves two contracts from the pre-R3-D\n // implementation that `SmrtHierarchical.getDescendants` alone does\n // not — global name-sort would interleave parents and grandchildren\n // (root → Zulu → Alpha would surface as `[Alpha, Zulu]`, putting\n // grandchild before parent), and storage-order BFS would render\n // unsorted siblings.\n const result: Folder[] = [];\n const queue: string[] = [rootId];\n const visited = new Set<string>([rootId]);\n\n while (queue.length > 0) {\n const currentId = queue.shift()!;\n const children = (await this.list({\n where: { parentId: currentId },\n orderBy: 'name ASC',\n })) as Folder[];\n\n for (const child of children) {\n if (child.id && !visited.has(child.id)) {\n visited.add(child.id);\n result.push(child);\n queue.push(child.id);\n }\n }\n }\n\n return result;\n }\n\n /**\n * Get the path from root to a given folder (ancestors + self).\n *\n * @param folderId - The folder ID to get the path for\n * @returns Array of folders from root to the given folder (inclusive)\n */\n async getPath(folderId: string): Promise<Folder[]> {\n const folder = (await this.get({ id: folderId })) as Folder | null;\n if (!folder) return [];\n const ancestors = await folder.getAncestors();\n return [...ancestors, folder];\n }\n\n /**\n * Get all assets that are direct children of a folder.\n *\n * Folder membership is recorded on `Asset.folderId`, not on the folder\n * itself, so this is delegated to the asset collection.\n *\n * @param folderId - The folder ID\n * @param assetCollection - An AssetCollection instance for querying\n * @returns Array of assets in this folder\n */\n async getContents(\n folderId: string,\n assetCollection: AssetCollection,\n ): Promise<Asset[]> {\n return (await assetCollection.list({\n where: { folderId },\n })) as Asset[];\n }\n\n /**\n * Move an asset into a folder (or out, by passing `null`).\n *\n * @param asset - The asset to move\n * @param folderId - The target folder ID (or null to move to root)\n */\n async moveAsset(asset: Asset, folderId: string | null): Promise<void> {\n asset.folderId = folderId;\n await asset.save();\n }\n}\n","export type MediaSupportFileVisibility =\n | 'visible'\n | 'hidden-retained'\n | 'drop-after-extract';\n\nexport interface MediaBundleFileDescriptor {\n path: string;\n relativePath?: string;\n name?: string;\n mimeType?: string;\n sizeBytes?: number;\n sha256?: string;\n modifiedAt?: Date | string;\n metadata?: Record<string, unknown>;\n}\n\nexport interface MediaBundleGpsTrackPoint {\n tSeconds: number;\n recordedAt?: Date | string | null;\n latitude: number;\n longitude: number;\n altitude?: number | null;\n heading?: number | null;\n speedMps?: number | null;\n sourceFilePath?: string;\n}\n\nexport interface MediaBundleNormalizedMetadata {\n captureTime?: Date | string | null;\n width?: number;\n height?: number;\n durationMs?: number;\n mimeType?: string;\n gpsTrack?: MediaBundleGpsTrackPoint[];\n raw?: Record<string, unknown>;\n private?: Record<string, unknown>;\n [key: string]: unknown;\n}\n\nexport interface MediaBundleSupportFileInspection {\n file: MediaBundleFileDescriptor;\n role: string;\n relationship: string;\n visibility?: MediaSupportFileVisibility;\n metadata?: MediaBundleNormalizedMetadata;\n}\n\nexport interface MediaBundleInspection {\n handlerId: string;\n handlerVersion: string;\n formatFamily: string;\n primary: MediaBundleFileDescriptor;\n supportFiles: MediaBundleSupportFileInspection[];\n metadata: MediaBundleNormalizedMetadata;\n capabilities: string[];\n warnings: string[];\n errors: string[];\n raw?: Record<string, unknown>;\n}\n\nexport type MediaBundleInspectionLike = MediaBundleInspection;\n\nexport interface PersistMediaBundleAssetInput {\n file: MediaBundleFileDescriptor;\n role: 'primary' | 'support';\n typeSlug?: string;\n parentAssetId?: string | null;\n relationship?: string;\n visibility?: MediaSupportFileVisibility;\n metadata: MediaBundleNormalizedMetadata;\n inspection: MediaBundleInspection;\n}\n\nexport interface PersistMediaBundleAssociationInput {\n primaryAssetId: string;\n supportAssetId: string;\n relationship: string;\n visibility: MediaSupportFileVisibility;\n metadata?: MediaBundleNormalizedMetadata;\n}\n\nexport interface PersistMediaBundleMetadataArtifactInput {\n primaryAssetId: string;\n inspection: MediaBundleInspection;\n}\n\nexport interface SmrtMediaBundlePersistenceAdapter {\n upsertAsset(input: PersistMediaBundleAssetInput): Promise<{ id: string }>;\n associateSupportFile?(\n input: PersistMediaBundleAssociationInput,\n ): Promise<void>;\n writeMetadataArtifact?(\n input: PersistMediaBundleMetadataArtifactInput,\n ): Promise<{ id: string } | null | undefined>;\n replaceGpsTrack?(\n primaryAssetId: string,\n points: MediaBundleGpsTrackPoint[],\n ): Promise<number>;\n}\n\nexport interface PersistMediaBundleInspectionOptions {\n primaryTypeSlug?: string;\n supportTypeSlug?: string;\n supportVisibility?: MediaSupportFileVisibility;\n writeGpsTrack?: boolean;\n writeMetadataArtifact?: boolean;\n}\n\nexport interface PersistMediaBundleInspectionResult {\n primaryAssetId: string;\n supportAssetIds: string[];\n metadataArtifactId?: string;\n gpsTrackPointCount: number;\n warnings: string[];\n}\n\nexport async function persistMediaBundleInspection(\n adapter: SmrtMediaBundlePersistenceAdapter,\n inspection: MediaBundleInspection,\n options: PersistMediaBundleInspectionOptions = {},\n): Promise<PersistMediaBundleInspectionResult> {\n const warnings = [...inspection.warnings];\n const primary = await adapter.upsertAsset({\n file: inspection.primary,\n role: 'primary',\n typeSlug: options.primaryTypeSlug,\n metadata: inspection.metadata,\n inspection,\n });\n\n const supportAssetIds: string[] = [];\n for (const support of inspection.supportFiles) {\n // Support metadata is retained on support assets; canonical GPS persistence\n // is attached to the primary media through inspection.metadata.gpsTrack.\n const visibility =\n support.visibility ?? options.supportVisibility ?? 'hidden-retained';\n if (visibility === 'drop-after-extract') continue;\n\n const supportAsset = await adapter.upsertAsset({\n file: support.file,\n role: 'support',\n typeSlug: options.supportTypeSlug,\n parentAssetId: primary.id,\n relationship: support.relationship,\n visibility,\n metadata: support.metadata ?? {},\n inspection,\n });\n supportAssetIds.push(supportAsset.id);\n\n await adapter.associateSupportFile?.({\n primaryAssetId: primary.id,\n supportAssetId: supportAsset.id,\n relationship: support.relationship,\n visibility,\n metadata: support.metadata,\n });\n }\n\n let metadataArtifactId: string | undefined;\n if (\n options.writeMetadataArtifact !== false &&\n adapter.writeMetadataArtifact\n ) {\n const artifact = await adapter.writeMetadataArtifact({\n primaryAssetId: primary.id,\n inspection,\n });\n metadataArtifactId = artifact?.id;\n }\n\n let gpsTrackPointCount = 0;\n const gpsTrack = inspection.metadata.gpsTrack ?? [];\n if (options.writeGpsTrack !== false && gpsTrack.length > 0) {\n if (adapter.replaceGpsTrack) {\n gpsTrackPointCount = await adapter.replaceGpsTrack(primary.id, gpsTrack);\n } else {\n warnings.push('adapter cannot persist gps track');\n }\n }\n\n return {\n primaryAssetId: primary.id,\n supportAssetIds,\n metadataArtifactId,\n gpsTrackPointCount,\n warnings,\n };\n}\n","import type { SmrtCollectionOptions } from '@happyvertical/smrt-core';\nimport { withSystemContext } from '@happyvertical/smrt-tenancy';\nimport type { Asset } from './asset';\nimport { AssetCollection } from './assets';\n\nexport const OWNED_ASSET_RELATIONSHIP_PATTERN = /^[a-zA-Z_][a-zA-Z0-9_]*$/;\n\nexport interface AssetOwnerRecord {\n getAssets(relationship?: string): Promise<Asset[]>;\n addAsset(\n asset: Asset,\n relationship?: string,\n sortOrder?: number,\n ): Promise<void>;\n removeAsset(assetId: string, relationship?: string): Promise<void>;\n}\n\nexport interface AssetOwnerCollection<OwnerType extends AssetOwnerRecord> {\n get(where: { id: string }): Promise<OwnerType | null>;\n}\n\nexport function assertValidOwnedAssetRelationship(relationship: string): void {\n if (!OWNED_ASSET_RELATIONSHIP_PATTERN.test(relationship)) {\n throw new Error(\n `Invalid relationship type \"${relationship}\"; must start with a letter or underscore and contain only letters, digits, and underscores`,\n );\n }\n}\n\nexport function assertValidOwnedAssetSortOrder(sortOrder: number): void {\n if (!Number.isInteger(sortOrder) || sortOrder < 0 || sortOrder > 2147483647) {\n throw new Error(\n `Invalid sortOrder \"${sortOrder}\"; must be a non-negative integer`,\n );\n }\n}\n\nexport async function resolveOwnedAssetsById(\n db: SmrtCollectionOptions['db'],\n assetIds: string[],\n tenantId?: string | null,\n): Promise<Asset[]> {\n if (assetIds.length === 0) {\n return [];\n }\n\n const assets = await AssetCollection.create({ db });\n const resolved = tenantId\n ? await withSystemContext(async () => assets.listByIds(assetIds))\n : await assets.listByIds(assetIds);\n const visibleAssets = tenantId\n ? resolved.filter(\n (asset) => asset.tenantId === tenantId || asset.tenantId === null,\n )\n : resolved;\n const assetsById = new Map(\n visibleAssets\n .filter((asset) => asset.id)\n .map((asset) => [asset.id as string, asset]),\n );\n\n return assetIds\n .map((assetId) => assetsById.get(assetId))\n .filter(Boolean) as Asset[];\n}\n\nasync function getOwnerRecord<OwnerType extends AssetOwnerRecord>(\n collection: AssetOwnerCollection<OwnerType>,\n ownerId: string,\n): Promise<OwnerType | null> {\n return collection.get({ id: ownerId });\n}\n\nexport async function getOwnedAssetsFromCollection<\n OwnerType extends AssetOwnerRecord,\n>(\n collection: AssetOwnerCollection<OwnerType>,\n ownerId: string,\n relationship?: string,\n): Promise<Asset[]> {\n const owner = await getOwnerRecord(collection, ownerId);\n if (!owner) {\n return [];\n }\n\n return owner.getAssets(relationship);\n}\n\nexport async function addOwnedAssetFromCollection<\n OwnerType extends AssetOwnerRecord,\n>(\n collection: AssetOwnerCollection<OwnerType>,\n ownerType: string,\n ownerId: string,\n asset: Asset,\n relationship = 'attachment',\n sortOrder = 0,\n): Promise<void> {\n const owner = await getOwnerRecord(collection, ownerId);\n if (!owner) {\n throw new Error(`${ownerType} '${ownerId}' not found`);\n }\n\n await owner.addAsset(asset, relationship, sortOrder);\n}\n\nexport async function removeOwnedAssetFromCollection<\n OwnerType extends AssetOwnerRecord,\n>(\n collection: AssetOwnerCollection<OwnerType>,\n ownerType: string,\n ownerId: string,\n assetId: string,\n relationship?: string,\n): Promise<void> {\n const owner = await getOwnerRecord(collection, ownerId);\n if (!owner) {\n throw new Error(`${ownerType} '${ownerId}' not found`);\n }\n\n await owner.removeAsset(assetId, relationship);\n}\n"],"names":["__decorateClass","tenantId"],"mappings":"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"}
|