@ai-matrx/media 0.8.0 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +15 -0
- package/dist/files/engine/api/assets.d.ts +223 -0
- package/dist/files/engine/api/assets.js +124 -0
- package/dist/files/engine/api/assets.js.map +1 -0
- package/dist/files/engine/api/direct.d.ts +43 -0
- package/dist/files/engine/api/direct.js +73 -0
- package/dist/files/engine/api/direct.js.map +1 -0
- package/dist/files/engine/api/fileOrganization.d.ts +34 -0
- package/dist/files/engine/api/fileOrganization.js +38 -0
- package/dist/files/engine/api/fileOrganization.js.map +1 -0
- package/dist/files/engine/api/files.d.ts +249 -0
- package/dist/files/engine/api/files.js +231 -0
- package/dist/files/engine/api/files.js.map +1 -0
- package/dist/files/engine/api/folders.d.ts +63 -0
- package/dist/files/engine/api/folders.js +56 -0
- package/dist/files/engine/api/folders.js.map +1 -0
- package/dist/files/engine/api/office.d.ts +27 -0
- package/dist/files/engine/api/office.js +19 -0
- package/dist/files/engine/api/office.js.map +1 -0
- package/dist/files/engine/api/permissions.d.ts +37 -0
- package/dist/files/engine/api/permissions.js +109 -0
- package/dist/files/engine/api/permissions.js.map +1 -0
- package/dist/files/engine/api/versions.d.ts +26 -0
- package/dist/files/engine/api/versions.js +37 -0
- package/dist/files/engine/api/versions.js.map +1 -0
- package/dist/files/engine/cache/idb-store.d.ts +95 -0
- package/dist/files/engine/cache/idb-store.js +172 -0
- package/dist/files/engine/cache/idb-store.js.map +1 -0
- package/dist/files/engine/cache/policy.d.ts +14 -0
- package/dist/files/engine/cache/policy.js +40 -0
- package/dist/files/engine/cache/policy.js.map +1 -0
- package/dist/files/engine/cache/register-service-worker.d.ts +57 -0
- package/dist/files/engine/cache/register-service-worker.js +80 -0
- package/dist/files/engine/cache/register-service-worker.js.map +1 -0
- package/dist/files/engine/db-types.d.ts +48865 -0
- package/dist/files/engine/db-types.js +1 -0
- package/dist/files/engine/db-types.js.map +1 -0
- package/dist/files/engine/filesDb.d.ts +1913 -0
- package/dist/files/engine/filesDb.js +28 -0
- package/dist/files/engine/filesDb.js.map +1 -0
- package/dist/files/engine/handler/handler.d.ts +82 -0
- package/dist/files/engine/handler/handler.js +124 -0
- package/dist/files/engine/handler/handler.js.map +1 -0
- package/dist/files/engine/handler/hooks/useFile.d.ts +17 -0
- package/dist/files/engine/handler/hooks/useFile.js +75 -0
- package/dist/files/engine/handler/hooks/useFile.js.map +1 -0
- package/dist/files/engine/handler/hooks/useFileUpload.d.ts +76 -0
- package/dist/files/engine/handler/hooks/useFileUpload.js +67 -0
- package/dist/files/engine/handler/hooks/useFileUpload.js.map +1 -0
- package/dist/files/engine/handler/input/normalize.d.ts +14 -0
- package/dist/files/engine/handler/input/normalize.js +365 -0
- package/dist/files/engine/handler/input/normalize.js.map +1 -0
- package/dist/files/engine/handler/intelligence/access.d.ts +35 -0
- package/dist/files/engine/handler/intelligence/access.js +85 -0
- package/dist/files/engine/handler/intelligence/access.js.map +1 -0
- package/dist/files/engine/handler/intelligence/magic-bytes.d.ts +21 -0
- package/dist/files/engine/handler/intelligence/magic-bytes.js +67 -0
- package/dist/files/engine/handler/intelligence/magic-bytes.js.map +1 -0
- package/dist/files/engine/handler/output/target.d.ts +19 -0
- package/dist/files/engine/handler/output/target.js +222 -0
- package/dist/files/engine/handler/output/target.js.map +1 -0
- package/dist/files/engine/handler/resolver.d.ts +34 -0
- package/dist/files/engine/handler/resolver.js +133 -0
- package/dist/files/engine/handler/resolver.js.map +1 -0
- package/dist/files/engine/handler/types.d.ts +367 -0
- package/dist/files/engine/handler/types.js +1 -0
- package/dist/files/engine/handler/types.js.map +1 -0
- package/dist/files/engine/handler/upload.d.ts +32 -0
- package/dist/files/engine/handler/upload.js +295 -0
- package/dist/files/engine/handler/upload.js.map +1 -0
- package/dist/files/engine/handler/utils/classify.d.ts +14 -0
- package/dist/files/engine/handler/utils/classify.js +27 -0
- package/dist/files/engine/handler/utils/classify.js.map +1 -0
- package/dist/files/engine/handler/utils/prefer-locator.d.ts +32 -0
- package/dist/files/engine/handler/utils/prefer-locator.js +34 -0
- package/dist/files/engine/handler/utils/prefer-locator.js.map +1 -0
- package/dist/files/engine/handler/utils/python-base.d.ts +129 -0
- package/dist/files/engine/handler/utils/python-base.js +60 -0
- package/dist/files/engine/handler/utils/python-base.js.map +1 -0
- package/dist/files/engine/hooks/blob-cache.d.ts +130 -0
- package/dist/files/engine/hooks/blob-cache.js +162 -0
- package/dist/files/engine/hooks/blob-cache.js.map +1 -0
- package/dist/files/engine/hooks/office-extraction-cache.d.ts +28 -0
- package/dist/files/engine/hooks/office-extraction-cache.js +67 -0
- package/dist/files/engine/hooks/office-extraction-cache.js.map +1 -0
- package/dist/files/engine/host/configure.d.ts +166 -0
- package/dist/files/engine/host/configure.js +30 -0
- package/dist/files/engine/host/configure.js.map +1 -0
- package/dist/files/engine/host/org.d.ts +8 -0
- package/dist/files/engine/host/org.js +16 -0
- package/dist/files/engine/host/org.js.map +1 -0
- package/dist/files/engine/host/python-client.d.ts +60 -0
- package/dist/files/engine/host/python-client.js +46 -0
- package/dist/files/engine/host/python-client.js.map +1 -0
- package/dist/files/engine/host/share-links.d.ts +20 -0
- package/dist/files/engine/host/share-links.js +32 -0
- package/dist/files/engine/host/share-links.js.map +1 -0
- package/dist/files/engine/host/store.d.ts +24 -0
- package/dist/files/engine/host/store.js +23 -0
- package/dist/files/engine/host/store.js.map +1 -0
- package/dist/files/engine/host/supabase.d.ts +3808 -0
- package/dist/files/engine/host/supabase.js +33 -0
- package/dist/files/engine/host/supabase.js.map +1 -0
- package/dist/files/engine/host/toast.d.ts +14 -0
- package/dist/files/engine/host/toast.js +20 -0
- package/dist/files/engine/host/toast.js.map +1 -0
- package/dist/files/engine/host/typed-client.d.ts +136 -0
- package/dist/files/engine/host/typed-client.js +66 -0
- package/dist/files/engine/host/typed-client.js.map +1 -0
- package/dist/files/engine/index.d.ts +22 -0
- package/dist/files/engine/index.js +49 -0
- package/dist/files/engine/index.js.map +1 -0
- package/dist/files/engine/media/our-file-sources.d.ts +39 -0
- package/dist/files/engine/media/our-file-sources.js +83 -0
- package/dist/files/engine/media/our-file-sources.js.map +1 -0
- package/dist/files/engine/media/signed-url.d.ts +1 -0
- package/dist/files/engine/media/signed-url.js +6 -0
- package/dist/files/engine/media/signed-url.js.map +1 -0
- package/dist/files/engine/redux/converters.d.ts +73 -0
- package/dist/files/engine/redux/converters.js +286 -0
- package/dist/files/engine/redux/converters.js.map +1 -0
- package/dist/files/engine/redux/file-hydration.d.ts +15 -0
- package/dist/files/engine/redux/file-hydration.js +46 -0
- package/dist/files/engine/redux/file-hydration.js.map +1 -0
- package/dist/files/engine/redux/file-tree-auth-boundary.d.ts +5 -0
- package/dist/files/engine/redux/file-tree-auth-boundary.js +28 -0
- package/dist/files/engine/redux/file-tree-auth-boundary.js.map +1 -0
- package/dist/files/engine/redux/file-tree-timeout.d.ts +32 -0
- package/dist/files/engine/redux/file-tree-timeout.js +73 -0
- package/dist/files/engine/redux/file-tree-timeout.js.map +1 -0
- package/dist/files/engine/redux/mutation-toast-middleware.d.ts +27 -0
- package/dist/files/engine/redux/mutation-toast-middleware.js +103 -0
- package/dist/files/engine/redux/mutation-toast-middleware.js.map +1 -0
- package/dist/files/engine/redux/realtime-middleware.d.ts +61 -0
- package/dist/files/engine/redux/realtime-middleware.js +431 -0
- package/dist/files/engine/redux/realtime-middleware.js.map +1 -0
- package/dist/files/engine/redux/request-ledger.d.ts +68 -0
- package/dist/files/engine/redux/request-ledger.js +66 -0
- package/dist/files/engine/redux/request-ledger.js.map +1 -0
- package/dist/files/engine/redux/selectors.d.ts +3254 -0
- package/dist/files/engine/redux/selectors.js +428 -0
- package/dist/files/engine/redux/selectors.js.map +1 -0
- package/dist/files/engine/redux/slice.d.ts +157 -0
- package/dist/files/engine/redux/slice.js +836 -0
- package/dist/files/engine/redux/slice.js.map +1 -0
- package/dist/files/engine/redux/thunks.d.ts +449 -0
- package/dist/files/engine/redux/thunks.js +1684 -0
- package/dist/files/engine/redux/thunks.js.map +1 -0
- package/dist/files/engine/redux/tree-utils.d.ts +90 -0
- package/dist/files/engine/redux/tree-utils.js +200 -0
- package/dist/files/engine/redux/tree-utils.js.map +1 -0
- package/dist/files/engine/support/claimsUser.d.ts +77 -0
- package/dist/files/engine/support/claimsUser.js +48 -0
- package/dist/files/engine/support/claimsUser.js.map +1 -0
- package/dist/files/engine/support/datetime.d.ts +6 -0
- package/dist/files/engine/support/datetime.js +15 -0
- package/dist/files/engine/support/datetime.js.map +1 -0
- package/dist/files/engine/support/document-visibility.d.ts +14 -0
- package/dist/files/engine/support/document-visibility.js +28 -0
- package/dist/files/engine/support/document-visibility.js.map +1 -0
- package/dist/files/engine/support/logger.d.ts +28 -0
- package/dist/files/engine/support/logger.js +36 -0
- package/dist/files/engine/support/logger.js.map +1 -0
- package/dist/files/engine/types.d.ts +1081 -0
- package/dist/files/engine/types.js +30 -0
- package/dist/files/engine/types.js.map +1 -0
- package/dist/files/engine/upload/cloudUpload.d.ts +182 -0
- package/dist/files/engine/upload/cloudUpload.js +418 -0
- package/dist/files/engine/upload/cloudUpload.js.map +1 -0
- package/dist/files/engine/upload/tusUpload.d.ts +100 -0
- package/dist/files/engine/upload/tusUpload.js +258 -0
- package/dist/files/engine/upload/tusUpload.js.map +1 -0
- package/dist/files/engine/upload/uploadDedupGuard.d.ts +58 -0
- package/dist/files/engine/upload/uploadDedupGuard.js +41 -0
- package/dist/files/engine/upload/uploadDedupGuard.js.map +1 -0
- package/dist/files/engine/upload/uploadGuardOpeners.d.ts +70 -0
- package/dist/files/engine/upload/uploadGuardOpeners.js +54 -0
- package/dist/files/engine/upload/uploadGuardOpeners.js.map +1 -0
- package/dist/files/engine/utils/file-types.d.ts +243 -0
- package/dist/files/engine/utils/file-types.js +1722 -0
- package/dist/files/engine/utils/file-types.js.map +1 -0
- package/dist/files/engine/utils/user-visible.d.ts +133 -0
- package/dist/files/engine/utils/user-visible.js +115 -0
- package/dist/files/engine/utils/user-visible.js.map +1 -0
- package/package.json +393 -9
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { createSessionRetry } from "@ai-matrx/data/db";
|
|
2
|
+
import { getFilesHost } from "./configure.js";
|
|
3
|
+
import {
|
|
4
|
+
isMissingSessionError,
|
|
5
|
+
SessionUnavailableError
|
|
6
|
+
} from "@ai-matrx/data/db";
|
|
7
|
+
import { getClaimsUser } from "../support/claimsUser.js";
|
|
8
|
+
function createClient() {
|
|
9
|
+
return getFilesHost("db").db;
|
|
10
|
+
}
|
|
11
|
+
const supabase = new Proxy({}, {
|
|
12
|
+
get(_target, key) {
|
|
13
|
+
const db = createClient();
|
|
14
|
+
const value = db[key];
|
|
15
|
+
return typeof value === "function" ? value.bind(db) : value;
|
|
16
|
+
}
|
|
17
|
+
});
|
|
18
|
+
function iamDb(client) {
|
|
19
|
+
return client.schema("iam");
|
|
20
|
+
}
|
|
21
|
+
const runWithSessionRetry = createSessionRetry({
|
|
22
|
+
auth: { getSession: () => createClient().auth.getSession() }
|
|
23
|
+
});
|
|
24
|
+
export {
|
|
25
|
+
SessionUnavailableError,
|
|
26
|
+
createClient,
|
|
27
|
+
getClaimsUser,
|
|
28
|
+
iamDb,
|
|
29
|
+
isMissingSessionError,
|
|
30
|
+
runWithSessionRetry,
|
|
31
|
+
supabase
|
|
32
|
+
};
|
|
33
|
+
//# sourceMappingURL=supabase.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../src/files/engine/host/supabase.ts"],"sourcesContent":["/**\n * Database seam — the host's supabase-js client, read at CALL time. Same\n * names the call sites imported from matrx-frontend's `utils/supabase/*` and\n * `lib/supabase/authRetry`.\n */\n\nimport type { SupabaseClient } from \"@supabase/supabase-js\";\nimport { createSessionRetry } from \"@ai-matrx/data/db\";\nimport type { FilesDatabase } from \"../db-types\";\nimport { getFilesHost } from \"./configure\";\n\nexport {\n isMissingSessionError,\n SessionUnavailableError,\n type AuthRetryableResult,\n} from \"@ai-matrx/data/db\";\nexport { getClaimsUser } from \"../support/claimsUser\";\n\nexport type FilesDb = SupabaseClient<FilesDatabase>;\n\n/** The configured host's database client. */\nexport function createClient(): FilesDb {\n return getFilesHost(\"db\").db as unknown as FilesDb;\n}\n\n/**\n * `supabase` as the call sites knew it: every property read goes to the\n * configured host's client at that moment (methods stay bound to it).\n */\nexport const supabase: FilesDb = new Proxy({} as FilesDb, {\n get(_target, key) {\n const db = createClient() as unknown as Record<PropertyKey, unknown>;\n const value = db[key];\n return typeof value === \"function\" ? (value as (...a: unknown[]) => unknown).bind(db) : value;\n },\n});\n\nexport function iamDb<C extends FilesDb>(client: C) {\n return client.schema(\"iam\");\n}\n\nexport const runWithSessionRetry = createSessionRetry({\n auth: { getSession: () => createClient().auth.getSession() },\n});\n"],"mappings":"AAOA,SAAS,0BAA0B;AAEnC,SAAS,oBAAoB;AAE7B;AAAA,EACE;AAAA,EACA;AAAA,OAEK;AACP,SAAS,qBAAqB;AAKvB,SAAS,eAAwB;AACtC,SAAO,aAAa,IAAI,EAAE;AAC5B;AAMO,MAAM,WAAoB,IAAI,MAAM,CAAC,GAAc;AAAA,EACxD,IAAI,SAAS,KAAK;AAChB,UAAM,KAAK,aAAa;AACxB,UAAM,QAAQ,GAAG,GAAG;AACpB,WAAO,OAAO,UAAU,aAAc,MAAuC,KAAK,EAAE,IAAI;AAAA,EAC1F;AACF,CAAC;AAEM,SAAS,MAAyB,QAAW;AAClD,SAAO,OAAO,OAAO,KAAK;AAC5B;AAEO,MAAM,sBAAsB,mBAAmB;AAAA,EACpD,MAAM,EAAE,YAAY,MAAM,aAAa,EAAE,KAAK,WAAW,EAAE;AAC7D,CAAC;","names":[]}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Notification seam — the host's toasts. A host without one logs to the
|
|
3
|
+
* console, so a failure is never silent.
|
|
4
|
+
*/
|
|
5
|
+
type Options = {
|
|
6
|
+
description?: string;
|
|
7
|
+
id?: string | number;
|
|
8
|
+
duration?: number;
|
|
9
|
+
};
|
|
10
|
+
export declare const toast: {
|
|
11
|
+
success(message: string, options?: Options): void;
|
|
12
|
+
error(message: string, options?: Options): void;
|
|
13
|
+
};
|
|
14
|
+
export {};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { getFilesHost, isFilesHostConfigured } from "./configure.js";
|
|
2
|
+
function port() {
|
|
3
|
+
return isFilesHostConfigured() ? getFilesHost("notify").notify : void 0;
|
|
4
|
+
}
|
|
5
|
+
const toast = {
|
|
6
|
+
success(message, options) {
|
|
7
|
+
const notify = port();
|
|
8
|
+
if (notify) notify.success(message, options);
|
|
9
|
+
else console.info(`[files] ${message}`, options?.description ?? "");
|
|
10
|
+
},
|
|
11
|
+
error(message, options) {
|
|
12
|
+
const notify = port();
|
|
13
|
+
if (notify) notify.error(message, options);
|
|
14
|
+
else console.error(`[files] ${message}`, options?.description ?? "");
|
|
15
|
+
}
|
|
16
|
+
};
|
|
17
|
+
export {
|
|
18
|
+
toast
|
|
19
|
+
};
|
|
20
|
+
//# sourceMappingURL=toast.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../src/files/engine/host/toast.ts"],"sourcesContent":["/**\n * Notification seam — the host's toasts. A host without one logs to the\n * console, so a failure is never silent.\n */\n\nimport { getFilesHost, isFilesHostConfigured } from \"./configure\";\n\ntype Options = { description?: string; id?: string | number; duration?: number };\n\nfunction port() {\n return isFilesHostConfigured() ? getFilesHost(\"notify\").notify : undefined;\n}\n\nexport const toast = {\n success(message: string, options?: Options): void {\n const notify = port();\n if (notify) notify.success(message, options);\n else console.info(`[files] ${message}`, options?.description ?? \"\");\n },\n error(message: string, options?: Options): void {\n const notify = port();\n if (notify) notify.error(message, options);\n else console.error(`[files] ${message}`, options?.description ?? \"\");\n },\n};\n"],"mappings":"AAKA,SAAS,cAAc,6BAA6B;AAIpD,SAAS,OAAO;AACd,SAAO,sBAAsB,IAAI,aAAa,QAAQ,EAAE,SAAS;AACnE;AAEO,MAAM,QAAQ;AAAA,EACnB,QAAQ,SAAiB,SAAyB;AAChD,UAAM,SAAS,KAAK;AACpB,QAAI,OAAQ,QAAO,QAAQ,SAAS,OAAO;AAAA,QACtC,SAAQ,KAAK,WAAW,OAAO,IAAI,SAAS,eAAe,EAAE;AAAA,EACpE;AAAA,EACA,MAAM,SAAiB,SAAyB;AAC9C,UAAM,SAAS,KAAK;AACpB,QAAI,OAAQ,QAAO,MAAM,SAAS,OAAO;AAAA,QACpC,SAAQ,MAAM,WAAW,OAAO,IAAI,SAAS,eAAe,EAAE;AAAA,EACrE;AACF;","names":[]}
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/api/typed-client.ts
|
|
3
|
+
*
|
|
4
|
+
* Path-keyed, contract-bound wrapper around the raw Python client
|
|
5
|
+
* (`lib/python-client.ts`). This is the ONLY sanctioned way to call the
|
|
6
|
+
* Python backend from feature code.
|
|
7
|
+
*
|
|
8
|
+
* Why this exists
|
|
9
|
+
* ---------------
|
|
10
|
+
* The raw helpers (`postJson<T>`, `postMultipart<T>`, …) take a plain
|
|
11
|
+
* `string` path and an *asserted* response type `T`. Nothing links the
|
|
12
|
+
* path you call, the body you send, and the response you claim to the
|
|
13
|
+
* server's actual OpenAPI contract. That gap shipped a production 400:
|
|
14
|
+
* the FE hand-mirrored `PreviewVariantSpec` with a `key` field while the
|
|
15
|
+
* backend had renamed it to `preset_id`. TypeScript was green because it
|
|
16
|
+
* was checking a hand-written lie against itself.
|
|
17
|
+
*
|
|
18
|
+
* These wrappers close the gap. Every function is generic over a *path
|
|
19
|
+
* literal* from the generated `paths` interface. The request body and the
|
|
20
|
+
* success response are DERIVED from `operations[...]`, never asserted:
|
|
21
|
+
*
|
|
22
|
+
* - Wrong path → compile error (not a key of `paths`).
|
|
23
|
+
* - Wrong body shape / key → compile error (must match `requestBody`).
|
|
24
|
+
* - Wrong response field → compile error (response is the real type).
|
|
25
|
+
*
|
|
26
|
+
* When the backend changes and `pnpm sync-types` regenerates
|
|
27
|
+
* `@ai-matrx/agents/generated/api-types`, every drifted callsite lights up
|
|
28
|
+
* red in the same PR. That is the whole point.
|
|
29
|
+
*
|
|
30
|
+
* Regenerate the contract: `pnpm sync-types` (Supabase + Python OpenAPI).
|
|
31
|
+
*
|
|
32
|
+
* NOTE: path-parameter endpoints (`/assets/{file_id}`) are handled by
|
|
33
|
+
* `buildPath()` — pass the template literal + params so the key still
|
|
34
|
+
* type-checks against `paths`. Query-only and no-param endpoints pass the
|
|
35
|
+
* path literal directly.
|
|
36
|
+
*/
|
|
37
|
+
import type { paths } from "@ai-matrx/agents/generated/api-types";
|
|
38
|
+
import { type RequestOptions, type ResponseMeta } from "./python-client.js";
|
|
39
|
+
type HttpMethod = "get" | "post" | "put" | "patch" | "delete";
|
|
40
|
+
/** Every path literal in the contract that defines a real operation for M. */
|
|
41
|
+
export type PathWith<M extends HttpMethod> = {
|
|
42
|
+
[P in keyof paths]: paths[P] extends Record<M, infer Op> ? Op extends undefined | never ? never : P : never;
|
|
43
|
+
}[keyof paths];
|
|
44
|
+
/** The operation object registered for (path P, method M). */
|
|
45
|
+
type OpOf<P extends keyof paths, M extends HttpMethod> = paths[P] extends Record<M, infer O> ? O : never;
|
|
46
|
+
/** JSON request body of an operation. `undefined` when the body is optional. */
|
|
47
|
+
export type JsonBodyOf<O> = O extends {
|
|
48
|
+
requestBody: {
|
|
49
|
+
content: {
|
|
50
|
+
"application/json": infer B;
|
|
51
|
+
};
|
|
52
|
+
};
|
|
53
|
+
} ? B : O extends {
|
|
54
|
+
requestBody?: {
|
|
55
|
+
content: {
|
|
56
|
+
"application/json": infer B;
|
|
57
|
+
};
|
|
58
|
+
};
|
|
59
|
+
} ? B | undefined : never;
|
|
60
|
+
/** The 2xx `application/json` success payload of an operation. */
|
|
61
|
+
export type OkJsonOf<O> = O extends {
|
|
62
|
+
responses: {
|
|
63
|
+
200: {
|
|
64
|
+
content: {
|
|
65
|
+
"application/json": infer T;
|
|
66
|
+
};
|
|
67
|
+
};
|
|
68
|
+
};
|
|
69
|
+
} ? T : O extends {
|
|
70
|
+
responses: {
|
|
71
|
+
201: {
|
|
72
|
+
content: {
|
|
73
|
+
"application/json": infer T;
|
|
74
|
+
};
|
|
75
|
+
};
|
|
76
|
+
};
|
|
77
|
+
} ? T : O extends {
|
|
78
|
+
responses: {
|
|
79
|
+
204: unknown;
|
|
80
|
+
};
|
|
81
|
+
} ? null : unknown;
|
|
82
|
+
type PostBody<P extends keyof paths> = JsonBodyOf<OpOf<P, "post">>;
|
|
83
|
+
type PatchBody<P extends keyof paths> = JsonBodyOf<OpOf<P, "patch">>;
|
|
84
|
+
type PutBody<P extends keyof paths> = JsonBodyOf<OpOf<P, "put">>;
|
|
85
|
+
type GetResult<P extends keyof paths> = OkJsonOf<OpOf<P, "get">>;
|
|
86
|
+
type PostResult<P extends keyof paths> = OkJsonOf<OpOf<P, "post">>;
|
|
87
|
+
type PatchResult<P extends keyof paths> = OkJsonOf<OpOf<P, "patch">>;
|
|
88
|
+
type PutResult<P extends keyof paths> = OkJsonOf<OpOf<P, "put">>;
|
|
89
|
+
type DeleteResult<P extends keyof paths> = OkJsonOf<OpOf<P, "delete">>;
|
|
90
|
+
type Envelope<T> = Promise<{
|
|
91
|
+
data: T;
|
|
92
|
+
meta: ResponseMeta;
|
|
93
|
+
}>;
|
|
94
|
+
/**
|
|
95
|
+
* Fill a path template's `{param}` slots while keeping the *template* literal
|
|
96
|
+
* (not the interpolated runtime string) as the type the client checks against
|
|
97
|
+
* `paths`. Usage:
|
|
98
|
+
*
|
|
99
|
+
* apiGet(buildPath("/assets/{file_id}", { file_id }))
|
|
100
|
+
*
|
|
101
|
+
* The returned branded string carries the template's literal type so the call
|
|
102
|
+
* still resolves to the correct operation.
|
|
103
|
+
*/
|
|
104
|
+
export declare function buildPath<P extends keyof paths>(template: P, params: Record<string, string | number>): P;
|
|
105
|
+
/** A query-string value; `null`/`undefined` entries are dropped. */
|
|
106
|
+
export type QueryScalar = string | number | boolean | null | undefined;
|
|
107
|
+
/** A query value: a scalar, or an array emitted as REPEATED params (`?k=a&k=b`). */
|
|
108
|
+
export type QueryValue = QueryScalar | readonly QueryScalar[];
|
|
109
|
+
/** Append a query object to a path literal, dropping empty values. */
|
|
110
|
+
export declare function withQuery<P extends string>(path: P, query?: Record<string, QueryValue>): P;
|
|
111
|
+
/**
|
|
112
|
+
* GET a JSON endpoint. Response is derived from the contract; the path literal
|
|
113
|
+
* stays contract-checked even with query params (passed via `opts.query`, which
|
|
114
|
+
* `withQuery` serializes onto the URL).
|
|
115
|
+
*/
|
|
116
|
+
export declare function apiGet<P extends PathWith<"get">>(path: P, opts?: RequestOptions & {
|
|
117
|
+
query?: Record<string, QueryValue>;
|
|
118
|
+
}): Envelope<GetResult<P>>;
|
|
119
|
+
/** POST a JSON body. Body AND response are derived from the contract. */
|
|
120
|
+
export declare function apiPost<P extends PathWith<"post">>(path: P, body: PostBody<P> extends never ? undefined : PostBody<P>, opts?: RequestOptions): Envelope<PostResult<P>>;
|
|
121
|
+
/** PATCH a JSON body. Body AND response are derived from the contract. */
|
|
122
|
+
export declare function apiPatch<P extends PathWith<"patch">>(path: P, body: PatchBody<P>, opts?: RequestOptions): Envelope<PatchResult<P>>;
|
|
123
|
+
/** PUT a JSON body. Body AND response are derived from the contract. */
|
|
124
|
+
export declare function apiPut<P extends PathWith<"put">>(path: P, body: PutBody<P>, opts?: RequestOptions): Envelope<PutResult<P>>;
|
|
125
|
+
/** DELETE an endpoint. Response is derived from the contract. */
|
|
126
|
+
export declare function apiDelete<P extends PathWith<"delete">>(path: P, opts?: RequestOptions): Envelope<DeleteResult<P>>;
|
|
127
|
+
/**
|
|
128
|
+
* POST a `multipart/form-data` body. The FormData is built by the caller
|
|
129
|
+
* (multipart field values are stringly at the wire), but the RESPONSE is
|
|
130
|
+
* still derived from the contract — killing the unchecked `<T>` assertion
|
|
131
|
+
* that let the preview 400 through. Type the objects you `JSON.stringify`
|
|
132
|
+
* into the form against their generated `components["schemas"][…]` type so
|
|
133
|
+
* the encoded payload can't drift either.
|
|
134
|
+
*/
|
|
135
|
+
export declare function apiMultipart<P extends PathWith<"post">>(path: P, form: FormData, opts?: RequestOptions): Envelope<PostResult<P>>;
|
|
136
|
+
export {};
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import {
|
|
2
|
+
del,
|
|
3
|
+
getJson,
|
|
4
|
+
patchJson,
|
|
5
|
+
postJson,
|
|
6
|
+
postMultipart,
|
|
7
|
+
putJson
|
|
8
|
+
} from "./python-client.js";
|
|
9
|
+
function buildPath(template, params) {
|
|
10
|
+
return template.replace(/\{([^}]+)\}/g, (_, k) => {
|
|
11
|
+
const v = params[k];
|
|
12
|
+
if (v === void 0) {
|
|
13
|
+
throw new Error(
|
|
14
|
+
`buildPath: missing path parameter "${k}" for template "${String(template)}"`
|
|
15
|
+
);
|
|
16
|
+
}
|
|
17
|
+
return encodeURIComponent(String(v));
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
function withQuery(path, query) {
|
|
21
|
+
if (!query) return path;
|
|
22
|
+
const qs = new URLSearchParams();
|
|
23
|
+
const appendScalar = (k, v) => {
|
|
24
|
+
if (v === null || v === void 0 || v === "") return;
|
|
25
|
+
qs.append(k, String(v));
|
|
26
|
+
};
|
|
27
|
+
for (const [k, v] of Object.entries(query)) {
|
|
28
|
+
if (Array.isArray(v)) {
|
|
29
|
+
for (const item of v) appendScalar(k, item);
|
|
30
|
+
} else {
|
|
31
|
+
appendScalar(k, v);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
const s = qs.toString();
|
|
35
|
+
return s ? `${path}${path.includes("?") ? "&" : "?"}${s}` : path;
|
|
36
|
+
}
|
|
37
|
+
function apiGet(path, opts) {
|
|
38
|
+
const { query, ...rest } = opts ?? {};
|
|
39
|
+
return getJson(withQuery(path, query), rest);
|
|
40
|
+
}
|
|
41
|
+
function apiPost(path, body, opts) {
|
|
42
|
+
return postJson(path, body, opts);
|
|
43
|
+
}
|
|
44
|
+
function apiPatch(path, body, opts) {
|
|
45
|
+
return patchJson(path, body, opts);
|
|
46
|
+
}
|
|
47
|
+
function apiPut(path, body, opts) {
|
|
48
|
+
return putJson(path, body, opts);
|
|
49
|
+
}
|
|
50
|
+
function apiDelete(path, opts) {
|
|
51
|
+
return del(path, opts);
|
|
52
|
+
}
|
|
53
|
+
function apiMultipart(path, form, opts) {
|
|
54
|
+
return postMultipart(path, form, opts);
|
|
55
|
+
}
|
|
56
|
+
export {
|
|
57
|
+
apiDelete,
|
|
58
|
+
apiGet,
|
|
59
|
+
apiMultipart,
|
|
60
|
+
apiPatch,
|
|
61
|
+
apiPost,
|
|
62
|
+
apiPut,
|
|
63
|
+
buildPath,
|
|
64
|
+
withQuery
|
|
65
|
+
};
|
|
66
|
+
//# sourceMappingURL=typed-client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../src/files/engine/host/typed-client.ts"],"sourcesContent":["/**\n * lib/api/typed-client.ts\n *\n * Path-keyed, contract-bound wrapper around the raw Python client\n * (`lib/python-client.ts`). This is the ONLY sanctioned way to call the\n * Python backend from feature code.\n *\n * Why this exists\n * ---------------\n * The raw helpers (`postJson<T>`, `postMultipart<T>`, …) take a plain\n * `string` path and an *asserted* response type `T`. Nothing links the\n * path you call, the body you send, and the response you claim to the\n * server's actual OpenAPI contract. That gap shipped a production 400:\n * the FE hand-mirrored `PreviewVariantSpec` with a `key` field while the\n * backend had renamed it to `preset_id`. TypeScript was green because it\n * was checking a hand-written lie against itself.\n *\n * These wrappers close the gap. Every function is generic over a *path\n * literal* from the generated `paths` interface. The request body and the\n * success response are DERIVED from `operations[...]`, never asserted:\n *\n * - Wrong path → compile error (not a key of `paths`).\n * - Wrong body shape / key → compile error (must match `requestBody`).\n * - Wrong response field → compile error (response is the real type).\n *\n * When the backend changes and `pnpm sync-types` regenerates\n * `@ai-matrx/agents/generated/api-types`, every drifted callsite lights up\n * red in the same PR. That is the whole point.\n *\n * Regenerate the contract: `pnpm sync-types` (Supabase + Python OpenAPI).\n *\n * NOTE: path-parameter endpoints (`/assets/{file_id}`) are handled by\n * `buildPath()` — pass the template literal + params so the key still\n * type-checks against `paths`. Query-only and no-param endpoints pass the\n * path literal directly.\n */\n\nimport type { paths } from \"@ai-matrx/agents/generated/api-types\";\nimport {\n del,\n getJson,\n patchJson,\n postJson,\n postMultipart,\n putJson,\n type RequestOptions,\n type ResponseMeta,\n} from \"./python-client\";\n\n// ---------------------------------------------------------------------------\n// Contract-derivation helpers (type-level only — zero runtime cost)\n// ---------------------------------------------------------------------------\n\ntype HttpMethod = \"get\" | \"post\" | \"put\" | \"patch\" | \"delete\";\n\n/** Every path literal in the contract that defines a real operation for M. */\nexport type PathWith<M extends HttpMethod> = {\n [P in keyof paths]: paths[P] extends Record<M, infer Op>\n ? Op extends undefined | never\n ? never\n : P\n : never;\n}[keyof paths];\n\n/** The operation object registered for (path P, method M). */\ntype OpOf<P extends keyof paths, M extends HttpMethod> = paths[P] extends Record<\n M,\n infer O\n>\n ? O\n : never;\n\n/** JSON request body of an operation. `undefined` when the body is optional. */\nexport type JsonBodyOf<O> = O extends {\n requestBody: { content: { \"application/json\": infer B } };\n}\n ? B\n : O extends { requestBody?: { content: { \"application/json\": infer B } } }\n ? B | undefined\n : never;\n\n/** The 2xx `application/json` success payload of an operation. */\nexport type OkJsonOf<O> = O extends {\n responses: { 200: { content: { \"application/json\": infer T } } };\n}\n ? T\n : O extends { responses: { 201: { content: { \"application/json\": infer T } } } }\n ? T\n : O extends { responses: { 204: unknown } }\n ? null\n : unknown;\n\n// Convenience aliases keyed by (path, method).\ntype PostBody<P extends keyof paths> = JsonBodyOf<OpOf<P, \"post\">>;\ntype PatchBody<P extends keyof paths> = JsonBodyOf<OpOf<P, \"patch\">>;\ntype PutBody<P extends keyof paths> = JsonBodyOf<OpOf<P, \"put\">>;\n\ntype GetResult<P extends keyof paths> = OkJsonOf<OpOf<P, \"get\">>;\ntype PostResult<P extends keyof paths> = OkJsonOf<OpOf<P, \"post\">>;\ntype PatchResult<P extends keyof paths> = OkJsonOf<OpOf<P, \"patch\">>;\ntype PutResult<P extends keyof paths> = OkJsonOf<OpOf<P, \"put\">>;\ntype DeleteResult<P extends keyof paths> = OkJsonOf<OpOf<P, \"delete\">>;\n\ntype Envelope<T> = Promise<{ data: T; meta: ResponseMeta }>;\n\n// ---------------------------------------------------------------------------\n// Path-parameter interpolation\n// ---------------------------------------------------------------------------\n\n/**\n * Fill a path template's `{param}` slots while keeping the *template* literal\n * (not the interpolated runtime string) as the type the client checks against\n * `paths`. Usage:\n *\n * apiGet(buildPath(\"/assets/{file_id}\", { file_id }))\n *\n * The returned branded string carries the template's literal type so the call\n * still resolves to the correct operation.\n */\nexport function buildPath<P extends keyof paths>(\n template: P,\n params: Record<string, string | number>,\n): P {\n return (template as string).replace(/\\{([^}]+)\\}/g, (_, k: string) => {\n const v = params[k];\n if (v === undefined) {\n throw new Error(\n `buildPath: missing path parameter \"${k}\" for template \"${String(template)}\"`,\n );\n }\n return encodeURIComponent(String(v));\n }) as P;\n}\n\n// ---------------------------------------------------------------------------\n// Contract-bound verbs\n// ---------------------------------------------------------------------------\n\n/** A query-string value; `null`/`undefined` entries are dropped. */\nexport type QueryScalar = string | number | boolean | null | undefined;\n/** A query value: a scalar, or an array emitted as REPEATED params (`?k=a&k=b`). */\nexport type QueryValue = QueryScalar | readonly QueryScalar[];\n\n/** Append a query object to a path literal, dropping empty values. */\nexport function withQuery<P extends string>(\n path: P,\n query?: Record<string, QueryValue>,\n): P {\n if (!query) return path;\n const qs = new URLSearchParams();\n const appendScalar = (k: string, v: QueryScalar) => {\n // Drop null / undefined / empty-string so absent params stay absent\n // (an empty value must not become `?k=` — that can change server behavior).\n if (v === null || v === undefined || v === \"\") return;\n qs.append(k, String(v));\n };\n for (const [k, v] of Object.entries(query)) {\n if (Array.isArray(v)) {\n // Arrays → repeated params (`?k=a&k=b`), matching FastAPI's List[str]\n // query parsing. An empty array emits nothing.\n for (const item of v) appendScalar(k, item);\n } else {\n appendScalar(k, v as QueryScalar);\n }\n }\n const s = qs.toString();\n return (s ? `${path}${path.includes(\"?\") ? \"&\" : \"?\"}${s}` : path) as P;\n}\n\n/**\n * GET a JSON endpoint. Response is derived from the contract; the path literal\n * stays contract-checked even with query params (passed via `opts.query`, which\n * `withQuery` serializes onto the URL).\n */\nexport function apiGet<P extends PathWith<\"get\">>(\n path: P,\n opts?: RequestOptions & { query?: Record<string, QueryValue> },\n): Envelope<GetResult<P>> {\n const { query, ...rest } = opts ?? {};\n return getJson<GetResult<P>>(withQuery(path, query), rest);\n}\n\n/** POST a JSON body. Body AND response are derived from the contract. */\nexport function apiPost<P extends PathWith<\"post\">>(\n path: P,\n body: PostBody<P> extends never ? undefined : PostBody<P>,\n opts?: RequestOptions,\n): Envelope<PostResult<P>> {\n return postJson<PostResult<P>, PostBody<P>>(path, body as PostBody<P>, opts);\n}\n\n/** PATCH a JSON body. Body AND response are derived from the contract. */\nexport function apiPatch<P extends PathWith<\"patch\">>(\n path: P,\n body: PatchBody<P>,\n opts?: RequestOptions,\n): Envelope<PatchResult<P>> {\n return patchJson<PatchResult<P>, PatchBody<P>>(path, body, opts);\n}\n\n/** PUT a JSON body. Body AND response are derived from the contract. */\nexport function apiPut<P extends PathWith<\"put\">>(\n path: P,\n body: PutBody<P>,\n opts?: RequestOptions,\n): Envelope<PutResult<P>> {\n return putJson<PutResult<P>, PutBody<P>>(path, body, opts);\n}\n\n/** DELETE an endpoint. Response is derived from the contract. */\nexport function apiDelete<P extends PathWith<\"delete\">>(\n path: P,\n opts?: RequestOptions,\n): Envelope<DeleteResult<P>> {\n return del<DeleteResult<P>>(path, opts) as Envelope<DeleteResult<P>>;\n}\n\n/**\n * POST a `multipart/form-data` body. The FormData is built by the caller\n * (multipart field values are stringly at the wire), but the RESPONSE is\n * still derived from the contract — killing the unchecked `<T>` assertion\n * that let the preview 400 through. Type the objects you `JSON.stringify`\n * into the form against their generated `components[\"schemas\"][…]` type so\n * the encoded payload can't drift either.\n */\nexport function apiMultipart<P extends PathWith<\"post\">>(\n path: P,\n form: FormData,\n opts?: RequestOptions,\n): Envelope<PostResult<P>> {\n return postMultipart<PostResult<P>>(path, form, opts);\n}\n"],"mappings":"AAsCA;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAGK;AAwEA,SAAS,UACd,UACA,QACG;AACH,SAAQ,SAAoB,QAAQ,gBAAgB,CAAC,GAAG,MAAc;AACpE,UAAM,IAAI,OAAO,CAAC;AAClB,QAAI,MAAM,QAAW;AACnB,YAAM,IAAI;AAAA,QACR,sCAAsC,CAAC,mBAAmB,OAAO,QAAQ,CAAC;AAAA,MAC5E;AAAA,IACF;AACA,WAAO,mBAAmB,OAAO,CAAC,CAAC;AAAA,EACrC,CAAC;AACH;AAYO,SAAS,UACd,MACA,OACG;AACH,MAAI,CAAC,MAAO,QAAO;AACnB,QAAM,KAAK,IAAI,gBAAgB;AAC/B,QAAM,eAAe,CAAC,GAAW,MAAmB;AAGlD,QAAI,MAAM,QAAQ,MAAM,UAAa,MAAM,GAAI;AAC/C,OAAG,OAAO,GAAG,OAAO,CAAC,CAAC;AAAA,EACxB;AACA,aAAW,CAAC,GAAG,CAAC,KAAK,OAAO,QAAQ,KAAK,GAAG;AAC1C,QAAI,MAAM,QAAQ,CAAC,GAAG;AAGpB,iBAAW,QAAQ,EAAG,cAAa,GAAG,IAAI;AAAA,IAC5C,OAAO;AACL,mBAAa,GAAG,CAAgB;AAAA,IAClC;AAAA,EACF;AACA,QAAM,IAAI,GAAG,SAAS;AACtB,SAAQ,IAAI,GAAG,IAAI,GAAG,KAAK,SAAS,GAAG,IAAI,MAAM,GAAG,GAAG,CAAC,KAAK;AAC/D;AAOO,SAAS,OACd,MACA,MACwB;AACxB,QAAM,EAAE,OAAO,GAAG,KAAK,IAAI,QAAQ,CAAC;AACpC,SAAO,QAAsB,UAAU,MAAM,KAAK,GAAG,IAAI;AAC3D;AAGO,SAAS,QACd,MACA,MACA,MACyB;AACzB,SAAO,SAAqC,MAAM,MAAqB,IAAI;AAC7E;AAGO,SAAS,SACd,MACA,MACA,MAC0B;AAC1B,SAAO,UAAwC,MAAM,MAAM,IAAI;AACjE;AAGO,SAAS,OACd,MACA,MACA,MACwB;AACxB,SAAO,QAAkC,MAAM,MAAM,IAAI;AAC3D;AAGO,SAAS,UACd,MACA,MAC2B;AAC3B,SAAO,IAAqB,MAAM,IAAI;AACxC;AAUO,SAAS,aACd,MACA,MACA,MACyB;AACzB,SAAO,cAA6B,MAAM,MAAM,IAAI;AACtD;","names":[]}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@ai-matrx/media/files/engine` — the Matrx files engine's front door (P16f):
|
|
3
|
+
* the `cloudFiles` slice and its two middlewares, the file handler (normalize →
|
|
4
|
+
* resolve → upload → target), our-file URL recognition, the upload guard, and
|
|
5
|
+
* the host port that wires it to a client. Every engine module is also its own
|
|
6
|
+
* subpath (`@ai-matrx/media/files/engine/<dir>/<module>`), so a host keeps
|
|
7
|
+
* module-level imports and mocks.
|
|
8
|
+
*
|
|
9
|
+
* Mount: `configureFilesHost({ db, server, store })` once, then put
|
|
10
|
+
* `cloudFilesReducer` under the `cloudFiles` key and add both middlewares.
|
|
11
|
+
*/
|
|
12
|
+
export { configureFilesHost, isFilesHostConfigured, FilesHostNotConfiguredError, type FilesHost, type FilesServerClient, type FilesShareLinks, type FilesShareLink, type FilesCreateShareLinkOptions, type FilesStore, type FilesScope, type FilesNotifyPort, type FilesRequestOptions, type FilesResponseMeta, type FilesUploadProgressEvent, type FilesDownloadProgressEvent, } from "./host/configure.js";
|
|
13
|
+
export { cloudFilesReducer } from "./redux/slice.js";
|
|
14
|
+
export { cloudFilesRealtimeMiddleware, attachCloudFilesRealtime, detachCloudFilesRealtime, } from "./redux/realtime-middleware.js";
|
|
15
|
+
export { cloudFilesMutationToastMiddleware } from "./redux/mutation-toast-middleware.js";
|
|
16
|
+
export { fileHandler } from "./handler/handler.js";
|
|
17
|
+
export type { FileSource, FileTarget, NormalizedFile, UploadedNormalizedFile, UploadOpts, } from "./handler/types.js";
|
|
18
|
+
export { normalize, fromCloudFile } from "./handler/input/normalize.js";
|
|
19
|
+
export { toMediaRef, toTarget } from "./handler/output/target.js";
|
|
20
|
+
export { OUR_FILE_URL_MARKERS, recognizeOurFileUrl, mightBeOurFileUrl, fileIdFromFileEndpointUrl, type OurFileMatch, } from "./media/our-file-sources.js";
|
|
21
|
+
export { requestUpload, registerUploadGuardHandler, unregisterUploadGuardHandler, type UploadGuardHandler, type UploadGuardResult, } from "./upload/uploadGuardOpeners.js";
|
|
22
|
+
export type { CloudFilesState, CloudFile } from "./types.js";
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import {
|
|
2
|
+
configureFilesHost,
|
|
3
|
+
isFilesHostConfigured,
|
|
4
|
+
FilesHostNotConfiguredError
|
|
5
|
+
} from "./host/configure.js";
|
|
6
|
+
import { cloudFilesReducer } from "./redux/slice.js";
|
|
7
|
+
import {
|
|
8
|
+
cloudFilesRealtimeMiddleware,
|
|
9
|
+
attachCloudFilesRealtime,
|
|
10
|
+
detachCloudFilesRealtime
|
|
11
|
+
} from "./redux/realtime-middleware.js";
|
|
12
|
+
import { cloudFilesMutationToastMiddleware } from "./redux/mutation-toast-middleware.js";
|
|
13
|
+
import { fileHandler } from "./handler/handler.js";
|
|
14
|
+
import { normalize, fromCloudFile } from "./handler/input/normalize.js";
|
|
15
|
+
import { toMediaRef, toTarget } from "./handler/output/target.js";
|
|
16
|
+
import {
|
|
17
|
+
OUR_FILE_URL_MARKERS,
|
|
18
|
+
recognizeOurFileUrl,
|
|
19
|
+
mightBeOurFileUrl,
|
|
20
|
+
fileIdFromFileEndpointUrl
|
|
21
|
+
} from "./media/our-file-sources.js";
|
|
22
|
+
import {
|
|
23
|
+
requestUpload,
|
|
24
|
+
registerUploadGuardHandler,
|
|
25
|
+
unregisterUploadGuardHandler
|
|
26
|
+
} from "./upload/uploadGuardOpeners.js";
|
|
27
|
+
export {
|
|
28
|
+
FilesHostNotConfiguredError,
|
|
29
|
+
OUR_FILE_URL_MARKERS,
|
|
30
|
+
attachCloudFilesRealtime,
|
|
31
|
+
cloudFilesMutationToastMiddleware,
|
|
32
|
+
cloudFilesRealtimeMiddleware,
|
|
33
|
+
cloudFilesReducer,
|
|
34
|
+
configureFilesHost,
|
|
35
|
+
detachCloudFilesRealtime,
|
|
36
|
+
fileHandler,
|
|
37
|
+
fileIdFromFileEndpointUrl,
|
|
38
|
+
fromCloudFile,
|
|
39
|
+
isFilesHostConfigured,
|
|
40
|
+
mightBeOurFileUrl,
|
|
41
|
+
normalize,
|
|
42
|
+
recognizeOurFileUrl,
|
|
43
|
+
registerUploadGuardHandler,
|
|
44
|
+
requestUpload,
|
|
45
|
+
toMediaRef,
|
|
46
|
+
toTarget,
|
|
47
|
+
unregisterUploadGuardHandler
|
|
48
|
+
};
|
|
49
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/files/engine/index.ts"],"sourcesContent":["/**\n * `@ai-matrx/media/files/engine` — the Matrx files engine's front door (P16f):\n * the `cloudFiles` slice and its two middlewares, the file handler (normalize →\n * resolve → upload → target), our-file URL recognition, the upload guard, and\n * the host port that wires it to a client. Every engine module is also its own\n * subpath (`@ai-matrx/media/files/engine/<dir>/<module>`), so a host keeps\n * module-level imports and mocks.\n *\n * Mount: `configureFilesHost({ db, server, store })` once, then put\n * `cloudFilesReducer` under the `cloudFiles` key and add both middlewares.\n */\n\nexport {\n configureFilesHost,\n isFilesHostConfigured,\n FilesHostNotConfiguredError,\n type FilesHost,\n type FilesServerClient,\n type FilesShareLinks,\n type FilesShareLink,\n type FilesCreateShareLinkOptions,\n type FilesStore,\n type FilesScope,\n type FilesNotifyPort,\n type FilesRequestOptions,\n type FilesResponseMeta,\n type FilesUploadProgressEvent,\n type FilesDownloadProgressEvent,\n} from \"./host/configure\";\n\nexport { cloudFilesReducer } from \"./redux/slice\";\nexport {\n cloudFilesRealtimeMiddleware,\n attachCloudFilesRealtime,\n detachCloudFilesRealtime,\n} from \"./redux/realtime-middleware\";\nexport { cloudFilesMutationToastMiddleware } from \"./redux/mutation-toast-middleware\";\n\nexport { fileHandler } from \"./handler/handler\";\nexport type {\n FileSource,\n FileTarget,\n NormalizedFile,\n UploadedNormalizedFile,\n UploadOpts,\n} from \"./handler/types\";\nexport { normalize, fromCloudFile } from \"./handler/input/normalize\";\nexport { toMediaRef, toTarget } from \"./handler/output/target\";\n\nexport {\n OUR_FILE_URL_MARKERS,\n recognizeOurFileUrl,\n mightBeOurFileUrl,\n fileIdFromFileEndpointUrl,\n type OurFileMatch,\n} from \"./media/our-file-sources\";\n\nexport {\n requestUpload,\n registerUploadGuardHandler,\n unregisterUploadGuardHandler,\n type UploadGuardHandler,\n type UploadGuardResult,\n} from \"./upload/uploadGuardOpeners\";\n\nexport type { CloudFilesState, CloudFile } from \"./types\";\n"],"mappings":"AAYA;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,OAaK;AAEP,SAAS,yBAAyB;AAClC;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP,SAAS,yCAAyC;AAElD,SAAS,mBAAmB;AAQ5B,SAAS,WAAW,qBAAqB;AACzC,SAAS,YAAY,gBAAgB;AAErC;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAEK;AAEP;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,OAGK;","names":[]}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { FileSource } from "../handler/types.js";
|
|
2
|
+
import { fileNameFromUrl, mimeFromUrl } from "@ai-matrx/data/files";
|
|
3
|
+
export { fileNameFromUrl, mimeFromUrl };
|
|
4
|
+
/**
|
|
5
|
+
* `{base}/files/{file_id}/download[?…]` or `{base}/media/{file_id}/v/{class}`
|
|
6
|
+
* on our hosts — an identity in disguise (QA F2). Delegates to the package
|
|
7
|
+
* recognizer; kept as a named export for the block/annotate call sites.
|
|
8
|
+
*/
|
|
9
|
+
export declare function fileIdFromFileEndpointUrl(url: string): string | null;
|
|
10
|
+
export interface OurFileMatch {
|
|
11
|
+
/** Strongest source we can hand the universal file handler. */
|
|
12
|
+
source: FileSource;
|
|
13
|
+
/** Stable identifier when we could recover one (re-mintable forever). */
|
|
14
|
+
fileId: string | null;
|
|
15
|
+
/** Best-effort mime sniffed from the URL (query content-type or extension). */
|
|
16
|
+
mime: string | null;
|
|
17
|
+
/** Human label for the origin, for debugging / telemetry. */
|
|
18
|
+
origin: string;
|
|
19
|
+
/**
|
|
20
|
+
* Whether the URL itself is durable (non-expiring). A signed S3 URL is NOT
|
|
21
|
+
* durable, but we still match it because we recovered a `fileId` to re-mint.
|
|
22
|
+
*/
|
|
23
|
+
durableUrl: boolean;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* The set of host/path markers that identify a URL as ours. This is the cheap
|
|
27
|
+
* pre-gate (substring test) used by the prefilter + splitter. Keep in sync
|
|
28
|
+
* with `eslint.config.mjs`.
|
|
29
|
+
*/
|
|
30
|
+
export declare const OUR_FILE_URL_MARKERS: readonly ["matrx-user-files.s3", "cdn.matrxserver", "/podcast-assets/", "/share/", "matrxserver.com/files/", "matrxserver.com/media/"];
|
|
31
|
+
/** Fast substring pre-check — no URL parse. Safe to run on every line. */
|
|
32
|
+
export declare function mightBeOurFileUrl(text: string): boolean;
|
|
33
|
+
/**
|
|
34
|
+
* Recognize an arbitrary URL as one of our files. Returns the strongest
|
|
35
|
+
* `FileSource` (identity beats opaque URL) plus a sniffed mime, or `null`
|
|
36
|
+
* when the URL is not ours. Pure + synchronous — no network. Ordered most
|
|
37
|
+
* specific / identity-recoverable first; the first match wins.
|
|
38
|
+
*/
|
|
39
|
+
export declare function recognizeOurFileUrl(url: string): OurFileMatch | null;
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createFileUrlRecognizer,
|
|
3
|
+
fileNameFromUrl,
|
|
4
|
+
isSignedUrl,
|
|
5
|
+
mimeFromUrl
|
|
6
|
+
} from "@ai-matrx/data/files";
|
|
7
|
+
import { extractFileIdFromUrl } from "../../../files.js";
|
|
8
|
+
const recognizer = createFileUrlRecognizer();
|
|
9
|
+
function fileIdFromFileEndpointUrl(url) {
|
|
10
|
+
return recognizer.fileIdFromFileEndpointUrl(url);
|
|
11
|
+
}
|
|
12
|
+
const OUR_FILE_URL_MARKERS = [
|
|
13
|
+
"matrx-user-files.s3",
|
|
14
|
+
"cdn.matrxserver",
|
|
15
|
+
"/podcast-assets/",
|
|
16
|
+
"/share/",
|
|
17
|
+
// Authenticated durable byte endpoints on our backend hosts
|
|
18
|
+
// (`{base}/files/{id}/download`, `{base}/media/{id}/v/{class}`).
|
|
19
|
+
"matrxserver.com/files/",
|
|
20
|
+
"matrxserver.com/media/"
|
|
21
|
+
];
|
|
22
|
+
function mightBeOurFileUrl(text) {
|
|
23
|
+
for (const marker of OUR_FILE_URL_MARKERS) {
|
|
24
|
+
if (text.includes(marker)) return true;
|
|
25
|
+
}
|
|
26
|
+
return false;
|
|
27
|
+
}
|
|
28
|
+
const SHARE_TOKEN_PATH_RE = /\/share\/([^/]+)(?:\/download)?$/i;
|
|
29
|
+
function recognizeOurFileUrl(url) {
|
|
30
|
+
if (!url || !mightBeOurFileUrl(url)) return null;
|
|
31
|
+
const mime = mimeFromUrl(url);
|
|
32
|
+
const durableUrl = !isSignedUrl(url);
|
|
33
|
+
const match = (origin, source, fileId) => ({ source, fileId, mime, origin, durableUrl });
|
|
34
|
+
const endpointFileId = recognizer.fileIdFromFileEndpointUrl(url);
|
|
35
|
+
if (endpointFileId) {
|
|
36
|
+
return match(
|
|
37
|
+
"files-endpoint",
|
|
38
|
+
{ kind: "file_id", fileId: endpointFileId, mime: mime ?? void 0 },
|
|
39
|
+
endpointFileId
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
const bucketFileId = recognizer.fileIdFromUserFilesUrl(url);
|
|
43
|
+
if (bucketFileId) {
|
|
44
|
+
return match(
|
|
45
|
+
"user-files-signed",
|
|
46
|
+
{ kind: "file_id", fileId: bucketFileId, mime: mime ?? void 0 },
|
|
47
|
+
bucketFileId
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
let parsed = null;
|
|
51
|
+
try {
|
|
52
|
+
parsed = new URL(url);
|
|
53
|
+
} catch {
|
|
54
|
+
parsed = null;
|
|
55
|
+
}
|
|
56
|
+
if (!parsed) return null;
|
|
57
|
+
if (/(^|\.)cdn\.matrxserver\.com$/i.test(parsed.hostname)) {
|
|
58
|
+
const cdnFileId = extractFileIdFromUrl(url);
|
|
59
|
+
return match(
|
|
60
|
+
"cdn",
|
|
61
|
+
cdnFileId ? { kind: "file_id", fileId: cdnFileId, mime: mime ?? void 0 } : { kind: "public_cdn", url, mime: mime ?? void 0 },
|
|
62
|
+
cdnFileId
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
const token = parsed.pathname.match(SHARE_TOKEN_PATH_RE)?.[1] ?? null;
|
|
66
|
+
if (token) {
|
|
67
|
+
return match(
|
|
68
|
+
"share-link",
|
|
69
|
+
{ kind: "share_link", token, mime: mime ?? void 0 },
|
|
70
|
+
null
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
75
|
+
export {
|
|
76
|
+
OUR_FILE_URL_MARKERS,
|
|
77
|
+
fileIdFromFileEndpointUrl,
|
|
78
|
+
fileNameFromUrl,
|
|
79
|
+
mightBeOurFileUrl,
|
|
80
|
+
mimeFromUrl,
|
|
81
|
+
recognizeOurFileUrl
|
|
82
|
+
};
|
|
83
|
+
//# sourceMappingURL=our-file-sources.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../src/files/engine/media/our-file-sources.ts"],"sourcesContent":["// lib/media/our-file-sources.ts\n//\n// \"Is this URL one of OUR files?\" — the host wiring over the canonical\n// recognizer in `@ai-matrx/data/files` (the C9 collapse of the former\n// module-local regex engine). The package owns URL policy: byte-endpoint\n// promotion (QA F2), signed-bucket file_id recovery, CDN detection, mime and\n// file-name sniffing, signed-URL classification. What stays here is HOST\n// IDENTITY only:\n//\n// - `OUR_FILE_URL_MARKERS` — the cheap substring pre-gate values used by\n// the prefilter + splitter so plain text and third-party links never pay\n// for a full `new URL()` parse (keep in sync with `eslint.config.mjs`);\n// - the mapping from a recognized URL to the handler's `FileSource`\n// vocabulary (`file_id` / `public_cdn` / `share_link`) — identity beats\n// an opaque URL, because the handler can re-mint a durable URL from\n// identity but not from an expiring one.\n\nimport type { FileSource } from \"../handler/types\";\nimport {\n createFileUrlRecognizer,\n fileNameFromUrl,\n isSignedUrl,\n mimeFromUrl,\n} from \"@ai-matrx/data/files\";\nimport { extractFileIdFromUrl } from \"../../../files\";\n\nexport { fileNameFromUrl, mimeFromUrl };\n\n/** The canonical recognizer, bound to the shipped Matrx origin defaults\n * (`*.matrxserver.com`, the user-files bucket, `cdn.matrxserver.com`).\n * Stateless — the media client's instance shares the same policy. */\nconst recognizer = createFileUrlRecognizer();\n\n/**\n * `{base}/files/{file_id}/download[?…]` or `{base}/media/{file_id}/v/{class}`\n * on our hosts — an identity in disguise (QA F2). Delegates to the package\n * recognizer; kept as a named export for the block/annotate call sites.\n */\nexport function fileIdFromFileEndpointUrl(url: string): string | null {\n return recognizer.fileIdFromFileEndpointUrl(url);\n}\n\nexport interface OurFileMatch {\n /** Strongest source we can hand the universal file handler. */\n source: FileSource;\n /** Stable identifier when we could recover one (re-mintable forever). */\n fileId: string | null;\n /** Best-effort mime sniffed from the URL (query content-type or extension). */\n mime: string | null;\n /** Human label for the origin, for debugging / telemetry. */\n origin: string;\n /**\n * Whether the URL itself is durable (non-expiring). A signed S3 URL is NOT\n * durable, but we still match it because we recovered a `fileId` to re-mint.\n */\n durableUrl: boolean;\n}\n\n/**\n * The set of host/path markers that identify a URL as ours. This is the cheap\n * pre-gate (substring test) used by the prefilter + splitter. Keep in sync\n * with `eslint.config.mjs`.\n */\nexport const OUR_FILE_URL_MARKERS = [\n \"matrx-user-files.s3\",\n \"cdn.matrxserver\",\n \"/podcast-assets/\",\n \"/share/\",\n // Authenticated durable byte endpoints on our backend hosts\n // (`{base}/files/{id}/download`, `{base}/media/{id}/v/{class}`).\n \"matrxserver.com/files/\",\n \"matrxserver.com/media/\",\n] as const;\n\n/** Fast substring pre-check — no URL parse. Safe to run on every line. */\nexport function mightBeOurFileUrl(text: string): boolean {\n for (const marker of OUR_FILE_URL_MARKERS) {\n if (text.includes(marker)) return true;\n }\n return false;\n}\n\nconst SHARE_TOKEN_PATH_RE = /\\/share\\/([^/]+)(?:\\/download)?$/i;\n\n/**\n * Recognize an arbitrary URL as one of our files. Returns the strongest\n * `FileSource` (identity beats opaque URL) plus a sniffed mime, or `null`\n * when the URL is not ours. Pure + synchronous — no network. Ordered most\n * specific / identity-recoverable first; the first match wins.\n */\nexport function recognizeOurFileUrl(url: string): OurFileMatch | null {\n if (!url || !mightBeOurFileUrl(url)) return null;\n\n const mime = mimeFromUrl(url);\n const durableUrl = !isSignedUrl(url);\n const match = (\n origin: string,\n source: FileSource,\n fileId: string | null,\n ): OurFileMatch => ({ source, fileId, mime, origin, durableUrl });\n\n // 1. Our authenticated durable byte endpoints → the file_id lane (QA F2).\n const endpointFileId = recognizer.fileIdFromFileEndpointUrl(url);\n if (endpointFileId) {\n return match(\n \"files-endpoint\",\n { kind: \"file_id\", fileId: endpointFileId, mime: mime ?? undefined },\n endpointFileId,\n );\n }\n\n // 2. Legacy signed user-files bucket → recover the file_id so the handler\n // re-mints a durable URL forever.\n const bucketFileId = recognizer.fileIdFromUserFilesUrl(url);\n if (bucketFileId) {\n return match(\n \"user-files-signed\",\n { kind: \"file_id\", fileId: bucketFileId, mime: mime ?? undefined },\n bucketFileId,\n );\n }\n\n let parsed: URL | null = null;\n try {\n parsed = new URL(url);\n } catch {\n parsed = null;\n }\n if (!parsed) return null;\n\n // 3. Public CDN — recover the file_id when present (durable identity);\n // otherwise the CDN URL is itself durable so we use it as-is.\n if (/(^|\\.)cdn\\.matrxserver\\.com$/i.test(parsed.hostname)) {\n const cdnFileId = extractFileIdFromUrl(url);\n return match(\n \"cdn\",\n cdnFileId\n ? { kind: \"file_id\", fileId: cdnFileId, mime: mime ?? undefined }\n : { kind: \"public_cdn\", url, mime: mime ?? undefined },\n cdnFileId,\n );\n }\n\n // 4. Share-link byte endpoint — `{backend}/share/{token}[/download]`. The\n // handler resolves bytes by token; lifetime is backend-managed.\n const token = parsed.pathname.match(SHARE_TOKEN_PATH_RE)?.[1] ?? null;\n if (token) {\n return match(\n \"share-link\",\n { kind: \"share_link\", token, mime: mime ?? undefined },\n null,\n );\n }\n\n return null;\n}\n"],"mappings":"AAkBA;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP,SAAS,4BAA4B;AAOrC,MAAM,aAAa,wBAAwB;AAOpC,SAAS,0BAA0B,KAA4B;AACpE,SAAO,WAAW,0BAA0B,GAAG;AACjD;AAuBO,MAAM,uBAAuB;AAAA,EAClC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA;AAAA;AAAA,EAGA;AAAA,EACA;AACF;AAGO,SAAS,kBAAkB,MAAuB;AACvD,aAAW,UAAU,sBAAsB;AACzC,QAAI,KAAK,SAAS,MAAM,EAAG,QAAO;AAAA,EACpC;AACA,SAAO;AACT;AAEA,MAAM,sBAAsB;AAQrB,SAAS,oBAAoB,KAAkC;AACpE,MAAI,CAAC,OAAO,CAAC,kBAAkB,GAAG,EAAG,QAAO;AAE5C,QAAM,OAAO,YAAY,GAAG;AAC5B,QAAM,aAAa,CAAC,YAAY,GAAG;AACnC,QAAM,QAAQ,CACZ,QACA,QACA,YACkB,EAAE,QAAQ,QAAQ,MAAM,QAAQ,WAAW;AAG/D,QAAM,iBAAiB,WAAW,0BAA0B,GAAG;AAC/D,MAAI,gBAAgB;AAClB,WAAO;AAAA,MACL;AAAA,MACA,EAAE,MAAM,WAAW,QAAQ,gBAAgB,MAAM,QAAQ,OAAU;AAAA,MACnE;AAAA,IACF;AAAA,EACF;AAIA,QAAM,eAAe,WAAW,uBAAuB,GAAG;AAC1D,MAAI,cAAc;AAChB,WAAO;AAAA,MACL;AAAA,MACA,EAAE,MAAM,WAAW,QAAQ,cAAc,MAAM,QAAQ,OAAU;AAAA,MACjE;AAAA,IACF;AAAA,EACF;AAEA,MAAI,SAAqB;AACzB,MAAI;AACF,aAAS,IAAI,IAAI,GAAG;AAAA,EACtB,QAAQ;AACN,aAAS;AAAA,EACX;AACA,MAAI,CAAC,OAAQ,QAAO;AAIpB,MAAI,gCAAgC,KAAK,OAAO,QAAQ,GAAG;AACzD,UAAM,YAAY,qBAAqB,GAAG;AAC1C,WAAO;AAAA,MACL;AAAA,MACA,YACI,EAAE,MAAM,WAAW,QAAQ,WAAW,MAAM,QAAQ,OAAU,IAC9D,EAAE,MAAM,cAAc,KAAK,MAAM,QAAQ,OAAU;AAAA,MACvD;AAAA,IACF;AAAA,EACF;AAIA,QAAM,QAAQ,OAAO,SAAS,MAAM,mBAAmB,IAAI,CAAC,KAAK;AACjE,MAAI,OAAO;AACT,WAAO;AAAA,MACL;AAAA,MACA,EAAE,MAAM,cAAc,OAAO,MAAM,QAAQ,OAAU;AAAA,MACrD;AAAA,IACF;AAAA,EACF;AAEA,SAAO;AACT;","names":[]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { isSignedUrl, SIGNED_URL_RE } from "@ai-matrx/data/files";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../src/files/engine/media/signed-url.ts"],"sourcesContent":["// lib/media/signed-url.ts\n//\n// THE single source of truth for \"is this a LEGACY signed, time-limited URL?\"\n// is now `@ai-matrx/data/files` (the C9 collapse) — both AWS signing dialects,\n// one regex, canonical in the package. Re-exported here so the existing call\n// sites (durability guards, adapters) keep one import path. The service\n// worker keeps its own byte-identical mirror (`cache/service-worker/src/sw.ts`\n// has no app imports) — change the package and the mirror together.\n\nexport { isSignedUrl, SIGNED_URL_RE } from \"@ai-matrx/data/files\";\n"],"mappings":"AASA,SAAS,aAAa,qBAAqB;","names":[]}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* features/files/redux/converters.ts
|
|
3
|
+
*
|
|
4
|
+
* Pure functions that translate DB rows (snake_case) and REST API shapes
|
|
5
|
+
* (snake_case) into the domain types (camelCase) used throughout the app.
|
|
6
|
+
*
|
|
7
|
+
* No Redux, no Supabase client — these are pure and testable.
|
|
8
|
+
*/
|
|
9
|
+
import type { CloudFile, CloudFilePermission, CloudFilePermissionRow, CloudFileReadRow, CloudFileVersion, CloudFileVersionReadRow, CloudFolder, CloudFolderRow, CloudShareLink, CloudTreeRow, FileRecordApi, IamResourcePermissionRpcRow, MediaRef, ResourceType } from "../types.js";
|
|
10
|
+
import type { ShareLink as CanonicalShareLink } from "../host/share-links.js";
|
|
11
|
+
import { toVisibility, fileIdToMediaRef, urlToMediaRef } from "../../../files.js";
|
|
12
|
+
export { toVisibility, fileIdToMediaRef, urlToMediaRef };
|
|
13
|
+
export declare function dbRowToCloudFile(row: CloudFileReadRow): CloudFile;
|
|
14
|
+
/**
|
|
15
|
+
* Convert the REST API's FileRecord (snake_case) to the domain type. Shape
|
|
16
|
+
* is identical to CloudFileRow modulo a few nullable defaults.
|
|
17
|
+
*/
|
|
18
|
+
export declare function apiFileRecordToCloudFile(row: FileRecordApi): CloudFile;
|
|
19
|
+
export declare function dbRowToCloudFolder(row: CloudFolderRow): CloudFolder;
|
|
20
|
+
export declare function dbRowToCloudFileVersion(row: CloudFileVersionReadRow): CloudFileVersion;
|
|
21
|
+
/**
|
|
22
|
+
* Map a canonical `iam.permissions` row (resource_type='file') into the file
|
|
23
|
+
* domain's grant shape.
|
|
24
|
+
*
|
|
25
|
+
* `iam.permissions` distinguishes the grant target by which of three mutually-
|
|
26
|
+
* exclusive columns is set — enforced by the CHECK `user_or_org_or_public`:
|
|
27
|
+
* - user grant: `granted_to_user_id` set → `granteeType: "user"`, `granteeId` = user id
|
|
28
|
+
* - org grant: `granted_to_organization_id`→ `granteeType: "group"`, `granteeId` = org id
|
|
29
|
+
* - public grant: `is_public = true` (both FKs null) → `granteeType: "public"`
|
|
30
|
+
*
|
|
31
|
+
* A PUBLIC grant has no grantee id. We must NOT collapse it to a
|
|
32
|
+
* `granteeType: "user"` grant with `granteeId: ""` — that silently mislabels
|
|
33
|
+
* "anyone with access" as an empty user grant (member counts, avatar stacks,
|
|
34
|
+
* and the user/group revoke path all then act on a bogus "" grantee). Instead
|
|
35
|
+
* it is modeled distinctly (`granteeType: "public"`) and carries the row's own
|
|
36
|
+
* `id` as `granteeId` so the value is real and unique (never `""`). Downstream
|
|
37
|
+
* member/avatar UIs exclude `"public"` grants; revoking one flips the
|
|
38
|
+
* resource's visibility rather than deleting a by-grantee grant.
|
|
39
|
+
*
|
|
40
|
+
* Grant expiry: `iam.permissions.expires_at` (timestamptz, nullable) is the
|
|
41
|
+
* canonical column — wire it straight through to the domain's `expiresAt`.
|
|
42
|
+
*/
|
|
43
|
+
export declare function dbRowToCloudFilePermission(row: CloudFilePermissionRow): CloudFilePermission;
|
|
44
|
+
/**
|
|
45
|
+
* Map the summary JSON from `iam.fn_*_resource_permission` RPCs into the file
|
|
46
|
+
* domain shape. The RPC omits the row `id` and canonical columns — callers
|
|
47
|
+
* that need the full row should read `iam.permissions` directly (see
|
|
48
|
+
* `loadPermissions`).
|
|
49
|
+
*/
|
|
50
|
+
export declare function rpcPermissionRowToCloudFilePermission(row: IamResourcePermissionRpcRow): CloudFilePermission;
|
|
51
|
+
/**
|
|
52
|
+
* Map a canonical share link (`utils/permissions/shareLinks.ts` `ShareLink`,
|
|
53
|
+
* from the `list_share_links` / `create_share_link` RPC family over
|
|
54
|
+
* `platform.share_links`) into the file domain shape. The RPCs don't echo the
|
|
55
|
+
* resource identity, so the caller supplies it.
|
|
56
|
+
*/
|
|
57
|
+
export declare function canonicalShareLinkToCloudShareLink(link: CanonicalShareLink, resourceType: ResourceType, resourceId: string): CloudShareLink;
|
|
58
|
+
/**
|
|
59
|
+
* Parse one row from `cloud_get_user_file_tree`. Returns null if the row
|
|
60
|
+
* doesn't have enough shape to use.
|
|
61
|
+
*
|
|
62
|
+
* Supports two shapes:
|
|
63
|
+
* - Explicit `kind: 'file' | 'folder'` discriminator.
|
|
64
|
+
* - Implicit: presence of `file_name` vs `folder_name`.
|
|
65
|
+
*/
|
|
66
|
+
export declare function parseCloudTreeRow(raw: unknown): CloudTreeRow | null;
|
|
67
|
+
/**
|
|
68
|
+
* Parse the full RPC return (array of rows). Skips malformed rows rather than
|
|
69
|
+
* throwing — a single bad row shouldn't blank the tree.
|
|
70
|
+
*/
|
|
71
|
+
export declare function parseCloudTreeRows(raw: unknown): CloudTreeRow[];
|
|
72
|
+
/** Build a MediaRef from an in-store CloudFile. Prefers `file_id`. */
|
|
73
|
+
export declare function cloudFileToMediaRef(file: CloudFile): MediaRef;
|