@objectstack/service-storage 7.1.0 → 7.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +1 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +28 -4
- package/dist/index.d.ts +28 -4
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/s3-storage-adapter.ts","../src/storage-service-plugin.ts","../src/local-storage-adapter.ts","../src/metadata-store.ts","../src/storage-routes.ts","../src/objects/system-file.object.ts","../src/objects/system-upload-session.object.ts","../src/swappable-storage-service.ts","../src/index.ts"],"sourcesContent":["// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\nimport {\n NoopMetricsRegistry,\n SEMCONV,\n type MetricsRegistry,\n} from '@objectstack/observability';\nimport type {\n IStorageService,\n StorageUploadOptions,\n StorageFileInfo,\n PresignedUploadDescriptor,\n PresignedDownloadDescriptor,\n} from '@objectstack/spec/contracts';\n\n/**\n * Configuration for the S3 storage adapter.\n */\nexport interface S3StorageAdapterOptions {\n /** S3 bucket name */\n bucket: string;\n /** AWS region (e.g. 'us-east-1') */\n region: string;\n /** Optional endpoint URL for S3-compatible services (MinIO, R2, etc.) */\n endpoint?: string;\n /** AWS access key ID (falls back to env/SDK chain) */\n accessKeyId?: string;\n /** AWS secret access key (falls back to env/SDK chain) */\n secretAccessKey?: string;\n /** Force path-style URLs (needed for MinIO / self-hosted) */\n forcePathStyle?: boolean;\n /** Optional MetricsRegistry for instrumentation. Defaults to NoopMetricsRegistry. */\n metrics?: MetricsRegistry;\n}\n\n/**\n * S3 storage adapter implementing IStorageService.\n *\n * Uses `@aws-sdk/client-s3` and `@aws-sdk/s3-request-presigner` as\n * peer dependencies. These must be installed separately when using the S3\n * adapter in production.\n *\n * @example\n * ```ts\n * const storage = new S3StorageAdapter({\n * bucket: 'my-bucket',\n * region: 'us-east-1',\n * });\n * await storage.upload('path/to/file.txt', buffer);\n * ```\n */\nexport class S3StorageAdapter implements IStorageService {\n private readonly bucket: string;\n private readonly region: string;\n private readonly endpoint?: string;\n private readonly forcePathStyle: boolean;\n private readonly metrics: MetricsRegistry;\n private clientPromise: Promise<any> | null = null;\n\n constructor(private readonly options: S3StorageAdapterOptions) {\n this.bucket = options.bucket;\n this.region = options.region;\n this.endpoint = options.endpoint;\n this.forcePathStyle = options.forcePathStyle ?? false;\n this.metrics = options.metrics ?? new NoopMetricsRegistry();\n }\n\n /**\n * Wrap a storage operation with metrics instrumentation.\n * Records ok/error counters, a duration histogram, and an error counter\n * keyed by error class on failure. Never swallows the underlying error.\n */\n private async track<T>(op: 'put' | 'get' | 'delete' | 'head' | 'list', fn: () => Promise<T>): Promise<T> {\n const started = Date.now();\n const baseLabels = { adapter: 's3', op } as const;\n try {\n const out = await fn();\n try {\n this.metrics.counter(SEMCONV.storageOperationsTotal, { ...baseLabels, result: 'ok' });\n this.metrics.histogram(SEMCONV.storageOperationDurationMs, Date.now() - started, baseLabels);\n } catch { /* never throw from instrumentation */ }\n return out;\n } catch (err: any) {\n try {\n this.metrics.counter(SEMCONV.storageOperationsTotal, { ...baseLabels, result: 'error' });\n this.metrics.histogram(SEMCONV.storageOperationDurationMs, Date.now() - started, baseLabels);\n const errorClass = err?.name || err?.constructor?.name || 'Error';\n this.metrics.counter(SEMCONV.storageErrorsTotal, { ...baseLabels, errorClass });\n } catch { /* never throw from instrumentation */ }\n throw err;\n }\n }\n\n /**\n * Lazily resolve the AWS S3 client to avoid crashing at import time when\n * `@aws-sdk/client-s3` isn't installed.\n */\n private async getClient(): Promise<any> {\n if (!this.clientPromise) {\n this.clientPromise = (async () => {\n let s3Mod: any;\n try {\n s3Mod = await import('@aws-sdk/client-s3');\n } catch {\n throw new Error(\n 'S3StorageAdapter requires @aws-sdk/client-s3. Install it with: pnpm add @aws-sdk/client-s3 @aws-sdk/s3-request-presigner',\n );\n }\n const { S3Client } = s3Mod;\n const clientOpts: any = { region: this.region };\n if (this.endpoint) clientOpts.endpoint = this.endpoint;\n if (this.forcePathStyle) clientOpts.forcePathStyle = true;\n if (this.options.accessKeyId && this.options.secretAccessKey) {\n clientOpts.credentials = {\n accessKeyId: this.options.accessKeyId,\n secretAccessKey: this.options.secretAccessKey,\n };\n }\n return new S3Client(clientOpts);\n })();\n }\n return this.clientPromise;\n }\n\n private async s3Mod(): Promise<any> {\n try {\n return await import('@aws-sdk/client-s3');\n } catch {\n throw new Error('S3StorageAdapter requires @aws-sdk/client-s3');\n }\n }\n\n private async presignerMod(): Promise<any> {\n try {\n return await import('@aws-sdk/s3-request-presigner');\n } catch {\n throw new Error('S3StorageAdapter requires @aws-sdk/s3-request-presigner');\n }\n }\n\n // ---------------------------------------------------------------------------\n // Basic operations\n // ---------------------------------------------------------------------------\n\n async upload(key: string, data: Buffer | ReadableStream, options?: StorageUploadOptions): Promise<void> {\n return this.track('put', async () => {\n const client = await this.getClient();\n const s3 = await this.s3Mod();\n const body = data instanceof Buffer ? data : await streamToBuffer(data);\n const cmd = new s3.PutObjectCommand({\n Bucket: this.bucket,\n Key: key,\n Body: body,\n ContentType: options?.contentType,\n Metadata: options?.metadata,\n ACL: options?.acl === 'public-read' ? 'public-read' : undefined,\n });\n await client.send(cmd);\n });\n }\n\n async download(key: string): Promise<Buffer> {\n return this.track('get', async () => {\n const client = await this.getClient();\n const s3 = await this.s3Mod();\n const cmd = new s3.GetObjectCommand({ Bucket: this.bucket, Key: key });\n const res = await client.send(cmd);\n return streamToBuffer(res.Body);\n });\n }\n\n async delete(key: string): Promise<void> {\n return this.track('delete', async () => {\n const client = await this.getClient();\n const s3 = await this.s3Mod();\n const cmd = new s3.DeleteObjectCommand({ Bucket: this.bucket, Key: key });\n await client.send(cmd);\n });\n }\n\n async exists(key: string): Promise<boolean> {\n return this.track('head', async () => {\n const client = await this.getClient();\n const s3 = await this.s3Mod();\n try {\n const cmd = new s3.HeadObjectCommand({ Bucket: this.bucket, Key: key });\n await client.send(cmd);\n return true;\n } catch (err: any) {\n if (err.name === 'NotFound' || err.$metadata?.httpStatusCode === 404) return false;\n throw err;\n }\n });\n }\n\n async getInfo(key: string): Promise<StorageFileInfo> {\n return this.track('head', async () => {\n const client = await this.getClient();\n const s3 = await this.s3Mod();\n const cmd = new s3.HeadObjectCommand({ Bucket: this.bucket, Key: key });\n const res = await client.send(cmd);\n return {\n key,\n size: res.ContentLength ?? 0,\n contentType: res.ContentType,\n lastModified: res.LastModified ?? new Date(),\n metadata: res.Metadata,\n };\n });\n }\n\n async list(prefix: string): Promise<StorageFileInfo[]> {\n return this.track('list', async () => {\n const client = await this.getClient();\n const s3 = await this.s3Mod();\n const cmd = new s3.ListObjectsV2Command({ Bucket: this.bucket, Prefix: prefix });\n const res = await client.send(cmd);\n return (res.Contents ?? []).map((item: any) => ({\n key: item.Key,\n size: item.Size ?? 0,\n lastModified: item.LastModified ?? new Date(),\n }));\n });\n }\n\n // ---------------------------------------------------------------------------\n // Presigned URLs\n // ---------------------------------------------------------------------------\n\n async getSignedUrl(key: string, expiresIn: number): Promise<string> {\n const desc = await this.getPresignedDownload(key, expiresIn);\n return desc.downloadUrl;\n }\n\n async getPresignedUpload(\n key: string,\n expiresIn: number,\n options?: StorageUploadOptions,\n ): Promise<PresignedUploadDescriptor> {\n const client = await this.getClient();\n const s3 = await this.s3Mod();\n const { getSignedUrl } = await this.presignerMod();\n const cmd = new s3.PutObjectCommand({\n Bucket: this.bucket,\n Key: key,\n ContentType: options?.contentType,\n Metadata: options?.metadata,\n ACL: options?.acl === 'public-read' ? 'public-read' : undefined,\n });\n const url = await getSignedUrl(client, cmd, { expiresIn });\n return {\n uploadUrl: url,\n method: 'PUT',\n headers: options?.contentType ? { 'content-type': options.contentType } : undefined,\n expiresIn,\n };\n }\n\n async getPresignedDownload(key: string, expiresIn: number): Promise<PresignedDownloadDescriptor> {\n const client = await this.getClient();\n const s3 = await this.s3Mod();\n const { getSignedUrl } = await this.presignerMod();\n const cmd = new s3.GetObjectCommand({ Bucket: this.bucket, Key: key });\n const url = await getSignedUrl(client, cmd, { expiresIn });\n return { downloadUrl: url, expiresIn };\n }\n\n // ---------------------------------------------------------------------------\n // Chunked / multipart upload\n // ---------------------------------------------------------------------------\n\n async initiateChunkedUpload(key: string, options?: StorageUploadOptions): Promise<string> {\n const client = await this.getClient();\n const s3 = await this.s3Mod();\n const cmd = new s3.CreateMultipartUploadCommand({\n Bucket: this.bucket,\n Key: key,\n ContentType: options?.contentType,\n Metadata: options?.metadata,\n });\n const res = await client.send(cmd);\n return res.UploadId!;\n }\n\n async uploadChunk(uploadId: string, partNumber: number, data: Buffer): Promise<string> {\n const client = await this.getClient();\n const s3 = await this.s3Mod();\n // We need the key — store the relationship elsewhere or pass via metadata.\n // For the S3 adapter, `uploadId` is the S3-native UploadId. The key is\n // tracked in the StorageMetadataStore (sys_upload_session.key).\n // Here we retrieve it from session state; the plugin ensures the correct\n // key is passed. However, the IStorageService contract doesn't include key\n // in uploadChunk — so we work around by storing the mapping in a WeakMap\n // keyed by uploadId. For a robust implementation we'll add a lookup:\n const key = this._uploadKeys?.get(uploadId);\n if (!key) {\n throw new Error('S3StorageAdapter: key not found for uploadId. Call setUploadKey() before uploadChunk().');\n }\n const cmd = new s3.UploadPartCommand({\n Bucket: this.bucket,\n Key: key,\n UploadId: uploadId,\n PartNumber: partNumber,\n Body: data,\n });\n const res = await client.send(cmd);\n return res.ETag!;\n }\n\n async completeChunkedUpload(\n uploadId: string,\n parts: Array<{ partNumber: number; eTag: string }>,\n ): Promise<string> {\n const client = await this.getClient();\n const s3 = await this.s3Mod();\n const key = this._uploadKeys?.get(uploadId);\n if (!key) {\n throw new Error('S3StorageAdapter: key not found for uploadId.');\n }\n const cmd = new s3.CompleteMultipartUploadCommand({\n Bucket: this.bucket,\n Key: key,\n UploadId: uploadId,\n MultipartUpload: {\n Parts: parts.map(p => ({ PartNumber: p.partNumber, ETag: p.eTag })),\n },\n });\n await client.send(cmd);\n this._uploadKeys?.delete(uploadId);\n return key;\n }\n\n async abortChunkedUpload(uploadId: string): Promise<void> {\n const client = await this.getClient();\n const s3 = await this.s3Mod();\n const key = this._uploadKeys?.get(uploadId);\n if (!key) return;\n const cmd = new s3.AbortMultipartUploadCommand({\n Bucket: this.bucket,\n Key: key,\n UploadId: uploadId,\n });\n await client.send(cmd);\n this._uploadKeys?.delete(uploadId);\n }\n\n // ---------------------------------------------------------------------------\n // Internal upload key tracking\n // ---------------------------------------------------------------------------\n private _uploadKeys: Map<string, string> = new Map();\n\n /**\n * Register the storage key for a multipart upload session. Must be called\n * by the StorageServicePlugin after `initiateChunkedUpload()` returns so\n * that subsequent `uploadChunk` / `completeChunkedUpload` calls can resolve\n * the S3 key without it being part of the IStorageService contract signature.\n */\n setUploadKey(uploadId: string, key: string): void {\n this._uploadKeys.set(uploadId, key);\n }\n}\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\nasync function streamToBuffer(stream: any): Promise<Buffer> {\n if (Buffer.isBuffer(stream)) return stream;\n if (stream instanceof Uint8Array) return Buffer.from(stream);\n const chunks: Uint8Array[] = [];\n if (typeof stream[Symbol.asyncIterator] === 'function') {\n for await (const chunk of stream) {\n chunks.push(typeof chunk === 'string' ? Buffer.from(chunk) : chunk);\n }\n } else if (stream.getReader) {\n const reader = stream.getReader();\n let done = false;\n while (!done) {\n const result = await reader.read();\n done = result.done;\n if (result.value) chunks.push(result.value);\n }\n } else {\n throw new Error('Cannot convert stream to buffer');\n }\n return Buffer.concat(chunks);\n}\n\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\nimport type { Plugin, PluginContext } from '@objectstack/core';\nimport type { IHttpServer, IDataEngine, IStorageService } from '@objectstack/spec/contracts';\nimport {\n OBSERVABILITY_METRICS_SERVICE,\n NoopMetricsRegistry,\n type MetricsRegistry,\n} from '@objectstack/observability';\nimport { LocalStorageAdapter } from './local-storage-adapter.js';\nimport type { LocalStorageAdapterOptions } from './local-storage-adapter.js';\nimport { S3StorageAdapter } from './s3-storage-adapter.js';\nimport type { S3StorageAdapterOptions } from './s3-storage-adapter.js';\nimport { StorageMetadataStore } from './metadata-store.js';\nimport { registerStorageRoutes } from './storage-routes.js';\nimport { SystemFile, SystemUploadSession } from './objects/index.js';\nimport { SwappableStorageService } from './swappable-storage-service.js';\n\n/**\n * Configuration options for the StorageServicePlugin.\n */\nexport interface StorageServicePluginOptions {\n /** Storage adapter type (default: 'local') */\n adapter?: 'local' | 's3';\n /** Options for the local storage adapter */\n local?: LocalStorageAdapterOptions;\n /** S3 configuration (used when adapter is 's3') */\n s3?: { bucket: string; region: string; endpoint?: string };\n /**\n * Whether to register REST routes with the HTTP server.\n * @default true\n */\n registerRoutes?: boolean;\n /**\n * Base path for storage REST routes.\n * @default '/api/v1/storage'\n */\n basePath?: string;\n /**\n * Default presigned URL TTL in seconds.\n * @default 3600\n */\n presignedTtl?: number;\n /**\n * Default chunked upload session TTL in seconds.\n * @default 86400\n */\n sessionTtl?: number;\n /**\n * Bind to the `storage` settings namespace and rebuild the inner\n * adapter on every `settings:changed` event. Disable to keep the\n * adapter constructor-driven (useful in tests). Default: true.\n */\n bindToSettings?: boolean;\n /**\n * Optional explicit metrics backend. Wins over the service-registry\n * lookup. Mostly an escape hatch for tests; production hosts should\n * register `ObservabilityServicePlugin` (from `@objectstack/runtime`)\n * once and let every service pick the host's backend up automatically.\n */\n metrics?: MetricsRegistry;\n}\n\n/**\n * StorageServicePlugin — Production IStorageService implementation.\n *\n * Registers a file storage service with the kernel during the init phase.\n * Supports local filesystem (development/testing/single-server) and\n * S3-compatible storage (production). Automatically mounts\n * `/api/v1/storage/*` REST routes via the `kernel:ready` hook when an\n * HTTP server is available.\n *\n * @example\n * ```ts\n * import { ObjectKernel } from '@objectstack/core';\n * import { StorageServicePlugin } from '@objectstack/service-storage';\n *\n * const kernel = new ObjectKernel();\n * kernel.use(new StorageServicePlugin({\n * adapter: 'local',\n * local: { rootDir: './uploads' },\n * }));\n * await kernel.bootstrap();\n *\n * const storage = kernel.getService('file-storage');\n * await storage.upload('file.txt', Buffer.from('hello'));\n * ```\n */\nexport class StorageServicePlugin implements Plugin {\n name = 'com.objectstack.service.storage';\n version = '1.0.0';\n type = 'standard';\n\n private readonly options: StorageServicePluginOptions;\n private storage: SwappableStorageService | null = null;\n private store: StorageMetadataStore | null = null;\n private metrics: MetricsRegistry = new NoopMetricsRegistry();\n\n constructor(options: StorageServicePluginOptions = {}) {\n this.options = { adapter: 'local', ...options };\n }\n\n /** Build a concrete adapter from a values map (settings-derived). */\n private async buildAdapterFromValues(values: Record<string, any>): Promise<IStorageService> {\n const adapter = String(values.adapter ?? 'local');\n if (adapter === 's3') {\n const bucket = values.s3_bucket as string | undefined;\n const region = values.s3_region as string | undefined;\n if (!bucket || !region) {\n throw new Error('StorageServicePlugin: S3 adapter requires s3_bucket and s3_region');\n }\n const opts: S3StorageAdapterOptions = {\n bucket,\n region,\n endpoint: (values.s3_endpoint as string | undefined) || undefined,\n accessKeyId: (values.s3_access_key_id as string | undefined) || undefined,\n secretAccessKey: (values.s3_secret_access_key as string | undefined) || undefined,\n forcePathStyle: !!values.s3_force_path_style,\n metrics: this.metrics,\n };\n return new S3StorageAdapter(opts);\n }\n const rootDir = (values.local_root as string | undefined) || './storage';\n return new LocalStorageAdapter({\n basePath: this.options.basePath ?? '/api/v1/storage',\n ...(this.options.local ?? {}),\n // settings value wins over any constructor-provided local.rootDir\n rootDir,\n metrics: this.metrics,\n } as LocalStorageAdapterOptions);\n }\n\n async init(ctx: PluginContext): Promise<void> {\n this.metrics = resolveMetrics(ctx, this.options.metrics);\n const adapter = this.options.adapter;\n let initial: IStorageService;\n if (adapter === 's3') {\n // Dynamically import the S3 adapter (to avoid top-level import of optional peer dep)\n const { S3StorageAdapter: S3Ctor } = await import('./s3-storage-adapter.js');\n const s3Opts = this.options.s3;\n if (!s3Opts) {\n throw new Error('StorageServicePlugin: s3 options are required when adapter is \"s3\"');\n }\n initial = new S3Ctor({ ...s3Opts, metrics: this.metrics });\n } else {\n const rootDir = this.options.local?.rootDir ?? './storage';\n const basePath = this.options.basePath ?? '/api/v1/storage';\n initial = new LocalStorageAdapter({ rootDir, basePath, ...this.options.local, metrics: this.metrics });\n }\n\n this.storage = new SwappableStorageService(initial, (prev, next) => {\n const prevName = (prev as any)?.constructor?.name ?? 'unknown';\n const nextName = (next as any)?.constructor?.name ?? 'unknown';\n ctx.logger.warn(\n `StorageServicePlugin: storage adapter swapped (${prevName} → ${nextName}). ` +\n 'Existing files were NOT migrated and may be unreachable through the new adapter.',\n );\n });\n\n ctx.registerService('file-storage', this.storage);\n ctx.logger.info(\n `StorageServicePlugin: registered ${adapter} storage adapter (swappable, metrics=${this.metrics.constructor?.name ?? 'unknown'})`,\n );\n\n // Register system objects via manifest service (if available)\n try {\n ctx.getService<{ register(m: any): void }>('manifest').register({\n id: 'com.objectstack.service.storage',\n name: 'Storage Service',\n version: '1.0.0',\n type: 'plugin',\n scope: 'project',\n objects: [SystemFile, SystemUploadSession],\n });\n } catch {\n // manifest service may not be available in all environments\n }\n }\n\n async start(ctx: PluginContext): Promise<void> {\n ctx.hook('kernel:ready', async () => {\n // ── HTTP routes (existing behaviour) ───────────────────────────\n if (this.options.registerRoutes !== false) {\n let httpServer: IHttpServer | null = null;\n try {\n httpServer = ctx.getService<IHttpServer>('http-server');\n } catch {\n // not available\n }\n\n if (httpServer && this.storage) {\n let engine: IDataEngine | null = null;\n try {\n engine = ctx.getService<IDataEngine>('objectql');\n } catch {\n // data engine not wired — use in-memory fallback\n }\n this.store = new StorageMetadataStore(engine);\n\n registerStorageRoutes(httpServer, this.storage, this.store, {\n basePath: this.options.basePath ?? '/api/v1/storage',\n presignedTtl: this.options.presignedTtl,\n sessionTtl: this.options.sessionTtl,\n });\n\n ctx.logger.info(\n 'StorageServicePlugin: REST routes registered at ' +\n (this.options.basePath ?? '/api/v1/storage'),\n );\n } else if (!httpServer) {\n ctx.logger.warn(\n 'StorageServicePlugin: no HTTP server available — REST routes not registered. ' +\n 'File storage is still accessible programmatically via kernel.getService(\"file-storage\").',\n );\n }\n }\n\n // ── Bind to the `storage` settings namespace ──────────────────\n // Allows the admin UI to swap adapters / credentials without\n // restart. Env-locked fields still win at the resolver layer.\n if (this.options.bindToSettings === false) return;\n try {\n const settings = ctx.getService<any>('settings');\n if (!settings || typeof settings.createClient !== 'function') return;\n\n const applySettings = async () => {\n if (!this.storage) return;\n try {\n const payload = await settings.getNamespace('storage');\n const values: Record<string, any> = {};\n for (const [k, v] of Object.entries(payload.values as Record<string, any>)) {\n values[k] = v?.value;\n }\n // No persisted values yet → keep the constructor-built adapter.\n const hasAny = Object.values(values).some((v) => v !== undefined && v !== null && v !== '');\n if (!hasAny) return;\n const next = await this.buildAdapterFromValues(values);\n this.storage.swap(next);\n } catch (err: any) {\n ctx.logger.warn(\n 'StorageServicePlugin: failed to apply storage settings: ' + (err?.message ?? err),\n );\n }\n };\n await applySettings();\n if (typeof settings.subscribe === 'function') {\n settings.subscribe('storage', () => {\n void applySettings();\n });\n ctx.logger.info('StorageServicePlugin: bound to settings:changed for namespace=storage');\n }\n\n // Register the live `storage/test` probe handler.\n if (typeof settings.registerAction === 'function' && this.storage) {\n const proxy = this.storage;\n settings.registerAction('storage', 'test', async ({ values, payload }: any) => {\n // Merge the (possibly unsaved) form state posted as\n // `payload.values` over the persisted snapshot so an operator\n // can validate edits before hitting \"Save\". Matches the\n // pattern used by ai/test and mail/test.\n const overrides = extractOverrides(payload);\n const merged: Record<string, unknown> = { ...(values ?? {}), ...overrides };\n const probeKey = `__objectstack_probe__/${Date.now()}-${Math.random().toString(36).slice(2, 10)}`;\n const probeBytes = Buffer.from(`probe@${new Date().toISOString()}`, 'utf-8');\n try {\n // If merged values are present, build a temporary adapter\n // so we can validate user-typed credentials without\n // committing them.\n let target: IStorageService = proxy;\n if (merged && Object.keys(merged).length > 0) {\n try {\n target = await this.buildAdapterFromValues(merged);\n } catch (err: any) {\n return { ok: false, severity: 'error', message: err?.message ?? String(err) };\n }\n }\n await target.upload(probeKey, probeBytes, { contentType: 'text/plain' });\n const got = await target.download(probeKey);\n if (!got || !Buffer.isBuffer(got) || got.toString('utf-8') !== probeBytes.toString('utf-8')) {\n return { ok: false, severity: 'error', message: 'Probe download did not match upload.' };\n }\n await target.delete(probeKey);\n const adapter = String(merged.adapter ?? this.options.adapter ?? 'local');\n return {\n ok: true,\n severity: 'info',\n message: `Storage round-trip succeeded (adapter=${adapter}).`,\n };\n } catch (err: any) {\n // Best-effort cleanup\n try { await (proxy as IStorageService).delete(probeKey); } catch { /* ignore */ }\n return { ok: false, severity: 'error', message: err?.message ?? String(err) };\n }\n });\n ctx.logger.info('StorageServicePlugin: registered settings action storage/test');\n }\n } catch {\n // settings service not present — manifest fallback handler stays\n }\n });\n }\n}\n\n/**\n * Look up the host's MetricsRegistry from the service registry, with\n * the canonical fallback chain (explicit override → registered service\n * → noop). Local helper to avoid making `service-storage` depend on\n * `@objectstack/runtime`.\n */\nfunction resolveMetrics(\n ctx: PluginContext,\n override: MetricsRegistry | undefined,\n): MetricsRegistry {\n if (override) return override;\n try {\n const m = ctx.getService<MetricsRegistry | undefined>(OBSERVABILITY_METRICS_SERVICE);\n if (m) return m;\n } catch {\n // Service not registered — silent fall-through.\n }\n return new NoopMetricsRegistry();\n}\n\nfunction extractOverrides(payload: unknown): Record<string, unknown> {\n if (!payload || typeof payload !== 'object') return {};\n const p = payload as Record<string, unknown>;\n if (p.values && typeof p.values === 'object' && p.values !== null) {\n return p.values as Record<string, unknown>;\n }\n return p;\n}\n\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\nimport { promises as fs, createReadStream, createWriteStream } from 'node:fs';\nimport { join, dirname } from 'node:path';\nimport { createHmac, randomUUID } from 'node:crypto';\nimport {\n NoopMetricsRegistry,\n SEMCONV,\n type MetricsRegistry,\n} from '@objectstack/observability';\nimport type {\n IStorageService,\n StorageUploadOptions,\n StorageFileInfo,\n PresignedUploadDescriptor,\n PresignedDownloadDescriptor,\n} from '@objectstack/spec/contracts';\n\n/**\n * Configuration options for LocalStorageAdapter.\n */\nexport interface LocalStorageAdapterOptions {\n /** Root directory for committed files */\n rootDir: string;\n /**\n * Public base URL the adapter prepends to presigned upload / download URLs.\n * Defaults to a relative path so the same-origin REST server handles the\n * request. Override (e.g. `https://api.example.com`) when the storage\n * routes are exposed on a different host.\n * @default ''\n */\n baseUrl?: string;\n /**\n * Base path of the local storage REST routes mounted by\n * `StorageServicePlugin`. Used to construct presigned URLs.\n * @default '/api/v1/storage'\n */\n basePath?: string;\n /**\n * HMAC secret used to sign presigned-upload tokens.\n * Auto-generated if omitted (suitable for single-process dev usage).\n */\n signingSecret?: string;\n /** Optional MetricsRegistry for instrumentation. Defaults to NoopMetricsRegistry. */\n metrics?: MetricsRegistry;\n}\n\ninterface PresignTokenPayload {\n k: string; // storage key\n ct?: string; // content-type\n exp: number; // expiry epoch seconds\n op: 'put' | 'get';\n}\n\n/**\n * Local filesystem storage adapter implementing IStorageService.\n *\n * Stores committed files under `rootDir/`, in-flight multipart parts under\n * `rootDir/.parts/<uploadId>/<chunkIndex>`. Presigned URLs are HMAC-signed\n * tokens redeemed against the local REST routes mounted by\n * `StorageServicePlugin` — letting the browser PUT bytes directly without\n * proxying through the application logic.\n *\n * Suitable for development, testing, and single-server deployments.\n */\nexport class LocalStorageAdapter implements IStorageService {\n private readonly rootDir: string;\n private readonly partsDir: string;\n private readonly baseUrl: string;\n private readonly basePath: string;\n private readonly signingSecret: string;\n private readonly metrics: MetricsRegistry;\n\n constructor(options: LocalStorageAdapterOptions) {\n this.rootDir = options.rootDir;\n this.partsDir = join(this.rootDir, '.parts');\n this.baseUrl = options.baseUrl ?? '';\n this.basePath = options.basePath ?? '/api/v1/storage';\n this.signingSecret = options.signingSecret ?? randomUUID();\n this.metrics = options.metrics ?? new NoopMetricsRegistry();\n }\n\n /**\n * Wrap a storage operation with metrics instrumentation. Never swallows\n * the underlying error; instrumentation failures are silently ignored.\n */\n private async track<T>(op: 'put' | 'get' | 'delete' | 'head' | 'list', fn: () => Promise<T>): Promise<T> {\n const started = Date.now();\n const baseLabels = { adapter: 'local', op } as const;\n try {\n const out = await fn();\n try {\n this.metrics.counter(SEMCONV.storageOperationsTotal, { ...baseLabels, result: 'ok' });\n this.metrics.histogram(SEMCONV.storageOperationDurationMs, Date.now() - started, baseLabels);\n } catch { /* never throw */ }\n return out;\n } catch (err: any) {\n try {\n this.metrics.counter(SEMCONV.storageOperationsTotal, { ...baseLabels, result: 'error' });\n this.metrics.histogram(SEMCONV.storageOperationDurationMs, Date.now() - started, baseLabels);\n const errorClass = err?.name || err?.constructor?.name || 'Error';\n this.metrics.counter(SEMCONV.storageErrorsTotal, { ...baseLabels, errorClass });\n } catch { /* never throw */ }\n throw err;\n }\n }\n\n // ---------------------------------------------------------------------------\n // Path helpers\n // ---------------------------------------------------------------------------\n\n private resolvePath(key: string): string {\n if (key.includes('..')) {\n throw new Error(`LocalStorageAdapter: path traversal not allowed (key=\"${key}\")`);\n }\n return join(this.rootDir, key);\n }\n\n private resolvePartPath(uploadId: string, partNumber: number): string {\n if (!/^[A-Za-z0-9_-]+$/.test(uploadId)) {\n throw new Error(`LocalStorageAdapter: invalid uploadId \"${uploadId}\"`);\n }\n return join(this.partsDir, uploadId, String(partNumber).padStart(8, '0'));\n }\n\n // ---------------------------------------------------------------------------\n // Basic file operations\n // ---------------------------------------------------------------------------\n\n async upload(\n key: string,\n data: Buffer | ReadableStream,\n _options?: StorageUploadOptions,\n ): Promise<void> {\n return this.track('put', async () => {\n const filePath = this.resolvePath(key);\n await fs.mkdir(dirname(filePath), { recursive: true });\n\n if (data instanceof Buffer) {\n await fs.writeFile(filePath, data);\n return;\n }\n\n // Convert ReadableStream to Buffer\n const chunks: Uint8Array[] = [];\n const reader = (data as ReadableStream).getReader();\n let done = false;\n while (!done) {\n const result = await reader.read();\n done = result.done;\n if (result.value) chunks.push(result.value);\n }\n await fs.writeFile(filePath, Buffer.concat(chunks));\n });\n }\n\n async download(key: string): Promise<Buffer> {\n return this.track('get', async () => fs.readFile(this.resolvePath(key)));\n }\n\n async delete(key: string): Promise<void> {\n return this.track('delete', async () => {\n await fs.unlink(this.resolvePath(key)).catch((err) => {\n if (err && err.code === 'ENOENT') return;\n throw err;\n });\n });\n }\n\n async exists(key: string): Promise<boolean> {\n return this.track('head', async () => {\n try {\n await fs.access(this.resolvePath(key));\n return true;\n } catch {\n return false;\n }\n });\n }\n\n async getInfo(key: string): Promise<StorageFileInfo> {\n return this.track('head', async () => {\n const filePath = this.resolvePath(key);\n const stat = await fs.stat(filePath);\n return { key, size: stat.size, lastModified: stat.mtime };\n });\n }\n\n async list(prefix: string): Promise<StorageFileInfo[]> {\n return this.track('list', async () => {\n const dirPath = this.resolvePath(prefix);\n try {\n const entries = await fs.readdir(dirPath);\n const results: StorageFileInfo[] = [];\n for (const entry of entries) {\n if (entry.startsWith('.')) continue;\n const fullKey = prefix ? `${prefix}/${entry}` : entry;\n try {\n // Inline stat to avoid double-counting `head` operations.\n const stat = await fs.stat(this.resolvePath(fullKey));\n results.push({ key: fullKey, size: stat.size, lastModified: stat.mtime });\n } catch {\n /* skip */\n }\n }\n return results;\n } catch {\n return [];\n }\n });\n }\n\n // ---------------------------------------------------------------------------\n // Presigned URL helpers\n // ---------------------------------------------------------------------------\n\n /**\n * Sign an opaque token for the given payload.\n * Format: base64url(JSON.stringify(payload)) + '.' + base64url(HMAC)\n */\n private signToken(payload: PresignTokenPayload): string {\n const b64 = Buffer.from(JSON.stringify(payload), 'utf8').toString('base64url');\n const sig = createHmac('sha256', this.signingSecret).update(b64).digest('base64url');\n return `${b64}.${sig}`;\n }\n\n /**\n * Verify and decode a presigned token. Throws on invalid signature or\n * expiration.\n */\n verifyToken(token: string, expectedOp: 'put' | 'get'): PresignTokenPayload {\n const [b64, sig] = token.split('.');\n if (!b64 || !sig) throw new Error('Invalid storage token format');\n\n const expected = createHmac('sha256', this.signingSecret).update(b64).digest('base64url');\n if (expected !== sig) throw new Error('Invalid storage token signature');\n\n let payload: PresignTokenPayload;\n try {\n payload = JSON.parse(Buffer.from(b64, 'base64url').toString('utf8'));\n } catch {\n throw new Error('Malformed storage token payload');\n }\n\n if (payload.op !== expectedOp) {\n throw new Error(`Storage token op mismatch (expected=\"${expectedOp}\", actual=\"${payload.op}\")`);\n }\n if (Date.now() / 1000 > payload.exp) {\n throw new Error('Storage token expired');\n }\n return payload;\n }\n\n async getPresignedUpload(\n key: string,\n expiresIn: number,\n options?: StorageUploadOptions,\n ): Promise<PresignedUploadDescriptor> {\n const exp = Math.floor(Date.now() / 1000) + Math.max(1, expiresIn);\n const token = this.signToken({ k: key, ct: options?.contentType, exp, op: 'put' });\n\n return {\n uploadUrl: `${this.baseUrl}${this.basePath}/_local/raw/${token}`,\n method: 'PUT',\n headers: options?.contentType ? { 'content-type': options.contentType } : { 'content-type': 'application/octet-stream' },\n expiresIn,\n downloadUrl: `${this.baseUrl}${this.basePath}/_local/file/${encodeURIComponent(key)}`,\n };\n }\n\n async getPresignedDownload(key: string, expiresIn: number): Promise<PresignedDownloadDescriptor> {\n const exp = Math.floor(Date.now() / 1000) + Math.max(1, expiresIn);\n const token = this.signToken({ k: key, exp, op: 'get' });\n return {\n downloadUrl: `${this.baseUrl}${this.basePath}/_local/raw/${token}`,\n expiresIn,\n };\n }\n\n async getSignedUrl(key: string, expiresIn: number): Promise<string> {\n const desc = await this.getPresignedDownload(key, expiresIn);\n return desc.downloadUrl;\n }\n\n // ---------------------------------------------------------------------------\n // Chunked / multipart upload\n // ---------------------------------------------------------------------------\n\n async initiateChunkedUpload(key: string, options?: StorageUploadOptions): Promise<string> {\n const uploadId = randomUUID().replace(/-/g, '');\n const dir = join(this.partsDir, uploadId);\n await fs.mkdir(dir, { recursive: true });\n const meta = {\n key,\n contentType: options?.contentType,\n metadata: options?.metadata,\n createdAt: new Date().toISOString(),\n };\n await fs.writeFile(join(dir, '_meta.json'), JSON.stringify(meta), 'utf8');\n return uploadId;\n }\n\n async uploadChunk(uploadId: string, partNumber: number, data: Buffer): Promise<string> {\n if (!Number.isInteger(partNumber) || partNumber < 1) {\n throw new Error(`uploadChunk: partNumber must be a positive integer (got ${partNumber})`);\n }\n const partPath = this.resolvePartPath(uploadId, partNumber);\n await fs.mkdir(dirname(partPath), { recursive: true });\n await fs.writeFile(partPath, data);\n // ETag for local mode = hex md5 of part bytes (matches S3 single-part ETag format)\n const { createHash } = await import('node:crypto');\n return createHash('md5').update(data).digest('hex');\n }\n\n async completeChunkedUpload(\n uploadId: string,\n parts: Array<{ partNumber: number; eTag: string }>,\n ): Promise<string> {\n const dir = join(this.partsDir, uploadId);\n let meta: { key?: string } = {};\n try {\n meta = JSON.parse(await fs.readFile(join(dir, '_meta.json'), 'utf8'));\n } catch {\n throw new Error(`Upload session \"${uploadId}\" not found`);\n }\n const targetKey = meta.key;\n if (!targetKey) {\n throw new Error(`Upload session \"${uploadId}\" missing target key`);\n }\n\n const sortedParts = [...parts].sort((a, b) => a.partNumber - b.partNumber);\n const finalPath = this.resolvePath(targetKey);\n await fs.mkdir(dirname(finalPath), { recursive: true });\n\n // Stream-concat parts into the final file\n const out = createWriteStream(finalPath);\n try {\n for (const p of sortedParts) {\n const partPath = this.resolvePartPath(uploadId, p.partNumber);\n await new Promise<void>((resolve, reject) => {\n const inp = createReadStream(partPath);\n inp.on('error', reject);\n inp.on('end', () => resolve());\n inp.pipe(out, { end: false });\n });\n }\n } finally {\n await new Promise<void>((resolve) => out.end(() => resolve()));\n }\n\n // Cleanup part directory\n await fs.rm(dir, { recursive: true, force: true });\n return targetKey;\n }\n\n async abortChunkedUpload(uploadId: string): Promise<void> {\n await fs.rm(join(this.partsDir, uploadId), { recursive: true, force: true });\n }\n}\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\nimport type { IDataEngine } from '@objectstack/spec/contracts';\n\n/**\n * Persisted file metadata record (matches `sys_file` object schema).\n */\nexport interface FileRecord {\n id: string;\n key: string;\n name: string;\n mime_type?: string;\n size?: number;\n scope?: string;\n bucket?: string;\n acl?: string;\n status: 'pending' | 'committed' | 'deleted';\n etag?: string;\n owner_id?: string;\n metadata?: string;\n created_at?: string;\n updated_at?: string;\n}\n\n/**\n * Persisted upload-session record (matches `sys_upload_session` object schema).\n */\nexport interface UploadSessionRecord {\n id: string;\n file_id: string;\n key: string;\n filename: string;\n mime_type?: string;\n total_size: number;\n chunk_size: number;\n total_chunks: number;\n uploaded_chunks?: number;\n uploaded_size?: number;\n parts?: string;\n resume_token?: string;\n backend_upload_id?: string;\n scope?: string;\n bucket?: string;\n metadata?: string;\n status: 'in_progress' | 'completing' | 'completed' | 'failed' | 'expired';\n started_at?: string;\n expires_at?: string;\n updated_at?: string;\n}\n\n/**\n * Storage metadata persistence.\n *\n * Backed by `IDataEngine` (objectql) when available — otherwise falls back to\n * a process-local Map (suitable for tests and dev environments where the\n * data engine isn't wired up).\n */\nexport class StorageMetadataStore {\n private readonly files = new Map<string, FileRecord>();\n private readonly sessions = new Map<string, UploadSessionRecord>();\n\n constructor(private readonly engine: IDataEngine | null) {}\n\n // ---------------------------------------------------------------------------\n // Files\n // ---------------------------------------------------------------------------\n\n async createFile(rec: FileRecord): Promise<FileRecord> {\n const now = new Date().toISOString();\n const full: FileRecord = { created_at: now, updated_at: now, ...rec };\n this.files.set(full.id, full);\n if (this.engine) {\n try {\n await this.engine.insert('sys_file', full);\n } catch {\n /* engine not available or schema not migrated — keep in-memory only */\n }\n }\n return full;\n }\n\n async getFile(id: string): Promise<FileRecord | null> {\n if (this.engine) {\n try {\n const found = await this.engine.findOne('sys_file', { where: { id } });\n if (found) return found as FileRecord;\n } catch {\n /* fall through to memory */\n }\n }\n return this.files.get(id) ?? null;\n }\n\n async updateFile(id: string, patch: Partial<FileRecord>): Promise<FileRecord | null> {\n const existing = await this.getFile(id);\n if (!existing) return null;\n const merged: FileRecord = { ...existing, ...patch, id, updated_at: new Date().toISOString() };\n this.files.set(id, merged);\n if (this.engine) {\n try {\n await this.engine.update('sys_file', merged as any, { where: { id } } as any);\n } catch {\n /* ignore */\n }\n }\n return merged;\n }\n\n async deleteFile(id: string): Promise<void> {\n this.files.delete(id);\n if (this.engine) {\n try {\n await this.engine.delete('sys_file', { where: { id } } as any);\n } catch {\n /* ignore */\n }\n }\n }\n\n // ---------------------------------------------------------------------------\n // Upload sessions\n // ---------------------------------------------------------------------------\n\n async createSession(rec: UploadSessionRecord): Promise<UploadSessionRecord> {\n const now = new Date().toISOString();\n const full: UploadSessionRecord = {\n uploaded_chunks: 0,\n uploaded_size: 0,\n parts: '[]',\n started_at: now,\n updated_at: now,\n ...rec,\n };\n this.sessions.set(full.id, full);\n if (this.engine) {\n try {\n await this.engine.insert('sys_upload_session', full);\n } catch {\n /* ignore */\n }\n }\n return full;\n }\n\n async getSession(id: string): Promise<UploadSessionRecord | null> {\n if (this.engine) {\n try {\n const found = await this.engine.findOne('sys_upload_session', { where: { id } });\n if (found) return found as UploadSessionRecord;\n } catch {\n /* ignore */\n }\n }\n return this.sessions.get(id) ?? null;\n }\n\n async updateSession(id: string, patch: Partial<UploadSessionRecord>): Promise<UploadSessionRecord | null> {\n const existing = await this.getSession(id);\n if (!existing) return null;\n const merged: UploadSessionRecord = {\n ...existing,\n ...patch,\n id,\n updated_at: new Date().toISOString(),\n };\n this.sessions.set(id, merged);\n if (this.engine) {\n try {\n await this.engine.update('sys_upload_session', merged as any, { where: { id } } as any);\n } catch {\n /* ignore */\n }\n }\n return merged;\n }\n\n async deleteSession(id: string): Promise<void> {\n this.sessions.delete(id);\n if (this.engine) {\n try {\n await this.engine.delete('sys_upload_session', { where: { id } } as any);\n } catch {\n /* ignore */\n }\n }\n }\n}\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\nimport { randomUUID } from 'node:crypto';\nimport type { IHttpServer, IHttpRequest, IHttpResponse, IStorageService } from '@objectstack/spec/contracts';\nimport type { StorageMetadataStore } from './metadata-store.js';\nimport type { LocalStorageAdapter } from './local-storage-adapter.js';\n\n/**\n * Options for the storage route registration helper.\n */\nexport interface StorageRoutesOptions {\n basePath?: string;\n /** Default presigned URL TTL in seconds */\n presignedTtl?: number;\n /** Default chunked upload session TTL in seconds */\n sessionTtl?: number;\n}\n\n/**\n * Register `/api/v1/storage/*` REST routes with the HTTP server.\n *\n * Implements the contract defined in `packages/spec/src/api/storage.zod.ts`\n * (`StorageApiContracts`). This function follows the \"autonomous plugin route\n * registration\" pattern used by `I18nServicePlugin`, `AuthPlugin`, etc.\n *\n * Routes:\n * - POST /storage/upload/presigned → get presigned upload URL\n * - POST /storage/upload/complete → mark upload as committed\n * - POST /storage/upload/chunked → initiate chunked upload\n * - PUT /storage/upload/chunked/:uploadId/chunk/:chunkIndex → upload a chunk\n * - POST /storage/upload/chunked/:uploadId/complete → complete chunked\n * - GET /storage/upload/chunked/:uploadId/progress → get upload progress\n * - GET /storage/files/:fileId/url → get download URL\n * - PUT /storage/_local/raw/:token → local adapter raw upload\n * - GET /storage/_local/raw/:token → local adapter raw download\n */\nexport function registerStorageRoutes(\n httpServer: IHttpServer,\n storage: IStorageService,\n store: StorageMetadataStore,\n opts: StorageRoutesOptions = {},\n): void {\n const basePath = opts.basePath ?? '/api/v1/storage';\n const presignedTtl = opts.presignedTtl ?? 3600;\n const sessionTtl = opts.sessionTtl ?? 86400;\n\n // ---------------------------------------------------------------------------\n // POST /storage/upload/presigned\n // ---------------------------------------------------------------------------\n httpServer.post(`${basePath}/upload/presigned`, async (req: IHttpRequest, res: IHttpResponse) => {\n try {\n const { filename, mimeType, size, scope, bucket } = req.body ?? {};\n if (!filename || !mimeType || size == null) {\n res.status(400).json({ error: 'filename, mimeType, and size are required' });\n return;\n }\n\n const fileId = randomUUID();\n const key = buildKey(scope ?? 'user', fileId, filename);\n\n // Persist pending file record\n await store.createFile({\n id: fileId,\n key,\n name: filename,\n mime_type: mimeType,\n size,\n scope: scope ?? 'user',\n bucket,\n acl: 'private',\n status: 'pending',\n });\n\n // If adapter supports presigned upload, use it; otherwise build a local stub URL\n let uploadUrl: string;\n let method: 'PUT' | 'POST' = 'PUT';\n let headers: Record<string, string> = { 'content-type': mimeType };\n let expiresIn = presignedTtl;\n\n if (storage.getPresignedUpload) {\n const desc = await storage.getPresignedUpload(key, presignedTtl, { contentType: mimeType });\n uploadUrl = desc.uploadUrl;\n method = desc.method;\n if (desc.headers) headers = desc.headers;\n expiresIn = desc.expiresIn;\n } else {\n // Fallback — caller should PUT to the standard raw endpoint\n uploadUrl = `${basePath}/_local/raw/${fileId}`;\n }\n\n res.json({\n data: {\n uploadUrl,\n method,\n headers,\n fileId,\n expiresIn,\n downloadUrl: `${basePath}/files/${fileId}/url`,\n },\n });\n } catch (err: any) {\n res.status(500).json({ error: err.message ?? 'Internal error' });\n }\n });\n\n // ---------------------------------------------------------------------------\n // POST /storage/upload/complete\n // ---------------------------------------------------------------------------\n httpServer.post(`${basePath}/upload/complete`, async (req: IHttpRequest, res: IHttpResponse) => {\n try {\n const { fileId, eTag } = req.body ?? {};\n if (!fileId) {\n res.status(400).json({ error: 'fileId is required' });\n return;\n }\n\n const file = await store.getFile(fileId);\n if (!file) {\n res.status(404).json({ error: 'File not found' });\n return;\n }\n\n const updated = await store.updateFile(fileId, {\n status: 'committed',\n etag: eTag ?? undefined,\n });\n\n res.json({\n data: {\n path: updated!.key,\n name: updated!.name,\n size: updated!.size ?? 0,\n mimeType: updated!.mime_type ?? 'application/octet-stream',\n lastModified: updated!.updated_at ?? new Date().toISOString(),\n created: updated!.created_at ?? new Date().toISOString(),\n etag: updated!.etag,\n },\n });\n } catch (err: any) {\n res.status(500).json({ error: err.message ?? 'Internal error' });\n }\n });\n\n // ---------------------------------------------------------------------------\n // POST /storage/upload/chunked\n // ---------------------------------------------------------------------------\n httpServer.post(`${basePath}/upload/chunked`, async (req: IHttpRequest, res: IHttpResponse) => {\n try {\n const { filename, mimeType, totalSize, chunkSize: reqChunkSize, scope, bucket, metadata } = req.body ?? {};\n if (!filename || !mimeType || !totalSize) {\n res.status(400).json({ error: 'filename, mimeType, and totalSize are required' });\n return;\n }\n\n const chunkSize = Math.max(reqChunkSize ?? 5242880, 5242880);\n const totalChunks = Math.ceil(totalSize / chunkSize);\n\n const fileId = randomUUID();\n const key = buildKey(scope ?? 'user', fileId, filename);\n\n // Create pending file\n await store.createFile({\n id: fileId,\n key,\n name: filename,\n mime_type: mimeType,\n size: totalSize,\n scope: scope ?? 'user',\n bucket,\n acl: 'private',\n status: 'pending',\n metadata: metadata ? JSON.stringify(metadata) : undefined,\n });\n\n // Initiate chunked upload in backend\n let backendUploadId: string | undefined;\n if (storage.initiateChunkedUpload) {\n backendUploadId = await storage.initiateChunkedUpload(key, { contentType: mimeType, metadata });\n // S3 adapter needs to know the key for subsequent chunk/complete calls\n if ('setUploadKey' in storage && typeof (storage as any).setUploadKey === 'function') {\n (storage as any).setUploadKey(backendUploadId, key);\n }\n }\n\n const uploadId = backendUploadId ?? randomUUID().replace(/-/g, '');\n const resumeToken = randomUUID();\n const expiresAt = new Date(Date.now() + sessionTtl * 1000).toISOString();\n\n await store.createSession({\n id: uploadId,\n file_id: fileId,\n key,\n filename,\n mime_type: mimeType,\n total_size: totalSize,\n chunk_size: chunkSize,\n total_chunks: totalChunks,\n resume_token: resumeToken,\n backend_upload_id: backendUploadId,\n scope: scope ?? 'user',\n bucket,\n metadata: metadata ? JSON.stringify(metadata) : undefined,\n status: 'in_progress',\n expires_at: expiresAt,\n });\n\n res.json({\n data: {\n uploadId,\n resumeToken,\n fileId,\n totalChunks,\n chunkSize,\n expiresAt,\n },\n });\n } catch (err: any) {\n res.status(500).json({ error: err.message ?? 'Internal error' });\n }\n });\n\n // ---------------------------------------------------------------------------\n // PUT /storage/upload/chunked/:uploadId/chunk/:chunkIndex\n // ---------------------------------------------------------------------------\n httpServer.put(`${basePath}/upload/chunked/:uploadId/chunk/:chunkIndex`, async (req: IHttpRequest, res: IHttpResponse) => {\n try {\n const { uploadId, chunkIndex: chunkIndexStr } = req.params;\n const chunkIndex = parseInt(chunkIndexStr, 10);\n if (!uploadId || isNaN(chunkIndex)) {\n res.status(400).json({ error: 'uploadId and chunkIndex are required' });\n return;\n }\n\n const session = await store.getSession(uploadId);\n if (!session) {\n res.status(404).json({ error: 'Upload session not found' });\n return;\n }\n\n // Verify resume token\n const token = (req.headers['x-resume-token'] ?? '') as string;\n if (session.resume_token && token !== session.resume_token) {\n res.status(403).json({ error: 'Invalid resume token' });\n return;\n }\n\n // Get raw body (binary data)\n let data: Buffer;\n if (req.rawBody) {\n data = await req.rawBody();\n } else if (Buffer.isBuffer(req.body)) {\n data = req.body;\n } else if (req.body instanceof ArrayBuffer) {\n data = Buffer.from(req.body);\n } else {\n res.status(400).json({ error: 'Binary body required' });\n return;\n }\n\n // Upload the chunk (S3 uses 1-based part numbers)\n let eTag = '';\n if (storage.uploadChunk) {\n eTag = await storage.uploadChunk(uploadId, chunkIndex + 1, data);\n }\n\n // Update session progress\n const currentParts: Array<{ chunkIndex: number; eTag: string }> = JSON.parse(session.parts ?? '[]');\n currentParts.push({ chunkIndex, eTag });\n const uploadedChunks = (session.uploaded_chunks ?? 0) + 1;\n const uploadedSize = (session.uploaded_size ?? 0) + data.byteLength;\n await store.updateSession(uploadId, {\n uploaded_chunks: uploadedChunks,\n uploaded_size: uploadedSize,\n parts: JSON.stringify(currentParts),\n });\n\n res.json({\n data: {\n chunkIndex,\n eTag,\n bytesReceived: data.byteLength,\n },\n });\n } catch (err: any) {\n res.status(500).json({ error: err.message ?? 'Internal error' });\n }\n });\n\n // ---------------------------------------------------------------------------\n // POST /storage/upload/chunked/:uploadId/complete\n // ---------------------------------------------------------------------------\n httpServer.post(`${basePath}/upload/chunked/:uploadId/complete`, async (req: IHttpRequest, res: IHttpResponse) => {\n try {\n const { uploadId } = req.params;\n const session = await store.getSession(uploadId);\n if (!session) {\n res.status(404).json({ error: 'Upload session not found' });\n return;\n }\n\n await store.updateSession(uploadId, { status: 'completing' });\n\n const partsFromBody = (req.body?.parts ?? []) as Array<{ chunkIndex: number; eTag: string }>;\n const partsForBackend = partsFromBody.map(p => ({\n partNumber: p.chunkIndex + 1,\n eTag: p.eTag,\n }));\n\n let finalKey = session.key;\n if (storage.completeChunkedUpload) {\n finalKey = await storage.completeChunkedUpload(uploadId, partsForBackend);\n }\n\n // Update file + session\n await store.updateFile(session.file_id, { status: 'committed', key: finalKey });\n await store.updateSession(uploadId, { status: 'completed' });\n\n res.json({\n data: {\n fileId: session.file_id,\n key: finalKey,\n size: session.total_size,\n mimeType: session.mime_type ?? 'application/octet-stream',\n url: `${basePath}/files/${session.file_id}/url`,\n },\n });\n } catch (err: any) {\n res.status(500).json({ error: err.message ?? 'Internal error' });\n }\n });\n\n // ---------------------------------------------------------------------------\n // GET /storage/upload/chunked/:uploadId/progress\n // ---------------------------------------------------------------------------\n httpServer.get(`${basePath}/upload/chunked/:uploadId/progress`, async (req: IHttpRequest, res: IHttpResponse) => {\n try {\n const { uploadId } = req.params;\n const session = await store.getSession(uploadId);\n if (!session) {\n res.status(404).json({ error: 'Upload session not found' });\n return;\n }\n\n const uploadedChunks = session.uploaded_chunks ?? 0;\n const uploadedSize = session.uploaded_size ?? 0;\n const percentComplete = session.total_size > 0\n ? Math.min(100, Math.round((uploadedSize / session.total_size) * 100))\n : 0;\n\n res.json({\n data: {\n uploadId: session.id,\n fileId: session.file_id,\n filename: session.filename,\n totalSize: session.total_size,\n uploadedSize,\n totalChunks: session.total_chunks,\n uploadedChunks,\n percentComplete,\n status: session.status,\n startedAt: session.started_at,\n expiresAt: session.expires_at,\n },\n });\n } catch (err: any) {\n res.status(500).json({ error: err.message ?? 'Internal error' });\n }\n });\n\n // ---------------------------------------------------------------------------\n // GET /storage/files/:fileId/url\n // ---------------------------------------------------------------------------\n httpServer.get(`${basePath}/files/:fileId/url`, async (req: IHttpRequest, res: IHttpResponse) => {\n try {\n const { fileId } = req.params;\n const file = await store.getFile(fileId);\n if (!file || file.status !== 'committed') {\n res.status(404).json({ error: 'File not found or not committed' });\n return;\n }\n\n let url: string;\n if (storage.getPresignedDownload) {\n const desc = await storage.getPresignedDownload(file.key, presignedTtl);\n url = desc.downloadUrl;\n } else if (storage.getSignedUrl) {\n url = await storage.getSignedUrl(file.key, presignedTtl);\n } else {\n url = `${basePath}/_local/file/${encodeURIComponent(file.key)}`;\n }\n\n res.json({ url });\n } catch (err: any) {\n res.status(500).json({ error: err.message ?? 'Internal error' });\n }\n });\n\n // ---------------------------------------------------------------------------\n // GET /storage/files/:fileId — stable redirect to the actual bytes.\n //\n // Frontend widgets (`ImageField`, `<img src>`, user avatars, org logos)\n // need a URL that:\n // - is stable (won't expire — records may live for years)\n // - serves the bytes directly when followed\n // The `/url` endpoint above returns JSON. This sibling endpoint resolves\n // to the same short-lived signed URL and 302-redirects so it can be used\n // verbatim in any browser context.\n // ---------------------------------------------------------------------------\n httpServer.get(`${basePath}/files/:fileId`, async (req: IHttpRequest, res: IHttpResponse) => {\n try {\n const { fileId } = req.params;\n const file = await store.getFile(fileId);\n if (!file || file.status !== 'committed') {\n res.status(404).json({ error: 'File not found or not committed' });\n return;\n }\n\n let url: string;\n if (storage.getPresignedDownload) {\n const desc = await storage.getPresignedDownload(file.key, presignedTtl);\n url = desc.downloadUrl;\n } else if (storage.getSignedUrl) {\n url = await storage.getSignedUrl(file.key, presignedTtl);\n } else {\n url = `${basePath}/_local/file/${encodeURIComponent(file.key)}`;\n }\n\n res.status(302).header('Location', url).send('');\n } catch (err: any) {\n res.status(500).json({ error: err.message ?? 'Internal error' });\n }\n });\n\n // ---------------------------------------------------------------------------\n // PUT /storage/_local/raw/:token — presigned raw upload (LocalStorageAdapter)\n // ---------------------------------------------------------------------------\n httpServer.put(`${basePath}/_local/raw/:token`, async (req: IHttpRequest, res: IHttpResponse) => {\n try {\n const { token } = req.params;\n const localAdapter = storage as LocalStorageAdapter;\n if (!localAdapter.verifyToken) {\n res.status(501).json({ error: 'Presigned raw upload not supported by this adapter' });\n return;\n }\n\n const payload = localAdapter.verifyToken(token, 'put');\n let data: Buffer;\n if (req.rawBody) {\n data = await req.rawBody();\n } else if (Buffer.isBuffer(req.body)) {\n data = req.body;\n } else {\n res.status(400).json({ error: 'Binary body required' });\n return;\n }\n\n await storage.upload(payload.k, data, { contentType: payload.ct });\n res.json({ ok: true, key: payload.k });\n } catch (err: any) {\n const statusCode = err.message?.includes('expired') || err.message?.includes('signature') ? 403 : 500;\n res.status(statusCode).json({ error: err.message ?? 'Upload failed' });\n }\n });\n\n // ---------------------------------------------------------------------------\n // GET /storage/_local/raw/:token — presigned raw download (LocalStorageAdapter)\n // ---------------------------------------------------------------------------\n httpServer.get(`${basePath}/_local/raw/:token`, async (req: IHttpRequest, res: IHttpResponse) => {\n try {\n const { token } = req.params;\n const localAdapter = storage as LocalStorageAdapter;\n if (!localAdapter.verifyToken) {\n res.status(501).json({ error: 'Presigned download not supported by this adapter' });\n return;\n }\n\n const payload = localAdapter.verifyToken(token, 'get');\n const data = await storage.download(payload.k);\n\n res.header('content-type', payload.ct ?? 'application/octet-stream');\n res.header('content-length', String(data.byteLength));\n res.send(data);\n } catch (err: any) {\n const statusCode = err.message?.includes('expired') || err.message?.includes('signature') ? 403 : 500;\n res.status(statusCode).json({ error: err.message ?? 'Download failed' });\n }\n });\n}\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\nfunction buildKey(scope: string, fileId: string, filename: string): string {\n const ext = filename.includes('.') ? '.' + filename.split('.').pop() : '';\n return `${scope}/${fileId}${ext}`;\n}\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\nimport { ObjectSchema, Field } from '@objectstack/spec/data';\n\n/**\n * System File Object\n *\n * Persisted metadata for files stored via the Storage Service.\n *\n * The Storage Service contract addresses files by `key` (path inside the\n * configured backend). The REST protocol (see `packages/spec/src/api/storage.zod.ts`)\n * exposes an opaque `fileId` so that:\n *\n * 1. Client code never needs to know — or be able to spoof — backend keys.\n * 2. Files can be moved between buckets / storage tiers without breaking links.\n * 3. Lifecycle status (uploading → committed → deleted) can be tracked.\n *\n * Belongs to `@objectstack/service-storage` per the\n * \"protocol + service ownership\" pattern used by `service-feed`.\n */\nexport const SystemFile = ObjectSchema.create({\n name: 'sys_file',\n label: 'System File',\n pluralLabel: 'System Files',\n icon: 'file',\n description: 'Storage service file metadata (fileId ↔ key mapping)',\n titleFormat: '{name}',\n compactLayout: ['name', 'mime_type', 'size', 'status', 'created_at'],\n\n fields: {\n id: Field.text({\n label: 'File ID',\n required: true,\n readonly: true,\n }),\n\n key: Field.text({\n label: 'Storage Key',\n required: true,\n searchable: true,\n }),\n\n name: Field.text({\n label: 'File Name',\n required: true,\n searchable: true,\n }),\n\n mime_type: Field.text({\n label: 'MIME Type',\n }),\n\n size: Field.number({\n label: 'Size (bytes)',\n }),\n\n scope: Field.select({\n label: 'Scope',\n options: [\n { label: 'User', value: 'user' },\n { label: 'Tenant', value: 'tenant' },\n { label: 'Public', value: 'public' },\n { label: 'Private', value: 'private' },\n { label: 'Temp', value: 'temp' },\n ],\n }),\n\n bucket: Field.text({\n label: 'Bucket',\n }),\n\n acl: Field.select({\n label: 'ACL',\n options: [\n { label: 'Private', value: 'private' },\n { label: 'Public Read', value: 'public_read' },\n ],\n }),\n\n status: Field.select({\n label: 'Status',\n required: true,\n options: [\n { label: 'Pending Upload', value: 'pending' },\n { label: 'Committed', value: 'committed' },\n { label: 'Deleted', value: 'deleted' },\n ],\n }),\n\n etag: Field.text({\n label: 'ETag',\n }),\n\n owner_id: Field.text({\n label: 'Owner ID',\n }),\n\n metadata: Field.text({\n label: 'Metadata (JSON)',\n }),\n\n created_at: Field.datetime({\n label: 'Created At',\n }),\n\n updated_at: Field.datetime({\n label: 'Updated At',\n }),\n },\n});\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\nimport { ObjectSchema, Field } from '@objectstack/spec/data';\n\n/**\n * System Upload Session Object\n *\n * Persisted state for in-flight chunked / multipart uploads.\n *\n * Tracks upload progress so that interrupted uploads can be resumed via\n * `POST /api/v1/storage/upload/chunked/:uploadId/progress`. Sessions are\n * cleaned up by the storage service on `complete` / `abort` / TTL expiry.\n */\nexport const SystemUploadSession = ObjectSchema.create({\n name: 'sys_upload_session',\n label: 'System Upload Session',\n pluralLabel: 'System Upload Sessions',\n icon: 'upload-cloud',\n description: 'Resumable multipart upload sessions tracked by service-storage',\n titleFormat: '{filename}',\n compactLayout: ['filename', 'status', 'uploaded_chunks', 'total_chunks', 'expires_at'],\n\n fields: {\n id: Field.text({\n label: 'Upload Session ID',\n required: true,\n readonly: true,\n }),\n\n file_id: Field.text({\n label: 'File ID',\n required: true,\n }),\n\n key: Field.text({\n label: 'Storage Key',\n required: true,\n }),\n\n filename: Field.text({\n label: 'Filename',\n required: true,\n }),\n\n mime_type: Field.text({\n label: 'MIME Type',\n }),\n\n total_size: Field.number({\n label: 'Total Size (bytes)',\n required: true,\n }),\n\n chunk_size: Field.number({\n label: 'Chunk Size (bytes)',\n required: true,\n }),\n\n total_chunks: Field.number({\n label: 'Total Chunks',\n required: true,\n }),\n\n uploaded_chunks: Field.number({\n label: 'Uploaded Chunks',\n }),\n\n uploaded_size: Field.number({\n label: 'Uploaded Size (bytes)',\n }),\n\n parts: Field.text({\n label: 'Uploaded Parts (JSON)',\n }),\n\n resume_token: Field.text({\n label: 'Resume Token',\n }),\n\n backend_upload_id: Field.text({\n label: 'Backend Upload ID',\n }),\n\n scope: Field.text({\n label: 'Scope',\n }),\n\n bucket: Field.text({\n label: 'Bucket',\n }),\n\n metadata: Field.text({\n label: 'Metadata (JSON)',\n }),\n\n status: Field.select({\n label: 'Status',\n required: true,\n options: [\n { label: 'In Progress', value: 'in_progress' },\n { label: 'Completing', value: 'completing' },\n { label: 'Completed', value: 'completed' },\n { label: 'Failed', value: 'failed' },\n { label: 'Expired', value: 'expired' },\n ],\n }),\n\n started_at: Field.datetime({\n label: 'Started At',\n }),\n\n expires_at: Field.datetime({\n label: 'Expires At',\n }),\n\n updated_at: Field.datetime({\n label: 'Updated At',\n }),\n },\n});\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\nimport type {\n IStorageService,\n StorageFileInfo,\n StorageUploadOptions,\n PresignedUploadDescriptor,\n PresignedDownloadDescriptor,\n} from '@objectstack/spec/contracts';\n\n/**\n * SwappableStorageService — IStorageService proxy with a swappable\n * inner adapter.\n *\n * Used by `StorageServicePlugin` so the kernel can register a stable\n * `file-storage` reference at init time, while the underlying adapter\n * (local FS / S3) is rebuilt on every `settings:changed` event for\n * the `storage` namespace.\n *\n * ⚠ Adapter swaps do NOT migrate previously uploaded files. Files\n * stored under the previous adapter become unreachable through the\n * new one. Callers are responsible for migrating data out-of-band.\n *\n * All `IStorageService` methods delegate to the current inner adapter.\n * Optional methods (list / presigned / chunked) probe the inner\n * adapter and surface a clear error when the active adapter does not\n * implement them.\n */\nexport class SwappableStorageService implements IStorageService {\n private inner: IStorageService;\n private readonly onSwap?: (previous: IStorageService, next: IStorageService) => void;\n\n constructor(\n initial: IStorageService,\n onSwap?: (previous: IStorageService, next: IStorageService) => void,\n ) {\n this.inner = initial;\n this.onSwap = onSwap;\n }\n\n /** Replace the inner adapter. */\n swap(next: IStorageService): void {\n const previous = this.inner;\n this.inner = next;\n this.onSwap?.(previous, next);\n }\n\n /** Expose the active inner adapter — primarily for tests. */\n getInner(): IStorageService {\n return this.inner;\n }\n\n upload(key: string, data: Buffer | ReadableStream, options?: StorageUploadOptions): Promise<void> {\n return this.inner.upload(key, data, options);\n }\n\n download(key: string): Promise<Buffer> {\n return this.inner.download(key);\n }\n\n delete(key: string): Promise<void> {\n return this.inner.delete(key);\n }\n\n exists(key: string): Promise<boolean> {\n return this.inner.exists(key);\n }\n\n getInfo(key: string): Promise<StorageFileInfo> {\n return this.inner.getInfo(key);\n }\n\n list(prefix: string): Promise<StorageFileInfo[]> {\n if (typeof this.inner.list !== 'function') {\n return Promise.reject(new Error('Active storage adapter does not support list()'));\n }\n return this.inner.list(prefix);\n }\n\n getSignedUrl(key: string, expiresIn: number): Promise<string> {\n if (typeof this.inner.getSignedUrl !== 'function') {\n return Promise.reject(new Error('Active storage adapter does not support getSignedUrl()'));\n }\n return this.inner.getSignedUrl(key, expiresIn);\n }\n\n getPresignedUpload(\n key: string,\n expiresIn: number,\n options?: StorageUploadOptions,\n ): Promise<PresignedUploadDescriptor> {\n if (typeof this.inner.getPresignedUpload !== 'function') {\n return Promise.reject(new Error('Active storage adapter does not support getPresignedUpload()'));\n }\n return this.inner.getPresignedUpload(key, expiresIn, options);\n }\n\n getPresignedDownload(key: string, expiresIn: number): Promise<PresignedDownloadDescriptor> {\n if (typeof this.inner.getPresignedDownload !== 'function') {\n return Promise.reject(new Error('Active storage adapter does not support getPresignedDownload()'));\n }\n return this.inner.getPresignedDownload(key, expiresIn);\n }\n\n initiateChunkedUpload(key: string, options?: StorageUploadOptions): Promise<string> {\n if (typeof this.inner.initiateChunkedUpload !== 'function') {\n return Promise.reject(new Error('Active storage adapter does not support initiateChunkedUpload()'));\n }\n return this.inner.initiateChunkedUpload(key, options);\n }\n\n uploadChunk(uploadId: string, partNumber: number, data: Buffer): Promise<string> {\n if (typeof this.inner.uploadChunk !== 'function') {\n return Promise.reject(new Error('Active storage adapter does not support uploadChunk()'));\n }\n return this.inner.uploadChunk(uploadId, partNumber, data);\n }\n\n completeChunkedUpload(\n uploadId: string,\n parts: Array<{ partNumber: number; eTag: string }>,\n ): Promise<string> {\n if (typeof this.inner.completeChunkedUpload !== 'function') {\n return Promise.reject(new Error('Active storage adapter does not support completeChunkedUpload()'));\n }\n return this.inner.completeChunkedUpload(uploadId, parts);\n }\n\n abortChunkedUpload(uploadId: string): Promise<void> {\n if (typeof this.inner.abortChunkedUpload !== 'function') {\n return Promise.reject(new Error('Active storage adapter does not support abortChunkedUpload()'));\n }\n return this.inner.abortChunkedUpload(uploadId);\n }\n\n /**\n * Verify a presigned HMAC token (LocalStorageAdapter-specific).\n *\n * `IStorageService` does not declare this method, but `storage-routes`\n * type-narrows the active storage to `LocalStorageAdapter` to handle the\n * `/_local/raw/:token` PUT and GET endpoints. Without a passthrough on\n * the swappable wrapper, the route sees `verifyToken === undefined` and\n * returns 501 even though the underlying local adapter supports it.\n */\n verifyToken(token: string, expectedOp?: 'put' | 'get'): { k: string; ct?: string; op: string; exp: number } {\n const inner = this.inner as unknown as {\n verifyToken?: (token: string, expectedOp?: 'put' | 'get') => { k: string; ct?: string; op: string; exp: number };\n };\n if (typeof inner.verifyToken !== 'function') {\n throw new Error('Active storage adapter does not support verifyToken()');\n }\n return inner.verifyToken(token, expectedOp);\n }\n}\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\nexport { StorageServicePlugin } from './storage-service-plugin.js';\nexport type { StorageServicePluginOptions } from './storage-service-plugin.js';\nexport { SwappableStorageService } from './swappable-storage-service.js';\nexport { LocalStorageAdapter } from './local-storage-adapter.js';\nexport type { LocalStorageAdapterOptions } from './local-storage-adapter.js';\nexport { S3StorageAdapter } from './s3-storage-adapter.js';\nexport type { S3StorageAdapterOptions } from './s3-storage-adapter.js';\nexport { StorageMetadataStore } from './metadata-store.js';\nexport type { FileRecord, UploadSessionRecord } from './metadata-store.js';\nexport { registerStorageRoutes } from './storage-routes.js';\nexport type { StorageRoutesOptions } from './storage-routes.js';\nexport { SystemFile, SystemUploadSession } from './objects/index.js';\n"],"mappings":"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","names":["NoopMetricsRegistry","SEMCONV","NoopMetricsRegistry","randomUUID","ObjectSchema","Field","NoopMetricsRegistry"]}
|
|
1
|
+
{"version":3,"sources":["../src/s3-storage-adapter.ts","../src/storage-service-plugin.ts","../src/local-storage-adapter.ts","../src/metadata-store.ts","../src/storage-routes.ts","../src/objects/system-file.object.ts","../src/objects/system-upload-session.object.ts","../src/swappable-storage-service.ts","../src/index.ts"],"sourcesContent":["// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\nimport {\n NoopMetricsRegistry,\n SEMCONV,\n type MetricsRegistry,\n} from '@objectstack/observability';\nimport type {\n IStorageService,\n StorageUploadOptions,\n StorageFileInfo,\n PresignedUploadDescriptor,\n PresignedDownloadDescriptor,\n} from '@objectstack/spec/contracts';\n\n/**\n * Configuration for the S3 storage adapter.\n */\nexport interface S3StorageAdapterOptions {\n /** S3 bucket name */\n bucket: string;\n /** AWS region (e.g. 'us-east-1') */\n region: string;\n /** Optional endpoint URL for S3-compatible services (MinIO, R2, etc.) */\n endpoint?: string;\n /** AWS access key ID (falls back to env/SDK chain) */\n accessKeyId?: string;\n /** AWS secret access key (falls back to env/SDK chain) */\n secretAccessKey?: string;\n /** Force path-style URLs (needed for MinIO / self-hosted) */\n forcePathStyle?: boolean;\n /** Optional MetricsRegistry for instrumentation. Defaults to NoopMetricsRegistry. */\n metrics?: MetricsRegistry;\n}\n\n/**\n * S3 storage adapter implementing IStorageService.\n *\n * Uses `@aws-sdk/client-s3` and `@aws-sdk/s3-request-presigner` as\n * peer dependencies. These must be installed separately when using the S3\n * adapter in production.\n *\n * @example\n * ```ts\n * const storage = new S3StorageAdapter({\n * bucket: 'my-bucket',\n * region: 'us-east-1',\n * });\n * await storage.upload('path/to/file.txt', buffer);\n * ```\n */\nexport class S3StorageAdapter implements IStorageService {\n private readonly bucket: string;\n private readonly region: string;\n private readonly endpoint?: string;\n private readonly forcePathStyle: boolean;\n private readonly metrics: MetricsRegistry;\n private clientPromise: Promise<any> | null = null;\n\n constructor(private readonly options: S3StorageAdapterOptions) {\n this.bucket = options.bucket;\n this.region = options.region;\n this.endpoint = options.endpoint;\n this.forcePathStyle = options.forcePathStyle ?? false;\n this.metrics = options.metrics ?? new NoopMetricsRegistry();\n }\n\n /**\n * Wrap a storage operation with metrics instrumentation.\n * Records ok/error counters, a duration histogram, and an error counter\n * keyed by error class on failure. Never swallows the underlying error.\n */\n private async track<T>(op: 'put' | 'get' | 'delete' | 'head' | 'list', fn: () => Promise<T>): Promise<T> {\n const started = Date.now();\n const baseLabels = { adapter: 's3', op } as const;\n try {\n const out = await fn();\n try {\n this.metrics.counter(SEMCONV.storageOperationsTotal, { ...baseLabels, result: 'ok' });\n this.metrics.histogram(SEMCONV.storageOperationDurationMs, Date.now() - started, baseLabels);\n } catch { /* never throw from instrumentation */ }\n return out;\n } catch (err: any) {\n try {\n this.metrics.counter(SEMCONV.storageOperationsTotal, { ...baseLabels, result: 'error' });\n this.metrics.histogram(SEMCONV.storageOperationDurationMs, Date.now() - started, baseLabels);\n const errorClass = err?.name || err?.constructor?.name || 'Error';\n this.metrics.counter(SEMCONV.storageErrorsTotal, { ...baseLabels, errorClass });\n } catch { /* never throw from instrumentation */ }\n throw err;\n }\n }\n\n /**\n * Lazily resolve the AWS S3 client to avoid crashing at import time when\n * `@aws-sdk/client-s3` isn't installed.\n */\n private async getClient(): Promise<any> {\n if (!this.clientPromise) {\n this.clientPromise = (async () => {\n let s3Mod: any;\n try {\n s3Mod = await import('@aws-sdk/client-s3');\n } catch {\n throw new Error(\n 'S3StorageAdapter requires @aws-sdk/client-s3. Install it with: pnpm add @aws-sdk/client-s3 @aws-sdk/s3-request-presigner',\n );\n }\n const { S3Client } = s3Mod;\n const clientOpts: any = { region: this.region };\n if (this.endpoint) clientOpts.endpoint = this.endpoint;\n if (this.forcePathStyle) clientOpts.forcePathStyle = true;\n if (this.options.accessKeyId && this.options.secretAccessKey) {\n clientOpts.credentials = {\n accessKeyId: this.options.accessKeyId,\n secretAccessKey: this.options.secretAccessKey,\n };\n }\n return new S3Client(clientOpts);\n })();\n }\n return this.clientPromise;\n }\n\n private async s3Mod(): Promise<any> {\n try {\n return await import('@aws-sdk/client-s3');\n } catch {\n throw new Error('S3StorageAdapter requires @aws-sdk/client-s3');\n }\n }\n\n private async presignerMod(): Promise<any> {\n try {\n return await import('@aws-sdk/s3-request-presigner');\n } catch {\n throw new Error('S3StorageAdapter requires @aws-sdk/s3-request-presigner');\n }\n }\n\n // ---------------------------------------------------------------------------\n // Basic operations\n // ---------------------------------------------------------------------------\n\n async upload(key: string, data: Buffer | ReadableStream, options?: StorageUploadOptions): Promise<void> {\n return this.track('put', async () => {\n const client = await this.getClient();\n const s3 = await this.s3Mod();\n const body = data instanceof Buffer ? data : await streamToBuffer(data);\n const cmd = new s3.PutObjectCommand({\n Bucket: this.bucket,\n Key: key,\n Body: body,\n ContentType: options?.contentType,\n Metadata: options?.metadata,\n ACL: options?.acl === 'public-read' ? 'public-read' : undefined,\n });\n await client.send(cmd);\n });\n }\n\n async download(key: string): Promise<Buffer> {\n return this.track('get', async () => {\n const client = await this.getClient();\n const s3 = await this.s3Mod();\n const cmd = new s3.GetObjectCommand({ Bucket: this.bucket, Key: key });\n const res = await client.send(cmd);\n return streamToBuffer(res.Body);\n });\n }\n\n async delete(key: string): Promise<void> {\n return this.track('delete', async () => {\n const client = await this.getClient();\n const s3 = await this.s3Mod();\n const cmd = new s3.DeleteObjectCommand({ Bucket: this.bucket, Key: key });\n await client.send(cmd);\n });\n }\n\n async exists(key: string): Promise<boolean> {\n return this.track('head', async () => {\n const client = await this.getClient();\n const s3 = await this.s3Mod();\n try {\n const cmd = new s3.HeadObjectCommand({ Bucket: this.bucket, Key: key });\n await client.send(cmd);\n return true;\n } catch (err: any) {\n if (err.name === 'NotFound' || err.$metadata?.httpStatusCode === 404) return false;\n throw err;\n }\n });\n }\n\n async getInfo(key: string): Promise<StorageFileInfo> {\n return this.track('head', async () => {\n const client = await this.getClient();\n const s3 = await this.s3Mod();\n const cmd = new s3.HeadObjectCommand({ Bucket: this.bucket, Key: key });\n const res = await client.send(cmd);\n return {\n key,\n size: res.ContentLength ?? 0,\n contentType: res.ContentType,\n lastModified: res.LastModified ?? new Date(),\n metadata: res.Metadata,\n };\n });\n }\n\n async list(prefix: string): Promise<StorageFileInfo[]> {\n return this.track('list', async () => {\n const client = await this.getClient();\n const s3 = await this.s3Mod();\n const cmd = new s3.ListObjectsV2Command({ Bucket: this.bucket, Prefix: prefix });\n const res = await client.send(cmd);\n return (res.Contents ?? []).map((item: any) => ({\n key: item.Key,\n size: item.Size ?? 0,\n lastModified: item.LastModified ?? new Date(),\n }));\n });\n }\n\n // ---------------------------------------------------------------------------\n // Presigned URLs\n // ---------------------------------------------------------------------------\n\n async getSignedUrl(key: string, expiresIn: number): Promise<string> {\n const desc = await this.getPresignedDownload(key, expiresIn);\n return desc.downloadUrl;\n }\n\n async getPresignedUpload(\n key: string,\n expiresIn: number,\n options?: StorageUploadOptions,\n ): Promise<PresignedUploadDescriptor> {\n const client = await this.getClient();\n const s3 = await this.s3Mod();\n const { getSignedUrl } = await this.presignerMod();\n const cmd = new s3.PutObjectCommand({\n Bucket: this.bucket,\n Key: key,\n ContentType: options?.contentType,\n Metadata: options?.metadata,\n ACL: options?.acl === 'public-read' ? 'public-read' : undefined,\n });\n const url = await getSignedUrl(client, cmd, { expiresIn });\n return {\n uploadUrl: url,\n method: 'PUT',\n headers: options?.contentType ? { 'content-type': options.contentType } : undefined,\n expiresIn,\n };\n }\n\n async getPresignedDownload(key: string, expiresIn: number): Promise<PresignedDownloadDescriptor> {\n const client = await this.getClient();\n const s3 = await this.s3Mod();\n const { getSignedUrl } = await this.presignerMod();\n const cmd = new s3.GetObjectCommand({ Bucket: this.bucket, Key: key });\n const url = await getSignedUrl(client, cmd, { expiresIn });\n return { downloadUrl: url, expiresIn };\n }\n\n // ---------------------------------------------------------------------------\n // Chunked / multipart upload\n // ---------------------------------------------------------------------------\n\n async initiateChunkedUpload(key: string, options?: StorageUploadOptions): Promise<string> {\n const client = await this.getClient();\n const s3 = await this.s3Mod();\n const cmd = new s3.CreateMultipartUploadCommand({\n Bucket: this.bucket,\n Key: key,\n ContentType: options?.contentType,\n Metadata: options?.metadata,\n });\n const res = await client.send(cmd);\n return res.UploadId!;\n }\n\n async uploadChunk(uploadId: string, partNumber: number, data: Buffer): Promise<string> {\n const client = await this.getClient();\n const s3 = await this.s3Mod();\n // We need the key — store the relationship elsewhere or pass via metadata.\n // For the S3 adapter, `uploadId` is the S3-native UploadId. The key is\n // tracked in the StorageMetadataStore (sys_upload_session.key).\n // Here we retrieve it from session state; the plugin ensures the correct\n // key is passed. However, the IStorageService contract doesn't include key\n // in uploadChunk — so we work around by storing the mapping in a WeakMap\n // keyed by uploadId. For a robust implementation we'll add a lookup:\n const key = this._uploadKeys?.get(uploadId);\n if (!key) {\n throw new Error('S3StorageAdapter: key not found for uploadId. Call setUploadKey() before uploadChunk().');\n }\n const cmd = new s3.UploadPartCommand({\n Bucket: this.bucket,\n Key: key,\n UploadId: uploadId,\n PartNumber: partNumber,\n Body: data,\n });\n const res = await client.send(cmd);\n return res.ETag!;\n }\n\n async completeChunkedUpload(\n uploadId: string,\n parts: Array<{ partNumber: number; eTag: string }>,\n ): Promise<string> {\n const client = await this.getClient();\n const s3 = await this.s3Mod();\n const key = this._uploadKeys?.get(uploadId);\n if (!key) {\n throw new Error('S3StorageAdapter: key not found for uploadId.');\n }\n const cmd = new s3.CompleteMultipartUploadCommand({\n Bucket: this.bucket,\n Key: key,\n UploadId: uploadId,\n MultipartUpload: {\n Parts: parts.map(p => ({ PartNumber: p.partNumber, ETag: p.eTag })),\n },\n });\n await client.send(cmd);\n this._uploadKeys?.delete(uploadId);\n return key;\n }\n\n async abortChunkedUpload(uploadId: string): Promise<void> {\n const client = await this.getClient();\n const s3 = await this.s3Mod();\n const key = this._uploadKeys?.get(uploadId);\n if (!key) return;\n const cmd = new s3.AbortMultipartUploadCommand({\n Bucket: this.bucket,\n Key: key,\n UploadId: uploadId,\n });\n await client.send(cmd);\n this._uploadKeys?.delete(uploadId);\n }\n\n // ---------------------------------------------------------------------------\n // Internal upload key tracking\n // ---------------------------------------------------------------------------\n private _uploadKeys: Map<string, string> = new Map();\n\n /**\n * Register the storage key for a multipart upload session. Must be called\n * by the StorageServicePlugin after `initiateChunkedUpload()` returns so\n * that subsequent `uploadChunk` / `completeChunkedUpload` calls can resolve\n * the S3 key without it being part of the IStorageService contract signature.\n */\n setUploadKey(uploadId: string, key: string): void {\n this._uploadKeys.set(uploadId, key);\n }\n}\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\nasync function streamToBuffer(stream: any): Promise<Buffer> {\n if (Buffer.isBuffer(stream)) return stream;\n if (stream instanceof Uint8Array) return Buffer.from(stream);\n const chunks: Uint8Array[] = [];\n if (typeof stream[Symbol.asyncIterator] === 'function') {\n for await (const chunk of stream) {\n chunks.push(typeof chunk === 'string' ? Buffer.from(chunk) : chunk);\n }\n } else if (stream.getReader) {\n const reader = stream.getReader();\n let done = false;\n while (!done) {\n const result = await reader.read();\n done = result.done;\n if (result.value) chunks.push(result.value);\n }\n } else {\n throw new Error('Cannot convert stream to buffer');\n }\n return Buffer.concat(chunks);\n}\n\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\nimport type { Plugin, PluginContext } from '@objectstack/core';\nimport type { IHttpServer, IDataEngine, IStorageService } from '@objectstack/spec/contracts';\nimport {\n OBSERVABILITY_METRICS_SERVICE,\n NoopMetricsRegistry,\n type MetricsRegistry,\n} from '@objectstack/observability';\nimport { LocalStorageAdapter } from './local-storage-adapter.js';\nimport type { LocalStorageAdapterOptions } from './local-storage-adapter.js';\nimport { S3StorageAdapter } from './s3-storage-adapter.js';\nimport type { S3StorageAdapterOptions } from './s3-storage-adapter.js';\nimport { StorageMetadataStore } from './metadata-store.js';\nimport { registerStorageRoutes } from './storage-routes.js';\nimport { SystemFile, SystemUploadSession } from './objects/index.js';\nimport { SwappableStorageService } from './swappable-storage-service.js';\n\n/**\n * Configuration options for the StorageServicePlugin.\n */\nexport interface StorageServicePluginOptions {\n /** Storage adapter type (default: 'local') */\n adapter?: 'local' | 's3';\n /** Options for the local storage adapter */\n local?: LocalStorageAdapterOptions;\n /** S3 configuration (used when adapter is 's3') */\n s3?: { bucket: string; region: string; endpoint?: string };\n /**\n * Whether to register REST routes with the HTTP server.\n * @default true\n */\n registerRoutes?: boolean;\n /**\n * Base path for storage REST routes.\n * @default '/api/v1/storage'\n */\n basePath?: string;\n /**\n * Default presigned URL TTL in seconds.\n * @default 3600\n */\n presignedTtl?: number;\n /**\n * Default chunked upload session TTL in seconds.\n * @default 86400\n */\n sessionTtl?: number;\n /**\n * Bind to the `storage` settings namespace and rebuild the inner\n * adapter on every `settings:changed` event. Disable to keep the\n * adapter constructor-driven (useful in tests). Default: true.\n */\n bindToSettings?: boolean;\n /**\n * Optional explicit metrics backend. Wins over the service-registry\n * lookup. Mostly an escape hatch for tests; production hosts should\n * register `ObservabilityServicePlugin` (from `@objectstack/runtime`)\n * once and let every service pick the host's backend up automatically.\n */\n metrics?: MetricsRegistry;\n}\n\n/**\n * StorageServicePlugin — Production IStorageService implementation.\n *\n * Registers a file storage service with the kernel during the init phase.\n * Supports local filesystem (development/testing/single-server) and\n * S3-compatible storage (production). Automatically mounts\n * `/api/v1/storage/*` REST routes via the `kernel:ready` hook when an\n * HTTP server is available.\n *\n * @example\n * ```ts\n * import { ObjectKernel } from '@objectstack/core';\n * import { StorageServicePlugin } from '@objectstack/service-storage';\n *\n * const kernel = new ObjectKernel();\n * kernel.use(new StorageServicePlugin({\n * adapter: 'local',\n * local: { rootDir: './uploads' },\n * }));\n * await kernel.bootstrap();\n *\n * const storage = kernel.getService('file-storage');\n * await storage.upload('file.txt', Buffer.from('hello'));\n * ```\n */\nexport class StorageServicePlugin implements Plugin {\n name = 'com.objectstack.service.storage';\n version = '1.0.0';\n type = 'standard';\n\n private readonly options: StorageServicePluginOptions;\n private storage: SwappableStorageService | null = null;\n private store: StorageMetadataStore | null = null;\n private metrics: MetricsRegistry = new NoopMetricsRegistry();\n\n constructor(options: StorageServicePluginOptions = {}) {\n this.options = { adapter: 'local', ...options };\n }\n\n /** Build a concrete adapter from a values map (settings-derived). */\n private async buildAdapterFromValues(values: Record<string, any>): Promise<IStorageService> {\n const adapter = String(values.adapter ?? 'local');\n if (adapter === 's3') {\n const bucket = values.s3_bucket as string | undefined;\n const region = values.s3_region as string | undefined;\n if (!bucket || !region) {\n throw new Error('StorageServicePlugin: S3 adapter requires s3_bucket and s3_region');\n }\n const opts: S3StorageAdapterOptions = {\n bucket,\n region,\n endpoint: (values.s3_endpoint as string | undefined) || undefined,\n accessKeyId: (values.s3_access_key_id as string | undefined) || undefined,\n secretAccessKey: (values.s3_secret_access_key as string | undefined) || undefined,\n forcePathStyle: !!values.s3_force_path_style,\n metrics: this.metrics,\n };\n return new S3StorageAdapter(opts);\n }\n const rootDir = (values.local_root as string | undefined) || './storage';\n return new LocalStorageAdapter({\n basePath: this.options.basePath ?? '/api/v1/storage',\n ...(this.options.local ?? {}),\n // settings value wins over any constructor-provided local.rootDir\n rootDir,\n metrics: this.metrics,\n } as LocalStorageAdapterOptions);\n }\n\n async init(ctx: PluginContext): Promise<void> {\n this.metrics = resolveMetrics(ctx, this.options.metrics);\n const adapter = this.options.adapter;\n let initial: IStorageService;\n if (adapter === 's3') {\n // Dynamically import the S3 adapter (to avoid top-level import of optional peer dep)\n const { S3StorageAdapter: S3Ctor } = await import('./s3-storage-adapter.js');\n const s3Opts = this.options.s3;\n if (!s3Opts) {\n throw new Error('StorageServicePlugin: s3 options are required when adapter is \"s3\"');\n }\n initial = new S3Ctor({ ...s3Opts, metrics: this.metrics });\n } else {\n const rootDir = this.options.local?.rootDir ?? './storage';\n const basePath = this.options.basePath ?? '/api/v1/storage';\n initial = new LocalStorageAdapter({ rootDir, basePath, ...this.options.local, metrics: this.metrics });\n }\n\n this.storage = new SwappableStorageService(initial, (prev, next) => {\n const prevName = (prev as any)?.constructor?.name ?? 'unknown';\n const nextName = (next as any)?.constructor?.name ?? 'unknown';\n ctx.logger.warn(\n `StorageServicePlugin: storage adapter swapped (${prevName} → ${nextName}). ` +\n 'Existing files were NOT migrated and may be unreachable through the new adapter.',\n );\n });\n\n ctx.registerService('file-storage', this.storage);\n ctx.logger.info(\n `StorageServicePlugin: registered ${adapter} storage adapter (swappable, metrics=${this.metrics.constructor?.name ?? 'unknown'})`,\n );\n\n // Register system objects via manifest service (if available)\n try {\n ctx.getService<{ register(m: any): void }>('manifest').register({\n id: 'com.objectstack.service.storage',\n name: 'Storage Service',\n version: '1.0.0',\n type: 'plugin',\n scope: 'system',\n objects: [SystemFile, SystemUploadSession],\n });\n } catch {\n // manifest service may not be available in all environments\n }\n }\n\n async start(ctx: PluginContext): Promise<void> {\n ctx.hook('kernel:ready', async () => {\n // ── HTTP routes (existing behaviour) ───────────────────────────\n if (this.options.registerRoutes !== false) {\n let httpServer: IHttpServer | null = null;\n try {\n httpServer = ctx.getService<IHttpServer>('http-server');\n } catch {\n // not available\n }\n\n if (httpServer && this.storage) {\n let engine: IDataEngine | null = null;\n try {\n engine = ctx.getService<IDataEngine>('objectql');\n } catch {\n // data engine not wired — use in-memory fallback\n }\n this.store = new StorageMetadataStore(engine);\n\n registerStorageRoutes(httpServer, this.storage, this.store, {\n basePath: this.options.basePath ?? '/api/v1/storage',\n presignedTtl: this.options.presignedTtl,\n sessionTtl: this.options.sessionTtl,\n });\n\n ctx.logger.info(\n 'StorageServicePlugin: REST routes registered at ' +\n (this.options.basePath ?? '/api/v1/storage'),\n );\n } else if (!httpServer) {\n ctx.logger.warn(\n 'StorageServicePlugin: no HTTP server available — REST routes not registered. ' +\n 'File storage is still accessible programmatically via kernel.getService(\"file-storage\").',\n );\n }\n }\n\n // ── Bind to the `storage` settings namespace ──────────────────\n // Allows the admin UI to swap adapters / credentials without\n // restart. Env-locked fields still win at the resolver layer.\n if (this.options.bindToSettings === false) return;\n try {\n const settings = ctx.getService<any>('settings');\n if (!settings || typeof settings.createClient !== 'function') return;\n\n const applySettings = async () => {\n if (!this.storage) return;\n try {\n const payload = await settings.getNamespace('storage');\n const values: Record<string, any> = {};\n for (const [k, v] of Object.entries(payload.values as Record<string, any>)) {\n values[k] = v?.value;\n }\n // No persisted values yet → keep the constructor-built adapter.\n const hasAny = Object.values(values).some((v) => v !== undefined && v !== null && v !== '');\n if (!hasAny) return;\n const next = await this.buildAdapterFromValues(values);\n this.storage.swap(next);\n } catch (err: any) {\n ctx.logger.warn(\n 'StorageServicePlugin: failed to apply storage settings: ' + (err?.message ?? err),\n );\n }\n };\n await applySettings();\n if (typeof settings.subscribe === 'function') {\n settings.subscribe('storage', () => {\n void applySettings();\n });\n ctx.logger.info('StorageServicePlugin: bound to settings:changed for namespace=storage');\n }\n\n // Register the live `storage/test` probe handler.\n if (typeof settings.registerAction === 'function' && this.storage) {\n const proxy = this.storage;\n settings.registerAction('storage', 'test', async ({ values, payload }: any) => {\n // Merge the (possibly unsaved) form state posted as\n // `payload.values` over the persisted snapshot so an operator\n // can validate edits before hitting \"Save\". Matches the\n // pattern used by ai/test and mail/test.\n const overrides = extractOverrides(payload);\n const merged: Record<string, unknown> = { ...(values ?? {}), ...overrides };\n const probeKey = `__objectstack_probe__/${Date.now()}-${Math.random().toString(36).slice(2, 10)}`;\n const probeBytes = Buffer.from(`probe@${new Date().toISOString()}`, 'utf-8');\n try {\n // If merged values are present, build a temporary adapter\n // so we can validate user-typed credentials without\n // committing them.\n let target: IStorageService = proxy;\n if (merged && Object.keys(merged).length > 0) {\n try {\n target = await this.buildAdapterFromValues(merged);\n } catch (err: any) {\n return { ok: false, severity: 'error', message: err?.message ?? String(err) };\n }\n }\n await target.upload(probeKey, probeBytes, { contentType: 'text/plain' });\n const got = await target.download(probeKey);\n if (!got || !Buffer.isBuffer(got) || got.toString('utf-8') !== probeBytes.toString('utf-8')) {\n return { ok: false, severity: 'error', message: 'Probe download did not match upload.' };\n }\n await target.delete(probeKey);\n const adapter = String(merged.adapter ?? this.options.adapter ?? 'local');\n return {\n ok: true,\n severity: 'info',\n message: `Storage round-trip succeeded (adapter=${adapter}).`,\n };\n } catch (err: any) {\n // Best-effort cleanup\n try { await (proxy as IStorageService).delete(probeKey); } catch { /* ignore */ }\n return { ok: false, severity: 'error', message: err?.message ?? String(err) };\n }\n });\n ctx.logger.info('StorageServicePlugin: registered settings action storage/test');\n }\n } catch {\n // settings service not present — manifest fallback handler stays\n }\n });\n }\n}\n\n/**\n * Look up the host's MetricsRegistry from the service registry, with\n * the canonical fallback chain (explicit override → registered service\n * → noop). Local helper to avoid making `service-storage` depend on\n * `@objectstack/runtime`.\n */\nfunction resolveMetrics(\n ctx: PluginContext,\n override: MetricsRegistry | undefined,\n): MetricsRegistry {\n if (override) return override;\n try {\n const m = ctx.getService<MetricsRegistry | undefined>(OBSERVABILITY_METRICS_SERVICE);\n if (m) return m;\n } catch {\n // Service not registered — silent fall-through.\n }\n return new NoopMetricsRegistry();\n}\n\nfunction extractOverrides(payload: unknown): Record<string, unknown> {\n if (!payload || typeof payload !== 'object') return {};\n const p = payload as Record<string, unknown>;\n if (p.values && typeof p.values === 'object' && p.values !== null) {\n return p.values as Record<string, unknown>;\n }\n return p;\n}\n\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\nimport { promises as fs, createReadStream, createWriteStream } from 'node:fs';\nimport { join, dirname } from 'node:path';\nimport { createHmac, randomUUID } from 'node:crypto';\nimport {\n NoopMetricsRegistry,\n SEMCONV,\n type MetricsRegistry,\n} from '@objectstack/observability';\nimport type {\n IStorageService,\n StorageUploadOptions,\n StorageFileInfo,\n PresignedUploadDescriptor,\n PresignedDownloadDescriptor,\n} from '@objectstack/spec/contracts';\n\n/**\n * Configuration options for LocalStorageAdapter.\n */\nexport interface LocalStorageAdapterOptions {\n /** Root directory for committed files */\n rootDir: string;\n /**\n * Public base URL the adapter prepends to presigned upload / download URLs.\n * Defaults to a relative path so the same-origin REST server handles the\n * request. Override (e.g. `https://api.example.com`) when the storage\n * routes are exposed on a different host.\n * @default ''\n */\n baseUrl?: string;\n /**\n * Base path of the local storage REST routes mounted by\n * `StorageServicePlugin`. Used to construct presigned URLs.\n * @default '/api/v1/storage'\n */\n basePath?: string;\n /**\n * HMAC secret used to sign presigned-upload tokens.\n * Auto-generated if omitted (suitable for single-process dev usage).\n */\n signingSecret?: string;\n /** Optional MetricsRegistry for instrumentation. Defaults to NoopMetricsRegistry. */\n metrics?: MetricsRegistry;\n}\n\ninterface PresignTokenPayload {\n k: string; // storage key\n ct?: string; // content-type\n exp: number; // expiry epoch seconds\n op: 'put' | 'get';\n}\n\n/**\n * Local filesystem storage adapter implementing IStorageService.\n *\n * Stores committed files under `rootDir/`, in-flight multipart parts under\n * `rootDir/.parts/<uploadId>/<chunkIndex>`. Presigned URLs are HMAC-signed\n * tokens redeemed against the local REST routes mounted by\n * `StorageServicePlugin` — letting the browser PUT bytes directly without\n * proxying through the application logic.\n *\n * Suitable for development, testing, and single-server deployments.\n */\nexport class LocalStorageAdapter implements IStorageService {\n private readonly rootDir: string;\n private readonly partsDir: string;\n private readonly baseUrl: string;\n private readonly basePath: string;\n private readonly signingSecret: string;\n private readonly metrics: MetricsRegistry;\n\n constructor(options: LocalStorageAdapterOptions) {\n this.rootDir = options.rootDir;\n this.partsDir = join(this.rootDir, '.parts');\n this.baseUrl = options.baseUrl ?? '';\n this.basePath = options.basePath ?? '/api/v1/storage';\n this.signingSecret = options.signingSecret ?? randomUUID();\n this.metrics = options.metrics ?? new NoopMetricsRegistry();\n }\n\n /**\n * Wrap a storage operation with metrics instrumentation. Never swallows\n * the underlying error; instrumentation failures are silently ignored.\n */\n private async track<T>(op: 'put' | 'get' | 'delete' | 'head' | 'list', fn: () => Promise<T>): Promise<T> {\n const started = Date.now();\n const baseLabels = { adapter: 'local', op } as const;\n try {\n const out = await fn();\n try {\n this.metrics.counter(SEMCONV.storageOperationsTotal, { ...baseLabels, result: 'ok' });\n this.metrics.histogram(SEMCONV.storageOperationDurationMs, Date.now() - started, baseLabels);\n } catch { /* never throw */ }\n return out;\n } catch (err: any) {\n try {\n this.metrics.counter(SEMCONV.storageOperationsTotal, { ...baseLabels, result: 'error' });\n this.metrics.histogram(SEMCONV.storageOperationDurationMs, Date.now() - started, baseLabels);\n const errorClass = err?.name || err?.constructor?.name || 'Error';\n this.metrics.counter(SEMCONV.storageErrorsTotal, { ...baseLabels, errorClass });\n } catch { /* never throw */ }\n throw err;\n }\n }\n\n // ---------------------------------------------------------------------------\n // Path helpers\n // ---------------------------------------------------------------------------\n\n private resolvePath(key: string): string {\n if (key.includes('..')) {\n throw new Error(`LocalStorageAdapter: path traversal not allowed (key=\"${key}\")`);\n }\n return join(this.rootDir, key);\n }\n\n private resolvePartPath(uploadId: string, partNumber: number): string {\n if (!/^[A-Za-z0-9_-]+$/.test(uploadId)) {\n throw new Error(`LocalStorageAdapter: invalid uploadId \"${uploadId}\"`);\n }\n return join(this.partsDir, uploadId, String(partNumber).padStart(8, '0'));\n }\n\n // ---------------------------------------------------------------------------\n // Basic file operations\n // ---------------------------------------------------------------------------\n\n async upload(\n key: string,\n data: Buffer | ReadableStream,\n _options?: StorageUploadOptions,\n ): Promise<void> {\n return this.track('put', async () => {\n const filePath = this.resolvePath(key);\n await fs.mkdir(dirname(filePath), { recursive: true });\n\n if (data instanceof Buffer) {\n await fs.writeFile(filePath, data);\n return;\n }\n\n // Convert ReadableStream to Buffer\n const chunks: Uint8Array[] = [];\n const reader = (data as ReadableStream).getReader();\n let done = false;\n while (!done) {\n const result = await reader.read();\n done = result.done;\n if (result.value) chunks.push(result.value);\n }\n await fs.writeFile(filePath, Buffer.concat(chunks));\n });\n }\n\n async download(key: string): Promise<Buffer> {\n return this.track('get', async () => fs.readFile(this.resolvePath(key)));\n }\n\n async delete(key: string): Promise<void> {\n return this.track('delete', async () => {\n await fs.unlink(this.resolvePath(key)).catch((err) => {\n if (err && err.code === 'ENOENT') return;\n throw err;\n });\n });\n }\n\n async exists(key: string): Promise<boolean> {\n return this.track('head', async () => {\n try {\n await fs.access(this.resolvePath(key));\n return true;\n } catch {\n return false;\n }\n });\n }\n\n async getInfo(key: string): Promise<StorageFileInfo> {\n return this.track('head', async () => {\n const filePath = this.resolvePath(key);\n const stat = await fs.stat(filePath);\n return { key, size: stat.size, lastModified: stat.mtime };\n });\n }\n\n async list(prefix: string): Promise<StorageFileInfo[]> {\n return this.track('list', async () => {\n const dirPath = this.resolvePath(prefix);\n try {\n const entries = await fs.readdir(dirPath);\n const results: StorageFileInfo[] = [];\n for (const entry of entries) {\n if (entry.startsWith('.')) continue;\n const fullKey = prefix ? `${prefix}/${entry}` : entry;\n try {\n // Inline stat to avoid double-counting `head` operations.\n const stat = await fs.stat(this.resolvePath(fullKey));\n results.push({ key: fullKey, size: stat.size, lastModified: stat.mtime });\n } catch {\n /* skip */\n }\n }\n return results;\n } catch {\n return [];\n }\n });\n }\n\n // ---------------------------------------------------------------------------\n // Presigned URL helpers\n // ---------------------------------------------------------------------------\n\n /**\n * Sign an opaque token for the given payload.\n * Format: base64url(JSON.stringify(payload)) + '.' + base64url(HMAC)\n */\n private signToken(payload: PresignTokenPayload): string {\n const b64 = Buffer.from(JSON.stringify(payload), 'utf8').toString('base64url');\n const sig = createHmac('sha256', this.signingSecret).update(b64).digest('base64url');\n return `${b64}.${sig}`;\n }\n\n /**\n * Verify and decode a presigned token. Throws on invalid signature or\n * expiration.\n */\n verifyToken(token: string, expectedOp: 'put' | 'get'): PresignTokenPayload {\n const [b64, sig] = token.split('.');\n if (!b64 || !sig) throw new Error('Invalid storage token format');\n\n const expected = createHmac('sha256', this.signingSecret).update(b64).digest('base64url');\n if (expected !== sig) throw new Error('Invalid storage token signature');\n\n let payload: PresignTokenPayload;\n try {\n payload = JSON.parse(Buffer.from(b64, 'base64url').toString('utf8'));\n } catch {\n throw new Error('Malformed storage token payload');\n }\n\n if (payload.op !== expectedOp) {\n throw new Error(`Storage token op mismatch (expected=\"${expectedOp}\", actual=\"${payload.op}\")`);\n }\n if (Date.now() / 1000 > payload.exp) {\n throw new Error('Storage token expired');\n }\n return payload;\n }\n\n async getPresignedUpload(\n key: string,\n expiresIn: number,\n options?: StorageUploadOptions,\n ): Promise<PresignedUploadDescriptor> {\n const exp = Math.floor(Date.now() / 1000) + Math.max(1, expiresIn);\n const token = this.signToken({ k: key, ct: options?.contentType, exp, op: 'put' });\n\n return {\n uploadUrl: `${this.baseUrl}${this.basePath}/_local/raw/${token}`,\n method: 'PUT',\n headers: options?.contentType ? { 'content-type': options.contentType } : { 'content-type': 'application/octet-stream' },\n expiresIn,\n downloadUrl: `${this.baseUrl}${this.basePath}/_local/file/${encodeURIComponent(key)}`,\n };\n }\n\n async getPresignedDownload(key: string, expiresIn: number): Promise<PresignedDownloadDescriptor> {\n const exp = Math.floor(Date.now() / 1000) + Math.max(1, expiresIn);\n const token = this.signToken({ k: key, exp, op: 'get' });\n return {\n downloadUrl: `${this.baseUrl}${this.basePath}/_local/raw/${token}`,\n expiresIn,\n };\n }\n\n async getSignedUrl(key: string, expiresIn: number): Promise<string> {\n const desc = await this.getPresignedDownload(key, expiresIn);\n return desc.downloadUrl;\n }\n\n // ---------------------------------------------------------------------------\n // Chunked / multipart upload\n // ---------------------------------------------------------------------------\n\n async initiateChunkedUpload(key: string, options?: StorageUploadOptions): Promise<string> {\n const uploadId = randomUUID().replace(/-/g, '');\n const dir = join(this.partsDir, uploadId);\n await fs.mkdir(dir, { recursive: true });\n const meta = {\n key,\n contentType: options?.contentType,\n metadata: options?.metadata,\n createdAt: new Date().toISOString(),\n };\n await fs.writeFile(join(dir, '_meta.json'), JSON.stringify(meta), 'utf8');\n return uploadId;\n }\n\n async uploadChunk(uploadId: string, partNumber: number, data: Buffer): Promise<string> {\n if (!Number.isInteger(partNumber) || partNumber < 1) {\n throw new Error(`uploadChunk: partNumber must be a positive integer (got ${partNumber})`);\n }\n const partPath = this.resolvePartPath(uploadId, partNumber);\n await fs.mkdir(dirname(partPath), { recursive: true });\n await fs.writeFile(partPath, data);\n // ETag for local mode = hex md5 of part bytes (matches S3 single-part ETag format)\n const { createHash } = await import('node:crypto');\n return createHash('md5').update(data).digest('hex');\n }\n\n async completeChunkedUpload(\n uploadId: string,\n parts: Array<{ partNumber: number; eTag: string }>,\n ): Promise<string> {\n const dir = join(this.partsDir, uploadId);\n let meta: { key?: string } = {};\n try {\n meta = JSON.parse(await fs.readFile(join(dir, '_meta.json'), 'utf8'));\n } catch {\n throw new Error(`Upload session \"${uploadId}\" not found`);\n }\n const targetKey = meta.key;\n if (!targetKey) {\n throw new Error(`Upload session \"${uploadId}\" missing target key`);\n }\n\n const sortedParts = [...parts].sort((a, b) => a.partNumber - b.partNumber);\n const finalPath = this.resolvePath(targetKey);\n await fs.mkdir(dirname(finalPath), { recursive: true });\n\n // Stream-concat parts into the final file\n const out = createWriteStream(finalPath);\n try {\n for (const p of sortedParts) {\n const partPath = this.resolvePartPath(uploadId, p.partNumber);\n await new Promise<void>((resolve, reject) => {\n const inp = createReadStream(partPath);\n inp.on('error', reject);\n inp.on('end', () => resolve());\n inp.pipe(out, { end: false });\n });\n }\n } finally {\n await new Promise<void>((resolve) => out.end(() => resolve()));\n }\n\n // Cleanup part directory\n await fs.rm(dir, { recursive: true, force: true });\n return targetKey;\n }\n\n async abortChunkedUpload(uploadId: string): Promise<void> {\n await fs.rm(join(this.partsDir, uploadId), { recursive: true, force: true });\n }\n}\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\nimport type { IDataEngine } from '@objectstack/spec/contracts';\n\n/**\n * Persisted file metadata record (matches `sys_file` object schema).\n */\nexport interface FileRecord {\n id: string;\n key: string;\n name: string;\n mime_type?: string;\n size?: number;\n scope?: string;\n bucket?: string;\n acl?: string;\n status: 'pending' | 'committed' | 'deleted';\n etag?: string;\n owner_id?: string;\n metadata?: string;\n created_at?: string;\n updated_at?: string;\n}\n\n/**\n * Persisted upload-session record (matches `sys_upload_session` object schema).\n */\nexport interface UploadSessionRecord {\n id: string;\n file_id: string;\n key: string;\n filename: string;\n mime_type?: string;\n total_size: number;\n chunk_size: number;\n total_chunks: number;\n uploaded_chunks?: number;\n uploaded_size?: number;\n parts?: string;\n resume_token?: string;\n backend_upload_id?: string;\n scope?: string;\n bucket?: string;\n metadata?: string;\n status: 'in_progress' | 'completing' | 'completed' | 'failed' | 'expired';\n started_at?: string;\n expires_at?: string;\n updated_at?: string;\n}\n\n/**\n * Storage metadata persistence.\n *\n * Backed by `IDataEngine` (objectql) when available — otherwise falls back to\n * a process-local Map (suitable for tests and dev environments where the\n * data engine isn't wired up).\n */\nexport class StorageMetadataStore {\n private readonly files = new Map<string, FileRecord>();\n private readonly sessions = new Map<string, UploadSessionRecord>();\n\n constructor(private readonly engine: IDataEngine | null) {}\n\n // ---------------------------------------------------------------------------\n // Files\n // ---------------------------------------------------------------------------\n\n async createFile(rec: FileRecord): Promise<FileRecord> {\n const now = new Date().toISOString();\n const full: FileRecord = { created_at: now, updated_at: now, ...rec };\n this.files.set(full.id, full);\n if (this.engine) {\n try {\n await this.engine.insert('sys_file', full);\n } catch {\n /* engine not available or schema not migrated — keep in-memory only */\n }\n }\n return full;\n }\n\n async getFile(id: string): Promise<FileRecord | null> {\n if (this.engine) {\n try {\n const found = await this.engine.findOne('sys_file', { where: { id } });\n if (found) return found as FileRecord;\n } catch {\n /* fall through to memory */\n }\n }\n return this.files.get(id) ?? null;\n }\n\n async updateFile(id: string, patch: Partial<FileRecord>): Promise<FileRecord | null> {\n const existing = await this.getFile(id);\n if (!existing) return null;\n const merged: FileRecord = { ...existing, ...patch, id, updated_at: new Date().toISOString() };\n this.files.set(id, merged);\n if (this.engine) {\n try {\n await this.engine.update('sys_file', merged as any, { where: { id } } as any);\n } catch {\n /* ignore */\n }\n }\n return merged;\n }\n\n async deleteFile(id: string): Promise<void> {\n this.files.delete(id);\n if (this.engine) {\n try {\n await this.engine.delete('sys_file', { where: { id } } as any);\n } catch {\n /* ignore */\n }\n }\n }\n\n // ---------------------------------------------------------------------------\n // Upload sessions\n // ---------------------------------------------------------------------------\n\n async createSession(rec: UploadSessionRecord): Promise<UploadSessionRecord> {\n const now = new Date().toISOString();\n const full: UploadSessionRecord = {\n uploaded_chunks: 0,\n uploaded_size: 0,\n parts: '[]',\n started_at: now,\n updated_at: now,\n ...rec,\n };\n this.sessions.set(full.id, full);\n if (this.engine) {\n try {\n await this.engine.insert('sys_upload_session', full);\n } catch {\n /* ignore */\n }\n }\n return full;\n }\n\n async getSession(id: string): Promise<UploadSessionRecord | null> {\n if (this.engine) {\n try {\n const found = await this.engine.findOne('sys_upload_session', { where: { id } });\n if (found) return found as UploadSessionRecord;\n } catch {\n /* ignore */\n }\n }\n return this.sessions.get(id) ?? null;\n }\n\n async updateSession(id: string, patch: Partial<UploadSessionRecord>): Promise<UploadSessionRecord | null> {\n const existing = await this.getSession(id);\n if (!existing) return null;\n const merged: UploadSessionRecord = {\n ...existing,\n ...patch,\n id,\n updated_at: new Date().toISOString(),\n };\n this.sessions.set(id, merged);\n if (this.engine) {\n try {\n await this.engine.update('sys_upload_session', merged as any, { where: { id } } as any);\n } catch {\n /* ignore */\n }\n }\n return merged;\n }\n\n async deleteSession(id: string): Promise<void> {\n this.sessions.delete(id);\n if (this.engine) {\n try {\n await this.engine.delete('sys_upload_session', { where: { id } } as any);\n } catch {\n /* ignore */\n }\n }\n }\n}\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\nimport { randomUUID } from 'node:crypto';\nimport type { IHttpServer, IHttpRequest, IHttpResponse, IStorageService } from '@objectstack/spec/contracts';\nimport type { StorageMetadataStore } from './metadata-store.js';\nimport type { LocalStorageAdapter } from './local-storage-adapter.js';\n\n/**\n * Options for the storage route registration helper.\n */\nexport interface StorageRoutesOptions {\n basePath?: string;\n /** Default presigned URL TTL in seconds */\n presignedTtl?: number;\n /** Default chunked upload session TTL in seconds */\n sessionTtl?: number;\n}\n\n/**\n * Register `/api/v1/storage/*` REST routes with the HTTP server.\n *\n * Implements the contract defined in `packages/spec/src/api/storage.zod.ts`\n * (`StorageApiContracts`). This function follows the \"autonomous plugin route\n * registration\" pattern used by `I18nServicePlugin`, `AuthPlugin`, etc.\n *\n * Routes:\n * - POST /storage/upload/presigned → get presigned upload URL\n * - POST /storage/upload/complete → mark upload as committed\n * - POST /storage/upload/chunked → initiate chunked upload\n * - PUT /storage/upload/chunked/:uploadId/chunk/:chunkIndex → upload a chunk\n * - POST /storage/upload/chunked/:uploadId/complete → complete chunked\n * - GET /storage/upload/chunked/:uploadId/progress → get upload progress\n * - GET /storage/files/:fileId/url → get download URL\n * - PUT /storage/_local/raw/:token → local adapter raw upload\n * - GET /storage/_local/raw/:token → local adapter raw download\n */\nexport function registerStorageRoutes(\n httpServer: IHttpServer,\n storage: IStorageService,\n store: StorageMetadataStore,\n opts: StorageRoutesOptions = {},\n): void {\n const basePath = opts.basePath ?? '/api/v1/storage';\n const presignedTtl = opts.presignedTtl ?? 3600;\n const sessionTtl = opts.sessionTtl ?? 86400;\n\n // ---------------------------------------------------------------------------\n // POST /storage/upload/presigned\n // ---------------------------------------------------------------------------\n httpServer.post(`${basePath}/upload/presigned`, async (req: IHttpRequest, res: IHttpResponse) => {\n try {\n const { filename, mimeType, size, scope, bucket } = req.body ?? {};\n if (!filename || !mimeType || size == null) {\n res.status(400).json({ error: 'filename, mimeType, and size are required' });\n return;\n }\n\n const fileId = randomUUID();\n const key = buildKey(scope ?? 'user', fileId, filename);\n\n // Persist pending file record\n await store.createFile({\n id: fileId,\n key,\n name: filename,\n mime_type: mimeType,\n size,\n scope: scope ?? 'user',\n bucket,\n acl: 'private',\n status: 'pending',\n });\n\n // If adapter supports presigned upload, use it; otherwise build a local stub URL\n let uploadUrl: string;\n let method: 'PUT' | 'POST' = 'PUT';\n let headers: Record<string, string> = { 'content-type': mimeType };\n let expiresIn = presignedTtl;\n\n if (storage.getPresignedUpload) {\n const desc = await storage.getPresignedUpload(key, presignedTtl, { contentType: mimeType });\n uploadUrl = desc.uploadUrl;\n method = desc.method;\n if (desc.headers) headers = desc.headers;\n expiresIn = desc.expiresIn;\n } else {\n // Fallback — caller should PUT to the standard raw endpoint\n uploadUrl = `${basePath}/_local/raw/${fileId}`;\n }\n\n res.json({\n data: {\n uploadUrl,\n method,\n headers,\n fileId,\n expiresIn,\n downloadUrl: `${basePath}/files/${fileId}/url`,\n },\n });\n } catch (err: any) {\n res.status(500).json({ error: err.message ?? 'Internal error' });\n }\n });\n\n // ---------------------------------------------------------------------------\n // POST /storage/upload/complete\n // ---------------------------------------------------------------------------\n httpServer.post(`${basePath}/upload/complete`, async (req: IHttpRequest, res: IHttpResponse) => {\n try {\n const { fileId, eTag } = req.body ?? {};\n if (!fileId) {\n res.status(400).json({ error: 'fileId is required' });\n return;\n }\n\n const file = await store.getFile(fileId);\n if (!file) {\n res.status(404).json({ error: 'File not found' });\n return;\n }\n\n const updated = await store.updateFile(fileId, {\n status: 'committed',\n etag: eTag ?? undefined,\n });\n\n res.json({\n data: {\n path: updated!.key,\n name: updated!.name,\n size: updated!.size ?? 0,\n mimeType: updated!.mime_type ?? 'application/octet-stream',\n lastModified: updated!.updated_at ?? new Date().toISOString(),\n created: updated!.created_at ?? new Date().toISOString(),\n etag: updated!.etag,\n },\n });\n } catch (err: any) {\n res.status(500).json({ error: err.message ?? 'Internal error' });\n }\n });\n\n // ---------------------------------------------------------------------------\n // POST /storage/upload/chunked\n // ---------------------------------------------------------------------------\n httpServer.post(`${basePath}/upload/chunked`, async (req: IHttpRequest, res: IHttpResponse) => {\n try {\n const { filename, mimeType, totalSize, chunkSize: reqChunkSize, scope, bucket, metadata } = req.body ?? {};\n if (!filename || !mimeType || !totalSize) {\n res.status(400).json({ error: 'filename, mimeType, and totalSize are required' });\n return;\n }\n\n const chunkSize = Math.max(reqChunkSize ?? 5242880, 5242880);\n const totalChunks = Math.ceil(totalSize / chunkSize);\n\n const fileId = randomUUID();\n const key = buildKey(scope ?? 'user', fileId, filename);\n\n // Create pending file\n await store.createFile({\n id: fileId,\n key,\n name: filename,\n mime_type: mimeType,\n size: totalSize,\n scope: scope ?? 'user',\n bucket,\n acl: 'private',\n status: 'pending',\n metadata: metadata ? JSON.stringify(metadata) : undefined,\n });\n\n // Initiate chunked upload in backend\n let backendUploadId: string | undefined;\n if (storage.initiateChunkedUpload) {\n backendUploadId = await storage.initiateChunkedUpload(key, { contentType: mimeType, metadata });\n // S3 adapter needs to know the key for subsequent chunk/complete calls\n if ('setUploadKey' in storage && typeof (storage as any).setUploadKey === 'function') {\n (storage as any).setUploadKey(backendUploadId, key);\n }\n }\n\n const uploadId = backendUploadId ?? randomUUID().replace(/-/g, '');\n const resumeToken = randomUUID();\n const expiresAt = new Date(Date.now() + sessionTtl * 1000).toISOString();\n\n await store.createSession({\n id: uploadId,\n file_id: fileId,\n key,\n filename,\n mime_type: mimeType,\n total_size: totalSize,\n chunk_size: chunkSize,\n total_chunks: totalChunks,\n resume_token: resumeToken,\n backend_upload_id: backendUploadId,\n scope: scope ?? 'user',\n bucket,\n metadata: metadata ? JSON.stringify(metadata) : undefined,\n status: 'in_progress',\n expires_at: expiresAt,\n });\n\n res.json({\n data: {\n uploadId,\n resumeToken,\n fileId,\n totalChunks,\n chunkSize,\n expiresAt,\n },\n });\n } catch (err: any) {\n res.status(500).json({ error: err.message ?? 'Internal error' });\n }\n });\n\n // ---------------------------------------------------------------------------\n // PUT /storage/upload/chunked/:uploadId/chunk/:chunkIndex\n // ---------------------------------------------------------------------------\n httpServer.put(`${basePath}/upload/chunked/:uploadId/chunk/:chunkIndex`, async (req: IHttpRequest, res: IHttpResponse) => {\n try {\n const { uploadId, chunkIndex: chunkIndexStr } = req.params;\n const chunkIndex = parseInt(chunkIndexStr, 10);\n if (!uploadId || isNaN(chunkIndex)) {\n res.status(400).json({ error: 'uploadId and chunkIndex are required' });\n return;\n }\n\n const session = await store.getSession(uploadId);\n if (!session) {\n res.status(404).json({ error: 'Upload session not found' });\n return;\n }\n\n // Verify resume token\n const token = (req.headers['x-resume-token'] ?? '') as string;\n if (session.resume_token && token !== session.resume_token) {\n res.status(403).json({ error: 'Invalid resume token' });\n return;\n }\n\n // Get raw body (binary data)\n let data: Buffer;\n if (req.rawBody) {\n data = await req.rawBody();\n } else if (Buffer.isBuffer(req.body)) {\n data = req.body;\n } else if (req.body instanceof ArrayBuffer) {\n data = Buffer.from(req.body);\n } else {\n res.status(400).json({ error: 'Binary body required' });\n return;\n }\n\n // Upload the chunk (S3 uses 1-based part numbers)\n let eTag = '';\n if (storage.uploadChunk) {\n eTag = await storage.uploadChunk(uploadId, chunkIndex + 1, data);\n }\n\n // Update session progress\n const currentParts: Array<{ chunkIndex: number; eTag: string }> = JSON.parse(session.parts ?? '[]');\n currentParts.push({ chunkIndex, eTag });\n const uploadedChunks = (session.uploaded_chunks ?? 0) + 1;\n const uploadedSize = (session.uploaded_size ?? 0) + data.byteLength;\n await store.updateSession(uploadId, {\n uploaded_chunks: uploadedChunks,\n uploaded_size: uploadedSize,\n parts: JSON.stringify(currentParts),\n });\n\n res.json({\n data: {\n chunkIndex,\n eTag,\n bytesReceived: data.byteLength,\n },\n });\n } catch (err: any) {\n res.status(500).json({ error: err.message ?? 'Internal error' });\n }\n });\n\n // ---------------------------------------------------------------------------\n // POST /storage/upload/chunked/:uploadId/complete\n // ---------------------------------------------------------------------------\n httpServer.post(`${basePath}/upload/chunked/:uploadId/complete`, async (req: IHttpRequest, res: IHttpResponse) => {\n try {\n const { uploadId } = req.params;\n const session = await store.getSession(uploadId);\n if (!session) {\n res.status(404).json({ error: 'Upload session not found' });\n return;\n }\n\n await store.updateSession(uploadId, { status: 'completing' });\n\n const partsFromBody = (req.body?.parts ?? []) as Array<{ chunkIndex: number; eTag: string }>;\n const partsForBackend = partsFromBody.map(p => ({\n partNumber: p.chunkIndex + 1,\n eTag: p.eTag,\n }));\n\n let finalKey = session.key;\n if (storage.completeChunkedUpload) {\n finalKey = await storage.completeChunkedUpload(uploadId, partsForBackend);\n }\n\n // Update file + session\n await store.updateFile(session.file_id, { status: 'committed', key: finalKey });\n await store.updateSession(uploadId, { status: 'completed' });\n\n res.json({\n data: {\n fileId: session.file_id,\n key: finalKey,\n size: session.total_size,\n mimeType: session.mime_type ?? 'application/octet-stream',\n url: `${basePath}/files/${session.file_id}/url`,\n },\n });\n } catch (err: any) {\n res.status(500).json({ error: err.message ?? 'Internal error' });\n }\n });\n\n // ---------------------------------------------------------------------------\n // GET /storage/upload/chunked/:uploadId/progress\n // ---------------------------------------------------------------------------\n httpServer.get(`${basePath}/upload/chunked/:uploadId/progress`, async (req: IHttpRequest, res: IHttpResponse) => {\n try {\n const { uploadId } = req.params;\n const session = await store.getSession(uploadId);\n if (!session) {\n res.status(404).json({ error: 'Upload session not found' });\n return;\n }\n\n const uploadedChunks = session.uploaded_chunks ?? 0;\n const uploadedSize = session.uploaded_size ?? 0;\n const percentComplete = session.total_size > 0\n ? Math.min(100, Math.round((uploadedSize / session.total_size) * 100))\n : 0;\n\n res.json({\n data: {\n uploadId: session.id,\n fileId: session.file_id,\n filename: session.filename,\n totalSize: session.total_size,\n uploadedSize,\n totalChunks: session.total_chunks,\n uploadedChunks,\n percentComplete,\n status: session.status,\n startedAt: session.started_at,\n expiresAt: session.expires_at,\n },\n });\n } catch (err: any) {\n res.status(500).json({ error: err.message ?? 'Internal error' });\n }\n });\n\n // ---------------------------------------------------------------------------\n // GET /storage/files/:fileId/url\n // ---------------------------------------------------------------------------\n httpServer.get(`${basePath}/files/:fileId/url`, async (req: IHttpRequest, res: IHttpResponse) => {\n try {\n const { fileId } = req.params;\n const file = await store.getFile(fileId);\n if (!file || file.status !== 'committed') {\n res.status(404).json({ error: 'File not found or not committed' });\n return;\n }\n\n let url: string;\n if (storage.getPresignedDownload) {\n const desc = await storage.getPresignedDownload(file.key, presignedTtl);\n url = desc.downloadUrl;\n } else if (storage.getSignedUrl) {\n url = await storage.getSignedUrl(file.key, presignedTtl);\n } else {\n url = `${basePath}/_local/file/${encodeURIComponent(file.key)}`;\n }\n\n res.json({ url });\n } catch (err: any) {\n res.status(500).json({ error: err.message ?? 'Internal error' });\n }\n });\n\n // ---------------------------------------------------------------------------\n // GET /storage/files/:fileId — stable redirect to the actual bytes.\n //\n // Frontend widgets (`ImageField`, `<img src>`, user avatars, org logos)\n // need a URL that:\n // - is stable (won't expire — records may live for years)\n // - serves the bytes directly when followed\n // The `/url` endpoint above returns JSON. This sibling endpoint resolves\n // to the same short-lived signed URL and 302-redirects so it can be used\n // verbatim in any browser context.\n // ---------------------------------------------------------------------------\n httpServer.get(`${basePath}/files/:fileId`, async (req: IHttpRequest, res: IHttpResponse) => {\n try {\n const { fileId } = req.params;\n const file = await store.getFile(fileId);\n if (!file || file.status !== 'committed') {\n res.status(404).json({ error: 'File not found or not committed' });\n return;\n }\n\n let url: string;\n if (storage.getPresignedDownload) {\n const desc = await storage.getPresignedDownload(file.key, presignedTtl);\n url = desc.downloadUrl;\n } else if (storage.getSignedUrl) {\n url = await storage.getSignedUrl(file.key, presignedTtl);\n } else {\n url = `${basePath}/_local/file/${encodeURIComponent(file.key)}`;\n }\n\n res.status(302).header('Location', url).send('');\n } catch (err: any) {\n res.status(500).json({ error: err.message ?? 'Internal error' });\n }\n });\n\n // ---------------------------------------------------------------------------\n // PUT /storage/_local/raw/:token — presigned raw upload (LocalStorageAdapter)\n // ---------------------------------------------------------------------------\n httpServer.put(`${basePath}/_local/raw/:token`, async (req: IHttpRequest, res: IHttpResponse) => {\n try {\n const { token } = req.params;\n const localAdapter = storage as LocalStorageAdapter;\n if (!localAdapter.verifyToken) {\n res.status(501).json({ error: 'Presigned raw upload not supported by this adapter' });\n return;\n }\n\n const payload = localAdapter.verifyToken(token, 'put');\n let data: Buffer;\n if (req.rawBody) {\n data = await req.rawBody();\n } else if (Buffer.isBuffer(req.body)) {\n data = req.body;\n } else {\n res.status(400).json({ error: 'Binary body required' });\n return;\n }\n\n await storage.upload(payload.k, data, { contentType: payload.ct });\n res.json({ ok: true, key: payload.k });\n } catch (err: any) {\n const statusCode = err.message?.includes('expired') || err.message?.includes('signature') ? 403 : 500;\n res.status(statusCode).json({ error: err.message ?? 'Upload failed' });\n }\n });\n\n // ---------------------------------------------------------------------------\n // GET /storage/_local/raw/:token — presigned raw download (LocalStorageAdapter)\n // ---------------------------------------------------------------------------\n httpServer.get(`${basePath}/_local/raw/:token`, async (req: IHttpRequest, res: IHttpResponse) => {\n try {\n const { token } = req.params;\n const localAdapter = storage as LocalStorageAdapter;\n if (!localAdapter.verifyToken) {\n res.status(501).json({ error: 'Presigned download not supported by this adapter' });\n return;\n }\n\n const payload = localAdapter.verifyToken(token, 'get');\n const data = await storage.download(payload.k);\n\n res.header('content-type', payload.ct ?? 'application/octet-stream');\n res.header('content-length', String(data.byteLength));\n res.send(data);\n } catch (err: any) {\n const statusCode = err.message?.includes('expired') || err.message?.includes('signature') ? 403 : 500;\n res.status(statusCode).json({ error: err.message ?? 'Download failed' });\n }\n });\n}\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\nfunction buildKey(scope: string, fileId: string, filename: string): string {\n const ext = filename.includes('.') ? '.' + filename.split('.').pop() : '';\n return `${scope}/${fileId}${ext}`;\n}\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\nimport { ObjectSchema, Field } from '@objectstack/spec/data';\n\n/**\n * System File Object\n *\n * Persisted metadata for files stored via the Storage Service.\n *\n * The Storage Service contract addresses files by `key` (path inside the\n * configured backend). The REST protocol (see `packages/spec/src/api/storage.zod.ts`)\n * exposes an opaque `fileId` so that:\n *\n * 1. Client code never needs to know — or be able to spoof — backend keys.\n * 2. Files can be moved between buckets / storage tiers without breaking links.\n * 3. Lifecycle status (uploading → committed → deleted) can be tracked.\n *\n * Belongs to `@objectstack/service-storage` per the\n * \"protocol + service ownership\" pattern used by `service-feed`.\n */\nexport const SystemFile = ObjectSchema.create({\n name: 'sys_file',\n label: 'System File',\n pluralLabel: 'System Files',\n icon: 'file',\n description: 'Storage service file metadata (fileId ↔ key mapping)',\n titleFormat: '{name}',\n compactLayout: ['name', 'mime_type', 'size', 'status', 'created_at'],\n\n fields: {\n id: Field.text({\n label: 'File ID',\n required: true,\n readonly: true,\n }),\n\n key: Field.text({\n label: 'Storage Key',\n required: true,\n searchable: true,\n }),\n\n name: Field.text({\n label: 'File Name',\n required: true,\n searchable: true,\n }),\n\n mime_type: Field.text({\n label: 'MIME Type',\n }),\n\n size: Field.number({\n label: 'Size (bytes)',\n }),\n\n scope: Field.select({\n label: 'Scope',\n options: [\n { label: 'User', value: 'user' },\n { label: 'Tenant', value: 'tenant' },\n { label: 'Public', value: 'public' },\n { label: 'Private', value: 'private' },\n { label: 'Temp', value: 'temp' },\n ],\n }),\n\n bucket: Field.text({\n label: 'Bucket',\n }),\n\n acl: Field.select({\n label: 'ACL',\n options: [\n { label: 'Private', value: 'private' },\n { label: 'Public Read', value: 'public_read' },\n ],\n }),\n\n status: Field.select({\n label: 'Status',\n required: true,\n options: [\n { label: 'Pending Upload', value: 'pending' },\n { label: 'Committed', value: 'committed' },\n { label: 'Deleted', value: 'deleted' },\n ],\n }),\n\n etag: Field.text({\n label: 'ETag',\n }),\n\n owner_id: Field.text({\n label: 'Owner ID',\n }),\n\n metadata: Field.text({\n label: 'Metadata (JSON)',\n }),\n\n created_at: Field.datetime({\n label: 'Created At',\n }),\n\n updated_at: Field.datetime({\n label: 'Updated At',\n }),\n },\n});\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\nimport { ObjectSchema, Field } from '@objectstack/spec/data';\n\n/**\n * System Upload Session Object\n *\n * Persisted state for in-flight chunked / multipart uploads.\n *\n * Tracks upload progress so that interrupted uploads can be resumed via\n * `POST /api/v1/storage/upload/chunked/:uploadId/progress`. Sessions are\n * cleaned up by the storage service on `complete` / `abort` / TTL expiry.\n */\nexport const SystemUploadSession = ObjectSchema.create({\n name: 'sys_upload_session',\n label: 'System Upload Session',\n pluralLabel: 'System Upload Sessions',\n icon: 'upload-cloud',\n description: 'Resumable multipart upload sessions tracked by service-storage',\n titleFormat: '{filename}',\n compactLayout: ['filename', 'status', 'uploaded_chunks', 'total_chunks', 'expires_at'],\n\n fields: {\n id: Field.text({\n label: 'Upload Session ID',\n required: true,\n readonly: true,\n }),\n\n file_id: Field.text({\n label: 'File ID',\n required: true,\n }),\n\n key: Field.text({\n label: 'Storage Key',\n required: true,\n }),\n\n filename: Field.text({\n label: 'Filename',\n required: true,\n }),\n\n mime_type: Field.text({\n label: 'MIME Type',\n }),\n\n total_size: Field.number({\n label: 'Total Size (bytes)',\n required: true,\n }),\n\n chunk_size: Field.number({\n label: 'Chunk Size (bytes)',\n required: true,\n }),\n\n total_chunks: Field.number({\n label: 'Total Chunks',\n required: true,\n }),\n\n uploaded_chunks: Field.number({\n label: 'Uploaded Chunks',\n }),\n\n uploaded_size: Field.number({\n label: 'Uploaded Size (bytes)',\n }),\n\n parts: Field.text({\n label: 'Uploaded Parts (JSON)',\n }),\n\n resume_token: Field.text({\n label: 'Resume Token',\n }),\n\n backend_upload_id: Field.text({\n label: 'Backend Upload ID',\n }),\n\n scope: Field.text({\n label: 'Scope',\n }),\n\n bucket: Field.text({\n label: 'Bucket',\n }),\n\n metadata: Field.text({\n label: 'Metadata (JSON)',\n }),\n\n status: Field.select({\n label: 'Status',\n required: true,\n options: [\n { label: 'In Progress', value: 'in_progress' },\n { label: 'Completing', value: 'completing' },\n { label: 'Completed', value: 'completed' },\n { label: 'Failed', value: 'failed' },\n { label: 'Expired', value: 'expired' },\n ],\n }),\n\n started_at: Field.datetime({\n label: 'Started At',\n }),\n\n expires_at: Field.datetime({\n label: 'Expires At',\n }),\n\n updated_at: Field.datetime({\n label: 'Updated At',\n }),\n },\n});\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\nimport type {\n IStorageService,\n StorageFileInfo,\n StorageUploadOptions,\n PresignedUploadDescriptor,\n PresignedDownloadDescriptor,\n} from '@objectstack/spec/contracts';\n\n/**\n * SwappableStorageService — IStorageService proxy with a swappable\n * inner adapter.\n *\n * Used by `StorageServicePlugin` so the kernel can register a stable\n * `file-storage` reference at init time, while the underlying adapter\n * (local FS / S3) is rebuilt on every `settings:changed` event for\n * the `storage` namespace.\n *\n * ⚠ Adapter swaps do NOT migrate previously uploaded files. Files\n * stored under the previous adapter become unreachable through the\n * new one. Callers are responsible for migrating data out-of-band.\n *\n * All `IStorageService` methods delegate to the current inner adapter.\n * Optional methods (list / presigned / chunked) probe the inner\n * adapter and surface a clear error when the active adapter does not\n * implement them.\n */\nexport class SwappableStorageService implements IStorageService {\n private inner: IStorageService;\n private readonly onSwap?: (previous: IStorageService, next: IStorageService) => void;\n\n constructor(\n initial: IStorageService,\n onSwap?: (previous: IStorageService, next: IStorageService) => void,\n ) {\n this.inner = initial;\n this.onSwap = onSwap;\n }\n\n /** Replace the inner adapter. */\n swap(next: IStorageService): void {\n const previous = this.inner;\n this.inner = next;\n this.onSwap?.(previous, next);\n }\n\n /** Expose the active inner adapter — primarily for tests. */\n getInner(): IStorageService {\n return this.inner;\n }\n\n upload(key: string, data: Buffer | ReadableStream, options?: StorageUploadOptions): Promise<void> {\n return this.inner.upload(key, data, options);\n }\n\n download(key: string): Promise<Buffer> {\n return this.inner.download(key);\n }\n\n delete(key: string): Promise<void> {\n return this.inner.delete(key);\n }\n\n exists(key: string): Promise<boolean> {\n return this.inner.exists(key);\n }\n\n getInfo(key: string): Promise<StorageFileInfo> {\n return this.inner.getInfo(key);\n }\n\n list(prefix: string): Promise<StorageFileInfo[]> {\n if (typeof this.inner.list !== 'function') {\n return Promise.reject(new Error('Active storage adapter does not support list()'));\n }\n return this.inner.list(prefix);\n }\n\n getSignedUrl(key: string, expiresIn: number): Promise<string> {\n if (typeof this.inner.getSignedUrl !== 'function') {\n return Promise.reject(new Error('Active storage adapter does not support getSignedUrl()'));\n }\n return this.inner.getSignedUrl(key, expiresIn);\n }\n\n getPresignedUpload(\n key: string,\n expiresIn: number,\n options?: StorageUploadOptions,\n ): Promise<PresignedUploadDescriptor> {\n if (typeof this.inner.getPresignedUpload !== 'function') {\n return Promise.reject(new Error('Active storage adapter does not support getPresignedUpload()'));\n }\n return this.inner.getPresignedUpload(key, expiresIn, options);\n }\n\n getPresignedDownload(key: string, expiresIn: number): Promise<PresignedDownloadDescriptor> {\n if (typeof this.inner.getPresignedDownload !== 'function') {\n return Promise.reject(new Error('Active storage adapter does not support getPresignedDownload()'));\n }\n return this.inner.getPresignedDownload(key, expiresIn);\n }\n\n initiateChunkedUpload(key: string, options?: StorageUploadOptions): Promise<string> {\n if (typeof this.inner.initiateChunkedUpload !== 'function') {\n return Promise.reject(new Error('Active storage adapter does not support initiateChunkedUpload()'));\n }\n return this.inner.initiateChunkedUpload(key, options);\n }\n\n uploadChunk(uploadId: string, partNumber: number, data: Buffer): Promise<string> {\n if (typeof this.inner.uploadChunk !== 'function') {\n return Promise.reject(new Error('Active storage adapter does not support uploadChunk()'));\n }\n return this.inner.uploadChunk(uploadId, partNumber, data);\n }\n\n completeChunkedUpload(\n uploadId: string,\n parts: Array<{ partNumber: number; eTag: string }>,\n ): Promise<string> {\n if (typeof this.inner.completeChunkedUpload !== 'function') {\n return Promise.reject(new Error('Active storage adapter does not support completeChunkedUpload()'));\n }\n return this.inner.completeChunkedUpload(uploadId, parts);\n }\n\n abortChunkedUpload(uploadId: string): Promise<void> {\n if (typeof this.inner.abortChunkedUpload !== 'function') {\n return Promise.reject(new Error('Active storage adapter does not support abortChunkedUpload()'));\n }\n return this.inner.abortChunkedUpload(uploadId);\n }\n\n /**\n * Verify a presigned HMAC token (LocalStorageAdapter-specific).\n *\n * `IStorageService` does not declare this method, but `storage-routes`\n * type-narrows the active storage to `LocalStorageAdapter` to handle the\n * `/_local/raw/:token` PUT and GET endpoints. Without a passthrough on\n * the swappable wrapper, the route sees `verifyToken === undefined` and\n * returns 501 even though the underlying local adapter supports it.\n */\n verifyToken(token: string, expectedOp?: 'put' | 'get'): { k: string; ct?: string; op: string; exp: number } {\n const inner = this.inner as unknown as {\n verifyToken?: (token: string, expectedOp?: 'put' | 'get') => { k: string; ct?: string; op: string; exp: number };\n };\n if (typeof inner.verifyToken !== 'function') {\n throw new Error('Active storage adapter does not support verifyToken()');\n }\n return inner.verifyToken(token, expectedOp);\n }\n}\n","// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.\n\nexport { StorageServicePlugin } from './storage-service-plugin.js';\nexport type { StorageServicePluginOptions } from './storage-service-plugin.js';\nexport { SwappableStorageService } from './swappable-storage-service.js';\nexport { LocalStorageAdapter } from './local-storage-adapter.js';\nexport type { LocalStorageAdapterOptions } from './local-storage-adapter.js';\nexport { S3StorageAdapter } from './s3-storage-adapter.js';\nexport type { S3StorageAdapterOptions } from './s3-storage-adapter.js';\nexport { StorageMetadataStore } from './metadata-store.js';\nexport type { FileRecord, UploadSessionRecord } from './metadata-store.js';\nexport { registerStorageRoutes } from './storage-routes.js';\nexport type { StorageRoutesOptions } from './storage-routes.js';\nexport { SystemFile, SystemUploadSession } from './objects/index.js';\n"],"mappings":";;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAEA;AAAA,EACE,uBAAAA;AAAA,EACA,WAAAC;AAAA,OAEK;AAwWP,eAAe,eAAe,QAA8B;AAC1D,MAAI,OAAO,SAAS,MAAM,EAAG,QAAO;AACpC,MAAI,kBAAkB,WAAY,QAAO,OAAO,KAAK,MAAM;AAC3D,QAAM,SAAuB,CAAC;AAC9B,MAAI,OAAO,OAAO,OAAO,aAAa,MAAM,YAAY;AACtD,qBAAiB,SAAS,QAAQ;AAChC,aAAO,KAAK,OAAO,UAAU,WAAW,OAAO,KAAK,KAAK,IAAI,KAAK;AAAA,IACpE;AAAA,EACF,WAAW,OAAO,WAAW;AAC3B,UAAM,SAAS,OAAO,UAAU;AAChC,QAAI,OAAO;AACX,WAAO,CAAC,MAAM;AACZ,YAAM,SAAS,MAAM,OAAO,KAAK;AACjC,aAAO,OAAO;AACd,UAAI,OAAO,MAAO,QAAO,KAAK,OAAO,KAAK;AAAA,IAC5C;AAAA,EACF,OAAO;AACL,UAAM,IAAI,MAAM,iCAAiC;AAAA,EACnD;AACA,SAAO,OAAO,OAAO,MAAM;AAC7B;AAlYA,IAmDa;AAnDb;AAAA;AAAA;AAmDO,IAAM,mBAAN,MAAkD;AAAA,MAQvD,YAA6B,SAAkC;AAAlC;AAF7B,aAAQ,gBAAqC;AAoS7C;AAAA;AAAA;AAAA,aAAQ,cAAmC,oBAAI,IAAI;AAjSjD,aAAK,SAAS,QAAQ;AACtB,aAAK,SAAS,QAAQ;AACtB,aAAK,WAAW,QAAQ;AACxB,aAAK,iBAAiB,QAAQ,kBAAkB;AAChD,aAAK,UAAU,QAAQ,WAAW,IAAID,qBAAoB;AAAA,MAC5D;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,MAOA,MAAc,MAAS,IAAgD,IAAkC;AACvG,cAAM,UAAU,KAAK,IAAI;AACzB,cAAM,aAAa,EAAE,SAAS,MAAM,GAAG;AACvC,YAAI;AACF,gBAAM,MAAM,MAAM,GAAG;AACrB,cAAI;AACF,iBAAK,QAAQ,QAAQC,SAAQ,wBAAwB,EAAE,GAAG,YAAY,QAAQ,KAAK,CAAC;AACpF,iBAAK,QAAQ,UAAUA,SAAQ,4BAA4B,KAAK,IAAI,IAAI,SAAS,UAAU;AAAA,UAC7F,QAAQ;AAAA,UAAyC;AACjD,iBAAO;AAAA,QACT,SAAS,KAAU;AACjB,cAAI;AACF,iBAAK,QAAQ,QAAQA,SAAQ,wBAAwB,EAAE,GAAG,YAAY,QAAQ,QAAQ,CAAC;AACvF,iBAAK,QAAQ,UAAUA,SAAQ,4BAA4B,KAAK,IAAI,IAAI,SAAS,UAAU;AAC3F,kBAAM,aAAa,KAAK,QAAQ,KAAK,aAAa,QAAQ;AAC1D,iBAAK,QAAQ,QAAQA,SAAQ,oBAAoB,EAAE,GAAG,YAAY,WAAW,CAAC;AAAA,UAChF,QAAQ;AAAA,UAAyC;AACjD,gBAAM;AAAA,QACR;AAAA,MACF;AAAA;AAAA;AAAA;AAAA;AAAA,MAMA,MAAc,YAA0B;AACtC,YAAI,CAAC,KAAK,eAAe;AACvB,eAAK,iBAAiB,YAAY;AAChC,gBAAI;AACJ,gBAAI;AACF,sBAAQ,MAAM,OAAO,oBAAoB;AAAA,YAC3C,QAAQ;AACN,oBAAM,IAAI;AAAA,gBACR;AAAA,cACF;AAAA,YACF;AACA,kBAAM,EAAE,SAAS,IAAI;AACrB,kBAAM,aAAkB,EAAE,QAAQ,KAAK,OAAO;AAC9C,gBAAI,KAAK,SAAU,YAAW,WAAW,KAAK;AAC9C,gBAAI,KAAK,eAAgB,YAAW,iBAAiB;AACrD,gBAAI,KAAK,QAAQ,eAAe,KAAK,QAAQ,iBAAiB;AAC5D,yBAAW,cAAc;AAAA,gBACvB,aAAa,KAAK,QAAQ;AAAA,gBAC1B,iBAAiB,KAAK,QAAQ;AAAA,cAChC;AAAA,YACF;AACA,mBAAO,IAAI,SAAS,UAAU;AAAA,UAChC,GAAG;AAAA,QACL;AACA,eAAO,KAAK;AAAA,MACd;AAAA,MAEA,MAAc,QAAsB;AAClC,YAAI;AACF,iBAAO,MAAM,OAAO,oBAAoB;AAAA,QAC1C,QAAQ;AACN,gBAAM,IAAI,MAAM,8CAA8C;AAAA,QAChE;AAAA,MACF;AAAA,MAEA,MAAc,eAA6B;AACzC,YAAI;AACF,iBAAO,MAAM,OAAO,+BAA+B;AAAA,QACrD,QAAQ;AACN,gBAAM,IAAI,MAAM,yDAAyD;AAAA,QAC3E;AAAA,MACF;AAAA;AAAA;AAAA;AAAA,MAMA,MAAM,OAAO,KAAa,MAA+B,SAA+C;AACtG,eAAO,KAAK,MAAM,OAAO,YAAY;AACnC,gBAAM,SAAS,MAAM,KAAK,UAAU;AACpC,gBAAM,KAAK,MAAM,KAAK,MAAM;AAC5B,gBAAM,OAAO,gBAAgB,SAAS,OAAO,MAAM,eAAe,IAAI;AACtE,gBAAM,MAAM,IAAI,GAAG,iBAAiB;AAAA,YAClC,QAAQ,KAAK;AAAA,YACb,KAAK;AAAA,YACL,MAAM;AAAA,YACN,aAAa,SAAS;AAAA,YACtB,UAAU,SAAS;AAAA,YACnB,KAAK,SAAS,QAAQ,gBAAgB,gBAAgB;AAAA,UACxD,CAAC;AACD,gBAAM,OAAO,KAAK,GAAG;AAAA,QACvB,CAAC;AAAA,MACH;AAAA,MAEA,MAAM,SAAS,KAA8B;AAC3C,eAAO,KAAK,MAAM,OAAO,YAAY;AACnC,gBAAM,SAAS,MAAM,KAAK,UAAU;AACpC,gBAAM,KAAK,MAAM,KAAK,MAAM;AAC5B,gBAAM,MAAM,IAAI,GAAG,iBAAiB,EAAE,QAAQ,KAAK,QAAQ,KAAK,IAAI,CAAC;AACrE,gBAAM,MAAM,MAAM,OAAO,KAAK,GAAG;AACjC,iBAAO,eAAe,IAAI,IAAI;AAAA,QAChC,CAAC;AAAA,MACH;AAAA,MAEA,MAAM,OAAO,KAA4B;AACvC,eAAO,KAAK,MAAM,UAAU,YAAY;AACtC,gBAAM,SAAS,MAAM,KAAK,UAAU;AACpC,gBAAM,KAAK,MAAM,KAAK,MAAM;AAC5B,gBAAM,MAAM,IAAI,GAAG,oBAAoB,EAAE,QAAQ,KAAK,QAAQ,KAAK,IAAI,CAAC;AACxE,gBAAM,OAAO,KAAK,GAAG;AAAA,QACvB,CAAC;AAAA,MACH;AAAA,MAEA,MAAM,OAAO,KAA+B;AAC1C,eAAO,KAAK,MAAM,QAAQ,YAAY;AACpC,gBAAM,SAAS,MAAM,KAAK,UAAU;AACpC,gBAAM,KAAK,MAAM,KAAK,MAAM;AAC5B,cAAI;AACF,kBAAM,MAAM,IAAI,GAAG,kBAAkB,EAAE,QAAQ,KAAK,QAAQ,KAAK,IAAI,CAAC;AACtE,kBAAM,OAAO,KAAK,GAAG;AACrB,mBAAO;AAAA,UACT,SAAS,KAAU;AACjB,gBAAI,IAAI,SAAS,cAAc,IAAI,WAAW,mBAAmB,IAAK,QAAO;AAC7E,kBAAM;AAAA,UACR;AAAA,QACF,CAAC;AAAA,MACH;AAAA,MAEA,MAAM,QAAQ,KAAuC;AACnD,eAAO,KAAK,MAAM,QAAQ,YAAY;AACpC,gBAAM,SAAS,MAAM,KAAK,UAAU;AACpC,gBAAM,KAAK,MAAM,KAAK,MAAM;AAC5B,gBAAM,MAAM,IAAI,GAAG,kBAAkB,EAAE,QAAQ,KAAK,QAAQ,KAAK,IAAI,CAAC;AACtE,gBAAM,MAAM,MAAM,OAAO,KAAK,GAAG;AACjC,iBAAO;AAAA,YACL;AAAA,YACA,MAAM,IAAI,iBAAiB;AAAA,YAC3B,aAAa,IAAI;AAAA,YACjB,cAAc,IAAI,gBAAgB,oBAAI,KAAK;AAAA,YAC3C,UAAU,IAAI;AAAA,UAChB;AAAA,QACF,CAAC;AAAA,MACH;AAAA,MAEA,MAAM,KAAK,QAA4C;AACrD,eAAO,KAAK,MAAM,QAAQ,YAAY;AACpC,gBAAM,SAAS,MAAM,KAAK,UAAU;AACpC,gBAAM,KAAK,MAAM,KAAK,MAAM;AAC5B,gBAAM,MAAM,IAAI,GAAG,qBAAqB,EAAE,QAAQ,KAAK,QAAQ,QAAQ,OAAO,CAAC;AAC/E,gBAAM,MAAM,MAAM,OAAO,KAAK,GAAG;AACjC,kBAAQ,IAAI,YAAY,CAAC,GAAG,IAAI,CAAC,UAAe;AAAA,YAC9C,KAAK,KAAK;AAAA,YACV,MAAM,KAAK,QAAQ;AAAA,YACnB,cAAc,KAAK,gBAAgB,oBAAI,KAAK;AAAA,UAC9C,EAAE;AAAA,QACJ,CAAC;AAAA,MACH;AAAA;AAAA;AAAA;AAAA,MAMA,MAAM,aAAa,KAAa,WAAoC;AAClE,cAAM,OAAO,MAAM,KAAK,qBAAqB,KAAK,SAAS;AAC3D,eAAO,KAAK;AAAA,MACd;AAAA,MAEA,MAAM,mBACJ,KACA,WACA,SACoC;AACpC,cAAM,SAAS,MAAM,KAAK,UAAU;AACpC,cAAM,KAAK,MAAM,KAAK,MAAM;AAC5B,cAAM,EAAE,aAAa,IAAI,MAAM,KAAK,aAAa;AACjD,cAAM,MAAM,IAAI,GAAG,iBAAiB;AAAA,UAClC,QAAQ,KAAK;AAAA,UACb,KAAK;AAAA,UACL,aAAa,SAAS;AAAA,UACtB,UAAU,SAAS;AAAA,UACnB,KAAK,SAAS,QAAQ,gBAAgB,gBAAgB;AAAA,QACxD,CAAC;AACD,cAAM,MAAM,MAAM,aAAa,QAAQ,KAAK,EAAE,UAAU,CAAC;AACzD,eAAO;AAAA,UACL,WAAW;AAAA,UACX,QAAQ;AAAA,UACR,SAAS,SAAS,cAAc,EAAE,gBAAgB,QAAQ,YAAY,IAAI;AAAA,UAC1E;AAAA,QACF;AAAA,MACF;AAAA,MAEA,MAAM,qBAAqB,KAAa,WAAyD;AAC/F,cAAM,SAAS,MAAM,KAAK,UAAU;AACpC,cAAM,KAAK,MAAM,KAAK,MAAM;AAC5B,cAAM,EAAE,aAAa,IAAI,MAAM,KAAK,aAAa;AACjD,cAAM,MAAM,IAAI,GAAG,iBAAiB,EAAE,QAAQ,KAAK,QAAQ,KAAK,IAAI,CAAC;AACrE,cAAM,MAAM,MAAM,aAAa,QAAQ,KAAK,EAAE,UAAU,CAAC;AACzD,eAAO,EAAE,aAAa,KAAK,UAAU;AAAA,MACvC;AAAA;AAAA;AAAA;AAAA,MAMA,MAAM,sBAAsB,KAAa,SAAiD;AACxF,cAAM,SAAS,MAAM,KAAK,UAAU;AACpC,cAAM,KAAK,MAAM,KAAK,MAAM;AAC5B,cAAM,MAAM,IAAI,GAAG,6BAA6B;AAAA,UAC9C,QAAQ,KAAK;AAAA,UACb,KAAK;AAAA,UACL,aAAa,SAAS;AAAA,UACtB,UAAU,SAAS;AAAA,QACrB,CAAC;AACD,cAAM,MAAM,MAAM,OAAO,KAAK,GAAG;AACjC,eAAO,IAAI;AAAA,MACb;AAAA,MAEA,MAAM,YAAY,UAAkB,YAAoB,MAA+B;AACrF,cAAM,SAAS,MAAM,KAAK,UAAU;AACpC,cAAM,KAAK,MAAM,KAAK,MAAM;AAQ5B,cAAM,MAAM,KAAK,aAAa,IAAI,QAAQ;AAC1C,YAAI,CAAC,KAAK;AACR,gBAAM,IAAI,MAAM,yFAAyF;AAAA,QAC3G;AACA,cAAM,MAAM,IAAI,GAAG,kBAAkB;AAAA,UACnC,QAAQ,KAAK;AAAA,UACb,KAAK;AAAA,UACL,UAAU;AAAA,UACV,YAAY;AAAA,UACZ,MAAM;AAAA,QACR,CAAC;AACD,cAAM,MAAM,MAAM,OAAO,KAAK,GAAG;AACjC,eAAO,IAAI;AAAA,MACb;AAAA,MAEA,MAAM,sBACJ,UACA,OACiB;AACjB,cAAM,SAAS,MAAM,KAAK,UAAU;AACpC,cAAM,KAAK,MAAM,KAAK,MAAM;AAC5B,cAAM,MAAM,KAAK,aAAa,IAAI,QAAQ;AAC1C,YAAI,CAAC,KAAK;AACR,gBAAM,IAAI,MAAM,+CAA+C;AAAA,QACjE;AACA,cAAM,MAAM,IAAI,GAAG,+BAA+B;AAAA,UAChD,QAAQ,KAAK;AAAA,UACb,KAAK;AAAA,UACL,UAAU;AAAA,UACV,iBAAiB;AAAA,YACf,OAAO,MAAM,IAAI,QAAM,EAAE,YAAY,EAAE,YAAY,MAAM,EAAE,KAAK,EAAE;AAAA,UACpE;AAAA,QACF,CAAC;AACD,cAAM,OAAO,KAAK,GAAG;AACrB,aAAK,aAAa,OAAO,QAAQ;AACjC,eAAO;AAAA,MACT;AAAA,MAEA,MAAM,mBAAmB,UAAiC;AACxD,cAAM,SAAS,MAAM,KAAK,UAAU;AACpC,cAAM,KAAK,MAAM,KAAK,MAAM;AAC5B,cAAM,MAAM,KAAK,aAAa,IAAI,QAAQ;AAC1C,YAAI,CAAC,IAAK;AACV,cAAM,MAAM,IAAI,GAAG,4BAA4B;AAAA,UAC7C,QAAQ,KAAK;AAAA,UACb,KAAK;AAAA,UACL,UAAU;AAAA,QACZ,CAAC;AACD,cAAM,OAAO,KAAK,GAAG;AACrB,aAAK,aAAa,OAAO,QAAQ;AAAA,MACnC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,MAaA,aAAa,UAAkB,KAAmB;AAChD,aAAK,YAAY,IAAI,UAAU,GAAG;AAAA,MACpC;AAAA,IACF;AAAA;AAAA;;;ACpWA;AAAA,EACE;AAAA,EACA,uBAAAC;AAAA,OAEK;;;ACNP,SAAS,YAAY,IAAI,kBAAkB,yBAAyB;AACpE,SAAS,MAAM,eAAe;AAC9B,SAAS,YAAY,kBAAkB;AACvC;AAAA,EACE;AAAA,EACA;AAAA,OAEK;AAwDA,IAAM,sBAAN,MAAqD;AAAA,EAQ1D,YAAY,SAAqC;AAC/C,SAAK,UAAU,QAAQ;AACvB,SAAK,WAAW,KAAK,KAAK,SAAS,QAAQ;AAC3C,SAAK,UAAU,QAAQ,WAAW;AAClC,SAAK,WAAW,QAAQ,YAAY;AACpC,SAAK,gBAAgB,QAAQ,iBAAiB,WAAW;AACzD,SAAK,UAAU,QAAQ,WAAW,IAAI,oBAAoB;AAAA,EAC5D;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAc,MAAS,IAAgD,IAAkC;AACvG,UAAM,UAAU,KAAK,IAAI;AACzB,UAAM,aAAa,EAAE,SAAS,SAAS,GAAG;AAC1C,QAAI;AACF,YAAM,MAAM,MAAM,GAAG;AACrB,UAAI;AACF,aAAK,QAAQ,QAAQ,QAAQ,wBAAwB,EAAE,GAAG,YAAY,QAAQ,KAAK,CAAC;AACpF,aAAK,QAAQ,UAAU,QAAQ,4BAA4B,KAAK,IAAI,IAAI,SAAS,UAAU;AAAA,MAC7F,QAAQ;AAAA,MAAoB;AAC5B,aAAO;AAAA,IACT,SAAS,KAAU;AACjB,UAAI;AACF,aAAK,QAAQ,QAAQ,QAAQ,wBAAwB,EAAE,GAAG,YAAY,QAAQ,QAAQ,CAAC;AACvF,aAAK,QAAQ,UAAU,QAAQ,4BAA4B,KAAK,IAAI,IAAI,SAAS,UAAU;AAC3F,cAAM,aAAa,KAAK,QAAQ,KAAK,aAAa,QAAQ;AAC1D,aAAK,QAAQ,QAAQ,QAAQ,oBAAoB,EAAE,GAAG,YAAY,WAAW,CAAC;AAAA,MAChF,QAAQ;AAAA,MAAoB;AAC5B,YAAM;AAAA,IACR;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAMQ,YAAY,KAAqB;AACvC,QAAI,IAAI,SAAS,IAAI,GAAG;AACtB,YAAM,IAAI,MAAM,yDAAyD,GAAG,IAAI;AAAA,IAClF;AACA,WAAO,KAAK,KAAK,SAAS,GAAG;AAAA,EAC/B;AAAA,EAEQ,gBAAgB,UAAkB,YAA4B;AACpE,QAAI,CAAC,mBAAmB,KAAK,QAAQ,GAAG;AACtC,YAAM,IAAI,MAAM,0CAA0C,QAAQ,GAAG;AAAA,IACvE;AACA,WAAO,KAAK,KAAK,UAAU,UAAU,OAAO,UAAU,EAAE,SAAS,GAAG,GAAG,CAAC;AAAA,EAC1E;AAAA;AAAA;AAAA;AAAA,EAMA,MAAM,OACJ,KACA,MACA,UACe;AACf,WAAO,KAAK,MAAM,OAAO,YAAY;AACnC,YAAM,WAAW,KAAK,YAAY,GAAG;AACrC,YAAM,GAAG,MAAM,QAAQ,QAAQ,GAAG,EAAE,WAAW,KAAK,CAAC;AAErD,UAAI,gBAAgB,QAAQ;AAC1B,cAAM,GAAG,UAAU,UAAU,IAAI;AACjC;AAAA,MACF;AAGA,YAAM,SAAuB,CAAC;AAC9B,YAAM,SAAU,KAAwB,UAAU;AAClD,UAAI,OAAO;AACX,aAAO,CAAC,MAAM;AACZ,cAAM,SAAS,MAAM,OAAO,KAAK;AACjC,eAAO,OAAO;AACd,YAAI,OAAO,MAAO,QAAO,KAAK,OAAO,KAAK;AAAA,MAC5C;AACA,YAAM,GAAG,UAAU,UAAU,OAAO,OAAO,MAAM,CAAC;AAAA,IACpD,CAAC;AAAA,EACH;AAAA,EAEA,MAAM,SAAS,KAA8B;AAC3C,WAAO,KAAK,MAAM,OAAO,YAAY,GAAG,SAAS,KAAK,YAAY,GAAG,CAAC,CAAC;AAAA,EACzE;AAAA,EAEA,MAAM,OAAO,KAA4B;AACvC,WAAO,KAAK,MAAM,UAAU,YAAY;AACtC,YAAM,GAAG,OAAO,KAAK,YAAY,GAAG,CAAC,EAAE,MAAM,CAAC,QAAQ;AACpD,YAAI,OAAO,IAAI,SAAS,SAAU;AAClC,cAAM;AAAA,MACR,CAAC;AAAA,IACH,CAAC;AAAA,EACH;AAAA,EAEA,MAAM,OAAO,KAA+B;AAC1C,WAAO,KAAK,MAAM,QAAQ,YAAY;AACpC,UAAI;AACF,cAAM,GAAG,OAAO,KAAK,YAAY,GAAG,CAAC;AACrC,eAAO;AAAA,MACT,QAAQ;AACN,eAAO;AAAA,MACT;AAAA,IACF,CAAC;AAAA,EACH;AAAA,EAEA,MAAM,QAAQ,KAAuC;AACnD,WAAO,KAAK,MAAM,QAAQ,YAAY;AACpC,YAAM,WAAW,KAAK,YAAY,GAAG;AACrC,YAAM,OAAO,MAAM,GAAG,KAAK,QAAQ;AACnC,aAAO,EAAE,KAAK,MAAM,KAAK,MAAM,cAAc,KAAK,MAAM;AAAA,IAC1D,CAAC;AAAA,EACH;AAAA,EAEA,MAAM,KAAK,QAA4C;AACrD,WAAO,KAAK,MAAM,QAAQ,YAAY;AACpC,YAAM,UAAU,KAAK,YAAY,MAAM;AACvC,UAAI;AACF,cAAM,UAAU,MAAM,GAAG,QAAQ,OAAO;AACxC,cAAM,UAA6B,CAAC;AACpC,mBAAW,SAAS,SAAS;AAC3B,cAAI,MAAM,WAAW,GAAG,EAAG;AAC3B,gBAAM,UAAU,SAAS,GAAG,MAAM,IAAI,KAAK,KAAK;AAChD,cAAI;AAEF,kBAAM,OAAO,MAAM,GAAG,KAAK,KAAK,YAAY,OAAO,CAAC;AACpD,oBAAQ,KAAK,EAAE,KAAK,SAAS,MAAM,KAAK,MAAM,cAAc,KAAK,MAAM,CAAC;AAAA,UAC1E,QAAQ;AAAA,UAER;AAAA,QACF;AACA,eAAO;AAAA,MACT,QAAQ;AACN,eAAO,CAAC;AAAA,MACV;AAAA,IACF,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUQ,UAAU,SAAsC;AACtD,UAAM,MAAM,OAAO,KAAK,KAAK,UAAU,OAAO,GAAG,MAAM,EAAE,SAAS,WAAW;AAC7E,UAAM,MAAM,WAAW,UAAU,KAAK,aAAa,EAAE,OAAO,GAAG,EAAE,OAAO,WAAW;AACnF,WAAO,GAAG,GAAG,IAAI,GAAG;AAAA,EACtB;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,YAAY,OAAe,YAAgD;AACzE,UAAM,CAAC,KAAK,GAAG,IAAI,MAAM,MAAM,GAAG;AAClC,QAAI,CAAC,OAAO,CAAC,IAAK,OAAM,IAAI,MAAM,8BAA8B;AAEhE,UAAM,WAAW,WAAW,UAAU,KAAK,aAAa,EAAE,OAAO,GAAG,EAAE,OAAO,WAAW;AACxF,QAAI,aAAa,IAAK,OAAM,IAAI,MAAM,iCAAiC;AAEvE,QAAI;AACJ,QAAI;AACF,gBAAU,KAAK,MAAM,OAAO,KAAK,KAAK,WAAW,EAAE,SAAS,MAAM,CAAC;AAAA,IACrE,QAAQ;AACN,YAAM,IAAI,MAAM,iCAAiC;AAAA,IACnD;AAEA,QAAI,QAAQ,OAAO,YAAY;AAC7B,YAAM,IAAI,MAAM,wCAAwC,UAAU,cAAc,QAAQ,EAAE,IAAI;AAAA,IAChG;AACA,QAAI,KAAK,IAAI,IAAI,MAAO,QAAQ,KAAK;AACnC,YAAM,IAAI,MAAM,uBAAuB;AAAA,IACzC;AACA,WAAO;AAAA,EACT;AAAA,EAEA,MAAM,mBACJ,KACA,WACA,SACoC;AACpC,UAAM,MAAM,KAAK,MAAM,KAAK,IAAI,IAAI,GAAI,IAAI,KAAK,IAAI,GAAG,SAAS;AACjE,UAAM,QAAQ,KAAK,UAAU,EAAE,GAAG,KAAK,IAAI,SAAS,aAAa,KAAK,IAAI,MAAM,CAAC;AAEjF,WAAO;AAAA,MACL,WAAW,GAAG,KAAK,OAAO,GAAG,KAAK,QAAQ,eAAe,KAAK;AAAA,MAC9D,QAAQ;AAAA,MACR,SAAS,SAAS,cAAc,EAAE,gBAAgB,QAAQ,YAAY,IAAI,EAAE,gBAAgB,2BAA2B;AAAA,MACvH;AAAA,MACA,aAAa,GAAG,KAAK,OAAO,GAAG,KAAK,QAAQ,gBAAgB,mBAAmB,GAAG,CAAC;AAAA,IACrF;AAAA,EACF;AAAA,EAEA,MAAM,qBAAqB,KAAa,WAAyD;AAC/F,UAAM,MAAM,KAAK,MAAM,KAAK,IAAI,IAAI,GAAI,IAAI,KAAK,IAAI,GAAG,SAAS;AACjE,UAAM,QAAQ,KAAK,UAAU,EAAE,GAAG,KAAK,KAAK,IAAI,MAAM,CAAC;AACvD,WAAO;AAAA,MACL,aAAa,GAAG,KAAK,OAAO,GAAG,KAAK,QAAQ,eAAe,KAAK;AAAA,MAChE;AAAA,IACF;AAAA,EACF;AAAA,EAEA,MAAM,aAAa,KAAa,WAAoC;AAClE,UAAM,OAAO,MAAM,KAAK,qBAAqB,KAAK,SAAS;AAC3D,WAAO,KAAK;AAAA,EACd;AAAA;AAAA;AAAA;AAAA,EAMA,MAAM,sBAAsB,KAAa,SAAiD;AACxF,UAAM,WAAW,WAAW,EAAE,QAAQ,MAAM,EAAE;AAC9C,UAAM,MAAM,KAAK,KAAK,UAAU,QAAQ;AACxC,UAAM,GAAG,MAAM,KAAK,EAAE,WAAW,KAAK,CAAC;AACvC,UAAM,OAAO;AAAA,MACX;AAAA,MACA,aAAa,SAAS;AAAA,MACtB,UAAU,SAAS;AAAA,MACnB,YAAW,oBAAI,KAAK,GAAE,YAAY;AAAA,IACpC;AACA,UAAM,GAAG,UAAU,KAAK,KAAK,YAAY,GAAG,KAAK,UAAU,IAAI,GAAG,MAAM;AACxE,WAAO;AAAA,EACT;AAAA,EAEA,MAAM,YAAY,UAAkB,YAAoB,MAA+B;AACrF,QAAI,CAAC,OAAO,UAAU,UAAU,KAAK,aAAa,GAAG;AACnD,YAAM,IAAI,MAAM,2DAA2D,UAAU,GAAG;AAAA,IAC1F;AACA,UAAM,WAAW,KAAK,gBAAgB,UAAU,UAAU;AAC1D,UAAM,GAAG,MAAM,QAAQ,QAAQ,GAAG,EAAE,WAAW,KAAK,CAAC;AACrD,UAAM,GAAG,UAAU,UAAU,IAAI;AAEjC,UAAM,EAAE,WAAW,IAAI,MAAM,OAAO,QAAa;AACjD,WAAO,WAAW,KAAK,EAAE,OAAO,IAAI,EAAE,OAAO,KAAK;AAAA,EACpD;AAAA,EAEA,MAAM,sBACJ,UACA,OACiB;AACjB,UAAM,MAAM,KAAK,KAAK,UAAU,QAAQ;AACxC,QAAI,OAAyB,CAAC;AAC9B,QAAI;AACF,aAAO,KAAK,MAAM,MAAM,GAAG,SAAS,KAAK,KAAK,YAAY,GAAG,MAAM,CAAC;AAAA,IACtE,QAAQ;AACN,YAAM,IAAI,MAAM,mBAAmB,QAAQ,aAAa;AAAA,IAC1D;AACA,UAAM,YAAY,KAAK;AACvB,QAAI,CAAC,WAAW;AACd,YAAM,IAAI,MAAM,mBAAmB,QAAQ,sBAAsB;AAAA,IACnE;AAEA,UAAM,cAAc,CAAC,GAAG,KAAK,EAAE,KAAK,CAAC,GAAG,MAAM,EAAE,aAAa,EAAE,UAAU;AACzE,UAAM,YAAY,KAAK,YAAY,SAAS;AAC5C,UAAM,GAAG,MAAM,QAAQ,SAAS,GAAG,EAAE,WAAW,KAAK,CAAC;AAGtD,UAAM,MAAM,kBAAkB,SAAS;AACvC,QAAI;AACF,iBAAW,KAAK,aAAa;AAC3B,cAAM,WAAW,KAAK,gBAAgB,UAAU,EAAE,UAAU;AAC5D,cAAM,IAAI,QAAc,CAAC,SAAS,WAAW;AAC3C,gBAAM,MAAM,iBAAiB,QAAQ;AACrC,cAAI,GAAG,SAAS,MAAM;AACtB,cAAI,GAAG,OAAO,MAAM,QAAQ,CAAC;AAC7B,cAAI,KAAK,KAAK,EAAE,KAAK,MAAM,CAAC;AAAA,QAC9B,CAAC;AAAA,MACH;AAAA,IACF,UAAE;AACA,YAAM,IAAI,QAAc,CAAC,YAAY,IAAI,IAAI,MAAM,QAAQ,CAAC,CAAC;AAAA,IAC/D;AAGA,UAAM,GAAG,GAAG,KAAK,EAAE,WAAW,MAAM,OAAO,KAAK,CAAC;AACjD,WAAO;AAAA,EACT;AAAA,EAEA,MAAM,mBAAmB,UAAiC;AACxD,UAAM,GAAG,GAAG,KAAK,KAAK,UAAU,QAAQ,GAAG,EAAE,WAAW,MAAM,OAAO,KAAK,CAAC;AAAA,EAC7E;AACF;;;AD3VA;;;AE8CO,IAAM,uBAAN,MAA2B;AAAA,EAIhC,YAA6B,QAA4B;AAA5B;AAH7B,SAAiB,QAAQ,oBAAI,IAAwB;AACrD,SAAiB,WAAW,oBAAI,IAAiC;AAAA,EAEP;AAAA;AAAA;AAAA;AAAA,EAM1D,MAAM,WAAW,KAAsC;AACrD,UAAM,OAAM,oBAAI,KAAK,GAAE,YAAY;AACnC,UAAM,OAAmB,EAAE,YAAY,KAAK,YAAY,KAAK,GAAG,IAAI;AACpE,SAAK,MAAM,IAAI,KAAK,IAAI,IAAI;AAC5B,QAAI,KAAK,QAAQ;AACf,UAAI;AACF,cAAM,KAAK,OAAO,OAAO,YAAY,IAAI;AAAA,MAC3C,QAAQ;AAAA,MAER;AAAA,IACF;AACA,WAAO;AAAA,EACT;AAAA,EAEA,MAAM,QAAQ,IAAwC;AACpD,QAAI,KAAK,QAAQ;AACf,UAAI;AACF,cAAM,QAAQ,MAAM,KAAK,OAAO,QAAQ,YAAY,EAAE,OAAO,EAAE,GAAG,EAAE,CAAC;AACrE,YAAI,MAAO,QAAO;AAAA,MACpB,QAAQ;AAAA,MAER;AAAA,IACF;AACA,WAAO,KAAK,MAAM,IAAI,EAAE,KAAK;AAAA,EAC/B;AAAA,EAEA,MAAM,WAAW,IAAY,OAAwD;AACnF,UAAM,WAAW,MAAM,KAAK,QAAQ,EAAE;AACtC,QAAI,CAAC,SAAU,QAAO;AACtB,UAAM,SAAqB,EAAE,GAAG,UAAU,GAAG,OAAO,IAAI,aAAY,oBAAI,KAAK,GAAE,YAAY,EAAE;AAC7F,SAAK,MAAM,IAAI,IAAI,MAAM;AACzB,QAAI,KAAK,QAAQ;AACf,UAAI;AACF,cAAM,KAAK,OAAO,OAAO,YAAY,QAAe,EAAE,OAAO,EAAE,GAAG,EAAE,CAAQ;AAAA,MAC9E,QAAQ;AAAA,MAER;AAAA,IACF;AACA,WAAO;AAAA,EACT;AAAA,EAEA,MAAM,WAAW,IAA2B;AAC1C,SAAK,MAAM,OAAO,EAAE;AACpB,QAAI,KAAK,QAAQ;AACf,UAAI;AACF,cAAM,KAAK,OAAO,OAAO,YAAY,EAAE,OAAO,EAAE,GAAG,EAAE,CAAQ;AAAA,MAC/D,QAAQ;AAAA,MAER;AAAA,IACF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAMA,MAAM,cAAc,KAAwD;AAC1E,UAAM,OAAM,oBAAI,KAAK,GAAE,YAAY;AACnC,UAAM,OAA4B;AAAA,MAChC,iBAAiB;AAAA,MACjB,eAAe;AAAA,MACf,OAAO;AAAA,MACP,YAAY;AAAA,MACZ,YAAY;AAAA,MACZ,GAAG;AAAA,IACL;AACA,SAAK,SAAS,IAAI,KAAK,IAAI,IAAI;AAC/B,QAAI,KAAK,QAAQ;AACf,UAAI;AACF,cAAM,KAAK,OAAO,OAAO,sBAAsB,IAAI;AAAA,MACrD,QAAQ;AAAA,MAER;AAAA,IACF;AACA,WAAO;AAAA,EACT;AAAA,EAEA,MAAM,WAAW,IAAiD;AAChE,QAAI,KAAK,QAAQ;AACf,UAAI;AACF,cAAM,QAAQ,MAAM,KAAK,OAAO,QAAQ,sBAAsB,EAAE,OAAO,EAAE,GAAG,EAAE,CAAC;AAC/E,YAAI,MAAO,QAAO;AAAA,MACpB,QAAQ;AAAA,MAER;AAAA,IACF;AACA,WAAO,KAAK,SAAS,IAAI,EAAE,KAAK;AAAA,EAClC;AAAA,EAEA,MAAM,cAAc,IAAY,OAA0E;AACxG,UAAM,WAAW,MAAM,KAAK,WAAW,EAAE;AACzC,QAAI,CAAC,SAAU,QAAO;AACtB,UAAM,SAA8B;AAAA,MAClC,GAAG;AAAA,MACH,GAAG;AAAA,MACH;AAAA,MACA,aAAY,oBAAI,KAAK,GAAE,YAAY;AAAA,IACrC;AACA,SAAK,SAAS,IAAI,IAAI,MAAM;AAC5B,QAAI,KAAK,QAAQ;AACf,UAAI;AACF,cAAM,KAAK,OAAO,OAAO,sBAAsB,QAAe,EAAE,OAAO,EAAE,GAAG,EAAE,CAAQ;AAAA,MACxF,QAAQ;AAAA,MAER;AAAA,IACF;AACA,WAAO;AAAA,EACT;AAAA,EAEA,MAAM,cAAc,IAA2B;AAC7C,SAAK,SAAS,OAAO,EAAE;AACvB,QAAI,KAAK,QAAQ;AACf,UAAI;AACF,cAAM,KAAK,OAAO,OAAO,sBAAsB,EAAE,OAAO,EAAE,GAAG,EAAE,CAAQ;AAAA,MACzE,QAAQ;AAAA,MAER;AAAA,IACF;AAAA,EACF;AACF;;;ACxLA,SAAS,cAAAC,mBAAkB;AAkCpB,SAAS,sBACd,YACA,SACA,OACA,OAA6B,CAAC,GACxB;AACN,QAAM,WAAW,KAAK,YAAY;AAClC,QAAM,eAAe,KAAK,gBAAgB;AAC1C,QAAM,aAAa,KAAK,cAAc;AAKtC,aAAW,KAAK,GAAG,QAAQ,qBAAqB,OAAO,KAAmB,QAAuB;AAC/F,QAAI;AACF,YAAM,EAAE,UAAU,UAAU,MAAM,OAAO,OAAO,IAAI,IAAI,QAAQ,CAAC;AACjE,UAAI,CAAC,YAAY,CAAC,YAAY,QAAQ,MAAM;AAC1C,YAAI,OAAO,GAAG,EAAE,KAAK,EAAE,OAAO,4CAA4C,CAAC;AAC3E;AAAA,MACF;AAEA,YAAM,SAASA,YAAW;AAC1B,YAAM,MAAM,SAAS,SAAS,QAAQ,QAAQ,QAAQ;AAGtD,YAAM,MAAM,WAAW;AAAA,QACrB,IAAI;AAAA,QACJ;AAAA,QACA,MAAM;AAAA,QACN,WAAW;AAAA,QACX;AAAA,QACA,OAAO,SAAS;AAAA,QAChB;AAAA,QACA,KAAK;AAAA,QACL,QAAQ;AAAA,MACV,CAAC;AAGD,UAAI;AACJ,UAAI,SAAyB;AAC7B,UAAI,UAAkC,EAAE,gBAAgB,SAAS;AACjE,UAAI,YAAY;AAEhB,UAAI,QAAQ,oBAAoB;AAC9B,cAAM,OAAO,MAAM,QAAQ,mBAAmB,KAAK,cAAc,EAAE,aAAa,SAAS,CAAC;AAC1F,oBAAY,KAAK;AACjB,iBAAS,KAAK;AACd,YAAI,KAAK,QAAS,WAAU,KAAK;AACjC,oBAAY,KAAK;AAAA,MACnB,OAAO;AAEL,oBAAY,GAAG,QAAQ,eAAe,MAAM;AAAA,MAC9C;AAEA,UAAI,KAAK;AAAA,QACP,MAAM;AAAA,UACJ;AAAA,UACA;AAAA,UACA;AAAA,UACA;AAAA,UACA;AAAA,UACA,aAAa,GAAG,QAAQ,UAAU,MAAM;AAAA,QAC1C;AAAA,MACF,CAAC;AAAA,IACH,SAAS,KAAU;AACjB,UAAI,OAAO,GAAG,EAAE,KAAK,EAAE,OAAO,IAAI,WAAW,iBAAiB,CAAC;AAAA,IACjE;AAAA,EACF,CAAC;AAKD,aAAW,KAAK,GAAG,QAAQ,oBAAoB,OAAO,KAAmB,QAAuB;AAC9F,QAAI;AACF,YAAM,EAAE,QAAQ,KAAK,IAAI,IAAI,QAAQ,CAAC;AACtC,UAAI,CAAC,QAAQ;AACX,YAAI,OAAO,GAAG,EAAE,KAAK,EAAE,OAAO,qBAAqB,CAAC;AACpD;AAAA,MACF;AAEA,YAAM,OAAO,MAAM,MAAM,QAAQ,MAAM;AACvC,UAAI,CAAC,MAAM;AACT,YAAI,OAAO,GAAG,EAAE,KAAK,EAAE,OAAO,iBAAiB,CAAC;AAChD;AAAA,MACF;AAEA,YAAM,UAAU,MAAM,MAAM,WAAW,QAAQ;AAAA,QAC7C,QAAQ;AAAA,QACR,MAAM,QAAQ;AAAA,MAChB,CAAC;AAED,UAAI,KAAK;AAAA,QACP,MAAM;AAAA,UACJ,MAAM,QAAS;AAAA,UACf,MAAM,QAAS;AAAA,UACf,MAAM,QAAS,QAAQ;AAAA,UACvB,UAAU,QAAS,aAAa;AAAA,UAChC,cAAc,QAAS,eAAc,oBAAI,KAAK,GAAE,YAAY;AAAA,UAC5D,SAAS,QAAS,eAAc,oBAAI,KAAK,GAAE,YAAY;AAAA,UACvD,MAAM,QAAS;AAAA,QACjB;AAAA,MACF,CAAC;AAAA,IACH,SAAS,KAAU;AACjB,UAAI,OAAO,GAAG,EAAE,KAAK,EAAE,OAAO,IAAI,WAAW,iBAAiB,CAAC;AAAA,IACjE;AAAA,EACF,CAAC;AAKD,aAAW,KAAK,GAAG,QAAQ,mBAAmB,OAAO,KAAmB,QAAuB;AAC7F,QAAI;AACF,YAAM,EAAE,UAAU,UAAU,WAAW,WAAW,cAAc,OAAO,QAAQ,SAAS,IAAI,IAAI,QAAQ,CAAC;AACzG,UAAI,CAAC,YAAY,CAAC,YAAY,CAAC,WAAW;AACxC,YAAI,OAAO,GAAG,EAAE,KAAK,EAAE,OAAO,iDAAiD,CAAC;AAChF;AAAA,MACF;AAEA,YAAM,YAAY,KAAK,IAAI,gBAAgB,SAAS,OAAO;AAC3D,YAAM,cAAc,KAAK,KAAK,YAAY,SAAS;AAEnD,YAAM,SAASA,YAAW;AAC1B,YAAM,MAAM,SAAS,SAAS,QAAQ,QAAQ,QAAQ;AAGtD,YAAM,MAAM,WAAW;AAAA,QACrB,IAAI;AAAA,QACJ;AAAA,QACA,MAAM;AAAA,QACN,WAAW;AAAA,QACX,MAAM;AAAA,QACN,OAAO,SAAS;AAAA,QAChB;AAAA,QACA,KAAK;AAAA,QACL,QAAQ;AAAA,QACR,UAAU,WAAW,KAAK,UAAU,QAAQ,IAAI;AAAA,MAClD,CAAC;AAGD,UAAI;AACJ,UAAI,QAAQ,uBAAuB;AACjC,0BAAkB,MAAM,QAAQ,sBAAsB,KAAK,EAAE,aAAa,UAAU,SAAS,CAAC;AAE9F,YAAI,kBAAkB,WAAW,OAAQ,QAAgB,iBAAiB,YAAY;AACpF,UAAC,QAAgB,aAAa,iBAAiB,GAAG;AAAA,QACpD;AAAA,MACF;AAEA,YAAM,WAAW,mBAAmBA,YAAW,EAAE,QAAQ,MAAM,EAAE;AACjE,YAAM,cAAcA,YAAW;AAC/B,YAAM,YAAY,IAAI,KAAK,KAAK,IAAI,IAAI,aAAa,GAAI,EAAE,YAAY;AAEvE,YAAM,MAAM,cAAc;AAAA,QACxB,IAAI;AAAA,QACJ,SAAS;AAAA,QACT;AAAA,QACA;AAAA,QACA,WAAW;AAAA,QACX,YAAY;AAAA,QACZ,YAAY;AAAA,QACZ,cAAc;AAAA,QACd,cAAc;AAAA,QACd,mBAAmB;AAAA,QACnB,OAAO,SAAS;AAAA,QAChB;AAAA,QACA,UAAU,WAAW,KAAK,UAAU,QAAQ,IAAI;AAAA,QAChD,QAAQ;AAAA,QACR,YAAY;AAAA,MACd,CAAC;AAED,UAAI,KAAK;AAAA,QACP,MAAM;AAAA,UACJ;AAAA,UACA;AAAA,UACA;AAAA,UACA;AAAA,UACA;AAAA,UACA;AAAA,QACF;AAAA,MACF,CAAC;AAAA,IACH,SAAS,KAAU;AACjB,UAAI,OAAO,GAAG,EAAE,KAAK,EAAE,OAAO,IAAI,WAAW,iBAAiB,CAAC;AAAA,IACjE;AAAA,EACF,CAAC;AAKD,aAAW,IAAI,GAAG,QAAQ,+CAA+C,OAAO,KAAmB,QAAuB;AACxH,QAAI;AACF,YAAM,EAAE,UAAU,YAAY,cAAc,IAAI,IAAI;AACpD,YAAM,aAAa,SAAS,eAAe,EAAE;AAC7C,UAAI,CAAC,YAAY,MAAM,UAAU,GAAG;AAClC,YAAI,OAAO,GAAG,EAAE,KAAK,EAAE,OAAO,uCAAuC,CAAC;AACtE;AAAA,MACF;AAEA,YAAM,UAAU,MAAM,MAAM,WAAW,QAAQ;AAC/C,UAAI,CAAC,SAAS;AACZ,YAAI,OAAO,GAAG,EAAE,KAAK,EAAE,OAAO,2BAA2B,CAAC;AAC1D;AAAA,MACF;AAGA,YAAM,QAAS,IAAI,QAAQ,gBAAgB,KAAK;AAChD,UAAI,QAAQ,gBAAgB,UAAU,QAAQ,cAAc;AAC1D,YAAI,OAAO,GAAG,EAAE,KAAK,EAAE,OAAO,uBAAuB,CAAC;AACtD;AAAA,MACF;AAGA,UAAI;AACJ,UAAI,IAAI,SAAS;AACf,eAAO,MAAM,IAAI,QAAQ;AAAA,MAC3B,WAAW,OAAO,SAAS,IAAI,IAAI,GAAG;AACpC,eAAO,IAAI;AAAA,MACb,WAAW,IAAI,gBAAgB,aAAa;AAC1C,eAAO,OAAO,KAAK,IAAI,IAAI;AAAA,MAC7B,OAAO;AACL,YAAI,OAAO,GAAG,EAAE,KAAK,EAAE,OAAO,uBAAuB,CAAC;AACtD;AAAA,MACF;AAGA,UAAI,OAAO;AACX,UAAI,QAAQ,aAAa;AACvB,eAAO,MAAM,QAAQ,YAAY,UAAU,aAAa,GAAG,IAAI;AAAA,MACjE;AAGA,YAAM,eAA4D,KAAK,MAAM,QAAQ,SAAS,IAAI;AAClG,mBAAa,KAAK,EAAE,YAAY,KAAK,CAAC;AACtC,YAAM,kBAAkB,QAAQ,mBAAmB,KAAK;AACxD,YAAM,gBAAgB,QAAQ,iBAAiB,KAAK,KAAK;AACzD,YAAM,MAAM,cAAc,UAAU;AAAA,QAClC,iBAAiB;AAAA,QACjB,eAAe;AAAA,QACf,OAAO,KAAK,UAAU,YAAY;AAAA,MACpC,CAAC;AAED,UAAI,KAAK;AAAA,QACP,MAAM;AAAA,UACJ;AAAA,UACA;AAAA,UACA,eAAe,KAAK;AAAA,QACtB;AAAA,MACF,CAAC;AAAA,IACH,SAAS,KAAU;AACjB,UAAI,OAAO,GAAG,EAAE,KAAK,EAAE,OAAO,IAAI,WAAW,iBAAiB,CAAC;AAAA,IACjE;AAAA,EACF,CAAC;AAKD,aAAW,KAAK,GAAG,QAAQ,sCAAsC,OAAO,KAAmB,QAAuB;AAChH,QAAI;AACF,YAAM,EAAE,SAAS,IAAI,IAAI;AACzB,YAAM,UAAU,MAAM,MAAM,WAAW,QAAQ;AAC/C,UAAI,CAAC,SAAS;AACZ,YAAI,OAAO,GAAG,EAAE,KAAK,EAAE,OAAO,2BAA2B,CAAC;AAC1D;AAAA,MACF;AAEA,YAAM,MAAM,cAAc,UAAU,EAAE,QAAQ,aAAa,CAAC;AAE5D,YAAM,gBAAiB,IAAI,MAAM,SAAS,CAAC;AAC3C,YAAM,kBAAkB,cAAc,IAAI,QAAM;AAAA,QAC9C,YAAY,EAAE,aAAa;AAAA,QAC3B,MAAM,EAAE;AAAA,MACV,EAAE;AAEF,UAAI,WAAW,QAAQ;AACvB,UAAI,QAAQ,uBAAuB;AACjC,mBAAW,MAAM,QAAQ,sBAAsB,UAAU,eAAe;AAAA,MAC1E;AAGA,YAAM,MAAM,WAAW,QAAQ,SAAS,EAAE,QAAQ,aAAa,KAAK,SAAS,CAAC;AAC9E,YAAM,MAAM,cAAc,UAAU,EAAE,QAAQ,YAAY,CAAC;AAE3D,UAAI,KAAK;AAAA,QACP,MAAM;AAAA,UACJ,QAAQ,QAAQ;AAAA,UAChB,KAAK;AAAA,UACL,MAAM,QAAQ;AAAA,UACd,UAAU,QAAQ,aAAa;AAAA,UAC/B,KAAK,GAAG,QAAQ,UAAU,QAAQ,OAAO;AAAA,QAC3C;AAAA,MACF,CAAC;AAAA,IACH,SAAS,KAAU;AACjB,UAAI,OAAO,GAAG,EAAE,KAAK,EAAE,OAAO,IAAI,WAAW,iBAAiB,CAAC;AAAA,IACjE;AAAA,EACF,CAAC;AAKD,aAAW,IAAI,GAAG,QAAQ,sCAAsC,OAAO,KAAmB,QAAuB;AAC/G,QAAI;AACF,YAAM,EAAE,SAAS,IAAI,IAAI;AACzB,YAAM,UAAU,MAAM,MAAM,WAAW,QAAQ;AAC/C,UAAI,CAAC,SAAS;AACZ,YAAI,OAAO,GAAG,EAAE,KAAK,EAAE,OAAO,2BAA2B,CAAC;AAC1D;AAAA,MACF;AAEA,YAAM,iBAAiB,QAAQ,mBAAmB;AAClD,YAAM,eAAe,QAAQ,iBAAiB;AAC9C,YAAM,kBAAkB,QAAQ,aAAa,IACzC,KAAK,IAAI,KAAK,KAAK,MAAO,eAAe,QAAQ,aAAc,GAAG,CAAC,IACnE;AAEJ,UAAI,KAAK;AAAA,QACP,MAAM;AAAA,UACJ,UAAU,QAAQ;AAAA,UAClB,QAAQ,QAAQ;AAAA,UAChB,UAAU,QAAQ;AAAA,UAClB,WAAW,QAAQ;AAAA,UACnB;AAAA,UACA,aAAa,QAAQ;AAAA,UACrB;AAAA,UACA;AAAA,UACA,QAAQ,QAAQ;AAAA,UAChB,WAAW,QAAQ;AAAA,UACnB,WAAW,QAAQ;AAAA,QACrB;AAAA,MACF,CAAC;AAAA,IACH,SAAS,KAAU;AACjB,UAAI,OAAO,GAAG,EAAE,KAAK,EAAE,OAAO,IAAI,WAAW,iBAAiB,CAAC;AAAA,IACjE;AAAA,EACF,CAAC;AAKD,aAAW,IAAI,GAAG,QAAQ,sBAAsB,OAAO,KAAmB,QAAuB;AAC/F,QAAI;AACF,YAAM,EAAE,OAAO,IAAI,IAAI;AACvB,YAAM,OAAO,MAAM,MAAM,QAAQ,MAAM;AACvC,UAAI,CAAC,QAAQ,KAAK,WAAW,aAAa;AACxC,YAAI,OAAO,GAAG,EAAE,KAAK,EAAE,OAAO,kCAAkC,CAAC;AACjE;AAAA,MACF;AAEA,UAAI;AACJ,UAAI,QAAQ,sBAAsB;AAChC,cAAM,OAAO,MAAM,QAAQ,qBAAqB,KAAK,KAAK,YAAY;AACtE,cAAM,KAAK;AAAA,MACb,WAAW,QAAQ,cAAc;AAC/B,cAAM,MAAM,QAAQ,aAAa,KAAK,KAAK,YAAY;AAAA,MACzD,OAAO;AACL,cAAM,GAAG,QAAQ,gBAAgB,mBAAmB,KAAK,GAAG,CAAC;AAAA,MAC/D;AAEA,UAAI,KAAK,EAAE,IAAI,CAAC;AAAA,IAClB,SAAS,KAAU;AACjB,UAAI,OAAO,GAAG,EAAE,KAAK,EAAE,OAAO,IAAI,WAAW,iBAAiB,CAAC;AAAA,IACjE;AAAA,EACF,CAAC;AAaD,aAAW,IAAI,GAAG,QAAQ,kBAAkB,OAAO,KAAmB,QAAuB;AAC3F,QAAI;AACF,YAAM,EAAE,OAAO,IAAI,IAAI;AACvB,YAAM,OAAO,MAAM,MAAM,QAAQ,MAAM;AACvC,UAAI,CAAC,QAAQ,KAAK,WAAW,aAAa;AACxC,YAAI,OAAO,GAAG,EAAE,KAAK,EAAE,OAAO,kCAAkC,CAAC;AACjE;AAAA,MACF;AAEA,UAAI;AACJ,UAAI,QAAQ,sBAAsB;AAChC,cAAM,OAAO,MAAM,QAAQ,qBAAqB,KAAK,KAAK,YAAY;AACtE,cAAM,KAAK;AAAA,MACb,WAAW,QAAQ,cAAc;AAC/B,cAAM,MAAM,QAAQ,aAAa,KAAK,KAAK,YAAY;AAAA,MACzD,OAAO;AACL,cAAM,GAAG,QAAQ,gBAAgB,mBAAmB,KAAK,GAAG,CAAC;AAAA,MAC/D;AAEA,UAAI,OAAO,GAAG,EAAE,OAAO,YAAY,GAAG,EAAE,KAAK,EAAE;AAAA,IACjD,SAAS,KAAU;AACjB,UAAI,OAAO,GAAG,EAAE,KAAK,EAAE,OAAO,IAAI,WAAW,iBAAiB,CAAC;AAAA,IACjE;AAAA,EACF,CAAC;AAKD,aAAW,IAAI,GAAG,QAAQ,sBAAsB,OAAO,KAAmB,QAAuB;AAC/F,QAAI;AACF,YAAM,EAAE,MAAM,IAAI,IAAI;AACtB,YAAM,eAAe;AACrB,UAAI,CAAC,aAAa,aAAa;AAC7B,YAAI,OAAO,GAAG,EAAE,KAAK,EAAE,OAAO,qDAAqD,CAAC;AACpF;AAAA,MACF;AAEA,YAAM,UAAU,aAAa,YAAY,OAAO,KAAK;AACrD,UAAI;AACJ,UAAI,IAAI,SAAS;AACf,eAAO,MAAM,IAAI,QAAQ;AAAA,MAC3B,WAAW,OAAO,SAAS,IAAI,IAAI,GAAG;AACpC,eAAO,IAAI;AAAA,MACb,OAAO;AACL,YAAI,OAAO,GAAG,EAAE,KAAK,EAAE,OAAO,uBAAuB,CAAC;AACtD;AAAA,MACF;AAEA,YAAM,QAAQ,OAAO,QAAQ,GAAG,MAAM,EAAE,aAAa,QAAQ,GAAG,CAAC;AACjE,UAAI,KAAK,EAAE,IAAI,MAAM,KAAK,QAAQ,EAAE,CAAC;AAAA,IACvC,SAAS,KAAU;AACjB,YAAM,aAAa,IAAI,SAAS,SAAS,SAAS,KAAK,IAAI,SAAS,SAAS,WAAW,IAAI,MAAM;AAClG,UAAI,OAAO,UAAU,EAAE,KAAK,EAAE,OAAO,IAAI,WAAW,gBAAgB,CAAC;AAAA,IACvE;AAAA,EACF,CAAC;AAKD,aAAW,IAAI,GAAG,QAAQ,sBAAsB,OAAO,KAAmB,QAAuB;AAC/F,QAAI;AACF,YAAM,EAAE,MAAM,IAAI,IAAI;AACtB,YAAM,eAAe;AACrB,UAAI,CAAC,aAAa,aAAa;AAC7B,YAAI,OAAO,GAAG,EAAE,KAAK,EAAE,OAAO,mDAAmD,CAAC;AAClF;AAAA,MACF;AAEA,YAAM,UAAU,aAAa,YAAY,OAAO,KAAK;AACrD,YAAM,OAAO,MAAM,QAAQ,SAAS,QAAQ,CAAC;AAE7C,UAAI,OAAO,gBAAgB,QAAQ,MAAM,0BAA0B;AACnE,UAAI,OAAO,kBAAkB,OAAO,KAAK,UAAU,CAAC;AACpD,UAAI,KAAK,IAAI;AAAA,IACf,SAAS,KAAU;AACjB,YAAM,aAAa,IAAI,SAAS,SAAS,SAAS,KAAK,IAAI,SAAS,SAAS,WAAW,IAAI,MAAM;AAClG,UAAI,OAAO,UAAU,EAAE,KAAK,EAAE,OAAO,IAAI,WAAW,kBAAkB,CAAC;AAAA,IACzE;AAAA,EACF,CAAC;AACH;AAMA,SAAS,SAAS,OAAe,QAAgB,UAA0B;AACzE,QAAM,MAAM,SAAS,SAAS,GAAG,IAAI,MAAM,SAAS,MAAM,GAAG,EAAE,IAAI,IAAI;AACvE,SAAO,GAAG,KAAK,IAAI,MAAM,GAAG,GAAG;AACjC;;;AC9eA,SAAS,cAAc,aAAa;AAkB7B,IAAM,aAAa,aAAa,OAAO;AAAA,EAC5C,MAAM;AAAA,EACN,OAAO;AAAA,EACP,aAAa;AAAA,EACb,MAAM;AAAA,EACN,aAAa;AAAA,EACb,aAAa;AAAA,EACb,eAAe,CAAC,QAAQ,aAAa,QAAQ,UAAU,YAAY;AAAA,EAEnE,QAAQ;AAAA,IACN,IAAI,MAAM,KAAK;AAAA,MACb,OAAO;AAAA,MACP,UAAU;AAAA,MACV,UAAU;AAAA,IACZ,CAAC;AAAA,IAED,KAAK,MAAM,KAAK;AAAA,MACd,OAAO;AAAA,MACP,UAAU;AAAA,MACV,YAAY;AAAA,IACd,CAAC;AAAA,IAED,MAAM,MAAM,KAAK;AAAA,MACf,OAAO;AAAA,MACP,UAAU;AAAA,MACV,YAAY;AAAA,IACd,CAAC;AAAA,IAED,WAAW,MAAM,KAAK;AAAA,MACpB,OAAO;AAAA,IACT,CAAC;AAAA,IAED,MAAM,MAAM,OAAO;AAAA,MACjB,OAAO;AAAA,IACT,CAAC;AAAA,IAED,OAAO,MAAM,OAAO;AAAA,MAClB,OAAO;AAAA,MACP,SAAS;AAAA,QACP,EAAE,OAAO,QAAQ,OAAO,OAAO;AAAA,QAC/B,EAAE,OAAO,UAAU,OAAO,SAAS;AAAA,QACnC,EAAE,OAAO,UAAU,OAAO,SAAS;AAAA,QACnC,EAAE,OAAO,WAAW,OAAO,UAAU;AAAA,QACrC,EAAE,OAAO,QAAQ,OAAO,OAAO;AAAA,MACjC;AAAA,IACF,CAAC;AAAA,IAED,QAAQ,MAAM,KAAK;AAAA,MACjB,OAAO;AAAA,IACT,CAAC;AAAA,IAED,KAAK,MAAM,OAAO;AAAA,MAChB,OAAO;AAAA,MACP,SAAS;AAAA,QACP,EAAE,OAAO,WAAW,OAAO,UAAU;AAAA,QACrC,EAAE,OAAO,eAAe,OAAO,cAAc;AAAA,MAC/C;AAAA,IACF,CAAC;AAAA,IAED,QAAQ,MAAM,OAAO;AAAA,MACnB,OAAO;AAAA,MACP,UAAU;AAAA,MACV,SAAS;AAAA,QACP,EAAE,OAAO,kBAAkB,OAAO,UAAU;AAAA,QAC5C,EAAE,OAAO,aAAa,OAAO,YAAY;AAAA,QACzC,EAAE,OAAO,WAAW,OAAO,UAAU;AAAA,MACvC;AAAA,IACF,CAAC;AAAA,IAED,MAAM,MAAM,KAAK;AAAA,MACf,OAAO;AAAA,IACT,CAAC;AAAA,IAED,UAAU,MAAM,KAAK;AAAA,MACnB,OAAO;AAAA,IACT,CAAC;AAAA,IAED,UAAU,MAAM,KAAK;AAAA,MACnB,OAAO;AAAA,IACT,CAAC;AAAA,IAED,YAAY,MAAM,SAAS;AAAA,MACzB,OAAO;AAAA,IACT,CAAC;AAAA,IAED,YAAY,MAAM,SAAS;AAAA,MACzB,OAAO;AAAA,IACT,CAAC;AAAA,EACH;AACF,CAAC;;;AC3GD,SAAS,gBAAAC,eAAc,SAAAC,cAAa;AAW7B,IAAM,sBAAsBD,cAAa,OAAO;AAAA,EACrD,MAAM;AAAA,EACN,OAAO;AAAA,EACP,aAAa;AAAA,EACb,MAAM;AAAA,EACN,aAAa;AAAA,EACb,aAAa;AAAA,EACb,eAAe,CAAC,YAAY,UAAU,mBAAmB,gBAAgB,YAAY;AAAA,EAErF,QAAQ;AAAA,IACN,IAAIC,OAAM,KAAK;AAAA,MACb,OAAO;AAAA,MACP,UAAU;AAAA,MACV,UAAU;AAAA,IACZ,CAAC;AAAA,IAED,SAASA,OAAM,KAAK;AAAA,MAClB,OAAO;AAAA,MACP,UAAU;AAAA,IACZ,CAAC;AAAA,IAED,KAAKA,OAAM,KAAK;AAAA,MACd,OAAO;AAAA,MACP,UAAU;AAAA,IACZ,CAAC;AAAA,IAED,UAAUA,OAAM,KAAK;AAAA,MACnB,OAAO;AAAA,MACP,UAAU;AAAA,IACZ,CAAC;AAAA,IAED,WAAWA,OAAM,KAAK;AAAA,MACpB,OAAO;AAAA,IACT,CAAC;AAAA,IAED,YAAYA,OAAM,OAAO;AAAA,MACvB,OAAO;AAAA,MACP,UAAU;AAAA,IACZ,CAAC;AAAA,IAED,YAAYA,OAAM,OAAO;AAAA,MACvB,OAAO;AAAA,MACP,UAAU;AAAA,IACZ,CAAC;AAAA,IAED,cAAcA,OAAM,OAAO;AAAA,MACzB,OAAO;AAAA,MACP,UAAU;AAAA,IACZ,CAAC;AAAA,IAED,iBAAiBA,OAAM,OAAO;AAAA,MAC5B,OAAO;AAAA,IACT,CAAC;AAAA,IAED,eAAeA,OAAM,OAAO;AAAA,MAC1B,OAAO;AAAA,IACT,CAAC;AAAA,IAED,OAAOA,OAAM,KAAK;AAAA,MAChB,OAAO;AAAA,IACT,CAAC;AAAA,IAED,cAAcA,OAAM,KAAK;AAAA,MACvB,OAAO;AAAA,IACT,CAAC;AAAA,IAED,mBAAmBA,OAAM,KAAK;AAAA,MAC5B,OAAO;AAAA,IACT,CAAC;AAAA,IAED,OAAOA,OAAM,KAAK;AAAA,MAChB,OAAO;AAAA,IACT,CAAC;AAAA,IAED,QAAQA,OAAM,KAAK;AAAA,MACjB,OAAO;AAAA,IACT,CAAC;AAAA,IAED,UAAUA,OAAM,KAAK;AAAA,MACnB,OAAO;AAAA,IACT,CAAC;AAAA,IAED,QAAQA,OAAM,OAAO;AAAA,MACnB,OAAO;AAAA,MACP,UAAU;AAAA,MACV,SAAS;AAAA,QACP,EAAE,OAAO,eAAe,OAAO,cAAc;AAAA,QAC7C,EAAE,OAAO,cAAc,OAAO,aAAa;AAAA,QAC3C,EAAE,OAAO,aAAa,OAAO,YAAY;AAAA,QACzC,EAAE,OAAO,UAAU,OAAO,SAAS;AAAA,QACnC,EAAE,OAAO,WAAW,OAAO,UAAU;AAAA,MACvC;AAAA,IACF,CAAC;AAAA,IAED,YAAYA,OAAM,SAAS;AAAA,MACzB,OAAO;AAAA,IACT,CAAC;AAAA,IAED,YAAYA,OAAM,SAAS;AAAA,MACzB,OAAO;AAAA,IACT,CAAC;AAAA,IAED,YAAYA,OAAM,SAAS;AAAA,MACzB,OAAO;AAAA,IACT,CAAC;AAAA,EACH;AACF,CAAC;;;AC3FM,IAAM,0BAAN,MAAyD;AAAA,EAI9D,YACE,SACA,QACA;AACA,SAAK,QAAQ;AACb,SAAK,SAAS;AAAA,EAChB;AAAA;AAAA,EAGA,KAAK,MAA6B;AAChC,UAAM,WAAW,KAAK;AACtB,SAAK,QAAQ;AACb,SAAK,SAAS,UAAU,IAAI;AAAA,EAC9B;AAAA;AAAA,EAGA,WAA4B;AAC1B,WAAO,KAAK;AAAA,EACd;AAAA,EAEA,OAAO,KAAa,MAA+B,SAA+C;AAChG,WAAO,KAAK,MAAM,OAAO,KAAK,MAAM,OAAO;AAAA,EAC7C;AAAA,EAEA,SAAS,KAA8B;AACrC,WAAO,KAAK,MAAM,SAAS,GAAG;AAAA,EAChC;AAAA,EAEA,OAAO,KAA4B;AACjC,WAAO,KAAK,MAAM,OAAO,GAAG;AAAA,EAC9B;AAAA,EAEA,OAAO,KAA+B;AACpC,WAAO,KAAK,MAAM,OAAO,GAAG;AAAA,EAC9B;AAAA,EAEA,QAAQ,KAAuC;AAC7C,WAAO,KAAK,MAAM,QAAQ,GAAG;AAAA,EAC/B;AAAA,EAEA,KAAK,QAA4C;AAC/C,QAAI,OAAO,KAAK,MAAM,SAAS,YAAY;AACzC,aAAO,QAAQ,OAAO,IAAI,MAAM,gDAAgD,CAAC;AAAA,IACnF;AACA,WAAO,KAAK,MAAM,KAAK,MAAM;AAAA,EAC/B;AAAA,EAEA,aAAa,KAAa,WAAoC;AAC5D,QAAI,OAAO,KAAK,MAAM,iBAAiB,YAAY;AACjD,aAAO,QAAQ,OAAO,IAAI,MAAM,wDAAwD,CAAC;AAAA,IAC3F;AACA,WAAO,KAAK,MAAM,aAAa,KAAK,SAAS;AAAA,EAC/C;AAAA,EAEA,mBACE,KACA,WACA,SACoC;AACpC,QAAI,OAAO,KAAK,MAAM,uBAAuB,YAAY;AACvD,aAAO,QAAQ,OAAO,IAAI,MAAM,8DAA8D,CAAC;AAAA,IACjG;AACA,WAAO,KAAK,MAAM,mBAAmB,KAAK,WAAW,OAAO;AAAA,EAC9D;AAAA,EAEA,qBAAqB,KAAa,WAAyD;AACzF,QAAI,OAAO,KAAK,MAAM,yBAAyB,YAAY;AACzD,aAAO,QAAQ,OAAO,IAAI,MAAM,gEAAgE,CAAC;AAAA,IACnG;AACA,WAAO,KAAK,MAAM,qBAAqB,KAAK,SAAS;AAAA,EACvD;AAAA,EAEA,sBAAsB,KAAa,SAAiD;AAClF,QAAI,OAAO,KAAK,MAAM,0BAA0B,YAAY;AAC1D,aAAO,QAAQ,OAAO,IAAI,MAAM,iEAAiE,CAAC;AAAA,IACpG;AACA,WAAO,KAAK,MAAM,sBAAsB,KAAK,OAAO;AAAA,EACtD;AAAA,EAEA,YAAY,UAAkB,YAAoB,MAA+B;AAC/E,QAAI,OAAO,KAAK,MAAM,gBAAgB,YAAY;AAChD,aAAO,QAAQ,OAAO,IAAI,MAAM,uDAAuD,CAAC;AAAA,IAC1F;AACA,WAAO,KAAK,MAAM,YAAY,UAAU,YAAY,IAAI;AAAA,EAC1D;AAAA,EAEA,sBACE,UACA,OACiB;AACjB,QAAI,OAAO,KAAK,MAAM,0BAA0B,YAAY;AAC1D,aAAO,QAAQ,OAAO,IAAI,MAAM,iEAAiE,CAAC;AAAA,IACpG;AACA,WAAO,KAAK,MAAM,sBAAsB,UAAU,KAAK;AAAA,EACzD;AAAA,EAEA,mBAAmB,UAAiC;AAClD,QAAI,OAAO,KAAK,MAAM,uBAAuB,YAAY;AACvD,aAAO,QAAQ,OAAO,IAAI,MAAM,8DAA8D,CAAC;AAAA,IACjG;AACA,WAAO,KAAK,MAAM,mBAAmB,QAAQ;AAAA,EAC/C;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAWA,YAAY,OAAe,YAAiF;AAC1G,UAAM,QAAQ,KAAK;AAGnB,QAAI,OAAO,MAAM,gBAAgB,YAAY;AAC3C,YAAM,IAAI,MAAM,uDAAuD;AAAA,IACzE;AACA,WAAO,MAAM,YAAY,OAAO,UAAU;AAAA,EAC5C;AACF;;;ANjEO,IAAM,uBAAN,MAA6C;AAAA,EAUlD,YAAY,UAAuC,CAAC,GAAG;AATvD,gBAAO;AACP,mBAAU;AACV,gBAAO;AAGP,SAAQ,UAA0C;AAClD,SAAQ,QAAqC;AAC7C,SAAQ,UAA2B,IAAIC,qBAAoB;AAGzD,SAAK,UAAU,EAAE,SAAS,SAAS,GAAG,QAAQ;AAAA,EAChD;AAAA;AAAA,EAGA,MAAc,uBAAuB,QAAuD;AAC1F,UAAM,UAAU,OAAO,OAAO,WAAW,OAAO;AAChD,QAAI,YAAY,MAAM;AACpB,YAAM,SAAS,OAAO;AACtB,YAAM,SAAS,OAAO;AACtB,UAAI,CAAC,UAAU,CAAC,QAAQ;AACtB,cAAM,IAAI,MAAM,mEAAmE;AAAA,MACrF;AACA,YAAM,OAAgC;AAAA,QACpC;AAAA,QACA;AAAA,QACA,UAAW,OAAO,eAAsC;AAAA,QACxD,aAAc,OAAO,oBAA2C;AAAA,QAChE,iBAAkB,OAAO,wBAA+C;AAAA,QACxE,gBAAgB,CAAC,CAAC,OAAO;AAAA,QACzB,SAAS,KAAK;AAAA,MAChB;AACA,aAAO,IAAI,iBAAiB,IAAI;AAAA,IAClC;AACA,UAAM,UAAW,OAAO,cAAqC;AAC7D,WAAO,IAAI,oBAAoB;AAAA,MAC7B,UAAU,KAAK,QAAQ,YAAY;AAAA,MACnC,GAAI,KAAK,QAAQ,SAAS,CAAC;AAAA;AAAA,MAE3B;AAAA,MACA,SAAS,KAAK;AAAA,IAChB,CAA+B;AAAA,EACjC;AAAA,EAEA,MAAM,KAAK,KAAmC;AAC5C,SAAK,UAAU,eAAe,KAAK,KAAK,QAAQ,OAAO;AACvD,UAAM,UAAU,KAAK,QAAQ;AAC7B,QAAI;AACJ,QAAI,YAAY,MAAM;AAEpB,YAAM,EAAE,kBAAkB,OAAO,IAAI,MAAM;AAC3C,YAAM,SAAS,KAAK,QAAQ;AAC5B,UAAI,CAAC,QAAQ;AACX,cAAM,IAAI,MAAM,oEAAoE;AAAA,MACtF;AACA,gBAAU,IAAI,OAAO,EAAE,GAAG,QAAQ,SAAS,KAAK,QAAQ,CAAC;AAAA,IAC3D,OAAO;AACL,YAAM,UAAU,KAAK,QAAQ,OAAO,WAAW;AAC/C,YAAM,WAAW,KAAK,QAAQ,YAAY;AAC1C,gBAAU,IAAI,oBAAoB,EAAE,SAAS,UAAU,GAAG,KAAK,QAAQ,OAAO,SAAS,KAAK,QAAQ,CAAC;AAAA,IACvG;AAEA,SAAK,UAAU,IAAI,wBAAwB,SAAS,CAAC,MAAM,SAAS;AAClE,YAAM,WAAY,MAAc,aAAa,QAAQ;AACrD,YAAM,WAAY,MAAc,aAAa,QAAQ;AACrD,UAAI,OAAO;AAAA,QACT,kDAAkD,QAAQ,WAAM,QAAQ;AAAA,MAE1E;AAAA,IACF,CAAC;AAED,QAAI,gBAAgB,gBAAgB,KAAK,OAAO;AAChD,QAAI,OAAO;AAAA,MACT,oCAAoC,OAAO,wCAAwC,KAAK,QAAQ,aAAa,QAAQ,SAAS;AAAA,IAChI;AAGA,QAAI;AACF,UAAI,WAAuC,UAAU,EAAE,SAAS;AAAA,QAC9D,IAAI;AAAA,QACJ,MAAM;AAAA,QACN,SAAS;AAAA,QACT,MAAM;AAAA,QACN,OAAO;AAAA,QACP,SAAS,CAAC,YAAY,mBAAmB;AAAA,MAC3C,CAAC;AAAA,IACH,QAAQ;AAAA,IAER;AAAA,EACF;AAAA,EAEA,MAAM,MAAM,KAAmC;AAC7C,QAAI,KAAK,gBAAgB,YAAY;AAEnC,UAAI,KAAK,QAAQ,mBAAmB,OAAO;AACzC,YAAI,aAAiC;AACrC,YAAI;AACF,uBAAa,IAAI,WAAwB,aAAa;AAAA,QACxD,QAAQ;AAAA,QAER;AAEA,YAAI,cAAc,KAAK,SAAS;AAC9B,cAAI,SAA6B;AACjC,cAAI;AACF,qBAAS,IAAI,WAAwB,UAAU;AAAA,UACjD,QAAQ;AAAA,UAER;AACA,eAAK,QAAQ,IAAI,qBAAqB,MAAM;AAE5C,gCAAsB,YAAY,KAAK,SAAS,KAAK,OAAO;AAAA,YAC1D,UAAU,KAAK,QAAQ,YAAY;AAAA,YACnC,cAAc,KAAK,QAAQ;AAAA,YAC3B,YAAY,KAAK,QAAQ;AAAA,UAC3B,CAAC;AAED,cAAI,OAAO;AAAA,YACT,sDACG,KAAK,QAAQ,YAAY;AAAA,UAC9B;AAAA,QACF,WAAW,CAAC,YAAY;AACtB,cAAI,OAAO;AAAA,YACT;AAAA,UAEF;AAAA,QACF;AAAA,MACF;AAKA,UAAI,KAAK,QAAQ,mBAAmB,MAAO;AAC3C,UAAI;AACF,cAAM,WAAW,IAAI,WAAgB,UAAU;AAC/C,YAAI,CAAC,YAAY,OAAO,SAAS,iBAAiB,WAAY;AAE9D,cAAM,gBAAgB,YAAY;AAChC,cAAI,CAAC,KAAK,QAAS;AACnB,cAAI;AACF,kBAAM,UAAU,MAAM,SAAS,aAAa,SAAS;AACrD,kBAAM,SAA8B,CAAC;AACrC,uBAAW,CAAC,GAAG,CAAC,KAAK,OAAO,QAAQ,QAAQ,MAA6B,GAAG;AAC1E,qBAAO,CAAC,IAAI,GAAG;AAAA,YACjB;AAEA,kBAAM,SAAS,OAAO,OAAO,MAAM,EAAE,KAAK,CAAC,MAAM,MAAM,UAAa,MAAM,QAAQ,MAAM,EAAE;AAC1F,gBAAI,CAAC,OAAQ;AACb,kBAAM,OAAO,MAAM,KAAK,uBAAuB,MAAM;AACrD,iBAAK,QAAQ,KAAK,IAAI;AAAA,UACxB,SAAS,KAAU;AACjB,gBAAI,OAAO;AAAA,cACT,8DAA8D,KAAK,WAAW;AAAA,YAChF;AAAA,UACF;AAAA,QACF;AACA,cAAM,cAAc;AACpB,YAAI,OAAO,SAAS,cAAc,YAAY;AAC5C,mBAAS,UAAU,WAAW,MAAM;AAClC,iBAAK,cAAc;AAAA,UACrB,CAAC;AACD,cAAI,OAAO,KAAK,uEAAuE;AAAA,QACzF;AAGA,YAAI,OAAO,SAAS,mBAAmB,cAAc,KAAK,SAAS;AACjE,gBAAM,QAAQ,KAAK;AACnB,mBAAS,eAAe,WAAW,QAAQ,OAAO,EAAE,QAAQ,QAAQ,MAAW;AAK7E,kBAAM,YAAY,iBAAiB,OAAO;AAC1C,kBAAM,SAAkC,EAAE,GAAI,UAAU,CAAC,GAAI,GAAG,UAAU;AAC1E,kBAAM,WAAW,yBAAyB,KAAK,IAAI,CAAC,IAAI,KAAK,OAAO,EAAE,SAAS,EAAE,EAAE,MAAM,GAAG,EAAE,CAAC;AAC/F,kBAAM,aAAa,OAAO,KAAK,UAAS,oBAAI,KAAK,GAAE,YAAY,CAAC,IAAI,OAAO;AAC3E,gBAAI;AAIF,kBAAI,SAA0B;AAC9B,kBAAI,UAAU,OAAO,KAAK,MAAM,EAAE,SAAS,GAAG;AAC5C,oBAAI;AACF,2BAAS,MAAM,KAAK,uBAAuB,MAAM;AAAA,gBACnD,SAAS,KAAU;AACjB,yBAAO,EAAE,IAAI,OAAO,UAAU,SAAS,SAAS,KAAK,WAAW,OAAO,GAAG,EAAE;AAAA,gBAC9E;AAAA,cACF;AACA,oBAAM,OAAO,OAAO,UAAU,YAAY,EAAE,aAAa,aAAa,CAAC;AACvE,oBAAM,MAAM,MAAM,OAAO,SAAS,QAAQ;AAC1C,kBAAI,CAAC,OAAO,CAAC,OAAO,SAAS,GAAG,KAAK,IAAI,SAAS,OAAO,MAAM,WAAW,SAAS,OAAO,GAAG;AAC3F,uBAAO,EAAE,IAAI,OAAO,UAAU,SAAS,SAAS,uCAAuC;AAAA,cACzF;AACA,oBAAM,OAAO,OAAO,QAAQ;AAC5B,oBAAM,UAAU,OAAO,OAAO,WAAW,KAAK,QAAQ,WAAW,OAAO;AACxE,qBAAO;AAAA,gBACL,IAAI;AAAA,gBACJ,UAAU;AAAA,gBACV,SAAS,yCAAyC,OAAO;AAAA,cAC3D;AAAA,YACF,SAAS,KAAU;AAEjB,kBAAI;AAAE,sBAAO,MAA0B,OAAO,QAAQ;AAAA,cAAG,QAAQ;AAAA,cAAe;AAChF,qBAAO,EAAE,IAAI,OAAO,UAAU,SAAS,SAAS,KAAK,WAAW,OAAO,GAAG,EAAE;AAAA,YAC9E;AAAA,UACF,CAAC;AACD,cAAI,OAAO,KAAK,+DAA+D;AAAA,QACjF;AAAA,MACF,QAAQ;AAAA,MAER;AAAA,IACF,CAAC;AAAA,EACH;AACF;AAQA,SAAS,eACP,KACA,UACiB;AACjB,MAAI,SAAU,QAAO;AACrB,MAAI;AACF,UAAM,IAAI,IAAI,WAAwC,6BAA6B;AACnF,QAAI,EAAG,QAAO;AAAA,EAChB,QAAQ;AAAA,EAER;AACA,SAAO,IAAIA,qBAAoB;AACjC;AAEA,SAAS,iBAAiB,SAA2C;AACnE,MAAI,CAAC,WAAW,OAAO,YAAY,SAAU,QAAO,CAAC;AACrD,QAAM,IAAI;AACV,MAAI,EAAE,UAAU,OAAO,EAAE,WAAW,YAAY,EAAE,WAAW,MAAM;AACjE,WAAO,EAAE;AAAA,EACX;AACA,SAAO;AACT;;;AOnUA;","names":["NoopMetricsRegistry","SEMCONV","NoopMetricsRegistry","randomUUID","ObjectSchema","Field","NoopMetricsRegistry"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@objectstack/service-storage",
|
|
3
|
-
"version": "7.1
|
|
3
|
+
"version": "7.2.1",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"description": "Storage Service for ObjectStack — implements IStorageService with local filesystem and S3 adapter skeleton",
|
|
6
6
|
"type": "module",
|
|
@@ -14,9 +14,9 @@
|
|
|
14
14
|
}
|
|
15
15
|
},
|
|
16
16
|
"dependencies": {
|
|
17
|
-
"@objectstack/core": "7.1
|
|
18
|
-
"@objectstack/observability": "7.1
|
|
19
|
-
"@objectstack/spec": "7.1
|
|
17
|
+
"@objectstack/core": "7.2.1",
|
|
18
|
+
"@objectstack/observability": "7.2.1",
|
|
19
|
+
"@objectstack/spec": "7.2.1"
|
|
20
20
|
},
|
|
21
21
|
"peerDependencies": {
|
|
22
22
|
"@aws-sdk/client-s3": "^3.0.0",
|