target-supabase-sdk 0.2.15 → 0.2.17
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/README.md +20 -0
- package/dist/browser.d.ts +6 -2
- package/dist/browser.d.ts.map +1 -1
- package/dist/browser.js +1 -1
- package/dist/chunks/{task-queue-DCY_0Qj0.js → task-queue-DufG7PlX.js} +295 -121
- package/dist/chunks/task-queue-DufG7PlX.js.map +1 -0
- package/dist/config/config.api.d.ts +61 -0
- package/dist/config/config.api.d.ts.map +1 -0
- package/dist/{service → config}/config.interface.d.ts +1 -3
- package/dist/config/config.interface.d.ts.map +1 -0
- package/dist/config/index.d.ts +9 -0
- package/dist/config/index.d.ts.map +1 -0
- package/dist/node/runtime-state/service-runtime-state.types.d.ts +1 -1
- package/dist/node/runtime-state/service-runtime-state.types.d.ts.map +1 -1
- package/dist/node/service-guard/registry-slot-guard-step.d.ts +1 -1
- package/dist/node/service-guard/registry-slot-guard-step.d.ts.map +1 -1
- package/dist/node/service-guard/service-guard.interface.d.ts +1 -1
- package/dist/node/service-guard/service-guard.interface.d.ts.map +1 -1
- package/dist/node/service-host/claim-registry-slot.d.ts +1 -1
- package/dist/node/service-host/claim-registry-slot.d.ts.map +1 -1
- package/dist/node/service-host/service-host.d.ts +1 -1
- package/dist/node/service-host/service-host.d.ts.map +1 -1
- package/dist/node/service-host/single-process-service.d.ts +1 -1
- package/dist/node/service-host/single-process-service.d.ts.map +1 -1
- package/dist/node.d.ts +2 -0
- package/dist/node.d.ts.map +1 -1
- package/dist/node.js +211 -6
- package/dist/node.js.map +1 -1
- package/dist/service/index.d.ts +5 -10
- package/dist/service/index.d.ts.map +1 -1
- package/dist/service/registry/index.d.ts +14 -0
- package/dist/service/registry/index.d.ts.map +1 -0
- package/dist/service/{registry-lifecycle.d.ts → registry/registry-lifecycle.d.ts} +1 -1
- package/dist/service/registry/registry-lifecycle.d.ts.map +1 -0
- package/dist/service/{config.api.d.ts → registry/registry.api.d.ts} +5 -65
- package/dist/service/registry/registry.api.d.ts.map +1 -0
- package/dist/service/registry/registry.constant.d.ts +23 -0
- package/dist/service/registry/registry.constant.d.ts.map +1 -0
- package/dist/service/registry/registry.interface.d.ts +15 -0
- package/dist/service/registry/registry.interface.d.ts.map +1 -0
- package/dist/service/{registry.service.d.ts → registry/registry.service.d.ts} +23 -15
- package/dist/service/registry/registry.service.d.ts.map +1 -0
- package/dist/service/service.interface.d.ts +0 -10
- package/dist/service/service.interface.d.ts.map +1 -1
- package/dist/shared/media/index.d.ts +14 -0
- package/dist/shared/media/index.d.ts.map +1 -0
- package/dist/shared/media/media.build.d.ts +20 -0
- package/dist/shared/media/media.build.d.ts.map +1 -0
- package/dist/shared/media/media.interface.d.ts +38 -0
- package/dist/shared/media/media.interface.d.ts.map +1 -0
- package/dist/shared/media/media.mime.d.ts +4 -0
- package/dist/shared/media/media.mime.d.ts.map +1 -0
- package/dist/shared/media/media.name.d.ts +6 -0
- package/dist/shared/media/media.name.d.ts.map +1 -0
- package/dist/shared/media/media.query.d.ts +8 -0
- package/dist/shared/media/media.query.d.ts.map +1 -0
- package/dist/shared/media/node/index.d.ts +9 -0
- package/dist/shared/media/node/index.d.ts.map +1 -0
- package/dist/shared/media/node/media.access.d.ts +40 -0
- package/dist/shared/media/node/media.access.d.ts.map +1 -0
- package/dist/shared/media/node/media.service.d.ts +30 -0
- package/dist/shared/media/node/media.service.d.ts.map +1 -0
- package/package.json +1 -1
- package/src/browser.ts +21 -9
- package/src/config/config.api.ts +72 -0
- package/src/{service → config}/config.interface.ts +1 -4
- package/src/config/index.ts +9 -0
- package/src/node/runtime-state/create-service-runtime-state-store.ts +1 -1
- package/src/node/runtime-state/service-runtime-state.types.ts +1 -1
- package/src/node/service-guard/registry-slot-guard-step.ts +4 -1
- package/src/node/service-guard/run-service-guard-tick.ts +1 -1
- package/src/node/service-guard/service-guard.interface.ts +1 -1
- package/src/node/service-host/claim-registry-slot.ts +2 -2
- package/src/node/service-host/service-host.ts +2 -2
- package/src/node/service-host/single-process-service.ts +1 -1
- package/src/node.ts +13 -0
- package/src/service/index.ts +30 -40
- package/src/service/registry/index.ts +65 -0
- package/src/service/{registry-lifecycle.ts → registry/registry-lifecycle.ts} +3 -3
- package/src/service/{config.api.ts → registry/registry.api.ts} +6 -80
- package/src/service/registry/registry.constant.ts +17 -0
- package/src/service/registry/registry.interface.ts +16 -0
- package/src/service/{registry.service.ts → registry/registry.service.ts} +66 -65
- package/src/service/service.interface.ts +0 -12
- package/src/shared/media/README.md +109 -0
- package/src/shared/media/index.ts +19 -0
- package/src/shared/media/media.build.ts +69 -0
- package/src/shared/media/media.interface.ts +45 -0
- package/src/shared/media/media.mime.ts +64 -0
- package/src/shared/media/media.name.ts +33 -0
- package/src/shared/media/media.query.ts +13 -0
- package/src/shared/media/node/index.ts +13 -0
- package/src/shared/media/node/media.access.ts +148 -0
- package/src/shared/media/node/media.service.ts +129 -0
- package/dist/chunks/task-queue-DCY_0Qj0.js.map +0 -1
- package/dist/service/config.api.d.ts.map +0 -1
- package/dist/service/config.interface.d.ts.map +0 -1
- package/dist/service/registry-lifecycle.d.ts.map +0 -1
- package/dist/service/registry.service.d.ts.map +0 -1
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { createReadStream } from "node:fs";
|
|
3
|
+
import { readdir, stat } from "node:fs/promises";
|
|
4
|
+
import { isAbsolute, resolve } from "node:path";
|
|
5
|
+
|
|
6
|
+
import { MEDIA_CONTENT_HASH_ALGORITHM, type MediaOriginal, type MediaValue } from "../media.interface";
|
|
7
|
+
import { guessMediaMime } from "../media.mime";
|
|
8
|
+
|
|
9
|
+
export interface CheckMediaAvailabilityInput {
|
|
10
|
+
value: MediaValue;
|
|
11
|
+
storageProvider: string;
|
|
12
|
+
locator: string;
|
|
13
|
+
/**
|
|
14
|
+
* This host's storage id (e.g. from `LOCAL_STORAGE_PROVIDER`).
|
|
15
|
+
* Local path checks only run when it equals `storageProvider`.
|
|
16
|
+
*/
|
|
17
|
+
localStorageProvider: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface MediaAvailabilityResult {
|
|
21
|
+
ok: boolean;
|
|
22
|
+
/** Absolute path when the locator was resolved on this host. */
|
|
23
|
+
absolutePath?: string;
|
|
24
|
+
reason?: string;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function sameStorageHost(storageProvider: string, localStorageProvider: string): boolean {
|
|
28
|
+
return storageProvider.trim() === localStorageProvider.trim();
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
async function sha256FileStreaming(absolutePath: string): Promise<string> {
|
|
32
|
+
return await new Promise((resolveHash, reject) => {
|
|
33
|
+
const hash = createHash(MEDIA_CONTENT_HASH_ALGORITHM);
|
|
34
|
+
const stream = createReadStream(absolutePath);
|
|
35
|
+
stream.on("data", (chunk) => {
|
|
36
|
+
hash.update(chunk);
|
|
37
|
+
});
|
|
38
|
+
stream.on("error", reject);
|
|
39
|
+
stream.on("end", () => {
|
|
40
|
+
resolveHash(hash.digest("hex"));
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
async function countFilesOneLevel(dirPath: string): Promise<number> {
|
|
46
|
+
const entries = await readdir(dirPath, { withFileTypes: true });
|
|
47
|
+
let count = 0;
|
|
48
|
+
for (const entry of entries) {
|
|
49
|
+
if (entry.isFile()) {
|
|
50
|
+
count += 1;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
return count;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Fast local reachability check for a media locator.
|
|
58
|
+
*
|
|
59
|
+
* - Requires `storageProvider === localStorageProvider` (cross-host → not ok).
|
|
60
|
+
* - `locator` must be an absolute path.
|
|
61
|
+
* - `image-list`: directory exists; only immediate children are considered.
|
|
62
|
+
* - other values: path exists and is a non-empty file.
|
|
63
|
+
*
|
|
64
|
+
* **Node-only** — under `shared/media/node/`; do not import from the browser entry.
|
|
65
|
+
*/
|
|
66
|
+
export async function checkMediaAvailability(input: CheckMediaAvailabilityInput): Promise<MediaAvailabilityResult> {
|
|
67
|
+
const storageProvider = input.storageProvider.trim();
|
|
68
|
+
const localStorageProvider = input.localStorageProvider.trim();
|
|
69
|
+
const locator = input.locator.trim();
|
|
70
|
+
|
|
71
|
+
if (storageProvider === "" || localStorageProvider === "") {
|
|
72
|
+
return { ok: false, reason: "storageProvider and localStorageProvider are required" };
|
|
73
|
+
}
|
|
74
|
+
if (locator === "") {
|
|
75
|
+
return { ok: false, reason: "locator is empty" };
|
|
76
|
+
}
|
|
77
|
+
if (!sameStorageHost(storageProvider, localStorageProvider)) {
|
|
78
|
+
return {
|
|
79
|
+
ok: false,
|
|
80
|
+
reason: `storageProvider not on this host (${storageProvider} !== ${localStorageProvider})`,
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
if (!isAbsolute(locator)) {
|
|
84
|
+
return { ok: false, reason: `locator must be an absolute path: ${locator}` };
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const absolutePath = resolve(locator);
|
|
88
|
+
|
|
89
|
+
try {
|
|
90
|
+
const info = await stat(absolutePath);
|
|
91
|
+
if (input.value === "image-list") {
|
|
92
|
+
if (!info.isDirectory()) {
|
|
93
|
+
return { ok: false, reason: `image-list locator is not a directory: ${absolutePath}` };
|
|
94
|
+
}
|
|
95
|
+
return { ok: true, absolutePath };
|
|
96
|
+
}
|
|
97
|
+
if (!info.isFile()) {
|
|
98
|
+
return { ok: false, reason: `locator is not a file: ${absolutePath}` };
|
|
99
|
+
}
|
|
100
|
+
if (info.size <= 0) {
|
|
101
|
+
return { ok: false, reason: `file is empty: ${absolutePath}` };
|
|
102
|
+
}
|
|
103
|
+
return { ok: true, absolutePath };
|
|
104
|
+
} catch (error) {
|
|
105
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
106
|
+
return { ok: false, reason: `locator not reachable: ${absolutePath} (${message})` };
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Derive `contentHash` / `size` / `mimeType` for {@link MediaOriginal} after availability succeeds.
|
|
112
|
+
*
|
|
113
|
+
* **Node-only** — under `shared/media/node/`; do not import from the browser entry.
|
|
114
|
+
*/
|
|
115
|
+
export async function probeMediaOriginal(input: {
|
|
116
|
+
value: MediaValue;
|
|
117
|
+
storageProvider: string;
|
|
118
|
+
locator: string;
|
|
119
|
+
absolutePath: string;
|
|
120
|
+
}): Promise<MediaOriginal> {
|
|
121
|
+
const storageProvider = input.storageProvider.trim();
|
|
122
|
+
const locator = input.locator.trim();
|
|
123
|
+
|
|
124
|
+
if (input.value === "image-list") {
|
|
125
|
+
const size = await countFilesOneLevel(input.absolutePath);
|
|
126
|
+
return {
|
|
127
|
+
storageProvider,
|
|
128
|
+
locator,
|
|
129
|
+
contentHash: "",
|
|
130
|
+
size,
|
|
131
|
+
mimeType: "",
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const info = await stat(input.absolutePath);
|
|
136
|
+
const mimeType = guessMediaMime(input.absolutePath, input.value);
|
|
137
|
+
if (mimeType == null) {
|
|
138
|
+
throw new Error(`probeMediaOriginal: cannot guess mimeType for ${input.value}: ${input.absolutePath}`);
|
|
139
|
+
}
|
|
140
|
+
const contentHash = await sha256FileStreaming(input.absolutePath);
|
|
141
|
+
return {
|
|
142
|
+
storageProvider,
|
|
143
|
+
locator,
|
|
144
|
+
contentHash,
|
|
145
|
+
size: info.size,
|
|
146
|
+
mimeType,
|
|
147
|
+
};
|
|
148
|
+
}
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { getPossibleTarget, isCreateTargetAlreadyExistsError, postTarget } from "../../../core.api";
|
|
2
|
+
import type { Link } from "../../../link/link.interface";
|
|
3
|
+
import { createLogger } from "../../log/core/create-logger";
|
|
4
|
+
import { buildMediaLinkDraft } from "../media.build";
|
|
5
|
+
import { isMediaValue, type MediaValue } from "../media.interface";
|
|
6
|
+
import { mediaLinkDedupFilters } from "../media.query";
|
|
7
|
+
import { checkMediaAvailability, probeMediaOriginal } from "./media.access";
|
|
8
|
+
|
|
9
|
+
const logger = createLogger({ module: "media" });
|
|
10
|
+
|
|
11
|
+
export interface FindMediaLinkInput {
|
|
12
|
+
value: MediaValue;
|
|
13
|
+
name: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/** Dedup lookup: same `category` + `value` + `name`. */
|
|
17
|
+
export async function findMediaLink(input: FindMediaLinkInput): Promise<Link | null> {
|
|
18
|
+
if (!isMediaValue(input.value)) {
|
|
19
|
+
throw new Error(`findMediaLink: unsupported media value: ${String(input.value)}`);
|
|
20
|
+
}
|
|
21
|
+
const name = input.name.trim();
|
|
22
|
+
if (name === "") {
|
|
23
|
+
throw new Error("findMediaLink: name is empty");
|
|
24
|
+
}
|
|
25
|
+
const { data } = await getPossibleTarget({
|
|
26
|
+
filterList: mediaLinkDedupFilters({ value: input.value, name }),
|
|
27
|
+
});
|
|
28
|
+
return data != null ? (data as Link) : null;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface RegisterMediaLinkInput {
|
|
32
|
+
value: MediaValue;
|
|
33
|
+
name: string;
|
|
34
|
+
storageProvider: string;
|
|
35
|
+
locator: string;
|
|
36
|
+
/** This host id for availability (e.g. `process.env.LOCAL_STORAGE_PROVIDER`). */
|
|
37
|
+
localStorageProvider: string;
|
|
38
|
+
description?: string;
|
|
39
|
+
tagList?: string[];
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export interface RegisterMediaLinkResult {
|
|
43
|
+
id: string;
|
|
44
|
+
created: boolean;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Register a media Link: dedup → availability → probe → `postTarget`.
|
|
49
|
+
* Idempotent when `category`+`value`+`name` already exist.
|
|
50
|
+
*
|
|
51
|
+
* **Node-only** — under `shared/media/node/`; do not import from the browser entry.
|
|
52
|
+
*/
|
|
53
|
+
export async function registerMediaLink(input: RegisterMediaLinkInput): Promise<RegisterMediaLinkResult> {
|
|
54
|
+
if (!isMediaValue(input.value)) {
|
|
55
|
+
throw new Error(`registerMediaLink: unsupported media value: ${String(input.value)}`);
|
|
56
|
+
}
|
|
57
|
+
const name = input.name.trim();
|
|
58
|
+
if (name === "") {
|
|
59
|
+
throw new Error("registerMediaLink: name is empty");
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const existing = await findMediaLink({ value: input.value, name });
|
|
63
|
+
if (existing != null) {
|
|
64
|
+
logger.info("media already registered", {
|
|
65
|
+
topic: "dedup",
|
|
66
|
+
data: { id: existing.id, value: input.value, name },
|
|
67
|
+
});
|
|
68
|
+
return { id: existing.id, created: false };
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const availability = await checkMediaAvailability({
|
|
72
|
+
value: input.value,
|
|
73
|
+
storageProvider: input.storageProvider,
|
|
74
|
+
locator: input.locator,
|
|
75
|
+
localStorageProvider: input.localStorageProvider,
|
|
76
|
+
});
|
|
77
|
+
if (!availability.ok || availability.absolutePath == null) {
|
|
78
|
+
throw new Error(`registerMediaLink: unavailable — ${availability.reason ?? "unknown"}`);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const probed = await probeMediaOriginal({
|
|
82
|
+
value: input.value,
|
|
83
|
+
storageProvider: input.storageProvider,
|
|
84
|
+
locator: input.locator,
|
|
85
|
+
absolutePath: availability.absolutePath,
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
const draft = buildMediaLinkDraft({
|
|
89
|
+
value: input.value,
|
|
90
|
+
name,
|
|
91
|
+
storageProvider: probed.storageProvider,
|
|
92
|
+
locator: probed.locator,
|
|
93
|
+
contentHash: probed.contentHash,
|
|
94
|
+
size: probed.size,
|
|
95
|
+
mimeType: probed.mimeType,
|
|
96
|
+
description: input.description,
|
|
97
|
+
tagList: input.tagList,
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
try {
|
|
101
|
+
const { data, error } = await postTarget({
|
|
102
|
+
name: draft.name,
|
|
103
|
+
value: draft.value,
|
|
104
|
+
category: draft.category,
|
|
105
|
+
tagList: draft.tagList,
|
|
106
|
+
details: draft.details,
|
|
107
|
+
});
|
|
108
|
+
if (error != null || data == null) {
|
|
109
|
+
throw new Error(error?.message ?? "registerMediaLink: postTarget failed");
|
|
110
|
+
}
|
|
111
|
+
logger.info("media registered", {
|
|
112
|
+
topic: "register",
|
|
113
|
+
data: { id: data.id, value: input.value, name, size: probed.size },
|
|
114
|
+
});
|
|
115
|
+
return { id: data.id, created: true };
|
|
116
|
+
} catch (error) {
|
|
117
|
+
if (isCreateTargetAlreadyExistsError(error)) {
|
|
118
|
+
const raced = await findMediaLink({ value: input.value, name });
|
|
119
|
+
if (raced != null) {
|
|
120
|
+
logger.info("media register race — using existing", {
|
|
121
|
+
topic: "dedup",
|
|
122
|
+
data: { id: raced.id, value: input.value, name },
|
|
123
|
+
});
|
|
124
|
+
return { id: raced.id, created: false };
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
throw error;
|
|
128
|
+
}
|
|
129
|
+
}
|