@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.
Files changed (47) hide show
  1. package/dist/cli.d.ts +3 -0
  2. package/dist/cli.d.ts.map +1 -0
  3. package/dist/cli.js +61 -0
  4. package/dist/cli.js.map +1 -0
  5. package/dist/client.d.ts +12 -0
  6. package/dist/client.d.ts.map +1 -0
  7. package/dist/client.js +26 -0
  8. package/dist/client.js.map +1 -0
  9. package/dist/constants.d.ts +5 -0
  10. package/dist/constants.d.ts.map +1 -0
  11. package/dist/constants.js +16 -0
  12. package/dist/constants.js.map +1 -0
  13. package/dist/contract.d.ts +198 -0
  14. package/dist/contract.d.ts.map +1 -0
  15. package/dist/contract.js +64 -0
  16. package/dist/contract.js.map +1 -0
  17. package/dist/index.d.ts +13 -0
  18. package/dist/index.d.ts.map +1 -0
  19. package/dist/index.js +12 -0
  20. package/dist/index.js.map +1 -0
  21. package/dist/install.d.ts +20 -0
  22. package/dist/install.d.ts.map +1 -0
  23. package/dist/install.js +67 -0
  24. package/dist/install.js.map +1 -0
  25. package/dist/internal-contract.d.ts +19 -0
  26. package/dist/internal-contract.d.ts.map +1 -0
  27. package/dist/internal-contract.js +19 -0
  28. package/dist/internal-contract.js.map +1 -0
  29. package/dist/resolve.d.ts +32 -0
  30. package/dist/resolve.d.ts.map +1 -0
  31. package/dist/resolve.js +145 -0
  32. package/dist/resolve.js.map +1 -0
  33. package/dist/schemas.d.ts +65 -0
  34. package/dist/schemas.d.ts.map +1 -0
  35. package/dist/schemas.js +53 -0
  36. package/dist/schemas.js.map +1 -0
  37. package/package.json +31 -0
  38. package/src/cli.ts +72 -0
  39. package/src/client.ts +38 -0
  40. package/src/constants.ts +19 -0
  41. package/src/contract.ts +188 -0
  42. package/src/index.ts +46 -0
  43. package/src/install.ts +101 -0
  44. package/src/internal-contract.ts +26 -0
  45. package/src/resolve.ts +215 -0
  46. package/src/schemas.ts +70 -0
  47. package/tsconfig.json +8 -0
@@ -0,0 +1,32 @@
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
+ import type { MarketClient } from './client.js';
11
+ export interface ResolvedAsset {
12
+ name: string;
13
+ version: string;
14
+ npmDependencies: Record<string, string>;
15
+ assetDependencies: Record<string, string>;
16
+ }
17
+ export interface ResolveResult {
18
+ assets: ResolvedAsset[];
19
+ npmDependencies: Record<string, string>;
20
+ }
21
+ interface AssetRequest {
22
+ name: string;
23
+ range: string;
24
+ }
25
+ export declare class ResolutionError extends Error {
26
+ constructor(message: string);
27
+ }
28
+ export declare function resolve(client: MarketClient['asset'], requests: AssetRequest[], opts?: {
29
+ includeUnapproved?: boolean;
30
+ }): Promise<ResolveResult>;
31
+ export {};
32
+ //# sourceMappingURL=resolve.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resolve.d.ts","sourceRoot":"","sources":["../src/resolve.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAGH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AAG/C,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,CAAA;IACZ,OAAO,EAAE,MAAM,CAAA;IACf,eAAe,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IACvC,iBAAiB,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CAC1C;AAED,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,aAAa,EAAE,CAAA;IACvB,eAAe,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CACxC;AAED,UAAU,YAAY;IACpB,IAAI,EAAE,MAAM,CAAA;IACZ,KAAK,EAAE,MAAM,CAAA;CACd;AAED,qBAAa,eAAgB,SAAQ,KAAK;gBAC5B,OAAO,EAAE,MAAM;CAI5B;AAED,wBAAsB,OAAO,CAC3B,MAAM,EAAE,YAAY,CAAC,OAAO,CAAC,EAC7B,QAAQ,EAAE,YAAY,EAAE,EACxB,IAAI,GAAE;IAAE,iBAAiB,CAAC,EAAE,OAAO,CAAA;CAAO,GACzC,OAAO,CAAC,aAAa,CAAC,CA6FxB"}
@@ -0,0 +1,145 @@
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
+ import * as semver from 'semver';
11
+ export class ResolutionError extends Error {
12
+ constructor(message) {
13
+ super(message);
14
+ this.name = 'ResolutionError';
15
+ }
16
+ }
17
+ export async function resolve(client, requests, opts = {}) {
18
+ // constraints[assetName] = list of { range, from } constraints
19
+ const constraints = new Map();
20
+ // resolved[assetName] = ResolvedAsset
21
+ const resolved = new Map();
22
+ // cache of fetched metadata
23
+ const metaCache = new Map();
24
+ // Seed constraints from the requested assets
25
+ for (const req of requests) {
26
+ addConstraint(constraints, req.name, req.range, '<root>');
27
+ }
28
+ // Iteratively resolve until stable
29
+ let unresolved = getUnresolved(constraints, resolved);
30
+ while (unresolved.length > 0) {
31
+ for (const assetName of unresolved) {
32
+ const meta = await fetchMeta(client, metaCache, assetName);
33
+ if (!meta) {
34
+ throw new ResolutionError(`Asset "${assetName}" not found.`);
35
+ }
36
+ // Filter to approved versions (unless --unapproved)
37
+ const candidates = meta.versions.filter((v) => opts.includeUnapproved || v.approved);
38
+ if (candidates.length === 0) {
39
+ throw new ResolutionError(`Asset "${assetName}" has no ${opts.includeUnapproved ? '' : 'approved '}versions.`);
40
+ }
41
+ // Collect all constraints for this asset
42
+ const assetConstraints = constraints.get(assetName) ?? [];
43
+ // Find the best (highest) version that satisfies ALL constraints
44
+ const candidateVersions = candidates.map((c) => c.version);
45
+ const satisfying = candidateVersions.filter((v) => assetConstraints.every((c) => semver.satisfies(v, c.range)));
46
+ if (satisfying.length === 0) {
47
+ const constraintDesc = assetConstraints
48
+ .map((c) => ` ${c.range} (from ${c.from})`)
49
+ .join('\n');
50
+ throw new ResolutionError(`No version of "${assetName}" satisfies all constraints:\n${constraintDesc}\n` +
51
+ `Available${opts.includeUnapproved ? '' : ' approved'}: ${candidateVersions.join(', ')}`);
52
+ }
53
+ const best = semver.maxSatisfying(satisfying, '*');
54
+ const versionMeta = candidates.find((c) => c.version === best);
55
+ const npmDeps = JSON.parse(versionMeta.npmDependencies);
56
+ const assetDeps = JSON.parse(versionMeta.assetDependencies);
57
+ resolved.set(assetName, {
58
+ name: assetName,
59
+ version: best,
60
+ npmDependencies: npmDeps,
61
+ assetDependencies: assetDeps,
62
+ });
63
+ // Add transitive asset dependency constraints
64
+ for (const [depName, depRange] of Object.entries(assetDeps)) {
65
+ addConstraint(constraints, depName, depRange, `${assetName}@${best}`);
66
+ }
67
+ }
68
+ unresolved = getUnresolved(constraints, resolved);
69
+ }
70
+ // Verify all resolved versions still satisfy constraints (transitive deps
71
+ // may have added new constraints after we resolved a version)
72
+ for (const [assetName, asset] of resolved) {
73
+ const assetConstraints = constraints.get(assetName) ?? [];
74
+ for (const c of assetConstraints) {
75
+ if (!semver.satisfies(asset.version, c.range)) {
76
+ throw new ResolutionError(`Conflict: "${assetName}@${asset.version}" does not satisfy ` +
77
+ `${c.range} (required by ${c.from}).`);
78
+ }
79
+ }
80
+ }
81
+ // Merge npm dependencies across all resolved assets
82
+ const mergedNpm = mergeNpmDependencies(Array.from(resolved.values()));
83
+ return {
84
+ assets: Array.from(resolved.values()),
85
+ npmDependencies: mergedNpm,
86
+ };
87
+ }
88
+ function addConstraint(constraints, name, range, from) {
89
+ const existing = constraints.get(name) ?? [];
90
+ existing.push({ range, from });
91
+ constraints.set(name, existing);
92
+ }
93
+ function getUnresolved(constraints, resolved) {
94
+ return Array.from(constraints.keys()).filter((name) => !resolved.has(name));
95
+ }
96
+ async function fetchMeta(client, cache, name) {
97
+ if (cache.has(name))
98
+ return cache.get(name);
99
+ const meta = await client.getByName({ name });
100
+ if (meta)
101
+ cache.set(name, meta);
102
+ return meta;
103
+ }
104
+ /**
105
+ * Merge npm dependency ranges from all resolved assets.
106
+ * For each package, check that all declared ranges are compatible
107
+ * (using semver.intersects). Return the narrowest range.
108
+ */
109
+ function mergeNpmDependencies(assets) {
110
+ // Collect all ranges per package
111
+ const rangesPerPkg = new Map();
112
+ for (const asset of assets) {
113
+ for (const [pkg, range] of Object.entries(asset.npmDependencies)) {
114
+ const existing = rangesPerPkg.get(pkg) ?? [];
115
+ existing.push({ range, from: `${asset.name}@${asset.version}` });
116
+ rangesPerPkg.set(pkg, existing);
117
+ }
118
+ }
119
+ const merged = {};
120
+ for (const [pkg, ranges] of rangesPerPkg) {
121
+ // Check pairwise compatibility
122
+ for (let i = 0; i < ranges.length; i++) {
123
+ for (let j = i + 1; j < ranges.length; j++) {
124
+ if (!semver.intersects(ranges[i].range, ranges[j].range)) {
125
+ throw new ResolutionError(`npm dependency conflict for "${pkg}":\n` +
126
+ ` ${ranges[i].range} (from ${ranges[i].from})\n` +
127
+ ` ${ranges[j].range} (from ${ranges[j].from})`);
128
+ }
129
+ }
130
+ }
131
+ // Use the narrowest (most constrained) range.
132
+ // Simple heuristic: pick the range with the highest minimum version.
133
+ let narrowest = ranges[0].range;
134
+ for (const r of ranges.slice(1)) {
135
+ const minCurrent = semver.minVersion(narrowest);
136
+ const minNew = semver.minVersion(r.range);
137
+ if (minCurrent && minNew && semver.gt(minNew, minCurrent)) {
138
+ narrowest = r.range;
139
+ }
140
+ }
141
+ merged[pkg] = narrowest;
142
+ }
143
+ return merged;
144
+ }
145
+ //# sourceMappingURL=resolve.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resolve.js","sourceRoot":"","sources":["../src/resolve.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,65 @@
1
+ import { z } from 'zod';
2
+ export declare const ASSET_TYPES: readonly ["generic", "model", "hdri", "material", "music"];
3
+ export type AssetType = (typeof ASSET_TYPES)[number];
4
+ export declare const assetTypeSchema: z.ZodEnum<{
5
+ generic: "generic";
6
+ model: "model";
7
+ hdri: "hdri";
8
+ material: "material";
9
+ music: "music";
10
+ }>;
11
+ export declare const semverSchema: z.ZodString;
12
+ export declare const assetNameSchema: z.ZodString;
13
+ export declare const npmDependenciesSchema: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodString>>;
14
+ export declare const assetDependenciesSchema: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodString>>;
15
+ export declare const uploadGenericSchema: z.ZodObject<{
16
+ name: z.ZodString;
17
+ version: z.ZodString;
18
+ description: z.ZodOptional<z.ZodString>;
19
+ npmDependencies: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodString>>;
20
+ assetDependencies: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodString>>;
21
+ tags: z.ZodDefault<z.ZodArray<z.ZodString>>;
22
+ }, z.core.$strip>;
23
+ export declare const uploadTypedSchema: z.ZodObject<{
24
+ name: z.ZodString;
25
+ version: z.ZodString;
26
+ description: z.ZodOptional<z.ZodString>;
27
+ tags: z.ZodDefault<z.ZodArray<z.ZodString>>;
28
+ }, z.core.$strip>;
29
+ export declare const uploadMaterialSchema: z.ZodObject<{
30
+ name: z.ZodString;
31
+ version: z.ZodString;
32
+ description: z.ZodOptional<z.ZodString>;
33
+ tags: z.ZodDefault<z.ZodArray<z.ZodString>>;
34
+ properties: z.ZodObject<{
35
+ color: z.ZodDefault<z.ZodString>;
36
+ roughness: z.ZodDefault<z.ZodNumber>;
37
+ metalness: z.ZodDefault<z.ZodNumber>;
38
+ normalScale: z.ZodDefault<z.ZodNumber>;
39
+ emissive: z.ZodDefault<z.ZodString>;
40
+ emissiveIntensity: z.ZodDefault<z.ZodNumber>;
41
+ }, z.core.$strip>;
42
+ }, z.core.$strip>;
43
+ export declare const updateProfileSchema: z.ZodObject<{
44
+ name: z.ZodOptional<z.ZodString>;
45
+ image: z.ZodOptional<z.ZodString>;
46
+ }, z.core.$strip>;
47
+ export declare const listAssetsSchema: z.ZodObject<{
48
+ page: z.ZodDefault<z.ZodNumber>;
49
+ limit: z.ZodDefault<z.ZodNumber>;
50
+ type: z.ZodOptional<z.ZodEnum<{
51
+ generic: "generic";
52
+ model: "model";
53
+ hdri: "hdri";
54
+ material: "material";
55
+ music: "music";
56
+ }>>;
57
+ tag: z.ZodOptional<z.ZodString>;
58
+ search: z.ZodOptional<z.ZodString>;
59
+ sort: z.ZodDefault<z.ZodEnum<{
60
+ newest: "newest";
61
+ alphabetical: "alphabetical";
62
+ relevance: "relevance";
63
+ }>>;
64
+ }, z.core.$strip>;
65
+ //# sourceMappingURL=schemas.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"schemas.d.ts","sourceRoot":"","sources":["../src/schemas.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAEvB,eAAO,MAAM,WAAW,4DAA6D,CAAA;AACrF,MAAM,MAAM,SAAS,GAAG,CAAC,OAAO,WAAW,CAAC,CAAC,MAAM,CAAC,CAAA;AAEpD,eAAO,MAAM,eAAe;;;;;;EAAsB,CAAA;AAElD,eAAO,MAAM,YAAY,aAKtB,CAAA;AAEH,eAAO,MAAM,eAAe,aAUxB,CAAA;AAEJ,eAAO,MAAM,qBAAqB,qDAA+C,CAAA;AAEjF,eAAO,MAAM,uBAAuB,qDAA+C,CAAA;AAEnF,eAAO,MAAM,mBAAmB;;;;;;;iBAO9B,CAAA;AAEF,eAAO,MAAM,iBAAiB;;;;;iBAK5B,CAAA;AAEF,eAAO,MAAM,oBAAoB;;;;;;;;;;;;;iBAS/B,CAAA;AAEF,eAAO,MAAM,mBAAmB;;;iBAG9B,CAAA;AAEF,eAAO,MAAM,gBAAgB;;;;;;;;;;;;;;;;;iBAO3B,CAAA"}
@@ -0,0 +1,53 @@
1
+ import { z } from 'zod';
2
+ export const ASSET_TYPES = ['generic', 'model', 'hdri', 'material', 'music'];
3
+ export const assetTypeSchema = z.enum(ASSET_TYPES);
4
+ export const semverSchema = z
5
+ .string()
6
+ .regex(/^\d+\.\d+\.\d+(-[a-zA-Z0-9.]+)?(\+[a-zA-Z0-9.]+)?$/, 'Must be a valid semver version (e.g. 1.0.0)');
7
+ export const assetNameSchema = z
8
+ .string()
9
+ .min(1)
10
+ .max(128)
11
+ .regex(/^[a-z0-9][a-z0-9-]*[a-z0-9]$/, 'Must be lowercase alphanumeric with hyphens, no leading/trailing hyphens')
12
+ .refine((name) => !name.endsWith('-example'), {
13
+ message: "Asset names cannot end with '-example' (reserved for generated examples)",
14
+ });
15
+ export const npmDependenciesSchema = z.record(z.string(), z.string()).default({});
16
+ export const assetDependenciesSchema = z.record(z.string(), z.string()).default({});
17
+ export const uploadGenericSchema = z.object({
18
+ name: assetNameSchema,
19
+ version: semverSchema,
20
+ description: z.string().max(1000).optional(),
21
+ npmDependencies: npmDependenciesSchema,
22
+ assetDependencies: assetDependenciesSchema,
23
+ tags: z.array(z.string()).default([]),
24
+ });
25
+ export const uploadTypedSchema = z.object({
26
+ name: assetNameSchema,
27
+ version: semverSchema,
28
+ description: z.string().max(1000).optional(),
29
+ tags: z.array(z.string()).default([]),
30
+ });
31
+ export const uploadMaterialSchema = uploadTypedSchema.extend({
32
+ properties: z.object({
33
+ color: z.string().default('#ffffff'),
34
+ roughness: z.number().min(0).max(1).default(0.5),
35
+ metalness: z.number().min(0).max(1).default(0),
36
+ normalScale: z.number().min(0).max(2).default(1),
37
+ emissive: z.string().default('#000000'),
38
+ emissiveIntensity: z.number().min(0).max(10).default(0),
39
+ }),
40
+ });
41
+ export const updateProfileSchema = z.object({
42
+ name: z.string().min(1).max(100).optional(),
43
+ image: z.string().url().optional(),
44
+ });
45
+ export const listAssetsSchema = z.object({
46
+ page: z.number().int().min(1).default(1),
47
+ limit: z.number().int().min(1).max(100).default(20),
48
+ type: assetTypeSchema.optional(),
49
+ tag: z.string().optional(),
50
+ search: z.string().max(200).optional(),
51
+ sort: z.enum(['newest', 'alphabetical', 'relevance']).default('newest'),
52
+ });
53
+ //# sourceMappingURL=schemas.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"schemas.js","sourceRoot":"","sources":["../src/schemas.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,31 @@
1
+ {
2
+ "name": "@drawcall/market",
3
+ "version": "0.1.0",
4
+ "type": "module",
5
+ "exports": {
6
+ ".": "./src/index.ts"
7
+ },
8
+ "bin": {
9
+ "market": "./dist/cli.js"
10
+ },
11
+ "scripts": {
12
+ "build": "tsc",
13
+ "dev": "tsx src/cli.ts",
14
+ "typecheck": "tsc --noEmit"
15
+ },
16
+ "dependencies": {
17
+ "@orpc/contract": "^1.0.0",
18
+ "@orpc/client": "^1.0.0",
19
+ "chalk": "^5.6.2",
20
+ "commander": "^14.0.3",
21
+ "nypm": "^0.6.0",
22
+ "semver": "^7.7.0",
23
+ "zod": "^4.3.6"
24
+ },
25
+ "devDependencies": {
26
+ "@types/node": "^25.6.0",
27
+ "@types/semver": "^7.5.0",
28
+ "tsx": "^4.19.0",
29
+ "typescript": "^5.7.0"
30
+ }
31
+ }
package/src/cli.ts ADDED
@@ -0,0 +1,72 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { Command } from 'commander'
4
+ import chalk from 'chalk'
5
+ import { createMarketClient } from './client.js'
6
+ import { resolve, ResolutionError, type ResolveResult } from './resolve.js'
7
+ import { install } from './install.js'
8
+
9
+ const program = new Command()
10
+
11
+ program.name('market').description('Install assets from the drawcall.ai market').version('0.1.0')
12
+
13
+ program
14
+ .command('install')
15
+ .description('Install assets into your project')
16
+ .argument('<assets...>', 'Assets to install (e.g. my-model, my-model@^2.0.0)')
17
+ .option('--unapproved', 'Include unapproved versions', false)
18
+ .option('--api <url>', 'API base URL', process.env.MARKET_API_URL ?? 'http://localhost:8787')
19
+ .option('--cwd <dir>', 'Project directory', process.cwd())
20
+ .action(async (assetArgs: string[], opts) => {
21
+ const client = createMarketClient({ baseUrl: opts.api })
22
+
23
+ // Parse asset[@range] arguments
24
+ const requests = assetArgs.map((arg) => {
25
+ const atIdx = arg.indexOf('@', 1)
26
+ if (atIdx > 0) {
27
+ return { name: arg.slice(0, atIdx), range: arg.slice(atIdx + 1) }
28
+ }
29
+ return { name: arg, range: '*' }
30
+ })
31
+
32
+ // Resolve
33
+ console.log(chalk.bold('Resolving dependencies...\n'))
34
+
35
+ let resolution: ResolveResult
36
+ try {
37
+ resolution = await resolve(client.asset, requests, {
38
+ includeUnapproved: opts.unapproved,
39
+ })
40
+ } catch (err) {
41
+ if (err instanceof ResolutionError) {
42
+ console.error(chalk.red('Resolution failed:\n') + err.message)
43
+ process.exit(1)
44
+ }
45
+ throw err
46
+ }
47
+
48
+ console.log(chalk.green(` ${resolution.assets.length} asset(s) resolved:\n`))
49
+ for (const asset of resolution.assets) {
50
+ console.log(` ${chalk.cyan(asset.name)}${chalk.dim('@' + asset.version)}`)
51
+ }
52
+
53
+ const npmCount = Object.keys(resolution.npmDependencies).length
54
+ if (npmCount > 0) {
55
+ console.log(chalk.green(`\n ${npmCount} npm dependenc${npmCount === 1 ? 'y' : 'ies'}:\n`))
56
+ for (const [pkg, range] of Object.entries(resolution.npmDependencies)) {
57
+ console.log(` ${pkg} ${chalk.dim(range)}`)
58
+ }
59
+ }
60
+
61
+ // Install
62
+ console.log(chalk.bold('\nInstalling...\n'))
63
+
64
+ await install(client, resolution, {
65
+ cwd: opts.cwd,
66
+ onProgress: (msg) => console.log(chalk.dim(` ${msg}`)),
67
+ })
68
+
69
+ console.log(chalk.bold.green('\nDone!'))
70
+ })
71
+
72
+ program.parse()
package/src/client.ts ADDED
@@ -0,0 +1,38 @@
1
+ import { createORPCClient } from '@orpc/client'
2
+ import { RPCLink } from '@orpc/client/fetch'
3
+ import type { ContractRouterClient } from '@orpc/contract'
4
+ import type { AppContract } from './contract.js'
5
+ import type { InternalContract } from './internal-contract.js'
6
+
7
+ export type MarketClient = ContractRouterClient<AppContract>
8
+ export type InternalClient = ContractRouterClient<InternalContract>
9
+
10
+ export interface MarketClientOptions {
11
+ baseUrl: string
12
+ fetch?: typeof globalThis.fetch
13
+ }
14
+
15
+ function resolveUrl(baseUrl: string, path: string): string {
16
+ if (baseUrl) return `${baseUrl}${path}`
17
+ // In browser environments, use the current origin for relative URLs
18
+ if (typeof globalThis !== 'undefined' && 'location' in globalThis) {
19
+ return `${(globalThis as unknown as { location: { origin: string } }).location.origin}${path}`
20
+ }
21
+ throw new Error('baseUrl is required in non-browser environments')
22
+ }
23
+
24
+ export function createMarketClient(opts: MarketClientOptions): MarketClient {
25
+ const link = new RPCLink({
26
+ url: resolveUrl(opts.baseUrl, '/api/rpc'),
27
+ fetch: opts.fetch,
28
+ })
29
+ return createORPCClient<MarketClient>(link)
30
+ }
31
+
32
+ export function createInternalClient(opts: MarketClientOptions): InternalClient {
33
+ const link = new RPCLink({
34
+ url: resolveUrl(opts.baseUrl, '/api/internal-rpc'),
35
+ fetch: opts.fetch,
36
+ })
37
+ return createORPCClient<InternalClient>(link)
38
+ }
@@ -0,0 +1,19 @@
1
+ import type { AssetType } from './schemas.js'
2
+
3
+ export const MAX_FILE_SIZE = 100 * 1024 * 1024 // 100MB
4
+
5
+ export const ALLOWED_EXTENSIONS: Record<AssetType, string[]> = {
6
+ generic: ['.zip'],
7
+ model: ['.gltf', '.glb'],
8
+ hdri: ['.hdr', '.exr'],
9
+ material: [], // material is submitted as JSON, no file upload
10
+ music: ['.mp3', '.wav', '.ogg', '.flac'],
11
+ }
12
+
13
+ export const ASSET_TYPE_LABELS: Record<AssetType, string> = {
14
+ generic: 'Generic',
15
+ model: '3D Model',
16
+ hdri: 'HDRI',
17
+ material: 'Material',
18
+ music: 'Audio',
19
+ }
@@ -0,0 +1,188 @@
1
+ import { oc } from '@orpc/contract'
2
+ import { z } from 'zod'
3
+ import {
4
+ assetNameSchema,
5
+ semverSchema,
6
+ listAssetsSchema,
7
+ updateProfileSchema,
8
+ uploadGenericSchema,
9
+ uploadTypedSchema,
10
+ uploadMaterialSchema,
11
+ } from './schemas.js'
12
+
13
+ // ─── Output types ─────────────────────────────────────────────────────────────
14
+ // These mirror what the server handlers return (Drizzle query results).
15
+ // We use z.custom<T>() so the contract carries full types for the client
16
+ // without duplicating every DB column as a Zod field.
17
+
18
+ export interface AssetVersion {
19
+ id: number
20
+ assetId: number
21
+ version: string
22
+ approved: boolean
23
+ npmDependencies: string
24
+ assetDependencies: string
25
+ sourceKey: string
26
+ buildOutputKey: string | null
27
+ thumbnailKey: string | null
28
+ buildError: string | null
29
+ readme: string | null
30
+ createdAt: Date
31
+ }
32
+
33
+ export interface Asset {
34
+ id: number
35
+ name: string
36
+ type: string
37
+ description: string | null
38
+ ownerId: string
39
+ createdAt: Date
40
+ updatedAt: Date
41
+ }
42
+
43
+ export interface AssetWithVersionsAndTags extends Asset {
44
+ versions: AssetVersion[]
45
+ tags: string[]
46
+ }
47
+
48
+ export interface AssetListItem {
49
+ id: number
50
+ name: string
51
+ type: string
52
+ description: string | null
53
+ ownerId: string
54
+ createdAt: Date
55
+ updatedAt: Date
56
+ latestVersion: string
57
+ thumbnailKey: string | null
58
+ approved: boolean
59
+ }
60
+
61
+ export interface PaginatedList<T> {
62
+ items: T[]
63
+ total: number
64
+ page: number
65
+ limit: number
66
+ totalPages: number
67
+ }
68
+
69
+ export interface User {
70
+ id: string
71
+ name: string
72
+ email: string
73
+ emailVerified: boolean
74
+ image: string | null
75
+ role: string
76
+ createdAt: Date
77
+ updatedAt: Date
78
+ }
79
+
80
+ export interface AssetWithVersions extends Asset {
81
+ versions: AssetVersion[]
82
+ }
83
+
84
+ export interface UnapprovedItem {
85
+ versionId: number
86
+ assetId: number
87
+ assetName: string
88
+ assetType: string
89
+ version: string
90
+ buildError: string | null
91
+ buildOutputKey: string | null
92
+ thumbnailKey: string | null
93
+ createdAt: Date
94
+ ownerName: string
95
+ ownerEmail: string
96
+ }
97
+
98
+ export interface TagWithCount {
99
+ id: number
100
+ name: string
101
+ count: number
102
+ }
103
+
104
+ export interface FileTreeEntry {
105
+ path: string
106
+ size: number
107
+ }
108
+
109
+ // ─── Contract ─────────────────────────────────────────────────────────────────
110
+
111
+ export const contract = {
112
+ asset: {
113
+ getByName: oc
114
+ .input(z.object({ name: assetNameSchema }))
115
+ .output(z.custom<AssetWithVersionsAndTags | null>()),
116
+
117
+ list: oc
118
+ .input(listAssetsSchema)
119
+ .output(z.custom<PaginatedList<AssetListItem>>()),
120
+
121
+ getVersionTree: oc
122
+ .input(z.object({ name: z.string(), version: semverSchema }))
123
+ .output(z.custom<FileTreeEntry[]>()),
124
+
125
+ getRawFile: oc
126
+ .input(z.object({ name: z.string(), version: semverSchema, path: z.string() }))
127
+ .output(z.instanceof(Blob)),
128
+ },
129
+
130
+ upload: {
131
+ generic: oc
132
+ .input(uploadGenericSchema.extend({ file: z.instanceof(File) }))
133
+ .output(z.custom<AssetVersion>()),
134
+
135
+ model: oc
136
+ .input(uploadTypedSchema.extend({ file: z.instanceof(File) }))
137
+ .output(z.custom<AssetVersion>()),
138
+
139
+ hdri: oc
140
+ .input(uploadTypedSchema.extend({ file: z.instanceof(File) }))
141
+ .output(z.custom<AssetVersion>()),
142
+
143
+ music: oc
144
+ .input(uploadTypedSchema.extend({ file: z.instanceof(File) }))
145
+ .output(z.custom<AssetVersion>()),
146
+
147
+ material: oc
148
+ .input(uploadMaterialSchema)
149
+ .output(z.custom<AssetVersion>()),
150
+ },
151
+
152
+ admin: {
153
+ listUnapproved: oc
154
+ .output(z.custom<UnapprovedItem[]>()),
155
+
156
+ approve: oc
157
+ .input(z.object({ assetName: z.string(), version: z.string() }))
158
+ .output(z.object({ success: z.boolean() })),
159
+
160
+ backfillEmbeddings: oc
161
+ .output(z.object({ indexed: z.number() })),
162
+ },
163
+
164
+ user: {
165
+ getProfile: oc
166
+ .output(z.custom<User | null>()),
167
+
168
+ updateProfile: oc
169
+ .input(updateProfileSchema)
170
+ .output(z.custom<User>()),
171
+
172
+ getApiKey: oc
173
+ .output(z.custom<{ prefix: string; createdAt: Date } | null>()),
174
+
175
+ regenerateApiKey: oc
176
+ .output(z.object({ key: z.string(), prefix: z.string() })),
177
+
178
+ myAssets: oc
179
+ .output(z.custom<AssetWithVersions[]>()),
180
+ },
181
+
182
+ tag: {
183
+ list: oc
184
+ .output(z.custom<TagWithCount[]>()),
185
+ },
186
+ }
187
+
188
+ export type AppContract = typeof contract