@drawcall/market 0.1.5 → 0.1.6
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/asset-implementation.d.ts +88 -0
- package/dist/asset-implementation.d.ts.map +1 -0
- package/dist/asset-implementation.js +2 -0
- package/dist/asset-implementation.js.map +1 -0
- package/dist/cli-client.d.ts +2 -2
- package/dist/cli-client.d.ts.map +1 -1
- package/dist/cli-client.js +4 -4
- package/dist/cli-client.js.map +1 -1
- package/dist/cli.js +104 -22
- package/dist/cli.js.map +1 -1
- package/dist/client.d.ts +1 -3
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +4 -12
- package/dist/client.js.map +1 -1
- package/dist/commands/generate.d.ts.map +1 -1
- package/dist/commands/generate.js +9 -4
- package/dist/commands/generate.js.map +1 -1
- package/dist/commands/install.d.ts.map +1 -1
- package/dist/commands/install.js +30 -28
- package/dist/commands/install.js.map +1 -1
- package/dist/commands/logout.js +3 -3
- package/dist/commands/logout.js.map +1 -1
- package/dist/commands/preview.d.ts +9 -0
- package/dist/commands/preview.d.ts.map +1 -0
- package/dist/commands/preview.js +50 -0
- package/dist/commands/preview.js.map +1 -0
- package/dist/commands/search.d.ts +4 -1
- package/dist/commands/search.d.ts.map +1 -1
- package/dist/commands/search.js +20 -10
- package/dist/commands/search.js.map +1 -1
- package/dist/commands/upload.d.ts +9 -0
- package/dist/commands/upload.d.ts.map +1 -0
- package/dist/commands/upload.js +220 -0
- package/dist/commands/upload.js.map +1 -0
- package/dist/config.d.ts +4 -7
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +8 -4
- package/dist/config.js.map +1 -1
- package/dist/constants.d.ts.map +1 -1
- package/dist/constants.js +2 -10
- package/dist/constants.js.map +1 -1
- package/dist/contract.d.ts +37 -134
- package/dist/contract.d.ts.map +1 -1
- package/dist/contract.js +10 -48
- package/dist/contract.js.map +1 -1
- package/dist/generate.d.ts +0 -2
- package/dist/generate.d.ts.map +1 -1
- package/dist/generate.js +5 -22
- package/dist/generate.js.map +1 -1
- package/dist/index.d.ts +5 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -3
- package/dist/index.js.map +1 -1
- package/dist/install.d.ts +1 -1
- package/dist/install.d.ts.map +1 -1
- package/dist/install.js +14 -11
- package/dist/install.js.map +1 -1
- package/dist/output.d.ts +26 -0
- package/dist/output.d.ts.map +1 -0
- package/dist/output.js +52 -0
- package/dist/output.js.map +1 -0
- package/dist/resolve.d.ts.map +1 -1
- package/dist/resolve.js +15 -26
- package/dist/resolve.js.map +1 -1
- package/dist/schemas.d.ts +31 -39
- package/dist/schemas.d.ts.map +1 -1
- package/dist/schemas.js +23 -25
- package/dist/schemas.js.map +1 -1
- package/package.json +13 -4
- package/src/asset-implementation.ts +114 -0
- package/src/cli-client.ts +7 -6
- package/src/cli.ts +144 -29
- package/src/client.ts +5 -15
- package/src/commands/generate.ts +9 -6
- package/src/commands/install.ts +34 -31
- package/src/commands/logout.ts +3 -3
- package/src/commands/preview.ts +69 -0
- package/src/commands/search.ts +27 -11
- package/src/commands/upload.ts +262 -0
- package/src/config.ts +11 -5
- package/src/constants.ts +2 -10
- package/src/contract.ts +22 -120
- package/src/generate.ts +5 -29
- package/src/index.ts +23 -14
- package/src/install.ts +24 -14
- package/src/output.ts +76 -0
- package/src/resolve.ts +22 -38
- package/src/schemas.ts +26 -27
- package/tsconfig.json +2 -1
- package/dist/commands/login.d.ts +0 -10
- package/dist/commands/login.d.ts.map +0 -1
- package/dist/commands/login.js +0 -92
- package/dist/commands/login.js.map +0 -1
- package/dist/internal-contract.d.ts +0 -19
- package/dist/internal-contract.d.ts.map +0 -1
- package/dist/internal-contract.js +0 -19
- package/dist/internal-contract.js.map +0 -1
- package/src/commands/login.ts +0 -113
- package/src/internal-contract.ts +0 -26
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
import * as fs from 'fs/promises'
|
|
2
|
+
import * as path from 'path'
|
|
3
|
+
import { zipSync } from 'fflate'
|
|
4
|
+
import ora from 'ora'
|
|
5
|
+
import semver from 'semver'
|
|
6
|
+
import { getCliClient } from '../cli-client.js'
|
|
7
|
+
import { unchangedUploadResult, uploadResult } from '../output.js'
|
|
8
|
+
import { assetNameSchema, semverSchema, type AssetType } from '../schemas.js'
|
|
9
|
+
|
|
10
|
+
export interface UploadCommandOptions {
|
|
11
|
+
type?: AssetType
|
|
12
|
+
version?: string
|
|
13
|
+
cwd?: string
|
|
14
|
+
baseUrl?: string
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export async function uploadCommand(
|
|
18
|
+
name: string,
|
|
19
|
+
fileFilter: string,
|
|
20
|
+
description: string,
|
|
21
|
+
opts: UploadCommandOptions,
|
|
22
|
+
): Promise<void> {
|
|
23
|
+
const spinner = ora({
|
|
24
|
+
text: 'Preparing upload',
|
|
25
|
+
isEnabled: Boolean(process.stderr.isTTY),
|
|
26
|
+
isSilent: !process.stderr.isTTY,
|
|
27
|
+
}).start()
|
|
28
|
+
|
|
29
|
+
try {
|
|
30
|
+
const parsedName = assetNameSchema.parse(name)
|
|
31
|
+
const parsedVersion = opts.version ? semverSchema.parse(opts.version) : undefined
|
|
32
|
+
const cwd = opts.cwd ?? process.cwd()
|
|
33
|
+
const file = await resolveOneFile(cwd, fileFilter)
|
|
34
|
+
const zip = await buildModelZip(file, description)
|
|
35
|
+
const { client } = await getCliClient({ baseUrl: opts.baseUrl, requireAuth: true })
|
|
36
|
+
const profile = await client.user.getProfile()
|
|
37
|
+
if (!profile) {
|
|
38
|
+
throw new Error('Not logged in. Run `market login` first.')
|
|
39
|
+
}
|
|
40
|
+
const existing = await client.asset.exact({
|
|
41
|
+
name: parsedName,
|
|
42
|
+
type: opts.type ?? 'model',
|
|
43
|
+
includeUnapproved: true,
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
if (existing && existing.ownerId !== profile.id) {
|
|
47
|
+
throw new Error(`Asset "${parsedName}" already exists and is owned by another user`)
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (existing) {
|
|
51
|
+
const latestZip = await client.asset.downloadZip({
|
|
52
|
+
name: parsedName,
|
|
53
|
+
version: existing.latestVersion,
|
|
54
|
+
})
|
|
55
|
+
const latestBytes = new Uint8Array(await latestZip.arrayBuffer())
|
|
56
|
+
if (existing.description === description && bytesEqual(latestBytes, zip)) {
|
|
57
|
+
spinner.stop()
|
|
58
|
+
console.log(unchangedUploadResult(parsedName, existing.latestVersion))
|
|
59
|
+
return
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const version = parsedVersion ?? nextVersion(existing?.latestVersion)
|
|
64
|
+
spinner.text = `Uploading ${parsedName}@${version}`
|
|
65
|
+
const uploaded = await client.asset.uploadZip({
|
|
66
|
+
name: parsedName,
|
|
67
|
+
type: opts.type ?? 'model',
|
|
68
|
+
version,
|
|
69
|
+
description,
|
|
70
|
+
npmDependencies: {},
|
|
71
|
+
assetDependencies: {},
|
|
72
|
+
tags: [],
|
|
73
|
+
zip: new File([toArrayBuffer(zip)], `${parsedName}-${version}.zip`, {
|
|
74
|
+
type: 'application/zip',
|
|
75
|
+
}),
|
|
76
|
+
})
|
|
77
|
+
|
|
78
|
+
spinner.stop()
|
|
79
|
+
console.log(uploadResult(parsedName, uploaded.version))
|
|
80
|
+
} catch (err) {
|
|
81
|
+
spinner.stop()
|
|
82
|
+
throw err
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
async function buildModelZip(file: string, description: string): Promise<Uint8Array> {
|
|
87
|
+
const name = path.basename(file)
|
|
88
|
+
if (!/\.(glb|gltf)$/i.test(name)) {
|
|
89
|
+
throw new Error('Upload file filter must resolve to a .glb or .gltf file')
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const files: Record<string, Uint8Array> = {
|
|
93
|
+
[`public/${name}`]: new Uint8Array(await fs.readFile(file)),
|
|
94
|
+
'README.md': new TextEncoder().encode(description),
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if (/\.gltf$/i.test(name)) {
|
|
98
|
+
for (const dependency of await gltfDependencies(file)) {
|
|
99
|
+
files[`public/${dependency.publicPath}`] = new Uint8Array(
|
|
100
|
+
await fs.readFile(dependency.source),
|
|
101
|
+
)
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
return zipSync(files, { mtime: new Date('1980-01-01T00:00:00.000Z'), level: 0 })
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
interface GltfDependency {
|
|
109
|
+
source: string
|
|
110
|
+
publicPath: string
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
async function gltfDependencies(file: string): Promise<GltfDependency[]> {
|
|
114
|
+
const gltf = JSON.parse(await fs.readFile(file, 'utf-8')) as {
|
|
115
|
+
buffers?: Array<{ uri?: string }>
|
|
116
|
+
images?: Array<{ uri?: string }>
|
|
117
|
+
}
|
|
118
|
+
const uris = [...(gltf.buffers ?? []), ...(gltf.images ?? [])]
|
|
119
|
+
.map((entry) => entry.uri)
|
|
120
|
+
.filter((uri): uri is string => Boolean(uri && !uri.startsWith('data:')))
|
|
121
|
+
const dependencies = new Map<string, GltfDependency>()
|
|
122
|
+
|
|
123
|
+
for (const uri of uris) {
|
|
124
|
+
const decodedUri = decodeURIComponent(uri)
|
|
125
|
+
const publicPath = toPublicDependencyPath(decodedUri)
|
|
126
|
+
dependencies.set(publicPath, {
|
|
127
|
+
source: await resolveGltfDependency(file, decodedUri),
|
|
128
|
+
publicPath,
|
|
129
|
+
})
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
return [...dependencies.values()]
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
async function resolveGltfDependency(file: string, uri: string): Promise<string> {
|
|
136
|
+
const dir = path.dirname(file)
|
|
137
|
+
const direct = path.resolve(dir, uri)
|
|
138
|
+
if (await isFile(direct)) return direct
|
|
139
|
+
|
|
140
|
+
const basename = path.basename(uri)
|
|
141
|
+
const alternateBasenames = [basename]
|
|
142
|
+
const withoutBlenderSuffix = basename.replace(/_\d{3}(\.[^.]+)$/u, '$1')
|
|
143
|
+
if (withoutBlenderSuffix !== basename) {
|
|
144
|
+
alternateBasenames.push(withoutBlenderSuffix)
|
|
145
|
+
}
|
|
146
|
+
let current = dir
|
|
147
|
+
for (let depth = 0; depth < 8; depth += 1) {
|
|
148
|
+
for (const candidateBasename of alternateBasenames) {
|
|
149
|
+
const candidates = [
|
|
150
|
+
path.join(current, 'Textures', candidateBasename),
|
|
151
|
+
path.join(current, 'textures', candidateBasename),
|
|
152
|
+
path.join(current, candidateBasename),
|
|
153
|
+
]
|
|
154
|
+
for (const candidate of candidates) {
|
|
155
|
+
if (await isFile(candidate)) return candidate
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const parent = path.dirname(current)
|
|
160
|
+
if (parent === current) break
|
|
161
|
+
current = parent
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
throw new Error(`Could not find glTF dependency "${uri}" for ${path.basename(file)}`)
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
async function isFile(file: string): Promise<boolean> {
|
|
168
|
+
return (await maybeStat(file))?.isFile() ?? false
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function toPublicDependencyPath(uri: string): string {
|
|
172
|
+
if (/^[a-z]+:/i.test(uri) || path.isAbsolute(uri)) {
|
|
173
|
+
throw new Error(`Unsupported external glTF dependency "${uri}"`)
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
const normalized = path
|
|
177
|
+
.normalize(uri)
|
|
178
|
+
.split(path.sep)
|
|
179
|
+
.filter((part) => part.length > 0 && part !== '.')
|
|
180
|
+
.join('/')
|
|
181
|
+
if (!normalized || normalized.startsWith('../') || normalized === '..') {
|
|
182
|
+
throw new Error(`Unsupported glTF dependency path "${uri}"`)
|
|
183
|
+
}
|
|
184
|
+
return normalized
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
async function resolveOneFile(cwd: string, fileFilter: string): Promise<string> {
|
|
188
|
+
const absolute = path.resolve(cwd, fileFilter)
|
|
189
|
+
const stat = await maybeStat(absolute)
|
|
190
|
+
if (stat?.isFile()) return absolute
|
|
191
|
+
|
|
192
|
+
const files = await listFiles(cwd)
|
|
193
|
+
const matches = files
|
|
194
|
+
.filter((file) => matchesFilter(path.relative(cwd, file), fileFilter))
|
|
195
|
+
.filter((file) => /\.(glb|gltf)$/i.test(file))
|
|
196
|
+
.sort()
|
|
197
|
+
|
|
198
|
+
if (matches.length === 0) {
|
|
199
|
+
throw new Error(`No .glb or .gltf files matched "${fileFilter}"`)
|
|
200
|
+
}
|
|
201
|
+
if (matches.length > 1) {
|
|
202
|
+
throw new Error(`File filter matched ${matches.length} models; upload one asset at a time`)
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
return matches[0]
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
async function maybeStat(file: string) {
|
|
209
|
+
try {
|
|
210
|
+
return await fs.stat(file)
|
|
211
|
+
} catch {
|
|
212
|
+
return null
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
async function listFiles(dir: string): Promise<string[]> {
|
|
217
|
+
const entries = await fs.readdir(dir, { withFileTypes: true })
|
|
218
|
+
const files: string[] = []
|
|
219
|
+
for (const entry of entries) {
|
|
220
|
+
if (entry.name === 'node_modules' || entry.name === '.git') continue
|
|
221
|
+
const fullPath = path.join(dir, entry.name)
|
|
222
|
+
if (entry.isDirectory()) {
|
|
223
|
+
files.push(...(await listFiles(fullPath)))
|
|
224
|
+
} else if (entry.isFile()) {
|
|
225
|
+
files.push(fullPath)
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
return files
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
function matchesFilter(file: string, filter: string): boolean {
|
|
232
|
+
const normalizedFile = file.split(path.sep).join('/')
|
|
233
|
+
const normalizedFilter = filter.split(path.sep).join('/')
|
|
234
|
+
const pattern =
|
|
235
|
+
'^' +
|
|
236
|
+
escapeRegExp(normalizedFilter)
|
|
237
|
+
.replace(/\\\*\\\*/g, '.*')
|
|
238
|
+
.replace(/\\\*/g, '[^/]*') +
|
|
239
|
+
'$'
|
|
240
|
+
return new RegExp(pattern).test(normalizedFile)
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
function nextVersion(latest?: string): string {
|
|
244
|
+
if (!latest) return '1.0.0'
|
|
245
|
+
return semver.inc(latest, 'patch') ?? '1.0.0'
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
function bytesEqual(a: Uint8Array, b: Uint8Array): boolean {
|
|
249
|
+
if (a.length !== b.length) return false
|
|
250
|
+
for (let i = 0; i < a.length; i += 1) {
|
|
251
|
+
if (a[i] !== b[i]) return false
|
|
252
|
+
}
|
|
253
|
+
return true
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
function toArrayBuffer(bytes: Uint8Array): ArrayBuffer {
|
|
257
|
+
return new Uint8Array(bytes).buffer
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
function escapeRegExp(value: string): string {
|
|
261
|
+
return value.replace(/[|\\{}()[\]^$+?.*]/g, '\\$&')
|
|
262
|
+
}
|
package/src/config.ts
CHANGED
|
@@ -3,12 +3,16 @@ import * as os from 'os'
|
|
|
3
3
|
import * as path from 'path'
|
|
4
4
|
import { z } from 'zod'
|
|
5
5
|
|
|
6
|
-
const
|
|
7
|
-
|
|
6
|
+
const rawConfigSchema = z.object({
|
|
7
|
+
authToken: z.string().min(1).optional(),
|
|
8
|
+
apiKey: z.string().min(1).optional(),
|
|
8
9
|
baseUrl: z.string().url().optional(),
|
|
9
10
|
})
|
|
10
11
|
|
|
11
|
-
export
|
|
12
|
+
export interface Config {
|
|
13
|
+
authToken: string
|
|
14
|
+
baseUrl?: string
|
|
15
|
+
}
|
|
12
16
|
|
|
13
17
|
function configDir(): string {
|
|
14
18
|
if (process.platform === 'win32' && process.env.APPDATA) {
|
|
@@ -25,8 +29,10 @@ function configPath(): string {
|
|
|
25
29
|
export async function loadConfig(): Promise<Config | null> {
|
|
26
30
|
try {
|
|
27
31
|
const raw = await fs.readFile(configPath(), 'utf-8')
|
|
28
|
-
const parsed =
|
|
29
|
-
|
|
32
|
+
const parsed = rawConfigSchema.safeParse(JSON.parse(raw))
|
|
33
|
+
if (!parsed.success) return null
|
|
34
|
+
const authToken = parsed.data.authToken ?? parsed.data.apiKey
|
|
35
|
+
return authToken ? { authToken, baseUrl: parsed.data.baseUrl } : null
|
|
30
36
|
} catch {
|
|
31
37
|
return null
|
|
32
38
|
}
|
package/src/constants.ts
CHANGED
|
@@ -3,17 +3,9 @@ import type { AssetType } from './schemas.js'
|
|
|
3
3
|
export const MAX_FILE_SIZE = 100 * 1024 * 1024 // 100MB
|
|
4
4
|
|
|
5
5
|
export const ALLOWED_EXTENSIONS: Record<AssetType, string[]> = {
|
|
6
|
-
|
|
7
|
-
model: ['.gltf', '.glb'],
|
|
8
|
-
hdri: ['.hdr', '.exr'],
|
|
9
|
-
material: [], // material is submitted as JSON, no file upload
|
|
10
|
-
music: ['.mp3', '.wav', '.ogg', '.flac'],
|
|
6
|
+
model: ['.zip'],
|
|
11
7
|
}
|
|
12
8
|
|
|
13
9
|
export const ASSET_TYPE_LABELS: Record<AssetType, string> = {
|
|
14
|
-
|
|
15
|
-
model: '3D Model',
|
|
16
|
-
hdri: 'HDRI',
|
|
17
|
-
material: 'Material',
|
|
18
|
-
music: 'Audio',
|
|
10
|
+
model: 'Model',
|
|
19
11
|
}
|
package/src/contract.ts
CHANGED
|
@@ -1,38 +1,27 @@
|
|
|
1
1
|
import { oc } from '@orpc/contract'
|
|
2
2
|
import { z } from 'zod'
|
|
3
3
|
import {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
4
|
+
downloadZipSchema,
|
|
5
|
+
exactAssetSchema,
|
|
6
|
+
generateAssetSchema,
|
|
7
7
|
listAssetsSchema,
|
|
8
8
|
updateProfileSchema,
|
|
9
|
-
|
|
10
|
-
uploadTypedSchema,
|
|
11
|
-
uploadMaterialSchema,
|
|
9
|
+
uploadZipSchema,
|
|
12
10
|
} from './schemas.js'
|
|
13
11
|
|
|
14
|
-
// ─── Output types ─────────────────────────────────────────────────────────────
|
|
15
|
-
// These mirror what the server handlers return (Drizzle query results).
|
|
16
|
-
// We use z.custom<T>() so the contract carries full types for the client
|
|
17
|
-
// without duplicating every DB column as a Zod field.
|
|
18
|
-
|
|
19
12
|
export interface AssetVersion {
|
|
20
|
-
id:
|
|
21
|
-
assetId:
|
|
13
|
+
id: string
|
|
14
|
+
assetId: string
|
|
22
15
|
version: string
|
|
23
16
|
approved: boolean
|
|
24
17
|
npmDependencies: string
|
|
25
18
|
assetDependencies: string
|
|
26
19
|
sourceKey: string
|
|
27
|
-
buildOutputKey: string | null
|
|
28
|
-
thumbnailKey: string | null
|
|
29
|
-
buildError: string | null
|
|
30
|
-
readme: string | null
|
|
31
20
|
createdAt: Date
|
|
32
21
|
}
|
|
33
22
|
|
|
34
23
|
export interface Asset {
|
|
35
|
-
id:
|
|
24
|
+
id: string
|
|
36
25
|
name: string
|
|
37
26
|
type: string
|
|
38
27
|
description: string | null
|
|
@@ -41,13 +30,8 @@ export interface Asset {
|
|
|
41
30
|
updatedAt: Date
|
|
42
31
|
}
|
|
43
32
|
|
|
44
|
-
export interface
|
|
45
|
-
|
|
46
|
-
tags: string[]
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
export interface AssetListItem {
|
|
50
|
-
id: number
|
|
33
|
+
export interface AssetSearchResult {
|
|
34
|
+
id: string
|
|
51
35
|
name: string
|
|
52
36
|
type: string
|
|
53
37
|
description: string | null
|
|
@@ -55,8 +39,9 @@ export interface AssetListItem {
|
|
|
55
39
|
createdAt: Date
|
|
56
40
|
updatedAt: Date
|
|
57
41
|
latestVersion: string
|
|
58
|
-
thumbnailKey: string | null
|
|
59
42
|
approved: boolean
|
|
43
|
+
npmDependencies: string
|
|
44
|
+
assetDependencies: string
|
|
60
45
|
}
|
|
61
46
|
|
|
62
47
|
export interface PaginatedList<T> {
|
|
@@ -74,95 +59,31 @@ export interface User {
|
|
|
74
59
|
emailVerified: boolean
|
|
75
60
|
image: string | null
|
|
76
61
|
role: string
|
|
62
|
+
isAdmin: boolean
|
|
77
63
|
createdAt: Date
|
|
78
64
|
updatedAt: Date
|
|
79
65
|
}
|
|
80
66
|
|
|
81
|
-
export interface
|
|
82
|
-
versions: AssetVersion[]
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
export interface UnapprovedItem {
|
|
86
|
-
versionId: number
|
|
87
|
-
assetId: number
|
|
67
|
+
export interface GenerateAssetResult {
|
|
88
68
|
assetName: string
|
|
89
|
-
assetType: string
|
|
90
69
|
version: string
|
|
91
|
-
buildError: string | null
|
|
92
|
-
buildOutputKey: string | null
|
|
93
|
-
thumbnailKey: string | null
|
|
94
|
-
createdAt: Date
|
|
95
|
-
ownerName: string
|
|
96
|
-
ownerEmail: string
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
export interface TagWithCount {
|
|
100
|
-
id: number
|
|
101
|
-
name: string
|
|
102
|
-
count: number
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
export interface FileTreeEntry {
|
|
106
|
-
path: string
|
|
107
|
-
size: number
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
export interface GenerateJobStatus {
|
|
111
|
-
jobId: string
|
|
112
|
-
state: 'queued' | 'running' | 'done' | 'failed'
|
|
113
|
-
message: string | null
|
|
114
|
-
assetName: string | null
|
|
115
|
-
version: string | null
|
|
116
|
-
error: string | null
|
|
117
70
|
}
|
|
118
71
|
|
|
119
|
-
// ─── Contract ─────────────────────────────────────────────────────────────────
|
|
120
|
-
|
|
121
72
|
export const contract = {
|
|
122
73
|
asset: {
|
|
123
|
-
|
|
124
|
-
.input(z.object({ name: assetNameSchema }))
|
|
125
|
-
.output(z.custom<AssetWithVersionsAndTags | null>()),
|
|
126
|
-
|
|
127
|
-
list: oc.input(listAssetsSchema).output(z.custom<PaginatedList<AssetListItem>>()),
|
|
74
|
+
search: oc.input(listAssetsSchema).output(z.custom<PaginatedList<AssetSearchResult>>()),
|
|
128
75
|
|
|
129
|
-
|
|
130
|
-
.input(z.object({ name: z.string(), version: semverSchema }))
|
|
131
|
-
.output(z.custom<FileTreeEntry[]>()),
|
|
76
|
+
exact: oc.input(exactAssetSchema).output(z.custom<AssetSearchResult | null>()),
|
|
132
77
|
|
|
133
|
-
|
|
134
|
-
.input(
|
|
135
|
-
.output(z.instanceof(Blob)),
|
|
136
|
-
},
|
|
137
|
-
|
|
138
|
-
upload: {
|
|
139
|
-
generic: oc
|
|
140
|
-
.input(uploadGenericSchema.extend({ file: z.instanceof(File) }))
|
|
78
|
+
uploadZip: oc
|
|
79
|
+
.input(uploadZipSchema.extend({ zip: z.instanceof(File) }))
|
|
141
80
|
.output(z.custom<AssetVersion>()),
|
|
142
81
|
|
|
143
|
-
|
|
144
|
-
.input(uploadTypedSchema.extend({ file: z.instanceof(File) }))
|
|
145
|
-
.output(z.custom<AssetVersion>()),
|
|
82
|
+
downloadZip: oc.input(downloadZipSchema).output(z.instanceof(Blob)),
|
|
146
83
|
|
|
147
|
-
|
|
148
|
-
.input(uploadTypedSchema.extend({ file: z.instanceof(File) }))
|
|
149
|
-
.output(z.custom<AssetVersion>()),
|
|
150
|
-
|
|
151
|
-
music: oc
|
|
152
|
-
.input(uploadTypedSchema.extend({ file: z.instanceof(File) }))
|
|
153
|
-
.output(z.custom<AssetVersion>()),
|
|
84
|
+
downloadPreviewImage: oc.input(downloadZipSchema).output(z.instanceof(Blob)),
|
|
154
85
|
|
|
155
|
-
|
|
156
|
-
},
|
|
157
|
-
|
|
158
|
-
admin: {
|
|
159
|
-
listUnapproved: oc.output(z.custom<UnapprovedItem[]>()),
|
|
160
|
-
|
|
161
|
-
approve: oc
|
|
162
|
-
.input(z.object({ assetName: z.string(), version: z.string() }))
|
|
163
|
-
.output(z.object({ success: z.boolean() })),
|
|
164
|
-
|
|
165
|
-
backfillEmbeddings: oc.output(z.object({ indexed: z.number() })),
|
|
86
|
+
generate: oc.input(generateAssetSchema).output(z.custom<GenerateAssetResult>()),
|
|
166
87
|
},
|
|
167
88
|
|
|
168
89
|
user: {
|
|
@@ -170,28 +91,9 @@ export const contract = {
|
|
|
170
91
|
|
|
171
92
|
updateProfile: oc.input(updateProfileSchema).output(z.custom<User>()),
|
|
172
93
|
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
regenerateApiKey: oc.output(z.object({ key: z.string(), prefix: z.string() })),
|
|
176
|
-
|
|
177
|
-
myAssets: oc.output(z.custom<AssetWithVersions[]>()),
|
|
178
|
-
},
|
|
179
|
-
|
|
180
|
-
tag: {
|
|
181
|
-
list: oc.output(z.custom<TagWithCount[]>()),
|
|
182
|
-
},
|
|
94
|
+
getAuthToken: oc.output(z.custom<{ prefix: string; createdAt: Date } | null>()),
|
|
183
95
|
|
|
184
|
-
|
|
185
|
-
start: oc
|
|
186
|
-
.input(
|
|
187
|
-
z.object({
|
|
188
|
-
description: z.string().min(3).max(1000),
|
|
189
|
-
type: assetTypeSchema.optional(),
|
|
190
|
-
}),
|
|
191
|
-
)
|
|
192
|
-
.output(z.object({ jobId: z.string() })),
|
|
193
|
-
|
|
194
|
-
status: oc.input(z.object({ jobId: z.string() })).output(z.custom<GenerateJobStatus>()),
|
|
96
|
+
createAuthToken: oc.output(z.object({ token: z.string(), prefix: z.string() })),
|
|
195
97
|
},
|
|
196
98
|
}
|
|
197
99
|
|
package/src/generate.ts
CHANGED
|
@@ -20,8 +20,6 @@ export interface GenerateResult {
|
|
|
20
20
|
|
|
21
21
|
export interface GenerateOptions {
|
|
22
22
|
onProgress?: (message: string) => void
|
|
23
|
-
pollIntervalMs?: number
|
|
24
|
-
timeoutMs?: number
|
|
25
23
|
}
|
|
26
24
|
|
|
27
25
|
export async function generateAndWait(
|
|
@@ -29,34 +27,12 @@ export async function generateAndWait(
|
|
|
29
27
|
input: GenerateInput,
|
|
30
28
|
opts: GenerateOptions = {},
|
|
31
29
|
): Promise<GenerateResult> {
|
|
32
|
-
const pollIntervalMs = opts.pollIntervalMs ?? 1500
|
|
33
|
-
const timeoutMs = opts.timeoutMs ?? 60_000
|
|
34
30
|
const report = opts.onProgress ?? (() => {})
|
|
35
31
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
const status = await client.generate.status({ jobId })
|
|
41
|
-
if (status.message) report(status.message)
|
|
42
|
-
|
|
43
|
-
if (status.state === 'done') {
|
|
44
|
-
if (!status.assetName || !status.version) {
|
|
45
|
-
throw new GenerateError('Generation completed without an asset name.')
|
|
46
|
-
}
|
|
47
|
-
return { assetName: status.assetName, version: status.version }
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
if (status.state === 'failed') {
|
|
51
|
-
throw new GenerateError(status.error ?? 'Generation failed.')
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
await sleep(pollIntervalMs)
|
|
32
|
+
report('Generating asset')
|
|
33
|
+
const result = await client.asset.generate(input)
|
|
34
|
+
if (!result.assetName || !result.version) {
|
|
35
|
+
throw new GenerateError('Generation completed without an asset name.')
|
|
55
36
|
}
|
|
56
|
-
|
|
57
|
-
throw new GenerateError(`Generation timed out after ${timeoutMs}ms.`)
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
function sleep(ms: number): Promise<void> {
|
|
61
|
-
return new Promise((resolve) => setTimeout(resolve, ms))
|
|
37
|
+
return result
|
|
62
38
|
}
|
package/src/index.ts
CHANGED
|
@@ -1,28 +1,36 @@
|
|
|
1
1
|
// Client
|
|
2
|
-
export { createMarketClient
|
|
3
|
-
export type { MarketClient,
|
|
2
|
+
export { createMarketClient } from './client.js'
|
|
3
|
+
export type { MarketClient, MarketClientOptions } from './client.js'
|
|
4
4
|
|
|
5
5
|
// Contracts
|
|
6
6
|
export { contract } from './contract.js'
|
|
7
7
|
export type { AppContract } from './contract.js'
|
|
8
|
-
export { internalContract } from './internal-contract.js'
|
|
9
|
-
export type { InternalContract } from './internal-contract.js'
|
|
10
8
|
|
|
11
9
|
// Contract output types
|
|
12
10
|
export type {
|
|
13
11
|
Asset,
|
|
14
12
|
AssetVersion,
|
|
15
|
-
|
|
16
|
-
AssetWithVersions,
|
|
17
|
-
AssetListItem,
|
|
13
|
+
AssetSearchResult,
|
|
18
14
|
PaginatedList,
|
|
19
15
|
User,
|
|
20
|
-
|
|
21
|
-
TagWithCount,
|
|
22
|
-
FileTreeEntry,
|
|
23
|
-
GenerateJobStatus,
|
|
16
|
+
GenerateAssetResult,
|
|
24
17
|
} from './contract.js'
|
|
25
18
|
|
|
19
|
+
export type {
|
|
20
|
+
AssetCatalog,
|
|
21
|
+
AssetDownloadZipInput,
|
|
22
|
+
AssetExactInput,
|
|
23
|
+
AssetGenerateInput,
|
|
24
|
+
AssetImplementation,
|
|
25
|
+
AssetImplementationContext,
|
|
26
|
+
AssetSearchIndex,
|
|
27
|
+
AssetSearchIndexEntry,
|
|
28
|
+
AssetSearchInput,
|
|
29
|
+
AssetUploadZipInput,
|
|
30
|
+
BlobStore,
|
|
31
|
+
SaveAssetVersionInput,
|
|
32
|
+
} from './asset-implementation.js'
|
|
33
|
+
|
|
26
34
|
// Resolve
|
|
27
35
|
export { resolve, ResolutionError } from './resolve.js'
|
|
28
36
|
export type { ResolvedAsset, ResolveResult } from './resolve.js'
|
|
@@ -39,9 +47,10 @@ export {
|
|
|
39
47
|
assetNameSchema,
|
|
40
48
|
npmDependenciesSchema,
|
|
41
49
|
assetDependenciesSchema,
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
50
|
+
uploadZipSchema,
|
|
51
|
+
downloadZipSchema,
|
|
52
|
+
exactAssetSchema,
|
|
53
|
+
generateAssetSchema,
|
|
45
54
|
updateProfileSchema,
|
|
46
55
|
listAssetsSchema,
|
|
47
56
|
} from './schemas.js'
|