@jini-ai/cms 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +201 -0
- package/README.md +84 -0
- package/dist/content-types/agent-tools.d.ts +65 -0
- package/dist/content-types/agent-tools.d.ts.map +1 -0
- package/dist/content-types/agent-tools.js +172 -0
- package/dist/content-types/agent-tools.js.map +1 -0
- package/dist/content-types/cleanup.d.ts +118 -0
- package/dist/content-types/cleanup.d.ts.map +1 -0
- package/dist/content-types/cleanup.js +65 -0
- package/dist/content-types/cleanup.js.map +1 -0
- package/dist/content-types/errors.d.ts +90 -0
- package/dist/content-types/errors.d.ts.map +1 -0
- package/dist/content-types/errors.js +119 -0
- package/dist/content-types/errors.js.map +1 -0
- package/dist/content-types/field-defs.d.ts +25 -0
- package/dist/content-types/field-defs.d.ts.map +1 -0
- package/dist/content-types/field-defs.js +155 -0
- package/dist/content-types/field-defs.js.map +1 -0
- package/dist/content-types/index-provisioning.d.ts +94 -0
- package/dist/content-types/index-provisioning.d.ts.map +1 -0
- package/dist/content-types/index-provisioning.js +133 -0
- package/dist/content-types/index-provisioning.js.map +1 -0
- package/dist/content-types/index.d.ts +63 -0
- package/dist/content-types/index.d.ts.map +1 -0
- package/dist/content-types/index.js +36 -0
- package/dist/content-types/index.js.map +1 -0
- package/dist/content-types/lifecycle-dispatch.d.ts +38 -0
- package/dist/content-types/lifecycle-dispatch.d.ts.map +1 -0
- package/dist/content-types/lifecycle-dispatch.js +28 -0
- package/dist/content-types/lifecycle-dispatch.js.map +1 -0
- package/dist/content-types/lifecycle.d.ts +104 -0
- package/dist/content-types/lifecycle.d.ts.map +1 -0
- package/dist/content-types/lifecycle.js +143 -0
- package/dist/content-types/lifecycle.js.map +1 -0
- package/dist/content-types/list.d.ts +44 -0
- package/dist/content-types/list.d.ts.map +1 -0
- package/dist/content-types/list.js +13 -0
- package/dist/content-types/list.js.map +1 -0
- package/dist/content-types/repo.memory.d.ts +72 -0
- package/dist/content-types/repo.memory.d.ts.map +1 -0
- package/dist/content-types/repo.memory.js +71 -0
- package/dist/content-types/repo.memory.js.map +1 -0
- package/dist/content-types/tool-registrations.d.ts +65 -0
- package/dist/content-types/tool-registrations.d.ts.map +1 -0
- package/dist/content-types/tool-registrations.js +180 -0
- package/dist/content-types/tool-registrations.js.map +1 -0
- package/dist/content-types/types.d.ts +78 -0
- package/dist/content-types/types.d.ts.map +1 -0
- package/dist/content-types/types.js +32 -0
- package/dist/content-types/types.js.map +1 -0
- package/dist/content-types/write-service.d.ts +141 -0
- package/dist/content-types/write-service.d.ts.map +1 -0
- package/dist/content-types/write-service.js +180 -0
- package/dist/content-types/write-service.js.map +1 -0
- package/dist/core/commands/change-set.d.ts +98 -0
- package/dist/core/commands/change-set.d.ts.map +1 -0
- package/dist/core/commands/change-set.js +2 -0
- package/dist/core/commands/change-set.js.map +1 -0
- package/dist/core/commands/command.d.ts +168 -0
- package/dist/core/commands/command.d.ts.map +1 -0
- package/dist/core/commands/command.js +173 -0
- package/dist/core/commands/command.js.map +1 -0
- package/dist/core/index.d.ts +45 -0
- package/dist/core/index.d.ts.map +1 -0
- package/dist/core/index.js +43 -0
- package/dist/core/index.js.map +1 -0
- package/dist/core/ports.d.ts +139 -0
- package/dist/core/ports.d.ts.map +1 -0
- package/dist/core/ports.js +2 -0
- package/dist/core/ports.js.map +1 -0
- package/dist/core/tools/registration-kit.d.ts +308 -0
- package/dist/core/tools/registration-kit.d.ts.map +1 -0
- package/dist/core/tools/registration-kit.js +381 -0
- package/dist/core/tools/registration-kit.js.map +1 -0
- package/dist/entries/agent-tools.d.ts +78 -0
- package/dist/entries/agent-tools.d.ts.map +1 -0
- package/dist/entries/agent-tools.js +158 -0
- package/dist/entries/agent-tools.js.map +1 -0
- package/dist/entries/errors.d.ts +43 -0
- package/dist/entries/errors.d.ts.map +1 -0
- package/dist/entries/errors.js +59 -0
- package/dist/entries/errors.js.map +1 -0
- package/dist/entries/field-validation.d.ts +70 -0
- package/dist/entries/field-validation.d.ts.map +1 -0
- package/dist/entries/field-validation.js +87 -0
- package/dist/entries/field-validation.js.map +1 -0
- package/dist/entries/index.d.ts +55 -0
- package/dist/entries/index.d.ts.map +1 -0
- package/dist/entries/index.js +38 -0
- package/dist/entries/index.js.map +1 -0
- package/dist/entries/lifecycle-dispatch.d.ts +15 -0
- package/dist/entries/lifecycle-dispatch.d.ts.map +1 -0
- package/dist/entries/lifecycle-dispatch.js +16 -0
- package/dist/entries/lifecycle-dispatch.js.map +1 -0
- package/dist/entries/list.d.ts +47 -0
- package/dist/entries/list.d.ts.map +1 -0
- package/dist/entries/list.js +12 -0
- package/dist/entries/list.js.map +1 -0
- package/dist/entries/repo.memory.d.ts +64 -0
- package/dist/entries/repo.memory.d.ts.map +1 -0
- package/dist/entries/repo.memory.js +73 -0
- package/dist/entries/repo.memory.js.map +1 -0
- package/dist/entries/tool-registrations.d.ts +52 -0
- package/dist/entries/tool-registrations.d.ts.map +1 -0
- package/dist/entries/tool-registrations.js +141 -0
- package/dist/entries/tool-registrations.js.map +1 -0
- package/dist/entries/types.d.ts +55 -0
- package/dist/entries/types.d.ts.map +1 -0
- package/dist/entries/types.js +2 -0
- package/dist/entries/types.js.map +1 -0
- package/dist/entries/write-service.d.ts +202 -0
- package/dist/entries/write-service.d.ts.map +1 -0
- package/dist/entries/write-service.js +204 -0
- package/dist/entries/write-service.js.map +1 -0
- package/dist/identity/admin-crud-service.d.ts +252 -0
- package/dist/identity/admin-crud-service.d.ts.map +1 -0
- package/dist/identity/admin-crud-service.js +437 -0
- package/dist/identity/admin-crud-service.js.map +1 -0
- package/dist/identity/agent-tool-input.d.ts +58 -0
- package/dist/identity/agent-tool-input.d.ts.map +1 -0
- package/dist/identity/agent-tool-input.js +126 -0
- package/dist/identity/agent-tool-input.js.map +1 -0
- package/dist/identity/agent-tools.d.ts +101 -0
- package/dist/identity/agent-tools.d.ts.map +1 -0
- package/dist/identity/agent-tools.js +290 -0
- package/dist/identity/agent-tools.js.map +1 -0
- package/dist/identity/auth-service.d.ts +87 -0
- package/dist/identity/auth-service.d.ts.map +1 -0
- package/dist/identity/auth-service.js +141 -0
- package/dist/identity/auth-service.js.map +1 -0
- package/dist/identity/authorize.d.ts +90 -0
- package/dist/identity/authorize.d.ts.map +1 -0
- package/dist/identity/authorize.js +94 -0
- package/dist/identity/authorize.js.map +1 -0
- package/dist/identity/grant-service.d.ts +198 -0
- package/dist/identity/grant-service.d.ts.map +1 -0
- package/dist/identity/grant-service.js +361 -0
- package/dist/identity/grant-service.js.map +1 -0
- package/dist/identity/hasher.d.ts +39 -0
- package/dist/identity/hasher.d.ts.map +1 -0
- package/dist/identity/hasher.js +39 -0
- package/dist/identity/hasher.js.map +1 -0
- package/dist/identity/index.d.ts +59 -0
- package/dist/identity/index.d.ts.map +1 -0
- package/dist/identity/index.js +51 -0
- package/dist/identity/index.js.map +1 -0
- package/dist/identity/password-policy.d.ts +58 -0
- package/dist/identity/password-policy.d.ts.map +1 -0
- package/dist/identity/password-policy.js +67 -0
- package/dist/identity/password-policy.js.map +1 -0
- package/dist/identity/permission-migrations.d.ts +86 -0
- package/dist/identity/permission-migrations.d.ts.map +1 -0
- package/dist/identity/permission-migrations.js +67 -0
- package/dist/identity/permission-migrations.js.map +1 -0
- package/dist/identity/permissions.d.ts +35 -0
- package/dist/identity/permissions.d.ts.map +1 -0
- package/dist/identity/permissions.js +603 -0
- package/dist/identity/permissions.js.map +1 -0
- package/dist/identity/ports.d.ts +201 -0
- package/dist/identity/ports.d.ts.map +1 -0
- package/dist/identity/ports.js +2 -0
- package/dist/identity/ports.js.map +1 -0
- package/dist/identity/repo.memory.d.ts +164 -0
- package/dist/identity/repo.memory.d.ts.map +1 -0
- package/dist/identity/repo.memory.js +224 -0
- package/dist/identity/repo.memory.js.map +1 -0
- package/dist/identity/seed.d.ts +42 -0
- package/dist/identity/seed.d.ts.map +1 -0
- package/dist/identity/seed.js +277 -0
- package/dist/identity/seed.js.map +1 -0
- package/dist/identity/tool-registrations.d.ts +50 -0
- package/dist/identity/tool-registrations.d.ts.map +1 -0
- package/dist/identity/tool-registrations.js +419 -0
- package/dist/identity/tool-registrations.js.map +1 -0
- package/dist/identity/types.d.ts +185 -0
- package/dist/identity/types.d.ts.map +1 -0
- package/dist/identity/types.js +64 -0
- package/dist/identity/types.js.map +1 -0
- package/dist/identity/username.d.ts +12 -0
- package/dist/identity/username.d.ts.map +1 -0
- package/dist/identity/username.js +14 -0
- package/dist/identity/username.js.map +1 -0
- package/dist/media/agent-tools.d.ts +66 -0
- package/dist/media/agent-tools.d.ts.map +1 -0
- package/dist/media/agent-tools.js +131 -0
- package/dist/media/agent-tools.js.map +1 -0
- package/dist/media/blob-gc-lock.d.ts +37 -0
- package/dist/media/blob-gc-lock.d.ts.map +1 -0
- package/dist/media/blob-gc-lock.js +55 -0
- package/dist/media/blob-gc-lock.js.map +1 -0
- package/dist/media/blob-gc.d.ts +246 -0
- package/dist/media/blob-gc.d.ts.map +1 -0
- package/dist/media/blob-gc.js +254 -0
- package/dist/media/blob-gc.js.map +1 -0
- package/dist/media/blob-key.d.ts +28 -0
- package/dist/media/blob-key.d.ts.map +1 -0
- package/dist/media/blob-key.js +11 -0
- package/dist/media/blob-key.js.map +1 -0
- package/dist/media/blob-store.fs.d.ts +23 -0
- package/dist/media/blob-store.fs.d.ts.map +1 -0
- package/dist/media/blob-store.fs.js +48 -0
- package/dist/media/blob-store.fs.js.map +1 -0
- package/dist/media/blob-store.memory.d.ts +22 -0
- package/dist/media/blob-store.memory.d.ts.map +1 -0
- package/dist/media/blob-store.memory.js +23 -0
- package/dist/media/blob-store.memory.js.map +1 -0
- package/dist/media/content-type-sniffer.d.ts +49 -0
- package/dist/media/content-type-sniffer.d.ts.map +1 -0
- package/dist/media/content-type-sniffer.js +141 -0
- package/dist/media/content-type-sniffer.js.map +1 -0
- package/dist/media/image-transformer.d.ts +45 -0
- package/dist/media/image-transformer.d.ts.map +1 -0
- package/dist/media/image-transformer.js +23 -0
- package/dist/media/image-transformer.js.map +1 -0
- package/dist/media/image-transformer.sharp.d.ts +26 -0
- package/dist/media/image-transformer.sharp.d.ts.map +1 -0
- package/dist/media/image-transformer.sharp.js +91 -0
- package/dist/media/image-transformer.sharp.js.map +1 -0
- package/dist/media/index.d.ts +43 -0
- package/dist/media/index.d.ts.map +1 -0
- package/dist/media/index.js +31 -0
- package/dist/media/index.js.map +1 -0
- package/dist/media/media-service.d.ts +244 -0
- package/dist/media/media-service.d.ts.map +1 -0
- package/dist/media/media-service.js +345 -0
- package/dist/media/media-service.js.map +1 -0
- package/dist/media/ports.d.ts +142 -0
- package/dist/media/ports.d.ts.map +1 -0
- package/dist/media/ports.js +2 -0
- package/dist/media/ports.js.map +1 -0
- package/dist/media/rendition-service.d.ts +66 -0
- package/dist/media/rendition-service.d.ts.map +1 -0
- package/dist/media/rendition-service.js +135 -0
- package/dist/media/rendition-service.js.map +1 -0
- package/dist/media/repo.memory.d.ts +107 -0
- package/dist/media/repo.memory.d.ts.map +1 -0
- package/dist/media/repo.memory.js +131 -0
- package/dist/media/repo.memory.js.map +1 -0
- package/dist/media/tool-registrations.d.ts +42 -0
- package/dist/media/tool-registrations.d.ts.map +1 -0
- package/dist/media/tool-registrations.js +121 -0
- package/dist/media/tool-registrations.js.map +1 -0
- package/dist/media/transform-lock.d.ts +36 -0
- package/dist/media/transform-lock.d.ts.map +1 -0
- package/dist/media/transform-lock.js +50 -0
- package/dist/media/transform-lock.js.map +1 -0
- package/dist/media/transform-registry.d.ts +103 -0
- package/dist/media/transform-registry.d.ts.map +1 -0
- package/dist/media/transform-registry.js +87 -0
- package/dist/media/transform-registry.js.map +1 -0
- package/dist/media/transform-types.d.ts +82 -0
- package/dist/media/transform-types.d.ts.map +1 -0
- package/dist/media/transform-types.js +47 -0
- package/dist/media/transform-types.js.map +1 -0
- package/dist/media/types.d.ts +150 -0
- package/dist/media/types.d.ts.map +1 -0
- package/dist/media/types.js +26 -0
- package/dist/media/types.js.map +1 -0
- package/dist/navigation/agent-tools.d.ts +90 -0
- package/dist/navigation/agent-tools.d.ts.map +1 -0
- package/dist/navigation/agent-tools.js +225 -0
- package/dist/navigation/agent-tools.js.map +1 -0
- package/dist/navigation/contracts.d.ts +98 -0
- package/dist/navigation/contracts.d.ts.map +1 -0
- package/dist/navigation/contracts.js +70 -0
- package/dist/navigation/contracts.js.map +1 -0
- package/dist/navigation/index.d.ts +39 -0
- package/dist/navigation/index.d.ts.map +1 -0
- package/dist/navigation/index.js +30 -0
- package/dist/navigation/index.js.map +1 -0
- package/dist/navigation/menu-service.d.ts +256 -0
- package/dist/navigation/menu-service.d.ts.map +1 -0
- package/dist/navigation/menu-service.js +382 -0
- package/dist/navigation/menu-service.js.map +1 -0
- package/dist/navigation/ports.d.ts +147 -0
- package/dist/navigation/ports.d.ts.map +1 -0
- package/dist/navigation/ports.js +2 -0
- package/dist/navigation/ports.js.map +1 -0
- package/dist/navigation/read-model.d.ts +11 -0
- package/dist/navigation/read-model.d.ts.map +1 -0
- package/dist/navigation/read-model.js +27 -0
- package/dist/navigation/read-model.js.map +1 -0
- package/dist/navigation/reconcile.d.ts +57 -0
- package/dist/navigation/reconcile.d.ts.map +1 -0
- package/dist/navigation/reconcile.js +33 -0
- package/dist/navigation/reconcile.js.map +1 -0
- package/dist/navigation/repo.memory.d.ts +137 -0
- package/dist/navigation/repo.memory.d.ts.map +1 -0
- package/dist/navigation/repo.memory.js +95 -0
- package/dist/navigation/repo.memory.js.map +1 -0
- package/dist/navigation/resolver.d.ts +110 -0
- package/dist/navigation/resolver.d.ts.map +1 -0
- package/dist/navigation/resolver.js +131 -0
- package/dist/navigation/resolver.js.map +1 -0
- package/dist/navigation/tool-registrations.d.ts +43 -0
- package/dist/navigation/tool-registrations.d.ts.map +1 -0
- package/dist/navigation/tool-registrations.js +126 -0
- package/dist/navigation/tool-registrations.js.map +1 -0
- package/dist/navigation/types.d.ts +207 -0
- package/dist/navigation/types.d.ts.map +1 -0
- package/dist/navigation/types.js +14 -0
- package/dist/navigation/types.js.map +1 -0
- package/dist/presentation/index.d.ts +15 -0
- package/dist/presentation/index.d.ts.map +1 -0
- package/dist/presentation/index.js +15 -0
- package/dist/presentation/index.js.map +1 -0
- package/dist/presentation/presentation.d.ts +72 -0
- package/dist/presentation/presentation.d.ts.map +1 -0
- package/dist/presentation/presentation.js +51 -0
- package/dist/presentation/presentation.js.map +1 -0
- package/dist/presentation/repo.memory.d.ts +9 -0
- package/dist/presentation/repo.memory.d.ts.map +1 -0
- package/dist/presentation/repo.memory.js +21 -0
- package/dist/presentation/repo.memory.js.map +1 -0
- package/dist/server/index.d.ts +28 -0
- package/dist/server/index.d.ts.map +1 -0
- package/dist/server/index.js +28 -0
- package/dist/server/index.js.map +1 -0
- package/dist/settings/agent-tools.d.ts +83 -0
- package/dist/settings/agent-tools.d.ts.map +1 -0
- package/dist/settings/agent-tools.js +200 -0
- package/dist/settings/agent-tools.js.map +1 -0
- package/dist/settings/agent-writable-preferences.d.ts +67 -0
- package/dist/settings/agent-writable-preferences.d.ts.map +1 -0
- package/dist/settings/agent-writable-preferences.js +144 -0
- package/dist/settings/agent-writable-preferences.js.map +1 -0
- package/dist/settings/change-feed.d.ts +74 -0
- package/dist/settings/change-feed.d.ts.map +1 -0
- package/dist/settings/change-feed.js +61 -0
- package/dist/settings/change-feed.js.map +1 -0
- package/dist/settings/definitions-dispatch.d.ts +51 -0
- package/dist/settings/definitions-dispatch.d.ts.map +1 -0
- package/dist/settings/definitions-dispatch.js +86 -0
- package/dist/settings/definitions-dispatch.js.map +1 -0
- package/dist/settings/dictionaries/index.d.ts +45 -0
- package/dist/settings/dictionaries/index.d.ts.map +1 -0
- package/dist/settings/dictionaries/index.js +69 -0
- package/dist/settings/dictionaries/index.js.map +1 -0
- package/dist/settings/dictionaries/settings-dialog.ar.d.ts +3 -0
- package/dist/settings/dictionaries/settings-dialog.ar.d.ts.map +1 -0
- package/dist/settings/dictionaries/settings-dialog.ar.js +28 -0
- package/dist/settings/dictionaries/settings-dialog.ar.js.map +1 -0
- package/dist/settings/dictionaries/settings-dialog.bn.d.ts +3 -0
- package/dist/settings/dictionaries/settings-dialog.bn.d.ts.map +1 -0
- package/dist/settings/dictionaries/settings-dialog.bn.js +28 -0
- package/dist/settings/dictionaries/settings-dialog.bn.js.map +1 -0
- package/dist/settings/dictionaries/settings-dialog.de.d.ts +3 -0
- package/dist/settings/dictionaries/settings-dialog.de.d.ts.map +1 -0
- package/dist/settings/dictionaries/settings-dialog.de.js +28 -0
- package/dist/settings/dictionaries/settings-dialog.de.js.map +1 -0
- package/dist/settings/dictionaries/settings-dialog.es.d.ts +3 -0
- package/dist/settings/dictionaries/settings-dialog.es.d.ts.map +1 -0
- package/dist/settings/dictionaries/settings-dialog.es.js +28 -0
- package/dist/settings/dictionaries/settings-dialog.es.js.map +1 -0
- package/dist/settings/dictionaries/settings-dialog.fa.d.ts +3 -0
- package/dist/settings/dictionaries/settings-dialog.fa.d.ts.map +1 -0
- package/dist/settings/dictionaries/settings-dialog.fa.js +28 -0
- package/dist/settings/dictionaries/settings-dialog.fa.js.map +1 -0
- package/dist/settings/dictionaries/settings-dialog.fr.d.ts +3 -0
- package/dist/settings/dictionaries/settings-dialog.fr.d.ts.map +1 -0
- package/dist/settings/dictionaries/settings-dialog.fr.js +28 -0
- package/dist/settings/dictionaries/settings-dialog.fr.js.map +1 -0
- package/dist/settings/dictionaries/settings-dialog.hi.d.ts +3 -0
- package/dist/settings/dictionaries/settings-dialog.hi.d.ts.map +1 -0
- package/dist/settings/dictionaries/settings-dialog.hi.js +28 -0
- package/dist/settings/dictionaries/settings-dialog.hi.js.map +1 -0
- package/dist/settings/dictionaries/settings-dialog.hu.d.ts +3 -0
- package/dist/settings/dictionaries/settings-dialog.hu.d.ts.map +1 -0
- package/dist/settings/dictionaries/settings-dialog.hu.js +28 -0
- package/dist/settings/dictionaries/settings-dialog.hu.js.map +1 -0
- package/dist/settings/dictionaries/settings-dialog.id.d.ts +3 -0
- package/dist/settings/dictionaries/settings-dialog.id.d.ts.map +1 -0
- package/dist/settings/dictionaries/settings-dialog.id.js +28 -0
- package/dist/settings/dictionaries/settings-dialog.id.js.map +1 -0
- package/dist/settings/dictionaries/settings-dialog.it.d.ts +3 -0
- package/dist/settings/dictionaries/settings-dialog.it.d.ts.map +1 -0
- package/dist/settings/dictionaries/settings-dialog.it.js +28 -0
- package/dist/settings/dictionaries/settings-dialog.it.js.map +1 -0
- package/dist/settings/dictionaries/settings-dialog.ja.d.ts +3 -0
- package/dist/settings/dictionaries/settings-dialog.ja.d.ts.map +1 -0
- package/dist/settings/dictionaries/settings-dialog.ja.js +28 -0
- package/dist/settings/dictionaries/settings-dialog.ja.js.map +1 -0
- package/dist/settings/dictionaries/settings-dialog.ko.d.ts +3 -0
- package/dist/settings/dictionaries/settings-dialog.ko.d.ts.map +1 -0
- package/dist/settings/dictionaries/settings-dialog.ko.js +28 -0
- package/dist/settings/dictionaries/settings-dialog.ko.js.map +1 -0
- package/dist/settings/dictionaries/settings-dialog.pl.d.ts +3 -0
- package/dist/settings/dictionaries/settings-dialog.pl.d.ts.map +1 -0
- package/dist/settings/dictionaries/settings-dialog.pl.js +28 -0
- package/dist/settings/dictionaries/settings-dialog.pl.js.map +1 -0
- package/dist/settings/dictionaries/settings-dialog.pt-BR.d.ts +3 -0
- package/dist/settings/dictionaries/settings-dialog.pt-BR.d.ts.map +1 -0
- package/dist/settings/dictionaries/settings-dialog.pt-BR.js +28 -0
- package/dist/settings/dictionaries/settings-dialog.pt-BR.js.map +1 -0
- package/dist/settings/dictionaries/settings-dialog.ru.d.ts +3 -0
- package/dist/settings/dictionaries/settings-dialog.ru.d.ts.map +1 -0
- package/dist/settings/dictionaries/settings-dialog.ru.js +28 -0
- package/dist/settings/dictionaries/settings-dialog.ru.js.map +1 -0
- package/dist/settings/dictionaries/settings-dialog.th.d.ts +3 -0
- package/dist/settings/dictionaries/settings-dialog.th.d.ts.map +1 -0
- package/dist/settings/dictionaries/settings-dialog.th.js +28 -0
- package/dist/settings/dictionaries/settings-dialog.th.js.map +1 -0
- package/dist/settings/dictionaries/settings-dialog.tr.d.ts +3 -0
- package/dist/settings/dictionaries/settings-dialog.tr.d.ts.map +1 -0
- package/dist/settings/dictionaries/settings-dialog.tr.js +28 -0
- package/dist/settings/dictionaries/settings-dialog.tr.js.map +1 -0
- package/dist/settings/dictionaries/settings-dialog.uk.d.ts +3 -0
- package/dist/settings/dictionaries/settings-dialog.uk.d.ts.map +1 -0
- package/dist/settings/dictionaries/settings-dialog.uk.js +28 -0
- package/dist/settings/dictionaries/settings-dialog.uk.js.map +1 -0
- package/dist/settings/dictionaries/settings-dialog.ur.d.ts +3 -0
- package/dist/settings/dictionaries/settings-dialog.ur.d.ts.map +1 -0
- package/dist/settings/dictionaries/settings-dialog.ur.js +28 -0
- package/dist/settings/dictionaries/settings-dialog.ur.js.map +1 -0
- package/dist/settings/dictionaries/settings-dialog.zh-CN.d.ts +3 -0
- package/dist/settings/dictionaries/settings-dialog.zh-CN.d.ts.map +1 -0
- package/dist/settings/dictionaries/settings-dialog.zh-CN.js +28 -0
- package/dist/settings/dictionaries/settings-dialog.zh-CN.js.map +1 -0
- package/dist/settings/dictionaries/settings-dialog.zh-TW.d.ts +3 -0
- package/dist/settings/dictionaries/settings-dialog.zh-TW.d.ts.map +1 -0
- package/dist/settings/dictionaries/settings-dialog.zh-TW.js +28 -0
- package/dist/settings/dictionaries/settings-dialog.zh-TW.js.map +1 -0
- package/dist/settings/ensure-definitions.d.ts +88 -0
- package/dist/settings/ensure-definitions.d.ts.map +1 -0
- package/dist/settings/ensure-definitions.js +91 -0
- package/dist/settings/ensure-definitions.js.map +1 -0
- package/dist/settings/errors.d.ts +36 -0
- package/dist/settings/errors.d.ts.map +1 -0
- package/dist/settings/errors.js +40 -0
- package/dist/settings/errors.js.map +1 -0
- package/dist/settings/index.d.ts +37 -0
- package/dist/settings/index.d.ts.map +1 -0
- package/dist/settings/index.js +36 -0
- package/dist/settings/index.js.map +1 -0
- package/dist/settings/ports.d.ts +117 -0
- package/dist/settings/ports.d.ts.map +1 -0
- package/dist/settings/ports.js +2 -0
- package/dist/settings/ports.js.map +1 -0
- package/dist/settings/purge-service.d.ts +53 -0
- package/dist/settings/purge-service.d.ts.map +1 -0
- package/dist/settings/purge-service.js +105 -0
- package/dist/settings/purge-service.js.map +1 -0
- package/dist/settings/repo.memory.d.ts +119 -0
- package/dist/settings/repo.memory.d.ts.map +1 -0
- package/dist/settings/repo.memory.js +199 -0
- package/dist/settings/repo.memory.js.map +1 -0
- package/dist/settings/settings.d.ts +124 -0
- package/dist/settings/settings.d.ts.map +1 -0
- package/dist/settings/settings.js +238 -0
- package/dist/settings/settings.js.map +1 -0
- package/dist/settings/tool-registrations.d.ts +56 -0
- package/dist/settings/tool-registrations.d.ts.map +1 -0
- package/dist/settings/tool-registrations.js +240 -0
- package/dist/settings/tool-registrations.js.map +1 -0
- package/dist/settings/types.d.ts +125 -0
- package/dist/settings/types.d.ts.map +1 -0
- package/dist/settings/types.js +3 -0
- package/dist/settings/types.js.map +1 -0
- package/dist/settings/ui-tab-definitions.d.ts +41 -0
- package/dist/settings/ui-tab-definitions.d.ts.map +1 -0
- package/dist/settings/ui-tab-definitions.js +148 -0
- package/dist/settings/ui-tab-definitions.js.map +1 -0
- package/dist/settings/write-service.d.ts +314 -0
- package/dist/settings/write-service.d.ts.map +1 -0
- package/dist/settings/write-service.js +677 -0
- package/dist/settings/write-service.js.map +1 -0
- package/dist/taxonomy/agent-tools.d.ts +76 -0
- package/dist/taxonomy/agent-tools.d.ts.map +1 -0
- package/dist/taxonomy/agent-tools.js +178 -0
- package/dist/taxonomy/agent-tools.js.map +1 -0
- package/dist/taxonomy/content-lookup.d.ts +45 -0
- package/dist/taxonomy/content-lookup.d.ts.map +1 -0
- package/dist/taxonomy/content-lookup.js +9 -0
- package/dist/taxonomy/content-lookup.js.map +1 -0
- package/dist/taxonomy/index.d.ts +38 -0
- package/dist/taxonomy/index.d.ts.map +1 -0
- package/dist/taxonomy/index.js +38 -0
- package/dist/taxonomy/index.js.map +1 -0
- package/dist/taxonomy/list.d.ts +39 -0
- package/dist/taxonomy/list.d.ts.map +1 -0
- package/dist/taxonomy/list.js +19 -0
- package/dist/taxonomy/list.js.map +1 -0
- package/dist/taxonomy/merge-term.d.ts +94 -0
- package/dist/taxonomy/merge-term.d.ts.map +1 -0
- package/dist/taxonomy/merge-term.js +65 -0
- package/dist/taxonomy/merge-term.js.map +1 -0
- package/dist/taxonomy/repo.memory.d.ts +160 -0
- package/dist/taxonomy/repo.memory.d.ts.map +1 -0
- package/dist/taxonomy/repo.memory.js +171 -0
- package/dist/taxonomy/repo.memory.js.map +1 -0
- package/dist/taxonomy/validation-chain.d.ts +114 -0
- package/dist/taxonomy/validation-chain.d.ts.map +1 -0
- package/dist/taxonomy/validation-chain.js +152 -0
- package/dist/taxonomy/validation-chain.js.map +1 -0
- package/dist/taxonomy/write-service.d.ts +387 -0
- package/dist/taxonomy/write-service.d.ts.map +1 -0
- package/dist/taxonomy/write-service.js +350 -0
- package/dist/taxonomy/write-service.js.map +1 -0
- package/dist/workspace/agent-tools.d.ts +67 -0
- package/dist/workspace/agent-tools.d.ts.map +1 -0
- package/dist/workspace/agent-tools.js +103 -0
- package/dist/workspace/agent-tools.js.map +1 -0
- package/dist/workspace/create.d.ts +116 -0
- package/dist/workspace/create.d.ts.map +1 -0
- package/dist/workspace/create.js +69 -0
- package/dist/workspace/create.js.map +1 -0
- package/dist/workspace/delete.d.ts +50 -0
- package/dist/workspace/delete.d.ts.map +1 -0
- package/dist/workspace/delete.js +24 -0
- package/dist/workspace/delete.js.map +1 -0
- package/dist/workspace/index.d.ts +27 -0
- package/dist/workspace/index.d.ts.map +1 -0
- package/dist/workspace/index.js +27 -0
- package/dist/workspace/index.js.map +1 -0
- package/dist/workspace/repo.memory.d.ts +34 -0
- package/dist/workspace/repo.memory.d.ts.map +1 -0
- package/dist/workspace/repo.memory.js +50 -0
- package/dist/workspace/repo.memory.js.map +1 -0
- package/dist/workspace/tool-registrations.d.ts +38 -0
- package/dist/workspace/tool-registrations.d.ts.map +1 -0
- package/dist/workspace/tool-registrations.js +71 -0
- package/dist/workspace/tool-registrations.js.map +1 -0
- package/dist/workspace/update.d.ts +52 -0
- package/dist/workspace/update.d.ts.map +1 -0
- package/dist/workspace/update.js +41 -0
- package/dist/workspace/update.js.map +1 -0
- package/package.json +149 -0
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
import type { ClockPort, IdGeneratorPort, UUID } from "../core/ports.js";
|
|
2
|
+
import { MediaConflictError, MediaNotFoundError, MediaSourceImmutableError, MediaStillReferencedError, MediaValidationError, type MediaRecord, type MediaSource } from "./types.js";
|
|
3
|
+
import type { AssetBlobRepoPort, AssetRenditionRepoPort, BlobStorePort, MediaRepoPort } from "./ports.js";
|
|
4
|
+
export { MediaConflictError, MediaNotFoundError, MediaSourceImmutableError, MediaStillReferencedError, MediaValidationError, };
|
|
5
|
+
/** Default byte-size cap (this pass's stand-in for the real ingress policy's streaming cap). */
|
|
6
|
+
export declare const DEFAULT_MAX_UPLOAD_BYTES: number;
|
|
7
|
+
/**
|
|
8
|
+
* Advisory MIME allowlist. SVG is deliberately EXCLUDED (not "TODO, forgot") —
|
|
9
|
+
* SVG must be sanitized at ingest before it's safe to store;
|
|
10
|
+
* that sanitizer is not built in this pass, so SVG upload is rejected rather
|
|
11
|
+
* than accepted unsanitized.
|
|
12
|
+
*
|
|
13
|
+
* `video/mp4`/`video/webm` (owner-directed, 2026-08-24): the two formats
|
|
14
|
+
* `content-type-sniffer.ts` already recognizes by magic bytes. Unlike the four
|
|
15
|
+
* image types above, an accepted video is never re-encoded — there is no
|
|
16
|
+
* `TransformFormat` for video (`transform-types.ts`'s union is image-only), so
|
|
17
|
+
* a video asset's public URL bypasses the transform/rendition pipeline
|
|
18
|
+
* entirely and serves the original bytes as-is (host concern; see Tovu's
|
|
19
|
+
* `routes/site/media-rendition.ts`). This widens the SAME advisory,
|
|
20
|
+
* client-declared-string check item 4 above already discloses as untrusted —
|
|
21
|
+
* no new ingress hardening was added for video.
|
|
22
|
+
*/
|
|
23
|
+
export declare const DEFAULT_ALLOWED_MIME_TYPES: ReadonlySet<string>;
|
|
24
|
+
/**
|
|
25
|
+
* Enforces the "settable-once-from-absent" write-once rule for
|
|
26
|
+
* `source.sha256`: absent -> set exactly once, then immutable.
|
|
27
|
+
*
|
|
28
|
+
* Not currently reachable through any exposed write path — `uploadMedia` only
|
|
29
|
+
* ever calls it with `existing = undefined` (a brand-new row), and
|
|
30
|
+
* `UpdateMediaMetadataInput` has no `sha256` field at all, so
|
|
31
|
+
* `updateMediaMetadata` can't trigger the rejection branch even by accident.
|
|
32
|
+
* Kept as an explicit, directly-testable invariant (see
|
|
33
|
+
* `__tests__/media-service.test.ts`) rather than an implicit one enforced only
|
|
34
|
+
* by "the type doesn't have the field" — and it's the seam any future
|
|
35
|
+
* "replace source" operation would have to go through.
|
|
36
|
+
*
|
|
37
|
+
* @complexity O(1).
|
|
38
|
+
* @overallScore 100
|
|
39
|
+
*/
|
|
40
|
+
export declare function resolveWriteOnceSource(required: {
|
|
41
|
+
existing: MediaSource | undefined;
|
|
42
|
+
requestedSha256: string;
|
|
43
|
+
}, _optional?: Record<string, never>): MediaSource;
|
|
44
|
+
export interface UploadMediaInput {
|
|
45
|
+
workspaceId: UUID;
|
|
46
|
+
bytes: Uint8Array;
|
|
47
|
+
filename: string;
|
|
48
|
+
contentType: string;
|
|
49
|
+
alt?: string | undefined;
|
|
50
|
+
caption?: string | undefined;
|
|
51
|
+
credit?: string | undefined;
|
|
52
|
+
/** Attributed on the `asset_blobs` row (required attribution). */
|
|
53
|
+
createdByPrincipal: string;
|
|
54
|
+
}
|
|
55
|
+
export interface UploadMediaDeps {
|
|
56
|
+
clock: ClockPort;
|
|
57
|
+
idGen: IdGeneratorPort;
|
|
58
|
+
mediaRepo: MediaRepoPort;
|
|
59
|
+
blobRepo: AssetBlobRepoPort;
|
|
60
|
+
renditionRepo: AssetRenditionRepoPort;
|
|
61
|
+
blobStore: BlobStorePort;
|
|
62
|
+
}
|
|
63
|
+
export interface UploadMediaRequired {
|
|
64
|
+
deps: UploadMediaDeps;
|
|
65
|
+
input: UploadMediaInput;
|
|
66
|
+
}
|
|
67
|
+
export interface UploadMediaOptional {
|
|
68
|
+
maxUploadBytes?: number | undefined;
|
|
69
|
+
allowedMimeTypes?: ReadonlySet<string> | undefined;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Validates, hashes, dedups by `(workspaceId, sha256)`, and writes a new media
|
|
73
|
+
* asset. Ordering (INV-1a): bytes are written (or found already
|
|
74
|
+
* present via dedup) BEFORE the media row is saved — a media row never exists
|
|
75
|
+
* without corresponding bytes.
|
|
76
|
+
*
|
|
77
|
+
* Blob dedup is per-hash, not per-upload: uploading the same bytes twice
|
|
78
|
+
* always creates two `MediaRecord`s (two distinct library entries, matching
|
|
79
|
+
* common CMS behavior) but writes the blob bytes only once. The dedup
|
|
80
|
+
* check-then-act sequence runs inside {@link withSha256Lock} (`blob-gc-lock.ts`)
|
|
81
|
+
* so it can't interleave with a concurrent `blob-gc.ts` delete-pass on the
|
|
82
|
+
* same sha256 (the "Serialization" invariant). If the existing blob row
|
|
83
|
+
* is `tombstoned` (a pending GC candidate), this resurrects it back to
|
|
84
|
+
* `active` in the same locked section instead of writing a duplicate blob —
|
|
85
|
+
* the dedup rule's explicit resurrect case.
|
|
86
|
+
*
|
|
87
|
+
* @complexity O(1) — one hash, one blob lookup, at most one blob write, one
|
|
88
|
+
* media write, one rendition write (the lock itself adds O(1) scheduling
|
|
89
|
+
* overhead, not a scan).
|
|
90
|
+
* @overallScore 100
|
|
91
|
+
*/
|
|
92
|
+
export declare function uploadMedia(required: UploadMediaRequired, optional?: UploadMediaOptional): Promise<{
|
|
93
|
+
media: MediaRecord;
|
|
94
|
+
}>;
|
|
95
|
+
export interface ListMediaRequired {
|
|
96
|
+
deps: {
|
|
97
|
+
mediaRepo: MediaRepoPort;
|
|
98
|
+
};
|
|
99
|
+
input: {
|
|
100
|
+
workspaceId: UUID;
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Lists all media in a workspace (all statuses — active + trashed; admin sees
|
|
105
|
+
* everything, mirrors an admin post listing). Purged assets are absent by
|
|
106
|
+
* construction (`purgeMedia` removes the row), not filtered here.
|
|
107
|
+
*
|
|
108
|
+
* @complexity O(1) repo call; the returned array is bounded by the
|
|
109
|
+
* workspace's media row count (repo-dependent — no pagination cap added in
|
|
110
|
+
* this pass, acceptable at walking-skeleton scale).
|
|
111
|
+
* @overallScore 100
|
|
112
|
+
*/
|
|
113
|
+
export declare function listMedia(required: ListMediaRequired, _optional?: Record<string, never>): Promise<{
|
|
114
|
+
media: MediaRecord[];
|
|
115
|
+
}>;
|
|
116
|
+
export interface GetMediaByIdRequired {
|
|
117
|
+
deps: {
|
|
118
|
+
mediaRepo: MediaRepoPort;
|
|
119
|
+
};
|
|
120
|
+
input: {
|
|
121
|
+
workspaceId: UUID;
|
|
122
|
+
id: UUID;
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Fetches one media asset by id, scoped to its workspace.
|
|
127
|
+
*
|
|
128
|
+
* @complexity O(1).
|
|
129
|
+
* @overallScore 100
|
|
130
|
+
*/
|
|
131
|
+
export declare function getMediaById(required: GetMediaByIdRequired, _optional?: Record<string, never>): Promise<{
|
|
132
|
+
media: MediaRecord;
|
|
133
|
+
}>;
|
|
134
|
+
export interface UpdateMediaMetadataInput {
|
|
135
|
+
workspaceId: UUID;
|
|
136
|
+
id: UUID;
|
|
137
|
+
title?: string | undefined;
|
|
138
|
+
alt?: string | undefined;
|
|
139
|
+
caption?: string | undefined;
|
|
140
|
+
credit?: string | undefined;
|
|
141
|
+
/**
|
|
142
|
+
* Quick-and-dirty sizing override fields (see `MediaRecord.width`/`height`/`cssClass` doc).
|
|
143
|
+
* `undefined` (the key omitted, or explicitly `undefined`) leaves the stored value unchanged —
|
|
144
|
+
* same optional-field/partial-patch contract every other field on this input already has.
|
|
145
|
+
* `null` explicitly CLEARS a previously-set override back to "not set". A provided number must be
|
|
146
|
+
* a positive integer (rejected with `MediaValidationError` otherwise) — this is public HTML output
|
|
147
|
+
* sizing, not a field worth silently coercing.
|
|
148
|
+
*/
|
|
149
|
+
width?: number | null | undefined;
|
|
150
|
+
height?: number | null | undefined;
|
|
151
|
+
/** Same undefined/null/value contract as `width`/`height` above. A non-empty string is trimmed;
|
|
152
|
+
* a string that trims to empty is stored as `null` (equivalent to clearing it), matching the
|
|
153
|
+
* "empty means unset" convention `title`/`alt`/etc. already follow via `.trim()`. */
|
|
154
|
+
cssClass?: string | null | undefined;
|
|
155
|
+
}
|
|
156
|
+
export interface UpdateMediaMetadataDeps {
|
|
157
|
+
clock: ClockPort;
|
|
158
|
+
mediaRepo: MediaRepoPort;
|
|
159
|
+
}
|
|
160
|
+
export interface UpdateMediaMetadataRequired {
|
|
161
|
+
deps: UpdateMediaMetadataDeps;
|
|
162
|
+
input: UpdateMediaMetadataInput;
|
|
163
|
+
}
|
|
164
|
+
export declare function updateMediaMetadata(required: UpdateMediaMetadataRequired, _optional?: Record<string, never>): Promise<{
|
|
165
|
+
media: MediaRecord;
|
|
166
|
+
}>;
|
|
167
|
+
export interface TrashMediaDeps {
|
|
168
|
+
clock: ClockPort;
|
|
169
|
+
mediaRepo: MediaRepoPort;
|
|
170
|
+
}
|
|
171
|
+
export interface TrashMediaRequired {
|
|
172
|
+
deps: TrashMediaDeps;
|
|
173
|
+
input: {
|
|
174
|
+
workspaceId: UUID;
|
|
175
|
+
id: UUID;
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* Soft delete. Idempotent — trashing an already-trashed asset is a no-op
|
|
180
|
+
* (mirrors `disableMember`'s idempotency), not an error.
|
|
181
|
+
*
|
|
182
|
+
* @complexity O(1).
|
|
183
|
+
* @overallScore 100
|
|
184
|
+
*/
|
|
185
|
+
export declare function trashMedia(required: TrashMediaRequired, _optional?: Record<string, never>): Promise<{
|
|
186
|
+
media: MediaRecord;
|
|
187
|
+
}>;
|
|
188
|
+
export interface PurgeMediaDeps {
|
|
189
|
+
mediaRepo: MediaRepoPort;
|
|
190
|
+
blobRepo: AssetBlobRepoPort;
|
|
191
|
+
renditionRepo: AssetRenditionRepoPort;
|
|
192
|
+
blobStore: BlobStorePort;
|
|
193
|
+
/**
|
|
194
|
+
* Optional — only used to timestamp the blob-GC tombstone-pass this
|
|
195
|
+
* function triggers after the media row is removed (`tombstoneBlobIfUnreferenced`,
|
|
196
|
+
* `blob-gc.ts`). Falls back to the system clock when omitted so a caller
|
|
197
|
+
* that doesn't wire a clock into `PurgeMediaDeps` keeps compiling and
|
|
198
|
+
* behaving correctly unchanged.
|
|
199
|
+
*/
|
|
200
|
+
clock?: ClockPort | undefined;
|
|
201
|
+
}
|
|
202
|
+
export interface PurgeMediaRequired {
|
|
203
|
+
deps: PurgeMediaDeps;
|
|
204
|
+
input: {
|
|
205
|
+
workspaceId: UUID;
|
|
206
|
+
id: UUID;
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
/**
|
|
210
|
+
* Hard delete. Deletion ladder (mirrors `deleteMenu`): purge is
|
|
211
|
+
* rejected with `MediaStillReferencedError` (409-style) unless the asset has
|
|
212
|
+
* already been trashed first.
|
|
213
|
+
*
|
|
214
|
+
* SIMPLIFICATION (disclosed): the real design's guard checks the derived
|
|
215
|
+
* `entry_refs` where-used index (any live reference from published content).
|
|
216
|
+
* That index doesn't exist — posts don't reference media by id in `bodyJson`
|
|
217
|
+
* yet — so this build uses "not yet trashed" as the stand-in referenced-check.
|
|
218
|
+
* This is strictly weaker than the real invariant: a media asset that IS
|
|
219
|
+
* trashed but still referenced by a post would purge here without complaint,
|
|
220
|
+
* whereas the real system would keep blocking it. Do not read this function's
|
|
221
|
+
* 409 as evidence the where-used index exists.
|
|
222
|
+
*
|
|
223
|
+
* Once past the guard: rendition rows are removed, then the media row is
|
|
224
|
+
* removed. After that removal commits, the blob-GC tombstone-pass
|
|
225
|
+
* (`tombstoneBlobIfUnreferenced`, `blob-gc.ts`) runs for the asset's sha256 —
|
|
226
|
+
* it marks the blob `tombstoned` if-and-only-if the real "unreferenced"
|
|
227
|
+
* predicate holds (no other non-purged media row shares the hash, plus the
|
|
228
|
+
* disclosed `entry_refs`/retained-snapshot stubs). This function does **not**
|
|
229
|
+
* delete blob bytes itself anymore: the delete-pass (`runBlobGcDeletePass`)
|
|
230
|
+
* is grace-period-gated (`gc_grace`, default 30 days) and the unlink-pass
|
|
231
|
+
* (`runBlobGcUnlinkPass`) that actually removes bytes runs as a separate,
|
|
232
|
+
* explicit step — nothing schedules either automatically yet (see
|
|
233
|
+
* `blob-gc.ts`'s file header). This is a real behavior change from this
|
|
234
|
+
* function's prior "delete row, best-effort unlink immediately" shortcut,
|
|
235
|
+
* which is exactly the gap this task closes.
|
|
236
|
+
*
|
|
237
|
+
* @complexity O(n) in the workspace's media row count, inherited from the
|
|
238
|
+
* tombstone-pass's `isBlobUnreferenced` scan (see `blob-gc.ts`).
|
|
239
|
+
* @overallScore 100
|
|
240
|
+
*/
|
|
241
|
+
export declare function purgeMedia(required: PurgeMediaRequired, _optional?: Record<string, never>): Promise<{
|
|
242
|
+
purged: true;
|
|
243
|
+
}>;
|
|
244
|
+
//# sourceMappingURL=media-service.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"media-service.d.ts","sourceRoot":"","sources":["../../src/media/media-service.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,345 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file `media` write/read service — walking-skeleton build.
|
|
3
|
+
*
|
|
4
|
+
* Mirrors a `post`-style feature's shape: plain async functions taking
|
|
5
|
+
* `{ deps, input }` (+ an optional second `options` object), typed domain
|
|
6
|
+
* errors from `./types`.
|
|
7
|
+
*
|
|
8
|
+
* SCOPE — what this file deliberately does NOT build (disclosed, not silently
|
|
9
|
+
* skipped; each deferred piece is named below):
|
|
10
|
+
*
|
|
11
|
+
* 1. **Blob GC** — now built for real (`blob-gc.ts`): a journaled,
|
|
12
|
+
* `withSha256Lock`-serialized tombstone -> delete-pass -> unlink-pass
|
|
13
|
+
* protocol with `gc_grace` retention. `purgeMedia` below no longer
|
|
14
|
+
* deletes blob bytes itself — after removing the media row it only
|
|
15
|
+
* triggers the tombstone-pass (`tombstoneBlobIfUnreferenced`); actual
|
|
16
|
+
* byte removal is deferred to `runBlobGcDeletePass` +
|
|
17
|
+
* `runBlobGcUnlinkPass` (or the `runBlobGcCycle` convenience wrapper),
|
|
18
|
+
* which nothing schedules automatically yet (no scheduler exists — see
|
|
19
|
+
* `blob-gc.ts`'s file header). `uploadMedia`'s dedup check is likewise
|
|
20
|
+
* serialized per-sha256 and resurrects a tombstoned blob instead of
|
|
21
|
+
* writing a duplicate. See `blob-gc.ts` for what's still
|
|
22
|
+
* disclosed-stubbed within that protocol (`entry_refs`, retained
|
|
23
|
+
* snapshots, the monthly orphan sweep).
|
|
24
|
+
* 2. **Named transform registry / real image processing** — the only
|
|
25
|
+
* rendition this build ever creates is a trivial "original" passthrough
|
|
26
|
+
* row that points at the source blob (created by `uploadMedia`). No
|
|
27
|
+
* `transform_registry` table, no eager/lazy generation, no
|
|
28
|
+
* `/m/{assetId}/{transformName}.v{version}/{slug}.{ext}` URL contract.
|
|
29
|
+
* 3. **Origin-isolated serving** — a host may add an authenticated,
|
|
30
|
+
* workspace-scoped admin route that serves original bytes, with content
|
|
31
|
+
* type derived at serve time by `content-type-sniffer.ts` rather than
|
|
32
|
+
* trusted from upload, since no real content type is stored anywhere —
|
|
33
|
+
* that route's own security model is a host concern. What is still NOT
|
|
34
|
+
* built, per the full design: a second, cookie-less media
|
|
35
|
+
* origin/process, and signed mint-URLs (a `media.download_original`
|
|
36
|
+
* permission would reserve that capability for a future route).
|
|
37
|
+
* `Content-Disposition` rules DO now exist, but only the one such a route
|
|
38
|
+
* needs (forcing `attachment` when the sniffed bytes are HTML/SVG-shaped).
|
|
39
|
+
* 4. **`MediaIngressPolicy`** — no SSRF guards, IP pinning, redirect
|
|
40
|
+
* handling, pixel-bomb caps, or magic-byte sniffing. Only a byte-size cap
|
|
41
|
+
* and an advisory `contentType` allowlist (see `DEFAULT_ALLOWED_MIME_TYPES`
|
|
42
|
+
* below) — an attacker-controlled `contentType` header is trusted, which
|
|
43
|
+
* the real ingress policy would never do.
|
|
44
|
+
* 5. **Where-used / `entry_refs`** — no real index exists because posts don't
|
|
45
|
+
* reference media by id in `bodyJson` yet. `purgeMedia`'s 409 guard uses
|
|
46
|
+
* "not yet trashed" as a stand-in for "referenced" (see its doc comment).
|
|
47
|
+
* 6. Virus scanning, remote-URL upload, video pipeline, S3 adapter — all
|
|
48
|
+
* explicitly deferred by design.
|
|
49
|
+
*
|
|
50
|
+
* See `types.ts`'s file header for the other disclosed scope adjustment: the
|
|
51
|
+
* bespoke `MediaRecord` table in place of a generic-entries model.
|
|
52
|
+
*/
|
|
53
|
+
import { createHash } from "node:crypto";
|
|
54
|
+
import { MediaConflictError, MediaNotFoundError, MediaSourceImmutableError, MediaStillReferencedError, MediaValidationError, } from "./types.js";
|
|
55
|
+
import { withSha256Lock } from "./blob-gc-lock.js";
|
|
56
|
+
import { tombstoneBlobIfUnreferenced } from "./blob-gc.js";
|
|
57
|
+
export { MediaConflictError, MediaNotFoundError, MediaSourceImmutableError, MediaStillReferencedError, MediaValidationError, };
|
|
58
|
+
/** Default byte-size cap (this pass's stand-in for the real ingress policy's streaming cap). */
|
|
59
|
+
export const DEFAULT_MAX_UPLOAD_BYTES = 10 * 1024 * 1024; // 10 MiB
|
|
60
|
+
/**
|
|
61
|
+
* Advisory MIME allowlist. SVG is deliberately EXCLUDED (not "TODO, forgot") —
|
|
62
|
+
* SVG must be sanitized at ingest before it's safe to store;
|
|
63
|
+
* that sanitizer is not built in this pass, so SVG upload is rejected rather
|
|
64
|
+
* than accepted unsanitized.
|
|
65
|
+
*
|
|
66
|
+
* `video/mp4`/`video/webm` (owner-directed, 2026-08-24): the two formats
|
|
67
|
+
* `content-type-sniffer.ts` already recognizes by magic bytes. Unlike the four
|
|
68
|
+
* image types above, an accepted video is never re-encoded — there is no
|
|
69
|
+
* `TransformFormat` for video (`transform-types.ts`'s union is image-only), so
|
|
70
|
+
* a video asset's public URL bypasses the transform/rendition pipeline
|
|
71
|
+
* entirely and serves the original bytes as-is (host concern; see Tovu's
|
|
72
|
+
* `routes/site/media-rendition.ts`). This widens the SAME advisory,
|
|
73
|
+
* client-declared-string check item 4 above already discloses as untrusted —
|
|
74
|
+
* no new ingress hardening was added for video.
|
|
75
|
+
*/
|
|
76
|
+
export const DEFAULT_ALLOWED_MIME_TYPES = new Set([
|
|
77
|
+
"image/jpeg",
|
|
78
|
+
"image/png",
|
|
79
|
+
"image/webp",
|
|
80
|
+
"image/gif",
|
|
81
|
+
"video/mp4",
|
|
82
|
+
"video/webm",
|
|
83
|
+
]);
|
|
84
|
+
/**
|
|
85
|
+
* Enforces the "settable-once-from-absent" write-once rule for
|
|
86
|
+
* `source.sha256`: absent -> set exactly once, then immutable.
|
|
87
|
+
*
|
|
88
|
+
* Not currently reachable through any exposed write path — `uploadMedia` only
|
|
89
|
+
* ever calls it with `existing = undefined` (a brand-new row), and
|
|
90
|
+
* `UpdateMediaMetadataInput` has no `sha256` field at all, so
|
|
91
|
+
* `updateMediaMetadata` can't trigger the rejection branch even by accident.
|
|
92
|
+
* Kept as an explicit, directly-testable invariant (see
|
|
93
|
+
* `__tests__/media-service.test.ts`) rather than an implicit one enforced only
|
|
94
|
+
* by "the type doesn't have the field" — and it's the seam any future
|
|
95
|
+
* "replace source" operation would have to go through.
|
|
96
|
+
*
|
|
97
|
+
* @complexity O(1).
|
|
98
|
+
* @overallScore 100
|
|
99
|
+
*/
|
|
100
|
+
export function resolveWriteOnceSource(required, _optional = {}) {
|
|
101
|
+
const { existing, requestedSha256 } = required;
|
|
102
|
+
if (existing && existing.sha256 !== requestedSha256) {
|
|
103
|
+
throw new MediaSourceImmutableError(`source.sha256 is write-once: cannot change '${existing.sha256}' to '${requestedSha256}'`);
|
|
104
|
+
}
|
|
105
|
+
return { sha256: requestedSha256 };
|
|
106
|
+
}
|
|
107
|
+
function deriveTitleFromFilename(filename) {
|
|
108
|
+
const base = filename.trim().replace(/\.[^./\\]+$/, "");
|
|
109
|
+
return base.trim() || "Untitled";
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Validates, hashes, dedups by `(workspaceId, sha256)`, and writes a new media
|
|
113
|
+
* asset. Ordering (INV-1a): bytes are written (or found already
|
|
114
|
+
* present via dedup) BEFORE the media row is saved — a media row never exists
|
|
115
|
+
* without corresponding bytes.
|
|
116
|
+
*
|
|
117
|
+
* Blob dedup is per-hash, not per-upload: uploading the same bytes twice
|
|
118
|
+
* always creates two `MediaRecord`s (two distinct library entries, matching
|
|
119
|
+
* common CMS behavior) but writes the blob bytes only once. The dedup
|
|
120
|
+
* check-then-act sequence runs inside {@link withSha256Lock} (`blob-gc-lock.ts`)
|
|
121
|
+
* so it can't interleave with a concurrent `blob-gc.ts` delete-pass on the
|
|
122
|
+
* same sha256 (the "Serialization" invariant). If the existing blob row
|
|
123
|
+
* is `tombstoned` (a pending GC candidate), this resurrects it back to
|
|
124
|
+
* `active` in the same locked section instead of writing a duplicate blob —
|
|
125
|
+
* the dedup rule's explicit resurrect case.
|
|
126
|
+
*
|
|
127
|
+
* @complexity O(1) — one hash, one blob lookup, at most one blob write, one
|
|
128
|
+
* media write, one rendition write (the lock itself adds O(1) scheduling
|
|
129
|
+
* overhead, not a scan).
|
|
130
|
+
* @overallScore 100
|
|
131
|
+
*/
|
|
132
|
+
export async function uploadMedia(required, optional = {}) {
|
|
133
|
+
const { deps, input } = required;
|
|
134
|
+
const maxUploadBytes = optional.maxUploadBytes ?? DEFAULT_MAX_UPLOAD_BYTES;
|
|
135
|
+
const allowedMimeTypes = optional.allowedMimeTypes ?? DEFAULT_ALLOWED_MIME_TYPES;
|
|
136
|
+
if (!allowedMimeTypes.has(input.contentType)) {
|
|
137
|
+
throw new MediaValidationError(`content type '${input.contentType}' is not allowed for upload`);
|
|
138
|
+
}
|
|
139
|
+
if (input.bytes.byteLength === 0) {
|
|
140
|
+
throw new MediaValidationError("uploaded file is empty");
|
|
141
|
+
}
|
|
142
|
+
if (input.bytes.byteLength > maxUploadBytes) {
|
|
143
|
+
throw new MediaValidationError(`uploaded file exceeds the ${maxUploadBytes}-byte size cap`);
|
|
144
|
+
}
|
|
145
|
+
const sha256 = createHash("sha256").update(input.bytes).digest("hex");
|
|
146
|
+
const nowIso = deps.clock.nowIso();
|
|
147
|
+
const storageKey = await withSha256Lock(sha256, async () => {
|
|
148
|
+
const existingBlob = await deps.blobRepo.findByHash({ workspaceId: input.workspaceId, sha256 });
|
|
149
|
+
if (existingBlob) {
|
|
150
|
+
if (existingBlob.status === "tombstoned") {
|
|
151
|
+
// Resurrect: cancel the pending GC in the same locked section rather
|
|
152
|
+
// than writing a duplicate blob (INV-1 dedup rule).
|
|
153
|
+
await deps.blobRepo.save({ ...existingBlob, status: "active", tombstonedAt: undefined });
|
|
154
|
+
}
|
|
155
|
+
return existingBlob.storageKey;
|
|
156
|
+
}
|
|
157
|
+
const written = await deps.blobStore.put({
|
|
158
|
+
workspaceId: input.workspaceId,
|
|
159
|
+
sha256,
|
|
160
|
+
bytes: input.bytes,
|
|
161
|
+
});
|
|
162
|
+
await deps.blobRepo.save({
|
|
163
|
+
id: deps.idGen.newId(),
|
|
164
|
+
workspaceId: input.workspaceId,
|
|
165
|
+
sha256,
|
|
166
|
+
storageKey: written.storageKey,
|
|
167
|
+
status: "active",
|
|
168
|
+
createdByPrincipal: input.createdByPrincipal,
|
|
169
|
+
createdAt: nowIso,
|
|
170
|
+
});
|
|
171
|
+
return written.storageKey;
|
|
172
|
+
});
|
|
173
|
+
const media = {
|
|
174
|
+
id: deps.idGen.newId(),
|
|
175
|
+
workspaceId: input.workspaceId,
|
|
176
|
+
title: deriveTitleFromFilename(input.filename),
|
|
177
|
+
alt: input.alt?.trim() ?? "",
|
|
178
|
+
caption: input.caption?.trim() ?? "",
|
|
179
|
+
credit: input.credit?.trim() ?? "",
|
|
180
|
+
source: resolveWriteOnceSource({ existing: undefined, requestedSha256: sha256 }),
|
|
181
|
+
status: "active",
|
|
182
|
+
createdAt: nowIso,
|
|
183
|
+
updatedAt: nowIso,
|
|
184
|
+
version: 1,
|
|
185
|
+
// No sizing UI on upload yet (only the edit panel got one — see `UpdateMediaMetadataInput`
|
|
186
|
+
// below) — every freshly uploaded asset starts with no override, set via a follow-up PATCH.
|
|
187
|
+
width: null,
|
|
188
|
+
height: null,
|
|
189
|
+
cssClass: null,
|
|
190
|
+
};
|
|
191
|
+
await deps.mediaRepo.save(media);
|
|
192
|
+
await deps.renditionRepo.save({
|
|
193
|
+
id: deps.idGen.newId(),
|
|
194
|
+
workspaceId: input.workspaceId,
|
|
195
|
+
assetId: media.id,
|
|
196
|
+
transformName: "original",
|
|
197
|
+
version: 1,
|
|
198
|
+
storageKey,
|
|
199
|
+
createdAt: nowIso,
|
|
200
|
+
});
|
|
201
|
+
return { media };
|
|
202
|
+
}
|
|
203
|
+
/**
|
|
204
|
+
* Lists all media in a workspace (all statuses — active + trashed; admin sees
|
|
205
|
+
* everything, mirrors an admin post listing). Purged assets are absent by
|
|
206
|
+
* construction (`purgeMedia` removes the row), not filtered here.
|
|
207
|
+
*
|
|
208
|
+
* @complexity O(1) repo call; the returned array is bounded by the
|
|
209
|
+
* workspace's media row count (repo-dependent — no pagination cap added in
|
|
210
|
+
* this pass, acceptable at walking-skeleton scale).
|
|
211
|
+
* @overallScore 100
|
|
212
|
+
*/
|
|
213
|
+
export async function listMedia(required, _optional = {}) {
|
|
214
|
+
const media = await required.deps.mediaRepo.list({ workspaceId: required.input.workspaceId });
|
|
215
|
+
return { media };
|
|
216
|
+
}
|
|
217
|
+
/**
|
|
218
|
+
* Fetches one media asset by id, scoped to its workspace.
|
|
219
|
+
*
|
|
220
|
+
* @complexity O(1).
|
|
221
|
+
* @overallScore 100
|
|
222
|
+
*/
|
|
223
|
+
export async function getMediaById(required, _optional = {}) {
|
|
224
|
+
const { workspaceId, id } = required.input;
|
|
225
|
+
const media = await required.deps.mediaRepo.findById({ workspaceId, id });
|
|
226
|
+
if (!media)
|
|
227
|
+
throw new MediaNotFoundError(`media '${id}' was not found`);
|
|
228
|
+
return { media };
|
|
229
|
+
}
|
|
230
|
+
/**
|
|
231
|
+
* Updates editorial-only fields (title/alt/caption/credit). `source.sha256` is
|
|
232
|
+
* write-once — this function's input type has no `sha256` field,
|
|
233
|
+
* so there is no code path here that can touch it (see `resolveWriteOnceSource`
|
|
234
|
+
* doc for the directly-tested invariant this relies on).
|
|
235
|
+
*
|
|
236
|
+
* @complexity O(1).
|
|
237
|
+
* @overallScore 100
|
|
238
|
+
*/
|
|
239
|
+
/** Validates a `width`/`height` override: must be a positive integer. `null` (explicit clear) and
|
|
240
|
+
* `undefined` (leave unchanged) both bypass this — only a real provided number is checked. */
|
|
241
|
+
function assertPositiveIntegerOrThrow(value, field) {
|
|
242
|
+
if (!Number.isInteger(value) || value <= 0) {
|
|
243
|
+
throw new MediaValidationError(`media.${field} must be a positive integer, got ${value}`);
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
export async function updateMediaMetadata(required, _optional = {}) {
|
|
247
|
+
const { deps, input } = required;
|
|
248
|
+
const existing = await deps.mediaRepo.findById({ workspaceId: input.workspaceId, id: input.id });
|
|
249
|
+
if (!existing)
|
|
250
|
+
throw new MediaNotFoundError(`media '${input.id}' was not found`);
|
|
251
|
+
if (input.width !== undefined && input.width !== null)
|
|
252
|
+
assertPositiveIntegerOrThrow(input.width, "width");
|
|
253
|
+
if (input.height !== undefined && input.height !== null)
|
|
254
|
+
assertPositiveIntegerOrThrow(input.height, "height");
|
|
255
|
+
const media = {
|
|
256
|
+
...existing,
|
|
257
|
+
title: input.title !== undefined ? input.title.trim() || existing.title : existing.title,
|
|
258
|
+
alt: input.alt !== undefined ? input.alt.trim() : existing.alt,
|
|
259
|
+
caption: input.caption !== undefined ? input.caption.trim() : existing.caption,
|
|
260
|
+
credit: input.credit !== undefined ? input.credit.trim() : existing.credit,
|
|
261
|
+
width: input.width !== undefined ? input.width : existing.width,
|
|
262
|
+
height: input.height !== undefined ? input.height : existing.height,
|
|
263
|
+
cssClass: input.cssClass !== undefined ? (input.cssClass === null ? null : input.cssClass.trim() || null) : existing.cssClass,
|
|
264
|
+
updatedAt: deps.clock.nowIso(),
|
|
265
|
+
version: existing.version + 1,
|
|
266
|
+
};
|
|
267
|
+
await deps.mediaRepo.save(media);
|
|
268
|
+
return { media };
|
|
269
|
+
}
|
|
270
|
+
/**
|
|
271
|
+
* Soft delete. Idempotent — trashing an already-trashed asset is a no-op
|
|
272
|
+
* (mirrors `disableMember`'s idempotency), not an error.
|
|
273
|
+
*
|
|
274
|
+
* @complexity O(1).
|
|
275
|
+
* @overallScore 100
|
|
276
|
+
*/
|
|
277
|
+
export async function trashMedia(required, _optional = {}) {
|
|
278
|
+
const { deps, input } = required;
|
|
279
|
+
const existing = await deps.mediaRepo.findById(input);
|
|
280
|
+
if (!existing)
|
|
281
|
+
throw new MediaNotFoundError(`media '${input.id}' was not found`);
|
|
282
|
+
if (existing.status === "trashed")
|
|
283
|
+
return { media: existing };
|
|
284
|
+
const media = {
|
|
285
|
+
...existing,
|
|
286
|
+
status: "trashed",
|
|
287
|
+
updatedAt: deps.clock.nowIso(),
|
|
288
|
+
version: existing.version + 1,
|
|
289
|
+
};
|
|
290
|
+
await deps.mediaRepo.save(media);
|
|
291
|
+
return { media };
|
|
292
|
+
}
|
|
293
|
+
/**
|
|
294
|
+
* Hard delete. Deletion ladder (mirrors `deleteMenu`): purge is
|
|
295
|
+
* rejected with `MediaStillReferencedError` (409-style) unless the asset has
|
|
296
|
+
* already been trashed first.
|
|
297
|
+
*
|
|
298
|
+
* SIMPLIFICATION (disclosed): the real design's guard checks the derived
|
|
299
|
+
* `entry_refs` where-used index (any live reference from published content).
|
|
300
|
+
* That index doesn't exist — posts don't reference media by id in `bodyJson`
|
|
301
|
+
* yet — so this build uses "not yet trashed" as the stand-in referenced-check.
|
|
302
|
+
* This is strictly weaker than the real invariant: a media asset that IS
|
|
303
|
+
* trashed but still referenced by a post would purge here without complaint,
|
|
304
|
+
* whereas the real system would keep blocking it. Do not read this function's
|
|
305
|
+
* 409 as evidence the where-used index exists.
|
|
306
|
+
*
|
|
307
|
+
* Once past the guard: rendition rows are removed, then the media row is
|
|
308
|
+
* removed. After that removal commits, the blob-GC tombstone-pass
|
|
309
|
+
* (`tombstoneBlobIfUnreferenced`, `blob-gc.ts`) runs for the asset's sha256 —
|
|
310
|
+
* it marks the blob `tombstoned` if-and-only-if the real "unreferenced"
|
|
311
|
+
* predicate holds (no other non-purged media row shares the hash, plus the
|
|
312
|
+
* disclosed `entry_refs`/retained-snapshot stubs). This function does **not**
|
|
313
|
+
* delete blob bytes itself anymore: the delete-pass (`runBlobGcDeletePass`)
|
|
314
|
+
* is grace-period-gated (`gc_grace`, default 30 days) and the unlink-pass
|
|
315
|
+
* (`runBlobGcUnlinkPass`) that actually removes bytes runs as a separate,
|
|
316
|
+
* explicit step — nothing schedules either automatically yet (see
|
|
317
|
+
* `blob-gc.ts`'s file header). This is a real behavior change from this
|
|
318
|
+
* function's prior "delete row, best-effort unlink immediately" shortcut,
|
|
319
|
+
* which is exactly the gap this task closes.
|
|
320
|
+
*
|
|
321
|
+
* @complexity O(n) in the workspace's media row count, inherited from the
|
|
322
|
+
* tombstone-pass's `isBlobUnreferenced` scan (see `blob-gc.ts`).
|
|
323
|
+
* @overallScore 100
|
|
324
|
+
*/
|
|
325
|
+
export async function purgeMedia(required, _optional = {}) {
|
|
326
|
+
const { deps, input } = required;
|
|
327
|
+
const existing = await deps.mediaRepo.findById(input);
|
|
328
|
+
if (!existing)
|
|
329
|
+
throw new MediaNotFoundError(`media '${input.id}' was not found`);
|
|
330
|
+
if (existing.status !== "trashed") {
|
|
331
|
+
throw new MediaStillReferencedError(`media '${input.id}' must be trashed before it can be purged`, [
|
|
332
|
+
`media '${input.id}' is still active (stand-in for the real entry_refs ` +
|
|
333
|
+
`where-used index, which is not implemented — see purgeMedia's doc comment)`,
|
|
334
|
+
]);
|
|
335
|
+
}
|
|
336
|
+
await deps.renditionRepo.removeByAsset({ workspaceId: input.workspaceId, assetId: input.id });
|
|
337
|
+
await deps.mediaRepo.remove({ workspaceId: input.workspaceId, id: input.id });
|
|
338
|
+
const clock = deps.clock ?? { nowIso: () => new Date().toISOString() };
|
|
339
|
+
await tombstoneBlobIfUnreferenced({
|
|
340
|
+
deps: { mediaRepo: deps.mediaRepo, blobRepo: deps.blobRepo, clock },
|
|
341
|
+
input: { workspaceId: input.workspaceId, sha256: existing.source.sha256 },
|
|
342
|
+
});
|
|
343
|
+
return { purged: true };
|
|
344
|
+
}
|
|
345
|
+
//# sourceMappingURL=media-service.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"media-service.js","sourceRoot":"","sources":["../../src/media/media-service.ts"],"names":[],"mappings":"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"}
|