@vivix-labs/material-library 0.0.0-stage → 1.0.0-beta
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/client.d.ts +14 -0
- package/dist/client.js +94 -0
- package/dist/server.d.ts +8 -0
- package/dist/server.js +568 -0
- package/dist/shared.d.ts +341 -0
- package/dist/shared.js +39 -0
- package/package.json +43 -3
- package/studio-plugin.json +6 -0
- package/README.md +0 -3
package/dist/client.d.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { MaterialConfig } from './shared.js';
|
|
3
|
+
export { Config } from './shared.js';
|
|
4
|
+
import { ClientContext } from 'vivix-studio/sdk/client';
|
|
5
|
+
import 'vivix-studio/sdk/shared';
|
|
6
|
+
|
|
7
|
+
declare function MaterialProperties({ record }: {
|
|
8
|
+
record: any;
|
|
9
|
+
}): React.JSX.Element;
|
|
10
|
+
|
|
11
|
+
declare const dependencies: (config: MaterialConfig) => string[];
|
|
12
|
+
declare function activate(client: ClientContext<MaterialConfig>): void;
|
|
13
|
+
|
|
14
|
+
export { MaterialProperties, activate as default, dependencies };
|
package/dist/client.js
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
// packages/material-library/src/ui/panels.tsx
|
|
2
|
+
import * as React from "react";
|
|
3
|
+
function ImportToProject(props) {
|
|
4
|
+
const { form, setForm, projects, save } = props;
|
|
5
|
+
return /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement("label", null, "Target project", /* @__PURE__ */ React.createElement(
|
|
6
|
+
"select",
|
|
7
|
+
{
|
|
8
|
+
value: form.targetProjectId,
|
|
9
|
+
onChange: (e) => setForm({ ...form, targetProjectId: e.target.value })
|
|
10
|
+
},
|
|
11
|
+
/* @__PURE__ */ React.createElement("option", { value: "" }, "Choose a project"),
|
|
12
|
+
projects.map((p) => /* @__PURE__ */ React.createElement("option", { key: p.objectId, value: p.objectId }, p.data.name))
|
|
13
|
+
)), /* @__PURE__ */ React.createElement(
|
|
14
|
+
"button",
|
|
15
|
+
{
|
|
16
|
+
className: "primary",
|
|
17
|
+
disabled: !form.targetProjectId,
|
|
18
|
+
onClick: () => void save("assets.importMaterial", {
|
|
19
|
+
ref: form.ref,
|
|
20
|
+
targetProjectId: form.targetProjectId
|
|
21
|
+
})
|
|
22
|
+
},
|
|
23
|
+
"Copy into project"
|
|
24
|
+
));
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// packages/material-library/src/client.tsx
|
|
28
|
+
import * as React2 from "react";
|
|
29
|
+
|
|
30
|
+
// packages/material-library/src/shared.ts
|
|
31
|
+
import { z } from "vivix-studio/sdk/shared";
|
|
32
|
+
var Config = z.discriminatedUnion("role", [
|
|
33
|
+
z.object({
|
|
34
|
+
role: z.literal("service"),
|
|
35
|
+
dataInstance: z.string().min(1).default("data"),
|
|
36
|
+
serviceInstance: z.string().min(1).default("library")
|
|
37
|
+
}).strict(),
|
|
38
|
+
z.object({
|
|
39
|
+
role: z.literal("client"),
|
|
40
|
+
serviceInstance: z.string().min(1).default("user:library")
|
|
41
|
+
}).strict()
|
|
42
|
+
]);
|
|
43
|
+
var materialFields = z.object({
|
|
44
|
+
name: z.string().trim().min(1).max(200),
|
|
45
|
+
kind: z.enum(["text", "image", "video", "audio", "file"]),
|
|
46
|
+
tags: z.array(z.string().trim().min(1).max(64)).max(50).transform((x) => [...new Set(x)]).default([]),
|
|
47
|
+
category: z.enum(["character", "scene", "style", "sound", "reference", "other"]).default("other"),
|
|
48
|
+
description: z.string().max(4e3).default(""),
|
|
49
|
+
origin: z.enum(["import", "collected"]),
|
|
50
|
+
sourceRef: z.object({ storeId: z.uuid(), objectId: z.uuid(), versionId: z.uuid() }).strict().nullable().default(null),
|
|
51
|
+
sourceLabel: z.object({
|
|
52
|
+
projectName: z.string(),
|
|
53
|
+
objectName: z.string(),
|
|
54
|
+
versionNumber: z.number().int().positive()
|
|
55
|
+
}).strict().nullable().default(null),
|
|
56
|
+
lifecycle: z.enum(["ready", "archived"]).default("ready"),
|
|
57
|
+
recommendedVersionId: z.uuid(),
|
|
58
|
+
licenseNote: z.string().max(4e3).default("")
|
|
59
|
+
}).strict();
|
|
60
|
+
var materialData = materialFields.refine(
|
|
61
|
+
(v) => v.origin !== "collected" || v.sourceRef !== null,
|
|
62
|
+
"Collected materials require a fixed source."
|
|
63
|
+
);
|
|
64
|
+
|
|
65
|
+
// packages/material-library/src/client.tsx
|
|
66
|
+
function MaterialProperties({ record }) {
|
|
67
|
+
return /* @__PURE__ */ React2.createElement("dl", null, /* @__PURE__ */ React2.createElement("dt", null, "Category"), /* @__PURE__ */ React2.createElement("dd", null, record.data.category), /* @__PURE__ */ React2.createElement("dt", null, "Tags"), /* @__PURE__ */ React2.createElement("dd", null, record.data.tags.map((t) => /* @__PURE__ */ React2.createElement("span", { key: t, className: "tag" }, t))), /* @__PURE__ */ React2.createElement("dt", null, "Source"), /* @__PURE__ */ React2.createElement("dd", null, record.data.sourceLabel ? `${record.data.sourceLabel.projectName} / ${record.data.sourceLabel.objectName} / v${record.data.sourceLabel.versionNumber}` : record.data.origin), /* @__PURE__ */ React2.createElement("dt", null, "Recommended version"), /* @__PURE__ */ React2.createElement("dd", null, "v", record.versions?.find((v) => v.versionId === record.data.recommendedVersionId)?.versionNumber ?? "\u2014"), /* @__PURE__ */ React2.createElement("dt", null, "Uses"), /* @__PURE__ */ React2.createElement("dd", null, record.usageCount ?? "Query usage"), /* @__PURE__ */ React2.createElement("dt", null, "License"), /* @__PURE__ */ React2.createElement("dd", null, record.data.licenseNote || "Unspecified"));
|
|
68
|
+
}
|
|
69
|
+
var dependencies = (config) => config.role === "client" ? [config.serviceInstance] : [];
|
|
70
|
+
function activate(client) {
|
|
71
|
+
client.provide({ serviceInstance: client.config.serviceInstance });
|
|
72
|
+
let active = true;
|
|
73
|
+
client.registerUI({
|
|
74
|
+
id: "library.import-form",
|
|
75
|
+
title: "Import material",
|
|
76
|
+
kind: "component",
|
|
77
|
+
component: (props) => active ? /* @__PURE__ */ React2.createElement(ImportToProject, { ...props }) : null
|
|
78
|
+
});
|
|
79
|
+
client.registerUI({
|
|
80
|
+
id: "library.properties",
|
|
81
|
+
title: "Material properties",
|
|
82
|
+
kind: "component",
|
|
83
|
+
component: (props) => active ? /* @__PURE__ */ React2.createElement(MaterialProperties, { ...props }) : null
|
|
84
|
+
});
|
|
85
|
+
client.onDispose(() => {
|
|
86
|
+
active = false;
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
export {
|
|
90
|
+
Config,
|
|
91
|
+
MaterialProperties,
|
|
92
|
+
activate as default,
|
|
93
|
+
dependencies
|
|
94
|
+
};
|
package/dist/server.d.ts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { ServerContext } from 'vivix-studio/sdk/server';
|
|
2
|
+
import { z } from 'vivix-studio/sdk/shared';
|
|
3
|
+
import { Config } from './shared.js';
|
|
4
|
+
|
|
5
|
+
declare const dependencies: (config: z.infer<typeof Config>) => string[];
|
|
6
|
+
declare function activate(ctx: ServerContext<z.infer<typeof Config>>): void;
|
|
7
|
+
|
|
8
|
+
export { Config, activate as default, dependencies };
|
package/dist/server.js
ADDED
|
@@ -0,0 +1,568 @@
|
|
|
1
|
+
// packages/material-library/src/server.ts
|
|
2
|
+
import { defineOperation, PluginError } from "vivix-studio/sdk/server";
|
|
3
|
+
import { z as z2 } from "vivix-studio/sdk/shared";
|
|
4
|
+
import { createHash } from "crypto";
|
|
5
|
+
import { createReadStream } from "fs";
|
|
6
|
+
|
|
7
|
+
// packages/material-library/src/shared.ts
|
|
8
|
+
import { z } from "vivix-studio/sdk/shared";
|
|
9
|
+
var Config = z.discriminatedUnion("role", [
|
|
10
|
+
z.object({
|
|
11
|
+
role: z.literal("service"),
|
|
12
|
+
dataInstance: z.string().min(1).default("data"),
|
|
13
|
+
serviceInstance: z.string().min(1).default("library")
|
|
14
|
+
}).strict(),
|
|
15
|
+
z.object({
|
|
16
|
+
role: z.literal("client"),
|
|
17
|
+
serviceInstance: z.string().min(1).default("user:library")
|
|
18
|
+
}).strict()
|
|
19
|
+
]);
|
|
20
|
+
var materialFields = z.object({
|
|
21
|
+
name: z.string().trim().min(1).max(200),
|
|
22
|
+
kind: z.enum(["text", "image", "video", "audio", "file"]),
|
|
23
|
+
tags: z.array(z.string().trim().min(1).max(64)).max(50).transform((x) => [...new Set(x)]).default([]),
|
|
24
|
+
category: z.enum(["character", "scene", "style", "sound", "reference", "other"]).default("other"),
|
|
25
|
+
description: z.string().max(4e3).default(""),
|
|
26
|
+
origin: z.enum(["import", "collected"]),
|
|
27
|
+
sourceRef: z.object({ storeId: z.uuid(), objectId: z.uuid(), versionId: z.uuid() }).strict().nullable().default(null),
|
|
28
|
+
sourceLabel: z.object({
|
|
29
|
+
projectName: z.string(),
|
|
30
|
+
objectName: z.string(),
|
|
31
|
+
versionNumber: z.number().int().positive()
|
|
32
|
+
}).strict().nullable().default(null),
|
|
33
|
+
lifecycle: z.enum(["ready", "archived"]).default("ready"),
|
|
34
|
+
recommendedVersionId: z.uuid(),
|
|
35
|
+
licenseNote: z.string().max(4e3).default("")
|
|
36
|
+
}).strict();
|
|
37
|
+
var materialData = materialFields.refine(
|
|
38
|
+
(v) => v.origin !== "collected" || v.sourceRef !== null,
|
|
39
|
+
"Collected materials require a fixed source."
|
|
40
|
+
);
|
|
41
|
+
|
|
42
|
+
// packages/material-library/src/server.ts
|
|
43
|
+
var dependencies = (config) => config.role === "service" ? [config.dataInstance] : [];
|
|
44
|
+
var ownerPackage = "@vivix/material-library";
|
|
45
|
+
var packageVersion = "0.1.22-ecommerce.20261110.1";
|
|
46
|
+
var id = z2.uuid();
|
|
47
|
+
var revision = z2.number().int().positive();
|
|
48
|
+
var ref = z2.object({ storeId: id, objectId: id, versionId: id }).strict();
|
|
49
|
+
var usageSchema = z2.object({
|
|
50
|
+
materialRef: ref,
|
|
51
|
+
projectId: z2.string().min(1).max(128),
|
|
52
|
+
targetAssetId: id,
|
|
53
|
+
targetVersionId: id,
|
|
54
|
+
name: z2.string().max(200).default(""),
|
|
55
|
+
active: z2.boolean().default(true)
|
|
56
|
+
}).strict();
|
|
57
|
+
function fail(code, message) {
|
|
58
|
+
throw new PluginError(code, message, "Review the material and the current project, then retry.");
|
|
59
|
+
}
|
|
60
|
+
var materialDefinition = {
|
|
61
|
+
typeId: "library.material",
|
|
62
|
+
schemaVersion: 1,
|
|
63
|
+
scopeKinds: ["user"],
|
|
64
|
+
validate: (value) => materialData.parse(value),
|
|
65
|
+
validateUpdate: (before, after) => {
|
|
66
|
+
if (before.kind !== after.kind || before.origin !== after.origin || ["storeId", "objectId", "versionId"].some(
|
|
67
|
+
(key) => before.sourceRef?.[key] !== after.sourceRef?.[key]
|
|
68
|
+
))
|
|
69
|
+
fail("IMMUTABLE_FIELD", "Material identity and source are immutable.");
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
var usageDefinition = {
|
|
73
|
+
typeId: "library.usage",
|
|
74
|
+
schemaVersion: 1,
|
|
75
|
+
scopeKinds: ["user"],
|
|
76
|
+
validate: (value) => usageSchema.parse(value),
|
|
77
|
+
validateUpdate: (before, after) => {
|
|
78
|
+
if (before.projectId !== after.projectId || before.targetAssetId !== after.targetAssetId || before.targetVersionId !== after.targetVersionId || ["storeId", "objectId", "versionId"].some(
|
|
79
|
+
(key) => before.materialRef[key] !== after.materialRef[key]
|
|
80
|
+
))
|
|
81
|
+
fail("IMMUTABLE_FIELD", "Usage identities refer to fixed source and target versions.");
|
|
82
|
+
}
|
|
83
|
+
};
|
|
84
|
+
var operationDescriptions = {
|
|
85
|
+
"documents.list": "List persisted extension documents in the bound scope. Does not create content.",
|
|
86
|
+
"documents.create": "Create a persisted extension document with the original write request key.",
|
|
87
|
+
"documents.update": "Save document content only at its observed revision; preserve the draft on conflict.",
|
|
88
|
+
"documents.delete": "Delete the specified extension document at its observed revision.",
|
|
89
|
+
"files.resolveLocation": "Resolve an exact immutable version and optional file ID to a readable local location and preview URL. Does not render or export.",
|
|
90
|
+
"files.openLocation": "Open the containing directory of an exact readable immutable version. Does not modify its bytes.",
|
|
91
|
+
"metadata.patch": "Update only the named object metadata using its observed revision and a stable write request key.",
|
|
92
|
+
"metadata.keys": "List metadata keys available in this binding for building actual queries and views.",
|
|
93
|
+
"library.list": "List library materials with filters and pagination; materials are distinct from project asset candidates.",
|
|
94
|
+
"library.get": "Read one material and its immutable saved versions before choosing a fixed reference.",
|
|
95
|
+
"library.import": "Create a library material from validated staged file IDs and retain its provenance.",
|
|
96
|
+
"library.importVersion": "Append an immutable material version from validated staged files at the observed revision.",
|
|
97
|
+
"library.update": "Update material name, tags, or settings at the observed revision while preserving saved bytes.",
|
|
98
|
+
"library.archive": "Archive the material without rewriting existing project copies or immutable references.",
|
|
99
|
+
"library.restore": "Restore an archived material without generating new content.",
|
|
100
|
+
"library.usage": "List actual project uses of the specified material/version. Continue paging until nextOffset is null.",
|
|
101
|
+
"library.cleanupImpact": "Read references and uses that protect a material before permanent purge.",
|
|
102
|
+
"library.purge": "Permanently purge eligible material content after impact checks and explicit confirmation."
|
|
103
|
+
};
|
|
104
|
+
function activate(ctx) {
|
|
105
|
+
if (ctx.config.role === "client") {
|
|
106
|
+
if (ctx.scope?.kind !== "project")
|
|
107
|
+
fail("SCOPE_MISMATCH", "Library clients belong to projects.");
|
|
108
|
+
if (!ctx.config.serviceInstance.startsWith("user:"))
|
|
109
|
+
fail("INVALID_CONFIG", "Library clients require an explicit user service instance.");
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
if (ctx.scope?.kind !== "user") fail("SCOPE_MISMATCH", "Library services belong to user scope.");
|
|
113
|
+
if (ctx.config.dataInstance.startsWith("user:"))
|
|
114
|
+
fail("INVALID_CONFIG", "The user library requires its local data instance.");
|
|
115
|
+
const scope = { kind: "user", id: ctx.scope.id };
|
|
116
|
+
const workspace = ctx.plugins.get(ctx.config.dataInstance);
|
|
117
|
+
const binding = (actorId) => workspace.bind({ scope, ownerPackage, packageVersion, actorId, access: ["data.workspace"] });
|
|
118
|
+
const data = binding("local");
|
|
119
|
+
data.register(materialDefinition);
|
|
120
|
+
data.register(usageDefinition);
|
|
121
|
+
const localCall = (call, operation) => {
|
|
122
|
+
const originalRequestKey = call.originalRequestKey ?? call.requestKey;
|
|
123
|
+
if (!call.requestKey || call.requestKey.length > 128 || !originalRequestKey || originalRequestKey.length > 128)
|
|
124
|
+
fail("INVALID_REQUEST_KEY", "Request keys must contain 1\u2013128 characters.");
|
|
125
|
+
const requestKey = "library-" + createHash("sha256").update(
|
|
126
|
+
JSON.stringify([
|
|
127
|
+
call.scope?.kind ?? scope.kind,
|
|
128
|
+
call.scope?.id ?? scope.id,
|
|
129
|
+
call.actor.id,
|
|
130
|
+
operation,
|
|
131
|
+
call.requestKey,
|
|
132
|
+
originalRequestKey,
|
|
133
|
+
call.sourceOperation ?? operation
|
|
134
|
+
])
|
|
135
|
+
).digest("hex");
|
|
136
|
+
return {
|
|
137
|
+
...call,
|
|
138
|
+
scope,
|
|
139
|
+
requestKey,
|
|
140
|
+
originalRequestKey,
|
|
141
|
+
sourceOperation: call.sourceOperation ?? operation
|
|
142
|
+
};
|
|
143
|
+
};
|
|
144
|
+
const material = (api, objectId, includeDeleted = false) => {
|
|
145
|
+
const object = api.get(objectId, includeDeleted);
|
|
146
|
+
if (object.typeId !== "library.material") fail("TYPE_MISMATCH", "Expected a material.");
|
|
147
|
+
return object;
|
|
148
|
+
};
|
|
149
|
+
const assertProject = (projectId, call) => {
|
|
150
|
+
if (!/^[a-zA-Z0-9_-]{1,128}$/.test(projectId) || call.scope?.kind !== "project" || call.scope.id !== projectId)
|
|
151
|
+
fail("SCOPE_MISMATCH", "Projects must actively report their own material usage.");
|
|
152
|
+
};
|
|
153
|
+
const handle = (name, input, handler, write = false, confirm = false) => ctx.handle(
|
|
154
|
+
defineOperation({
|
|
155
|
+
id: name,
|
|
156
|
+
title: name,
|
|
157
|
+
description: operationDescriptions[name] || "Operate on the specified scoped record using the declared schema and observed revision.",
|
|
158
|
+
input,
|
|
159
|
+
output: z2.any(),
|
|
160
|
+
effects: [write ? "write" : "read"],
|
|
161
|
+
confirm: confirm ? "user" : "none"
|
|
162
|
+
}),
|
|
163
|
+
handler
|
|
164
|
+
);
|
|
165
|
+
async function importFiles(input, call, snapshot2) {
|
|
166
|
+
const api = binding(call.actor.id);
|
|
167
|
+
const operationName = input.materialId ? "library.importVersion" : snapshot2 ? "library.collect" : "library.import";
|
|
168
|
+
const boundCall = localCall(call, operationName);
|
|
169
|
+
const receipt = {
|
|
170
|
+
scope: { ...call.scope ?? scope },
|
|
171
|
+
actorId: call.actor.id,
|
|
172
|
+
operation: operationName,
|
|
173
|
+
requestKey: boundCall.originalRequestKey,
|
|
174
|
+
invocationRequestKey: call.requestKey,
|
|
175
|
+
storageRequestKey: boundCall.requestKey,
|
|
176
|
+
sourceOperation: boundCall.sourceOperation
|
|
177
|
+
};
|
|
178
|
+
const replay = api.reserve(operationName, input, boundCall);
|
|
179
|
+
if (replay) return replay.result;
|
|
180
|
+
const identityCall = { ...call, requestKey: boundCall.requestKey };
|
|
181
|
+
const operationId = stable(api.storeId, identityCall, operationName, "import"), objectId = input.materialId || stable(operationId, identityCall, operationName, "object"), versionId = stable(operationId, identityCall, operationName, "version");
|
|
182
|
+
let stagingIds = input.stagingFileIds;
|
|
183
|
+
if (snapshot2) {
|
|
184
|
+
stagingIds = [];
|
|
185
|
+
for (const file of snapshot2.files) {
|
|
186
|
+
const staged = await api.stage(file.source, file.originalName, call.actor.id);
|
|
187
|
+
if (staged.sha256 !== file.sha256 || staged.sizeBytes !== file.sizeBytes)
|
|
188
|
+
fail("FILE_CORRUPT", "Snapshot bytes do not match the fixed source file.");
|
|
189
|
+
stagingIds.push(staged.stagingFileId);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
const published = await api.publish(
|
|
193
|
+
stagingIds,
|
|
194
|
+
objectId,
|
|
195
|
+
versionId,
|
|
196
|
+
operationId,
|
|
197
|
+
call.actor.id,
|
|
198
|
+
snapshot2?.files.map((file) => file.role)
|
|
199
|
+
);
|
|
200
|
+
if (snapshot2 && published.kind !== snapshot2.kind)
|
|
201
|
+
fail("TYPE_MISMATCH", "Snapshot bytes do not match the stated kind.");
|
|
202
|
+
const result = api.operation(operationName, input, boundCall, (transaction) => {
|
|
203
|
+
const object = input.materialId ? transaction.get(objectId) : transaction.create(
|
|
204
|
+
"library.material",
|
|
205
|
+
materialData.parse({
|
|
206
|
+
name: input.name || snapshot2?.sourceLabel.objectName,
|
|
207
|
+
kind: published.kind,
|
|
208
|
+
origin: snapshot2 ? "collected" : "import",
|
|
209
|
+
sourceRef: snapshot2?.ref || null,
|
|
210
|
+
sourceLabel: snapshot2?.sourceLabel || null,
|
|
211
|
+
recommendedVersionId: versionId
|
|
212
|
+
}),
|
|
213
|
+
snapshot2?.metadata || {},
|
|
214
|
+
objectId
|
|
215
|
+
);
|
|
216
|
+
if (object.typeId !== "library.material" || object.data.kind !== published.kind)
|
|
217
|
+
fail("TYPE_MISMATCH", "Content kind does not match the material.");
|
|
218
|
+
const version = transaction.commitVersion(
|
|
219
|
+
objectId,
|
|
220
|
+
input.expectedRevision || object.revision,
|
|
221
|
+
{
|
|
222
|
+
versionId,
|
|
223
|
+
parentVersionId: input.parentVersionId,
|
|
224
|
+
sourceRef: snapshot2?.ref,
|
|
225
|
+
origin: snapshot2 ? "copied" : "upload",
|
|
226
|
+
payload: {
|
|
227
|
+
...snapshot2?.payload || {},
|
|
228
|
+
...published.details,
|
|
229
|
+
kind: published.kind,
|
|
230
|
+
primaryFileId: published.files[0].fileId,
|
|
231
|
+
attachments: published.files.slice(1).map((file) => ({ fileId: file.fileId, role: file.role })),
|
|
232
|
+
generation: snapshot2?.payload?.generation || null
|
|
233
|
+
},
|
|
234
|
+
metadata: snapshot2?.metadata,
|
|
235
|
+
files: published.files
|
|
236
|
+
}
|
|
237
|
+
);
|
|
238
|
+
return { material: transaction.get(objectId), version, receipt };
|
|
239
|
+
});
|
|
240
|
+
await api.finishFiles(operationId);
|
|
241
|
+
return result;
|
|
242
|
+
}
|
|
243
|
+
const publish = async (input, call) => {
|
|
244
|
+
const projectId = input.projectId || (call.scope?.kind === "project" ? call.scope.id : "");
|
|
245
|
+
assertProject(projectId, call);
|
|
246
|
+
ref.parse(input.ref);
|
|
247
|
+
const label = materialFields.shape.sourceLabel.parse(input.sourceLabel);
|
|
248
|
+
if (!label || !input.files.length || input.files.length > 20 || input.files[0].role !== "primary" || input.payload?.primaryFileId !== input.files[0].fileId)
|
|
249
|
+
fail("INVALID_SNAPSHOT", "A fixed snapshot requires its primary file and source label.");
|
|
250
|
+
const { files, ...snapshotFields } = input;
|
|
251
|
+
const intent = {
|
|
252
|
+
...snapshotFields,
|
|
253
|
+
projectId,
|
|
254
|
+
files: files.map(({ source: _source, ...file }) => file)
|
|
255
|
+
};
|
|
256
|
+
return importFiles(intent, call, input);
|
|
257
|
+
};
|
|
258
|
+
const snapshot = async (source) => {
|
|
259
|
+
ref.parse(source);
|
|
260
|
+
const object = material(data, source.objectId), version = data.version(source), files = data.files(source);
|
|
261
|
+
const snapshots = [];
|
|
262
|
+
for (const file of files) {
|
|
263
|
+
const location = await data.location(source, file.fileId);
|
|
264
|
+
snapshots.push({
|
|
265
|
+
...file,
|
|
266
|
+
source: (async function* () {
|
|
267
|
+
yield* createReadStream(location.absolutePath);
|
|
268
|
+
})()
|
|
269
|
+
});
|
|
270
|
+
}
|
|
271
|
+
return { material: object, version, files: snapshots };
|
|
272
|
+
};
|
|
273
|
+
const reportUsage = (input, call) => {
|
|
274
|
+
const usage = usageSchema.parse(input);
|
|
275
|
+
assertProject(usage.projectId, call);
|
|
276
|
+
const api = binding(call.actor.id);
|
|
277
|
+
material(api, usage.materialRef.objectId, true);
|
|
278
|
+
api.version(usage.materialRef);
|
|
279
|
+
const usageId = identity([
|
|
280
|
+
usage.materialRef,
|
|
281
|
+
usage.projectId,
|
|
282
|
+
usage.targetAssetId,
|
|
283
|
+
usage.targetVersionId
|
|
284
|
+
]);
|
|
285
|
+
return api.operation(
|
|
286
|
+
"library.usage.upsert",
|
|
287
|
+
usage,
|
|
288
|
+
localCall(call, "library.usage.upsert"),
|
|
289
|
+
(transaction) => {
|
|
290
|
+
let current;
|
|
291
|
+
try {
|
|
292
|
+
current = transaction.get(usageId, true);
|
|
293
|
+
} catch (error) {
|
|
294
|
+
if (error.code !== "OBJECT_NOT_FOUND") throw error;
|
|
295
|
+
}
|
|
296
|
+
return current ? transaction.update(usageId, current.revision, usage) : transaction.create("library.usage", usage, {}, usageId);
|
|
297
|
+
}
|
|
298
|
+
);
|
|
299
|
+
};
|
|
300
|
+
const removeUsage = (input, call) => {
|
|
301
|
+
const removal = z2.object({
|
|
302
|
+
projectId: z2.string().min(1).max(128),
|
|
303
|
+
targetAssetId: id,
|
|
304
|
+
targetVersionId: id.optional(),
|
|
305
|
+
materialRef: ref.optional()
|
|
306
|
+
}).strict().parse(input);
|
|
307
|
+
assertProject(removal.projectId, call);
|
|
308
|
+
const api = binding(call.actor.id);
|
|
309
|
+
return api.operation(
|
|
310
|
+
"library.usage.remove",
|
|
311
|
+
removal,
|
|
312
|
+
localCall(call, "library.usage.remove"),
|
|
313
|
+
(transaction) => {
|
|
314
|
+
let offset = 0, removed = 0;
|
|
315
|
+
while (true) {
|
|
316
|
+
const page = api.list({
|
|
317
|
+
typeId: "library.usage",
|
|
318
|
+
targetAssetId: removal.targetAssetId,
|
|
319
|
+
limit: 200,
|
|
320
|
+
offset
|
|
321
|
+
});
|
|
322
|
+
for (const current of page.items) {
|
|
323
|
+
const usage = current.data;
|
|
324
|
+
if (usage.active && usage.projectId === removal.projectId && (!removal.targetVersionId || usage.targetVersionId === removal.targetVersionId) && (!removal.materialRef || ["storeId", "objectId", "versionId"].every(
|
|
325
|
+
(key) => usage.materialRef[key] === removal.materialRef[key]
|
|
326
|
+
))) {
|
|
327
|
+
transaction.update(current.objectId, current.revision, { ...usage, active: false });
|
|
328
|
+
removed++;
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
if (page.nextOffset === null) return { removed };
|
|
332
|
+
offset = page.nextOffset;
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
);
|
|
336
|
+
};
|
|
337
|
+
const service = {
|
|
338
|
+
data,
|
|
339
|
+
publish,
|
|
340
|
+
snapshot,
|
|
341
|
+
reportUsage,
|
|
342
|
+
upsertUsage: reportUsage,
|
|
343
|
+
removeUsage
|
|
344
|
+
};
|
|
345
|
+
ctx.provide(service);
|
|
346
|
+
handle(
|
|
347
|
+
"library.list",
|
|
348
|
+
z2.object({
|
|
349
|
+
search: z2.string().optional(),
|
|
350
|
+
kind: z2.string().optional(),
|
|
351
|
+
tag: z2.string().optional(),
|
|
352
|
+
metadataEquals: z2.record(z2.string(), z2.any()).optional(),
|
|
353
|
+
metadataContains: z2.record(z2.string(), z2.any()).optional(),
|
|
354
|
+
sort: z2.enum(["name", "createdAt", "updatedAt"]).optional(),
|
|
355
|
+
direction: z2.enum(["asc", "desc"]).optional(),
|
|
356
|
+
limit: z2.number().int().min(1).max(200).optional(),
|
|
357
|
+
offset: z2.number().int().nonnegative().optional()
|
|
358
|
+
}).strict(),
|
|
359
|
+
async (input) => {
|
|
360
|
+
const result = data.list({ typeId: "library.material", ...input });
|
|
361
|
+
return {
|
|
362
|
+
...result,
|
|
363
|
+
items: await Promise.all(
|
|
364
|
+
result.items.map(async (item) => {
|
|
365
|
+
const versions = data.versions(item.objectId);
|
|
366
|
+
return {
|
|
367
|
+
...item,
|
|
368
|
+
versions,
|
|
369
|
+
usageCount: data.usageCount(item.objectId),
|
|
370
|
+
availability: versions.length ? await data.availability({
|
|
371
|
+
storeId: data.storeId,
|
|
372
|
+
objectId: item.objectId,
|
|
373
|
+
versionId: versions.at(-1).versionId
|
|
374
|
+
}) : "no_content"
|
|
375
|
+
};
|
|
376
|
+
})
|
|
377
|
+
)
|
|
378
|
+
};
|
|
379
|
+
}
|
|
380
|
+
);
|
|
381
|
+
handle("library.get", z2.object({ materialId: id }).strict(), (input) => ({
|
|
382
|
+
material: material(data, input.materialId, true),
|
|
383
|
+
versions: data.versions(input.materialId),
|
|
384
|
+
usages: data.usages({ objectId: input.materialId })
|
|
385
|
+
}));
|
|
386
|
+
handle(
|
|
387
|
+
"library.import",
|
|
388
|
+
z2.object({ name: z2.string().min(1).max(200), stagingFileIds: z2.array(id).min(1).max(20) }).strict(),
|
|
389
|
+
importFiles,
|
|
390
|
+
true
|
|
391
|
+
);
|
|
392
|
+
handle(
|
|
393
|
+
"library.importVersion",
|
|
394
|
+
z2.object({
|
|
395
|
+
materialId: id,
|
|
396
|
+
expectedRevision: revision,
|
|
397
|
+
parentVersionId: id.optional(),
|
|
398
|
+
stagingFileIds: z2.array(id).min(1).max(20)
|
|
399
|
+
}).strict(),
|
|
400
|
+
importFiles,
|
|
401
|
+
true
|
|
402
|
+
);
|
|
403
|
+
handle(
|
|
404
|
+
"library.update",
|
|
405
|
+
z2.object({
|
|
406
|
+
materialId: id,
|
|
407
|
+
expectedRevision: revision,
|
|
408
|
+
changes: materialFields.omit({ kind: true, origin: true, sourceRef: true, sourceLabel: true }).extend({
|
|
409
|
+
tags: materialFields.shape.tags.unwrap(),
|
|
410
|
+
category: materialFields.shape.category.unwrap(),
|
|
411
|
+
description: materialFields.shape.description.unwrap(),
|
|
412
|
+
lifecycle: materialFields.shape.lifecycle.unwrap(),
|
|
413
|
+
licenseNote: materialFields.shape.licenseNote.unwrap()
|
|
414
|
+
}).partial().strict()
|
|
415
|
+
}).strict(),
|
|
416
|
+
(input, call) => {
|
|
417
|
+
const api = binding(call.actor.id);
|
|
418
|
+
return api.operation(
|
|
419
|
+
"library.update",
|
|
420
|
+
input,
|
|
421
|
+
localCall(call, "library.update"),
|
|
422
|
+
(transaction) => {
|
|
423
|
+
const object = material(api, input.materialId);
|
|
424
|
+
if (input.changes.recommendedVersionId)
|
|
425
|
+
api.version({
|
|
426
|
+
storeId: api.storeId,
|
|
427
|
+
objectId: object.objectId,
|
|
428
|
+
versionId: input.changes.recommendedVersionId
|
|
429
|
+
});
|
|
430
|
+
return transaction.update(object.objectId, input.expectedRevision, {
|
|
431
|
+
...object.data,
|
|
432
|
+
...input.changes
|
|
433
|
+
});
|
|
434
|
+
}
|
|
435
|
+
);
|
|
436
|
+
},
|
|
437
|
+
true
|
|
438
|
+
);
|
|
439
|
+
for (const action of ["delete", "restore"])
|
|
440
|
+
handle(
|
|
441
|
+
"library." + action,
|
|
442
|
+
z2.object({ materialId: id, expectedRevision: revision }).strict(),
|
|
443
|
+
(input, call) => {
|
|
444
|
+
const api = binding(call.actor.id);
|
|
445
|
+
return api.operation(
|
|
446
|
+
"library." + action,
|
|
447
|
+
input,
|
|
448
|
+
localCall(call, "library." + action),
|
|
449
|
+
(transaction) => {
|
|
450
|
+
const object = material(api, input.materialId, true);
|
|
451
|
+
return transaction.update(
|
|
452
|
+
object.objectId,
|
|
453
|
+
input.expectedRevision,
|
|
454
|
+
object.data,
|
|
455
|
+
void 0,
|
|
456
|
+
action === "delete" ? (/* @__PURE__ */ new Date()).toISOString() : null
|
|
457
|
+
);
|
|
458
|
+
}
|
|
459
|
+
);
|
|
460
|
+
},
|
|
461
|
+
true
|
|
462
|
+
);
|
|
463
|
+
handle(
|
|
464
|
+
"library.usage",
|
|
465
|
+
z2.object({
|
|
466
|
+
materialId: id,
|
|
467
|
+
versionId: id.optional(),
|
|
468
|
+
limit: z2.number().int().min(1).max(200).optional(),
|
|
469
|
+
offset: z2.number().int().nonnegative().optional()
|
|
470
|
+
}).strict(),
|
|
471
|
+
(input) => {
|
|
472
|
+
material(data, input.materialId, true);
|
|
473
|
+
const limit = input.limit ?? 50, offset = input.offset ?? 0;
|
|
474
|
+
const items = data.usages({
|
|
475
|
+
objectId: input.materialId,
|
|
476
|
+
versionId: input.versionId,
|
|
477
|
+
limit,
|
|
478
|
+
offset
|
|
479
|
+
});
|
|
480
|
+
const hasMore = data.usages({
|
|
481
|
+
objectId: input.materialId,
|
|
482
|
+
versionId: input.versionId,
|
|
483
|
+
limit: 1,
|
|
484
|
+
offset: offset + limit
|
|
485
|
+
}).length > 0;
|
|
486
|
+
return { items, nextOffset: hasMore ? offset + limit : null };
|
|
487
|
+
}
|
|
488
|
+
);
|
|
489
|
+
const locationInput = z2.union([
|
|
490
|
+
z2.object({ source: ref, fileId: id.optional() }).strict(),
|
|
491
|
+
z2.object({ ref, fileId: id.optional() }).strict()
|
|
492
|
+
]);
|
|
493
|
+
handle("files.resolveLocation", locationInput, (input) => {
|
|
494
|
+
material(data, (input.source || input.ref).objectId, true);
|
|
495
|
+
return data.location(input.source || input.ref, input.fileId);
|
|
496
|
+
});
|
|
497
|
+
handle("files.openLocation", locationInput, async (input) => {
|
|
498
|
+
material(data, (input.source || input.ref).objectId, true);
|
|
499
|
+
const location = await data.location(input.source || input.ref, input.fileId);
|
|
500
|
+
if (process.platform !== "darwin")
|
|
501
|
+
fail("OPEN_LOCATION_UNAVAILABLE", "Open-location is not verified on this platform.");
|
|
502
|
+
const { execFile } = await import("child_process"), { promisify } = await import("util");
|
|
503
|
+
await promisify(execFile)("open", ["-R", location.absolutePath]);
|
|
504
|
+
return location;
|
|
505
|
+
});
|
|
506
|
+
handle(
|
|
507
|
+
"metadata.patch",
|
|
508
|
+
z2.object({
|
|
509
|
+
objectId: id,
|
|
510
|
+
expectedRevision: revision,
|
|
511
|
+
set: z2.record(z2.string(), z2.any()),
|
|
512
|
+
remove: z2.array(z2.string())
|
|
513
|
+
}).strict(),
|
|
514
|
+
(input, call) => {
|
|
515
|
+
const api = binding(call.actor.id);
|
|
516
|
+
material(api, input.objectId);
|
|
517
|
+
return api.patchMetadata(
|
|
518
|
+
input.objectId,
|
|
519
|
+
input.expectedRevision,
|
|
520
|
+
input.set,
|
|
521
|
+
input.remove,
|
|
522
|
+
localCall(call, "metadata.patch")
|
|
523
|
+
);
|
|
524
|
+
},
|
|
525
|
+
true
|
|
526
|
+
);
|
|
527
|
+
handle(
|
|
528
|
+
"metadata.keys",
|
|
529
|
+
z2.object({ limit: z2.number().int().min(1).max(200).optional() }).strict(),
|
|
530
|
+
(input) => data.metadataKeys("library.material", input.limit)
|
|
531
|
+
);
|
|
532
|
+
handle("library.cleanupImpact", z2.object({ materialId: id }).strict(), (input) => {
|
|
533
|
+
material(data, input.materialId, true);
|
|
534
|
+
return data.impact(input.materialId);
|
|
535
|
+
});
|
|
536
|
+
handle(
|
|
537
|
+
"library.purge",
|
|
538
|
+
z2.object({ materialId: id, expectedRevision: revision }).strict(),
|
|
539
|
+
(input, call) => {
|
|
540
|
+
const api = binding(call.actor.id);
|
|
541
|
+
material(api, input.materialId, true);
|
|
542
|
+
return api.purge(input.materialId, input.expectedRevision, localCall(call, "objects.purge"));
|
|
543
|
+
},
|
|
544
|
+
true,
|
|
545
|
+
true
|
|
546
|
+
);
|
|
547
|
+
}
|
|
548
|
+
function identity(value) {
|
|
549
|
+
const hash = createHash("sha256").update(JSON.stringify(value)).digest("hex");
|
|
550
|
+
return `${hash.slice(0, 8)}-${hash.slice(8, 12)}-4${hash.slice(13, 16)}-a${hash.slice(17, 20)}-${hash.slice(20, 32)}`;
|
|
551
|
+
}
|
|
552
|
+
function stable(seed, call, operation, name) {
|
|
553
|
+
if (!call.requestKey) fail("INVALID_REQUEST_KEY", "A request key is required.");
|
|
554
|
+
return identity([
|
|
555
|
+
seed,
|
|
556
|
+
call.scope?.kind ?? "user",
|
|
557
|
+
call.scope?.id ?? "user",
|
|
558
|
+
call.actor.id,
|
|
559
|
+
operation,
|
|
560
|
+
call.requestKey,
|
|
561
|
+
name
|
|
562
|
+
]);
|
|
563
|
+
}
|
|
564
|
+
export {
|
|
565
|
+
Config,
|
|
566
|
+
activate as default,
|
|
567
|
+
dependencies
|
|
568
|
+
};
|
package/dist/shared.d.ts
ADDED
|
@@ -0,0 +1,341 @@
|
|
|
1
|
+
import { CallContext as CallContext$1, z } from 'vivix-studio/sdk/shared';
|
|
2
|
+
|
|
3
|
+
type Scope = {
|
|
4
|
+
kind: 'user' | 'project';
|
|
5
|
+
id: string;
|
|
6
|
+
};
|
|
7
|
+
type CallContext = CallContext$1 & {
|
|
8
|
+
scope?: Scope;
|
|
9
|
+
confirmed?: boolean;
|
|
10
|
+
originalRequestKey?: string;
|
|
11
|
+
sourceOperation?: string;
|
|
12
|
+
};
|
|
13
|
+
type JSONValue = null | boolean | number | string | JSONValue[] | {
|
|
14
|
+
[key: string]: JSONValue;
|
|
15
|
+
};
|
|
16
|
+
interface RecordValue {
|
|
17
|
+
storeId: string;
|
|
18
|
+
scope: Scope;
|
|
19
|
+
objectId: string;
|
|
20
|
+
typeId: string;
|
|
21
|
+
schemaVersion: number;
|
|
22
|
+
revision: number;
|
|
23
|
+
data: any;
|
|
24
|
+
metadata: Record<string, JSONValue>;
|
|
25
|
+
createdAt: string;
|
|
26
|
+
updatedAt: string;
|
|
27
|
+
deletedAt: string | null;
|
|
28
|
+
}
|
|
29
|
+
interface VersionRef {
|
|
30
|
+
storeId: string;
|
|
31
|
+
objectId: string;
|
|
32
|
+
versionId: string;
|
|
33
|
+
}
|
|
34
|
+
interface VersionValue {
|
|
35
|
+
versionId: string;
|
|
36
|
+
objectId: string;
|
|
37
|
+
versionNumber: number;
|
|
38
|
+
schemaVersion: number;
|
|
39
|
+
parentVersionId: string | null;
|
|
40
|
+
sourceRef: VersionRef | null;
|
|
41
|
+
origin: string;
|
|
42
|
+
taskId: string | null;
|
|
43
|
+
payload: any;
|
|
44
|
+
metadata: any;
|
|
45
|
+
contentDigest: string;
|
|
46
|
+
createdAt: string;
|
|
47
|
+
}
|
|
48
|
+
interface FileValue {
|
|
49
|
+
fileId: string;
|
|
50
|
+
relativePath: string;
|
|
51
|
+
originalName: string;
|
|
52
|
+
mimeType: string;
|
|
53
|
+
sizeBytes: number;
|
|
54
|
+
sha256: string;
|
|
55
|
+
createdAt: string;
|
|
56
|
+
role: string;
|
|
57
|
+
ordinal: number;
|
|
58
|
+
}
|
|
59
|
+
type ProbeRunner = (path: string, signal?: AbortSignal, context?: {
|
|
60
|
+
directory: string;
|
|
61
|
+
}) => Promise<{
|
|
62
|
+
stdout: string;
|
|
63
|
+
stderr: string;
|
|
64
|
+
}>;
|
|
65
|
+
interface StageOptions {
|
|
66
|
+
signal?: AbortSignal;
|
|
67
|
+
probeRunner?: ProbeRunner;
|
|
68
|
+
onPrepared?: (stagingFileId: string) => void | Promise<void>;
|
|
69
|
+
}
|
|
70
|
+
interface StagedFile {
|
|
71
|
+
stagingFileId: string;
|
|
72
|
+
kind: string;
|
|
73
|
+
mimeType: string;
|
|
74
|
+
sizeBytes: number;
|
|
75
|
+
sha256: string;
|
|
76
|
+
originalName: string;
|
|
77
|
+
details: Record<string, any>;
|
|
78
|
+
}
|
|
79
|
+
type SqlScalar = null | string | number;
|
|
80
|
+
interface SqlInput {
|
|
81
|
+
sql: string;
|
|
82
|
+
params?: SqlScalar[] | Record<string, SqlScalar>;
|
|
83
|
+
timeoutMs?: number;
|
|
84
|
+
maxRows?: number;
|
|
85
|
+
limit?: number;
|
|
86
|
+
offset?: number;
|
|
87
|
+
}
|
|
88
|
+
type SqlRow = Record<string, JSONValue>;
|
|
89
|
+
interface MutationInput {
|
|
90
|
+
table: string;
|
|
91
|
+
action: 'insert' | 'update' | 'delete';
|
|
92
|
+
key?: Record<string, SqlScalar>;
|
|
93
|
+
values?: Record<string, SqlScalar>;
|
|
94
|
+
expectedRevision?: number;
|
|
95
|
+
}
|
|
96
|
+
interface MutationResult {
|
|
97
|
+
changes: number;
|
|
98
|
+
items: SqlRow[];
|
|
99
|
+
}
|
|
100
|
+
interface Query {
|
|
101
|
+
typeId: string;
|
|
102
|
+
search?: string;
|
|
103
|
+
kind?: string;
|
|
104
|
+
tag?: string;
|
|
105
|
+
targetAssetId?: string;
|
|
106
|
+
taskId?: string;
|
|
107
|
+
outputIndex?: number;
|
|
108
|
+
metadataEquals?: Record<string, JSONValue>;
|
|
109
|
+
metadataContains?: Record<string, JSONValue>;
|
|
110
|
+
sort?: 'name' | 'createdAt' | 'updatedAt';
|
|
111
|
+
direction?: 'asc' | 'desc';
|
|
112
|
+
limit?: number;
|
|
113
|
+
offset?: number;
|
|
114
|
+
includeDeleted?: boolean;
|
|
115
|
+
}
|
|
116
|
+
interface Transaction {
|
|
117
|
+
get(id: string, includeDeleted?: boolean): RecordValue;
|
|
118
|
+
create(typeId: string, data: any, metadata?: any, id?: string): RecordValue;
|
|
119
|
+
update(id: string, expectedRevision: number, data: any, metadata?: any, deletedAt?: string | null): RecordValue;
|
|
120
|
+
commitVersion(id: string, expectedRevision: number, value: {
|
|
121
|
+
parentVersionId?: string | null;
|
|
122
|
+
sourceRef?: VersionRef | null;
|
|
123
|
+
origin: string;
|
|
124
|
+
taskId?: string | null;
|
|
125
|
+
payload: any;
|
|
126
|
+
metadata?: any;
|
|
127
|
+
files?: FileValue[];
|
|
128
|
+
versionId?: string;
|
|
129
|
+
}): VersionValue;
|
|
130
|
+
/** Clear only mutable record-level references owned by this binding; version references remain immutable. */
|
|
131
|
+
removeRelations(sourceObjectId: string, kind: 'reference'): void;
|
|
132
|
+
relation(sourceObjectId: string, sourceVersionId: string | null, target: VersionRef, kind: 'source' | 'input' | 'reference'): void;
|
|
133
|
+
}
|
|
134
|
+
interface DataAPI {
|
|
135
|
+
readonly storeId: string;
|
|
136
|
+
readonly scope: Scope;
|
|
137
|
+
get(id: string, includeDeleted?: boolean): RecordValue;
|
|
138
|
+
list(query: Query): {
|
|
139
|
+
items: RecordValue[];
|
|
140
|
+
nextOffset: number | null;
|
|
141
|
+
};
|
|
142
|
+
query(input: SqlInput): Promise<SqlRow[]>;
|
|
143
|
+
mutate(input: MutationInput, call: CallContext): Promise<MutationResult>;
|
|
144
|
+
versions(id: string): VersionValue[];
|
|
145
|
+
version(ref: VersionRef): VersionValue;
|
|
146
|
+
files(ref: VersionRef): FileValue[];
|
|
147
|
+
receipt(name: string, input: unknown, call: CallContext): {
|
|
148
|
+
result: any;
|
|
149
|
+
} | null;
|
|
150
|
+
reserve(name: string, input: unknown, call: CallContext): {
|
|
151
|
+
result: any;
|
|
152
|
+
} | null;
|
|
153
|
+
operation<T>(name: string, input: unknown, call: CallContext, action: (tx: Transaction) => T): T;
|
|
154
|
+
patchMetadata(id: string, revision: number, set: Record<string, JSONValue>, remove: string[], call: CallContext): RecordValue;
|
|
155
|
+
metadataKeys(typeId: string, limit?: number): {
|
|
156
|
+
key: string;
|
|
157
|
+
types: string[];
|
|
158
|
+
observedCount: number;
|
|
159
|
+
}[];
|
|
160
|
+
stage(source: AsyncIterable<Uint8Array>, originalName: string, actorId: string, options?: StageOptions): Promise<StagedFile>;
|
|
161
|
+
stageText(text: string, actorId: string): Promise<StagedFile>;
|
|
162
|
+
publish(stagingIds: string[], objectId: string, versionId: string, operationId: string, actorId: string, fileRoles?: string[]): Promise<{
|
|
163
|
+
files: FileValue[];
|
|
164
|
+
details: any;
|
|
165
|
+
kind: string;
|
|
166
|
+
}>;
|
|
167
|
+
finishFiles(operationId: string): Promise<void>;
|
|
168
|
+
discardStaging(stagingFileId: string): Promise<void>;
|
|
169
|
+
discardPublication(operationId: string): Promise<void>;
|
|
170
|
+
location(ref: VersionRef, fileId?: string): Promise<{
|
|
171
|
+
source: VersionRef;
|
|
172
|
+
absolutePath: string;
|
|
173
|
+
fileId: string;
|
|
174
|
+
mimeType: string;
|
|
175
|
+
availability: 'ready';
|
|
176
|
+
previewUrl: string | null;
|
|
177
|
+
}>;
|
|
178
|
+
availability(ref: VersionRef): Promise<'ready' | 'missing' | 'corrupt'>;
|
|
179
|
+
openFile(ref: VersionRef, fileId: string, signal: AbortSignal): Promise<ReadableStream<Uint8Array>>;
|
|
180
|
+
usages(ref: {
|
|
181
|
+
objectId: string;
|
|
182
|
+
versionId?: string;
|
|
183
|
+
limit?: number;
|
|
184
|
+
offset?: number;
|
|
185
|
+
}): any[];
|
|
186
|
+
usageCount(objectId: string): number;
|
|
187
|
+
documents(action: 'create' | 'update' | 'delete', input: any, call: CallContext): RecordValue;
|
|
188
|
+
impact(id: string): any;
|
|
189
|
+
purge(id: string, revision: number, call: CallContext): any;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
declare const Config: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
193
|
+
role: z.ZodLiteral<"service">;
|
|
194
|
+
dataInstance: z.ZodDefault<z.ZodString>;
|
|
195
|
+
serviceInstance: z.ZodDefault<z.ZodString>;
|
|
196
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
197
|
+
role: z.ZodLiteral<"client">;
|
|
198
|
+
serviceInstance: z.ZodDefault<z.ZodString>;
|
|
199
|
+
}, z.core.$strict>], "role">;
|
|
200
|
+
type MaterialConfig = z.infer<typeof Config>;
|
|
201
|
+
interface SnapshotFile extends FileValue {
|
|
202
|
+
source: AsyncIterable<Uint8Array>;
|
|
203
|
+
}
|
|
204
|
+
interface MaterialPublishInput {
|
|
205
|
+
projectId?: string;
|
|
206
|
+
ref: VersionRef;
|
|
207
|
+
sourceLabel: {
|
|
208
|
+
projectName: string;
|
|
209
|
+
objectName: string;
|
|
210
|
+
versionNumber: number;
|
|
211
|
+
};
|
|
212
|
+
name?: string;
|
|
213
|
+
kind: 'text' | 'image' | 'video' | 'audio' | 'file';
|
|
214
|
+
payload: any;
|
|
215
|
+
metadata?: Record<string, any>;
|
|
216
|
+
files: SnapshotFile[];
|
|
217
|
+
}
|
|
218
|
+
interface MaterialSnapshot {
|
|
219
|
+
material: RecordValue;
|
|
220
|
+
version: VersionValue;
|
|
221
|
+
files: SnapshotFile[];
|
|
222
|
+
}
|
|
223
|
+
interface UsageReport {
|
|
224
|
+
materialRef: VersionRef;
|
|
225
|
+
projectId: string;
|
|
226
|
+
targetAssetId: string;
|
|
227
|
+
targetVersionId: string;
|
|
228
|
+
name?: string;
|
|
229
|
+
active?: boolean;
|
|
230
|
+
}
|
|
231
|
+
interface UsageRemoval {
|
|
232
|
+
projectId: string;
|
|
233
|
+
targetAssetId: string;
|
|
234
|
+
materialRef?: VersionRef;
|
|
235
|
+
targetVersionId?: string;
|
|
236
|
+
}
|
|
237
|
+
interface LibraryReceipt {
|
|
238
|
+
scope: Scope;
|
|
239
|
+
actorId: string;
|
|
240
|
+
operation: string;
|
|
241
|
+
requestKey: string;
|
|
242
|
+
invocationRequestKey: string;
|
|
243
|
+
storageRequestKey: string;
|
|
244
|
+
sourceOperation: string;
|
|
245
|
+
}
|
|
246
|
+
interface LibraryService {
|
|
247
|
+
readonly data: DataAPI;
|
|
248
|
+
publish(input: MaterialPublishInput, call: CallContext): Promise<{
|
|
249
|
+
material: RecordValue;
|
|
250
|
+
version: VersionValue;
|
|
251
|
+
receipt?: LibraryReceipt;
|
|
252
|
+
}>;
|
|
253
|
+
snapshot(ref: VersionRef): Promise<MaterialSnapshot>;
|
|
254
|
+
reportUsage(input: UsageReport, call: CallContext): RecordValue;
|
|
255
|
+
upsertUsage(input: UsageReport, call: CallContext): RecordValue;
|
|
256
|
+
removeUsage(input: UsageRemoval, call: CallContext): {
|
|
257
|
+
removed: number;
|
|
258
|
+
};
|
|
259
|
+
}
|
|
260
|
+
declare const materialFields: z.ZodObject<{
|
|
261
|
+
name: z.ZodString;
|
|
262
|
+
kind: z.ZodEnum<{
|
|
263
|
+
file: "file";
|
|
264
|
+
text: "text";
|
|
265
|
+
image: "image";
|
|
266
|
+
video: "video";
|
|
267
|
+
audio: "audio";
|
|
268
|
+
}>;
|
|
269
|
+
tags: z.ZodDefault<z.ZodPipe<z.ZodArray<z.ZodString>, z.ZodTransform<string[], string[]>>>;
|
|
270
|
+
category: z.ZodDefault<z.ZodEnum<{
|
|
271
|
+
character: "character";
|
|
272
|
+
scene: "scene";
|
|
273
|
+
style: "style";
|
|
274
|
+
sound: "sound";
|
|
275
|
+
reference: "reference";
|
|
276
|
+
other: "other";
|
|
277
|
+
}>>;
|
|
278
|
+
description: z.ZodDefault<z.ZodString>;
|
|
279
|
+
origin: z.ZodEnum<{
|
|
280
|
+
import: "import";
|
|
281
|
+
collected: "collected";
|
|
282
|
+
}>;
|
|
283
|
+
sourceRef: z.ZodDefault<z.ZodNullable<z.ZodObject<{
|
|
284
|
+
storeId: z.ZodUUID;
|
|
285
|
+
objectId: z.ZodUUID;
|
|
286
|
+
versionId: z.ZodUUID;
|
|
287
|
+
}, z.core.$strict>>>;
|
|
288
|
+
sourceLabel: z.ZodDefault<z.ZodNullable<z.ZodObject<{
|
|
289
|
+
projectName: z.ZodString;
|
|
290
|
+
objectName: z.ZodString;
|
|
291
|
+
versionNumber: z.ZodNumber;
|
|
292
|
+
}, z.core.$strict>>>;
|
|
293
|
+
lifecycle: z.ZodDefault<z.ZodEnum<{
|
|
294
|
+
ready: "ready";
|
|
295
|
+
archived: "archived";
|
|
296
|
+
}>>;
|
|
297
|
+
recommendedVersionId: z.ZodUUID;
|
|
298
|
+
licenseNote: z.ZodDefault<z.ZodString>;
|
|
299
|
+
}, z.core.$strict>;
|
|
300
|
+
declare const materialData: z.ZodObject<{
|
|
301
|
+
name: z.ZodString;
|
|
302
|
+
kind: z.ZodEnum<{
|
|
303
|
+
file: "file";
|
|
304
|
+
text: "text";
|
|
305
|
+
image: "image";
|
|
306
|
+
video: "video";
|
|
307
|
+
audio: "audio";
|
|
308
|
+
}>;
|
|
309
|
+
tags: z.ZodDefault<z.ZodPipe<z.ZodArray<z.ZodString>, z.ZodTransform<string[], string[]>>>;
|
|
310
|
+
category: z.ZodDefault<z.ZodEnum<{
|
|
311
|
+
character: "character";
|
|
312
|
+
scene: "scene";
|
|
313
|
+
style: "style";
|
|
314
|
+
sound: "sound";
|
|
315
|
+
reference: "reference";
|
|
316
|
+
other: "other";
|
|
317
|
+
}>>;
|
|
318
|
+
description: z.ZodDefault<z.ZodString>;
|
|
319
|
+
origin: z.ZodEnum<{
|
|
320
|
+
import: "import";
|
|
321
|
+
collected: "collected";
|
|
322
|
+
}>;
|
|
323
|
+
sourceRef: z.ZodDefault<z.ZodNullable<z.ZodObject<{
|
|
324
|
+
storeId: z.ZodUUID;
|
|
325
|
+
objectId: z.ZodUUID;
|
|
326
|
+
versionId: z.ZodUUID;
|
|
327
|
+
}, z.core.$strict>>>;
|
|
328
|
+
sourceLabel: z.ZodDefault<z.ZodNullable<z.ZodObject<{
|
|
329
|
+
projectName: z.ZodString;
|
|
330
|
+
objectName: z.ZodString;
|
|
331
|
+
versionNumber: z.ZodNumber;
|
|
332
|
+
}, z.core.$strict>>>;
|
|
333
|
+
lifecycle: z.ZodDefault<z.ZodEnum<{
|
|
334
|
+
ready: "ready";
|
|
335
|
+
archived: "archived";
|
|
336
|
+
}>>;
|
|
337
|
+
recommendedVersionId: z.ZodUUID;
|
|
338
|
+
licenseNote: z.ZodDefault<z.ZodString>;
|
|
339
|
+
}, z.core.$strict>;
|
|
340
|
+
|
|
341
|
+
export { Config, type LibraryReceipt, type LibraryService, type MaterialConfig, type MaterialPublishInput, type MaterialSnapshot, type SnapshotFile, type UsageRemoval, type UsageReport, materialData, materialFields };
|
package/dist/shared.js
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
// packages/material-library/src/shared.ts
|
|
2
|
+
import { z } from "vivix-studio/sdk/shared";
|
|
3
|
+
var Config = z.discriminatedUnion("role", [
|
|
4
|
+
z.object({
|
|
5
|
+
role: z.literal("service"),
|
|
6
|
+
dataInstance: z.string().min(1).default("data"),
|
|
7
|
+
serviceInstance: z.string().min(1).default("library")
|
|
8
|
+
}).strict(),
|
|
9
|
+
z.object({
|
|
10
|
+
role: z.literal("client"),
|
|
11
|
+
serviceInstance: z.string().min(1).default("user:library")
|
|
12
|
+
}).strict()
|
|
13
|
+
]);
|
|
14
|
+
var materialFields = z.object({
|
|
15
|
+
name: z.string().trim().min(1).max(200),
|
|
16
|
+
kind: z.enum(["text", "image", "video", "audio", "file"]),
|
|
17
|
+
tags: z.array(z.string().trim().min(1).max(64)).max(50).transform((x) => [...new Set(x)]).default([]),
|
|
18
|
+
category: z.enum(["character", "scene", "style", "sound", "reference", "other"]).default("other"),
|
|
19
|
+
description: z.string().max(4e3).default(""),
|
|
20
|
+
origin: z.enum(["import", "collected"]),
|
|
21
|
+
sourceRef: z.object({ storeId: z.uuid(), objectId: z.uuid(), versionId: z.uuid() }).strict().nullable().default(null),
|
|
22
|
+
sourceLabel: z.object({
|
|
23
|
+
projectName: z.string(),
|
|
24
|
+
objectName: z.string(),
|
|
25
|
+
versionNumber: z.number().int().positive()
|
|
26
|
+
}).strict().nullable().default(null),
|
|
27
|
+
lifecycle: z.enum(["ready", "archived"]).default("ready"),
|
|
28
|
+
recommendedVersionId: z.uuid(),
|
|
29
|
+
licenseNote: z.string().max(4e3).default("")
|
|
30
|
+
}).strict();
|
|
31
|
+
var materialData = materialFields.refine(
|
|
32
|
+
(v) => v.origin !== "collected" || v.sourceRef !== null,
|
|
33
|
+
"Collected materials require a fixed source."
|
|
34
|
+
);
|
|
35
|
+
export {
|
|
36
|
+
Config,
|
|
37
|
+
materialData,
|
|
38
|
+
materialFields
|
|
39
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,46 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vivix-labs/material-library",
|
|
3
|
-
"version": "
|
|
4
|
-
"
|
|
5
|
-
"
|
|
3
|
+
"version": "1.0.0-beta",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"publishConfig": {
|
|
6
|
+
"access": "public",
|
|
7
|
+
"registry": "https://registry.npmjs.org/",
|
|
8
|
+
"tag": "beta"
|
|
9
|
+
},
|
|
10
|
+
"license": "MIT",
|
|
11
|
+
"files": [
|
|
12
|
+
"dist",
|
|
13
|
+
"studio-plugin.json",
|
|
14
|
+
"skills",
|
|
15
|
+
"presets"
|
|
16
|
+
],
|
|
17
|
+
"engines": {
|
|
18
|
+
"node": ">=22.12.0"
|
|
19
|
+
},
|
|
20
|
+
"exports": {
|
|
21
|
+
"./studio-plugin.json": "./studio-plugin.json",
|
|
22
|
+
"./server": {
|
|
23
|
+
"types": "./dist/server.d.ts",
|
|
24
|
+
"import": "./dist/server.js"
|
|
25
|
+
},
|
|
26
|
+
"./shared": {
|
|
27
|
+
"types": "./dist/shared.d.ts",
|
|
28
|
+
"import": "./dist/shared.js"
|
|
29
|
+
},
|
|
30
|
+
"./client": {
|
|
31
|
+
"types": "./dist/client.d.ts",
|
|
32
|
+
"import": "./dist/client.js"
|
|
33
|
+
}
|
|
34
|
+
},
|
|
35
|
+
"peerDependencies": {
|
|
36
|
+
"vivix-studio": ">=2.1.0-0",
|
|
37
|
+
"@vivix-labs/creative-data": "1.0.0-beta",
|
|
38
|
+
"react": "19.3.1"
|
|
39
|
+
},
|
|
40
|
+
"devDependencies": {
|
|
41
|
+
"@vivix-labs/creative-data": "1.0.0-beta"
|
|
42
|
+
},
|
|
43
|
+
"scripts": {
|
|
44
|
+
"build": "node ../../scripts/build-product-packages.mjs material-library"
|
|
45
|
+
}
|
|
6
46
|
}
|
package/README.md
DELETED