@drawcall/market 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +61 -0
- package/dist/cli.js.map +1 -0
- package/dist/client.d.ts +12 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +26 -0
- package/dist/client.js.map +1 -0
- package/dist/constants.d.ts +5 -0
- package/dist/constants.d.ts.map +1 -0
- package/dist/constants.js +16 -0
- package/dist/constants.js.map +1 -0
- package/dist/contract.d.ts +198 -0
- package/dist/contract.d.ts.map +1 -0
- package/dist/contract.js +64 -0
- package/dist/contract.js.map +1 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +12 -0
- package/dist/index.js.map +1 -0
- package/dist/install.d.ts +20 -0
- package/dist/install.d.ts.map +1 -0
- package/dist/install.js +67 -0
- package/dist/install.js.map +1 -0
- package/dist/internal-contract.d.ts +19 -0
- package/dist/internal-contract.d.ts.map +1 -0
- package/dist/internal-contract.js +19 -0
- package/dist/internal-contract.js.map +1 -0
- package/dist/resolve.d.ts +32 -0
- package/dist/resolve.d.ts.map +1 -0
- package/dist/resolve.js +145 -0
- package/dist/resolve.js.map +1 -0
- package/dist/schemas.d.ts +65 -0
- package/dist/schemas.d.ts.map +1 -0
- package/dist/schemas.js +53 -0
- package/dist/schemas.js.map +1 -0
- package/package.json +31 -0
- package/src/cli.ts +72 -0
- package/src/client.ts +38 -0
- package/src/constants.ts +19 -0
- package/src/contract.ts +188 -0
- package/src/index.ts +46 -0
- package/src/install.ts +101 -0
- package/src/internal-contract.ts +26 -0
- package/src/resolve.ts +215 -0
- package/src/schemas.ts +70 -0
- package/tsconfig.json +8 -0
package/src/index.ts
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
// Client
|
|
2
|
+
export { createMarketClient, createInternalClient } from './client.js'
|
|
3
|
+
export type { MarketClient, InternalClient, MarketClientOptions } from './client.js'
|
|
4
|
+
|
|
5
|
+
// Contracts
|
|
6
|
+
export { contract } from './contract.js'
|
|
7
|
+
export type { AppContract } from './contract.js'
|
|
8
|
+
export { internalContract } from './internal-contract.js'
|
|
9
|
+
export type { InternalContract } from './internal-contract.js'
|
|
10
|
+
|
|
11
|
+
// Contract output types
|
|
12
|
+
export type {
|
|
13
|
+
Asset,
|
|
14
|
+
AssetVersion,
|
|
15
|
+
AssetWithVersionsAndTags,
|
|
16
|
+
AssetWithVersions,
|
|
17
|
+
AssetListItem,
|
|
18
|
+
PaginatedList,
|
|
19
|
+
User,
|
|
20
|
+
UnapprovedItem,
|
|
21
|
+
TagWithCount,
|
|
22
|
+
FileTreeEntry,
|
|
23
|
+
} from './contract.js'
|
|
24
|
+
|
|
25
|
+
// Resolve
|
|
26
|
+
export { resolve, ResolutionError } from './resolve.js'
|
|
27
|
+
export type { ResolvedAsset, ResolveResult } from './resolve.js'
|
|
28
|
+
|
|
29
|
+
// Schemas
|
|
30
|
+
export {
|
|
31
|
+
ASSET_TYPES,
|
|
32
|
+
assetTypeSchema,
|
|
33
|
+
semverSchema,
|
|
34
|
+
assetNameSchema,
|
|
35
|
+
npmDependenciesSchema,
|
|
36
|
+
assetDependenciesSchema,
|
|
37
|
+
uploadGenericSchema,
|
|
38
|
+
uploadTypedSchema,
|
|
39
|
+
uploadMaterialSchema,
|
|
40
|
+
updateProfileSchema,
|
|
41
|
+
listAssetsSchema,
|
|
42
|
+
} from './schemas.js'
|
|
43
|
+
export type { AssetType } from './schemas.js'
|
|
44
|
+
|
|
45
|
+
// Constants
|
|
46
|
+
export { MAX_FILE_SIZE, ALLOWED_EXTENSIONS, ASSET_TYPE_LABELS } from './constants.js'
|
package/src/install.ts
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Install resolved assets into a local project.
|
|
3
|
+
*
|
|
4
|
+
* 1. Downloads asset files into ./src/{assetName}/ via the oRPC client.
|
|
5
|
+
* 2. Merges npm dependencies into package.json.
|
|
6
|
+
* 3. Runs the package manager to install npm deps.
|
|
7
|
+
*
|
|
8
|
+
* NOTE: This module uses Node.js APIs (fs, path, nypm) and is only
|
|
9
|
+
* used by the CLI binary — it is NOT exported from the package index.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import * as fs from 'fs/promises'
|
|
13
|
+
import * as path from 'path'
|
|
14
|
+
import { detectPackageManager, installDependencies } from 'nypm'
|
|
15
|
+
import type { MarketClient } from './client.js'
|
|
16
|
+
import type { ResolveResult } from './resolve.js'
|
|
17
|
+
|
|
18
|
+
export interface InstallOptions {
|
|
19
|
+
/** Project root directory (default: cwd) */
|
|
20
|
+
cwd?: string
|
|
21
|
+
/** Log progress */
|
|
22
|
+
onProgress?: (message: string) => void
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export async function install(
|
|
26
|
+
client: MarketClient,
|
|
27
|
+
resolution: ResolveResult,
|
|
28
|
+
opts: InstallOptions = {},
|
|
29
|
+
): Promise<void> {
|
|
30
|
+
const cwd = opts.cwd ?? process.cwd()
|
|
31
|
+
const log = opts.onProgress ?? (() => {})
|
|
32
|
+
|
|
33
|
+
// Run asset file downloads and npm dep installation in parallel
|
|
34
|
+
await Promise.all([
|
|
35
|
+
downloadAssets(client, resolution, cwd, log),
|
|
36
|
+
installNpmDeps(resolution, cwd, log),
|
|
37
|
+
])
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
async function downloadAssets(
|
|
41
|
+
client: MarketClient,
|
|
42
|
+
resolution: ResolveResult,
|
|
43
|
+
cwd: string,
|
|
44
|
+
log: (msg: string) => void,
|
|
45
|
+
): Promise<void> {
|
|
46
|
+
for (const asset of resolution.assets) {
|
|
47
|
+
const destDir = path.join(cwd, 'src', asset.name)
|
|
48
|
+
await fs.mkdir(destDir, { recursive: true })
|
|
49
|
+
|
|
50
|
+
log(`Downloading ${asset.name}@${asset.version}...`)
|
|
51
|
+
|
|
52
|
+
const tree = await client.asset.getVersionTree({ name: asset.name, version: asset.version })
|
|
53
|
+
|
|
54
|
+
for (const file of tree) {
|
|
55
|
+
const filePath = path.join(destDir, file.path)
|
|
56
|
+
await fs.mkdir(path.dirname(filePath), { recursive: true })
|
|
57
|
+
|
|
58
|
+
const blob = await client.asset.getRawFile({
|
|
59
|
+
name: asset.name,
|
|
60
|
+
version: asset.version,
|
|
61
|
+
path: file.path,
|
|
62
|
+
})
|
|
63
|
+
const content = Buffer.from(await blob.arrayBuffer())
|
|
64
|
+
await fs.writeFile(filePath, content)
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
log(` ${tree.length} files → src/${asset.name}/`)
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
async function installNpmDeps(
|
|
72
|
+
resolution: ResolveResult,
|
|
73
|
+
cwd: string,
|
|
74
|
+
log: (msg: string) => void,
|
|
75
|
+
): Promise<void> {
|
|
76
|
+
const deps = resolution.npmDependencies
|
|
77
|
+
if (Object.keys(deps).length === 0) return
|
|
78
|
+
|
|
79
|
+
// Read existing package.json
|
|
80
|
+
const pkgPath = path.join(cwd, 'package.json')
|
|
81
|
+
let pkg: any
|
|
82
|
+
try {
|
|
83
|
+
pkg = JSON.parse(await fs.readFile(pkgPath, 'utf-8'))
|
|
84
|
+
} catch {
|
|
85
|
+
pkg = { name: 'my-project', private: true, dependencies: {} }
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Merge dependencies
|
|
89
|
+
pkg.dependencies = { ...pkg.dependencies, ...deps }
|
|
90
|
+
await fs.writeFile(pkgPath, JSON.stringify(pkg, null, 2) + '\n')
|
|
91
|
+
|
|
92
|
+
log(`Installing npm dependencies: ${Object.keys(deps).join(', ')}`)
|
|
93
|
+
|
|
94
|
+
// Detect package manager, fall back to npm
|
|
95
|
+
const pm = await detectPackageManager(cwd).catch(() => null)
|
|
96
|
+
const pmName = pm?.name ?? 'npm'
|
|
97
|
+
|
|
98
|
+
log(`Using ${pmName}...`)
|
|
99
|
+
await installDependencies({ cwd, packageManager: { name: pmName, command: pmName } })
|
|
100
|
+
log('npm dependencies installed.')
|
|
101
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { oc } from '@orpc/contract'
|
|
2
|
+
import { z } from 'zod'
|
|
3
|
+
|
|
4
|
+
export const internalContract = {
|
|
5
|
+
buildUpload: oc
|
|
6
|
+
.input(
|
|
7
|
+
z.object({
|
|
8
|
+
key: z.string(),
|
|
9
|
+
content: z.string(), // base64-encoded
|
|
10
|
+
contentType: z.string(),
|
|
11
|
+
}),
|
|
12
|
+
)
|
|
13
|
+
.output(z.object({ ok: z.boolean() })),
|
|
14
|
+
|
|
15
|
+
buildComplete: oc
|
|
16
|
+
.input(
|
|
17
|
+
z.object({
|
|
18
|
+
assetName: z.string(),
|
|
19
|
+
version: z.string(),
|
|
20
|
+
buildOutputKey: z.string(),
|
|
21
|
+
}),
|
|
22
|
+
)
|
|
23
|
+
.output(z.object({ ok: z.boolean() })),
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export type InternalContract = typeof internalContract
|
package/src/resolve.ts
ADDED
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dependency resolver for market assets.
|
|
3
|
+
*
|
|
4
|
+
* 1. Collects the target assets and all transitive asset dependencies.
|
|
5
|
+
* 2. For each asset, finds a version that satisfies ALL constraints from
|
|
6
|
+
* every dependent.
|
|
7
|
+
* 3. Merges npm dependency ranges across all resolved assets and checks
|
|
8
|
+
* that they are compatible.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import * as semver from 'semver'
|
|
12
|
+
import type { MarketClient } from './client.js'
|
|
13
|
+
import type { AssetWithVersionsAndTags } from './contract.js'
|
|
14
|
+
|
|
15
|
+
export interface ResolvedAsset {
|
|
16
|
+
name: string
|
|
17
|
+
version: string
|
|
18
|
+
npmDependencies: Record<string, string>
|
|
19
|
+
assetDependencies: Record<string, string>
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface ResolveResult {
|
|
23
|
+
assets: ResolvedAsset[]
|
|
24
|
+
npmDependencies: Record<string, string>
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
interface AssetRequest {
|
|
28
|
+
name: string
|
|
29
|
+
range: string // semver range, e.g. "^1.0.0" or "*"
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export class ResolutionError extends Error {
|
|
33
|
+
constructor(message: string) {
|
|
34
|
+
super(message)
|
|
35
|
+
this.name = 'ResolutionError'
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export async function resolve(
|
|
40
|
+
client: MarketClient['asset'],
|
|
41
|
+
requests: AssetRequest[],
|
|
42
|
+
opts: { includeUnapproved?: boolean } = {},
|
|
43
|
+
): Promise<ResolveResult> {
|
|
44
|
+
// constraints[assetName] = list of { range, from } constraints
|
|
45
|
+
const constraints: Map<string, { range: string; from: string }[]> = new Map()
|
|
46
|
+
// resolved[assetName] = ResolvedAsset
|
|
47
|
+
const resolved: Map<string, ResolvedAsset> = new Map()
|
|
48
|
+
// cache of fetched metadata
|
|
49
|
+
const metaCache: Map<string, AssetWithVersionsAndTags> = new Map()
|
|
50
|
+
|
|
51
|
+
// Seed constraints from the requested assets
|
|
52
|
+
for (const req of requests) {
|
|
53
|
+
addConstraint(constraints, req.name, req.range, '<root>')
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Iteratively resolve until stable
|
|
57
|
+
let unresolved = getUnresolved(constraints, resolved)
|
|
58
|
+
while (unresolved.length > 0) {
|
|
59
|
+
for (const assetName of unresolved) {
|
|
60
|
+
const meta = await fetchMeta(client, metaCache, assetName)
|
|
61
|
+
if (!meta) {
|
|
62
|
+
throw new ResolutionError(`Asset "${assetName}" not found.`)
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Filter to approved versions (unless --unapproved)
|
|
66
|
+
const candidates = meta.versions.filter((v) => opts.includeUnapproved || v.approved)
|
|
67
|
+
|
|
68
|
+
if (candidates.length === 0) {
|
|
69
|
+
throw new ResolutionError(
|
|
70
|
+
`Asset "${assetName}" has no ${opts.includeUnapproved ? '' : 'approved '}versions.`,
|
|
71
|
+
)
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Collect all constraints for this asset
|
|
75
|
+
const assetConstraints = constraints.get(assetName) ?? []
|
|
76
|
+
|
|
77
|
+
// Find the best (highest) version that satisfies ALL constraints
|
|
78
|
+
const candidateVersions = candidates.map((c) => c.version)
|
|
79
|
+
const satisfying = candidateVersions.filter((v) =>
|
|
80
|
+
assetConstraints.every((c) => semver.satisfies(v, c.range)),
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
if (satisfying.length === 0) {
|
|
84
|
+
const constraintDesc = assetConstraints
|
|
85
|
+
.map((c) => ` ${c.range} (from ${c.from})`)
|
|
86
|
+
.join('\n')
|
|
87
|
+
throw new ResolutionError(
|
|
88
|
+
`No version of "${assetName}" satisfies all constraints:\n${constraintDesc}\n` +
|
|
89
|
+
`Available${opts.includeUnapproved ? '' : ' approved'}: ${candidateVersions.join(', ')}`,
|
|
90
|
+
)
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const best = semver.maxSatisfying(satisfying, '*')!
|
|
94
|
+
const versionMeta = candidates.find((c) => c.version === best)!
|
|
95
|
+
|
|
96
|
+
const npmDeps: Record<string, string> = JSON.parse(versionMeta.npmDependencies)
|
|
97
|
+
const assetDeps: Record<string, string> = JSON.parse(versionMeta.assetDependencies)
|
|
98
|
+
|
|
99
|
+
resolved.set(assetName, {
|
|
100
|
+
name: assetName,
|
|
101
|
+
version: best,
|
|
102
|
+
npmDependencies: npmDeps,
|
|
103
|
+
assetDependencies: assetDeps,
|
|
104
|
+
})
|
|
105
|
+
|
|
106
|
+
// Add transitive asset dependency constraints
|
|
107
|
+
for (const [depName, depRange] of Object.entries(assetDeps)) {
|
|
108
|
+
addConstraint(constraints, depName, depRange, `${assetName}@${best}`)
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
unresolved = getUnresolved(constraints, resolved)
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Verify all resolved versions still satisfy constraints (transitive deps
|
|
116
|
+
// may have added new constraints after we resolved a version)
|
|
117
|
+
for (const [assetName, asset] of resolved) {
|
|
118
|
+
const assetConstraints = constraints.get(assetName) ?? []
|
|
119
|
+
for (const c of assetConstraints) {
|
|
120
|
+
if (!semver.satisfies(asset.version, c.range)) {
|
|
121
|
+
throw new ResolutionError(
|
|
122
|
+
`Conflict: "${assetName}@${asset.version}" does not satisfy ` +
|
|
123
|
+
`${c.range} (required by ${c.from}).`,
|
|
124
|
+
)
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// Merge npm dependencies across all resolved assets
|
|
130
|
+
const mergedNpm = mergeNpmDependencies(Array.from(resolved.values()))
|
|
131
|
+
|
|
132
|
+
return {
|
|
133
|
+
assets: Array.from(resolved.values()),
|
|
134
|
+
npmDependencies: mergedNpm,
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function addConstraint(
|
|
139
|
+
constraints: Map<string, { range: string; from: string }[]>,
|
|
140
|
+
name: string,
|
|
141
|
+
range: string,
|
|
142
|
+
from: string,
|
|
143
|
+
) {
|
|
144
|
+
const existing = constraints.get(name) ?? []
|
|
145
|
+
existing.push({ range, from })
|
|
146
|
+
constraints.set(name, existing)
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function getUnresolved(
|
|
150
|
+
constraints: Map<string, { range: string; from: string }[]>,
|
|
151
|
+
resolved: Map<string, ResolvedAsset>,
|
|
152
|
+
): string[] {
|
|
153
|
+
return Array.from(constraints.keys()).filter((name) => !resolved.has(name))
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
async function fetchMeta(
|
|
157
|
+
client: MarketClient['asset'],
|
|
158
|
+
cache: Map<string, AssetWithVersionsAndTags>,
|
|
159
|
+
name: string,
|
|
160
|
+
): Promise<AssetWithVersionsAndTags | null> {
|
|
161
|
+
if (cache.has(name)) return cache.get(name)!
|
|
162
|
+
const meta = await client.getByName({ name })
|
|
163
|
+
if (meta) cache.set(name, meta)
|
|
164
|
+
return meta
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Merge npm dependency ranges from all resolved assets.
|
|
169
|
+
* For each package, check that all declared ranges are compatible
|
|
170
|
+
* (using semver.intersects). Return the narrowest range.
|
|
171
|
+
*/
|
|
172
|
+
function mergeNpmDependencies(assets: ResolvedAsset[]): Record<string, string> {
|
|
173
|
+
// Collect all ranges per package
|
|
174
|
+
const rangesPerPkg: Map<string, { range: string; from: string }[]> = new Map()
|
|
175
|
+
|
|
176
|
+
for (const asset of assets) {
|
|
177
|
+
for (const [pkg, range] of Object.entries(asset.npmDependencies)) {
|
|
178
|
+
const existing = rangesPerPkg.get(pkg) ?? []
|
|
179
|
+
existing.push({ range, from: `${asset.name}@${asset.version}` })
|
|
180
|
+
rangesPerPkg.set(pkg, existing)
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
const merged: Record<string, string> = {}
|
|
185
|
+
|
|
186
|
+
for (const [pkg, ranges] of rangesPerPkg) {
|
|
187
|
+
// Check pairwise compatibility
|
|
188
|
+
for (let i = 0; i < ranges.length; i++) {
|
|
189
|
+
for (let j = i + 1; j < ranges.length; j++) {
|
|
190
|
+
if (!semver.intersects(ranges[i].range, ranges[j].range)) {
|
|
191
|
+
throw new ResolutionError(
|
|
192
|
+
`npm dependency conflict for "${pkg}":\n` +
|
|
193
|
+
` ${ranges[i].range} (from ${ranges[i].from})\n` +
|
|
194
|
+
` ${ranges[j].range} (from ${ranges[j].from})`,
|
|
195
|
+
)
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// Use the narrowest (most constrained) range.
|
|
201
|
+
// Simple heuristic: pick the range with the highest minimum version.
|
|
202
|
+
let narrowest = ranges[0].range
|
|
203
|
+
for (const r of ranges.slice(1)) {
|
|
204
|
+
const minCurrent = semver.minVersion(narrowest)
|
|
205
|
+
const minNew = semver.minVersion(r.range)
|
|
206
|
+
if (minCurrent && minNew && semver.gt(minNew, minCurrent)) {
|
|
207
|
+
narrowest = r.range
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
merged[pkg] = narrowest
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
return merged
|
|
215
|
+
}
|
package/src/schemas.ts
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { z } from 'zod'
|
|
2
|
+
|
|
3
|
+
export const ASSET_TYPES = ['generic', 'model', 'hdri', 'material', 'music'] as const
|
|
4
|
+
export type AssetType = (typeof ASSET_TYPES)[number]
|
|
5
|
+
|
|
6
|
+
export const assetTypeSchema = z.enum(ASSET_TYPES)
|
|
7
|
+
|
|
8
|
+
export const semverSchema = z
|
|
9
|
+
.string()
|
|
10
|
+
.regex(
|
|
11
|
+
/^\d+\.\d+\.\d+(-[a-zA-Z0-9.]+)?(\+[a-zA-Z0-9.]+)?$/,
|
|
12
|
+
'Must be a valid semver version (e.g. 1.0.0)',
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
export const assetNameSchema = z
|
|
16
|
+
.string()
|
|
17
|
+
.min(1)
|
|
18
|
+
.max(128)
|
|
19
|
+
.regex(
|
|
20
|
+
/^[a-z0-9][a-z0-9-]*[a-z0-9]$/,
|
|
21
|
+
'Must be lowercase alphanumeric with hyphens, no leading/trailing hyphens',
|
|
22
|
+
)
|
|
23
|
+
.refine((name) => !name.endsWith('-example'), {
|
|
24
|
+
message: "Asset names cannot end with '-example' (reserved for generated examples)",
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
export const npmDependenciesSchema = z.record(z.string(), z.string()).default({})
|
|
28
|
+
|
|
29
|
+
export const assetDependenciesSchema = z.record(z.string(), z.string()).default({})
|
|
30
|
+
|
|
31
|
+
export const uploadGenericSchema = z.object({
|
|
32
|
+
name: assetNameSchema,
|
|
33
|
+
version: semverSchema,
|
|
34
|
+
description: z.string().max(1000).optional(),
|
|
35
|
+
npmDependencies: npmDependenciesSchema,
|
|
36
|
+
assetDependencies: assetDependenciesSchema,
|
|
37
|
+
tags: z.array(z.string()).default([]),
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
export const uploadTypedSchema = z.object({
|
|
41
|
+
name: assetNameSchema,
|
|
42
|
+
version: semverSchema,
|
|
43
|
+
description: z.string().max(1000).optional(),
|
|
44
|
+
tags: z.array(z.string()).default([]),
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
export const uploadMaterialSchema = uploadTypedSchema.extend({
|
|
48
|
+
properties: z.object({
|
|
49
|
+
color: z.string().default('#ffffff'),
|
|
50
|
+
roughness: z.number().min(0).max(1).default(0.5),
|
|
51
|
+
metalness: z.number().min(0).max(1).default(0),
|
|
52
|
+
normalScale: z.number().min(0).max(2).default(1),
|
|
53
|
+
emissive: z.string().default('#000000'),
|
|
54
|
+
emissiveIntensity: z.number().min(0).max(10).default(0),
|
|
55
|
+
}),
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
export const updateProfileSchema = z.object({
|
|
59
|
+
name: z.string().min(1).max(100).optional(),
|
|
60
|
+
image: z.string().url().optional(),
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
export const listAssetsSchema = z.object({
|
|
64
|
+
page: z.number().int().min(1).default(1),
|
|
65
|
+
limit: z.number().int().min(1).max(100).default(20),
|
|
66
|
+
type: assetTypeSchema.optional(),
|
|
67
|
+
tag: z.string().optional(),
|
|
68
|
+
search: z.string().max(200).optional(),
|
|
69
|
+
sort: z.enum(['newest', 'alphabetical', 'relevance']).default('newest'),
|
|
70
|
+
})
|