@drawcall/market 0.6.14 → 0.7.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/src/resolve.ts CHANGED
@@ -11,7 +11,9 @@
11
11
 
12
12
  import * as semver from 'semver'
13
13
  import type { MarketClient } from './client.js'
14
- import type { AssetExactResult } from './contract.js'
14
+ import type { AssetFileEntry } from './contract.js'
15
+ import { lookupAssets } from './refs.js'
16
+ import type { AssetRefEntry } from './v1/index.js'
15
17
  import {
16
18
  assetDependencyAlias,
17
19
  type AssetDependencyAlias,
@@ -28,6 +30,8 @@ export interface ResolvedAsset {
28
30
  npmDependencies: Record<string, string>
29
31
  assetDependencies: AssetDependencies
30
32
  skillDependencies: Record<string, string>
33
+ /** The resolved version's file index, carried from resolution so install needs no further reads. */
34
+ files: AssetFileEntry[]
31
35
  }
32
36
 
33
37
  export interface ResolveResult {
@@ -74,7 +78,7 @@ export async function resolve(
74
78
  requests: AssetRequest[],
75
79
  opts: { includeUnapproved?: boolean; key?: string } = {},
76
80
  ): Promise<ResolveResult> {
77
- const metaCache = new Map<string, AssetExactResult>()
81
+ const metaCache = new Map<string, AssetRefEntry>()
78
82
  const includeUnapproved = opts.includeUnapproved ?? false
79
83
  let exactPins = exactPinsFor(rootConstraints(requests))
80
84
  const seenPinSets = new Set<string>()
@@ -122,7 +126,7 @@ async function resolvePass(
122
126
  requests: AssetRequest[],
123
127
  initialPins: ExactPins,
124
128
  includeUnapproved: boolean,
125
- metaCache: Map<string, AssetExactResult>,
129
+ metaCache: Map<string, AssetRefEntry>,
126
130
  key?: string,
127
131
  ): Promise<ResolutionPass> {
128
132
  const constraints = rootConstraints(requests)
@@ -138,19 +142,34 @@ async function resolvePass(
138
142
  const names = [...new Set(wave)].filter((name) => !resolved.has(name))
139
143
  wave = []
140
144
 
141
- const metas = await Promise.all(
142
- names.map(async (assetName) => {
143
- const constrainedExact = requiredExactVersion(assetName, constraints.get(assetName) ?? [])
144
- const exactVersion = constrainedExact ?? initialPins.get(assetName) ?? null
145
- return {
146
- assetName,
147
- exactVersion,
148
- meta: await fetchMeta(client, metaCache, assetName, exactVersion, includeUnapproved, key),
149
- }
150
- }),
151
- )
145
+ const wanted = names.map((assetName) => {
146
+ const constrainedExact = requiredExactVersion(assetName, constraints.get(assetName) ?? [])
147
+ const exactVersion = constrainedExact ?? initialPins.get(assetName) ?? null
148
+ return { assetName, exactVersion }
149
+ })
152
150
 
153
- for (const { assetName, exactVersion, meta } of metas) {
151
+ // One collection read per wave; the cache spans convergence passes, so a
152
+ // re-pass that pins already-seen versions makes no further requests.
153
+ const misses = wanted.filter(
154
+ ({ assetName, exactVersion }) => !metaCache.has(metaKey(assetName, exactVersion)),
155
+ )
156
+ const fetched = await lookupAssets(
157
+ client,
158
+ misses.map(({ assetName, exactVersion }) => ({
159
+ name: assetName,
160
+ version: exactVersion ?? undefined,
161
+ })),
162
+ { includeUnapproved, key },
163
+ )
164
+ misses.forEach(({ assetName, exactVersion }, index) => {
165
+ const entry = fetched[index]
166
+ if (!entry) return
167
+ metaCache.set(metaKey(assetName, exactVersion), entry)
168
+ metaCache.set(metaKey(assetName, entry.version), entry)
169
+ })
170
+
171
+ for (const { assetName, exactVersion } of wanted) {
172
+ const meta = metaCache.get(metaKey(assetName, exactVersion)) ?? null
154
173
  if (!meta) {
155
174
  if (exactVersion) {
156
175
  throw new ResolutionError(
@@ -166,12 +185,13 @@ async function resolvePass(
166
185
  throw new ResolutionError(`Asset "${assetName}" has no approved versions.`)
167
186
  }
168
187
 
169
- const selectedVersion = exactVersion
170
- ? exactMetadataVersion(meta, assetName, exactVersion)
171
- : latestMetadataVersion(meta)
172
- // The meta demonstrably answers for this version; caching it under the exact key makes the
173
- // convergence re-pass (which pins every asset) resolve without a single further request.
174
- metaCache.set(`${assetName}@${selectedVersion}`, meta)
188
+ if (exactVersion && meta.version !== exactVersion) {
189
+ throw new ResolutionError(
190
+ `Market API did not return requested version "${assetName}@${exactVersion}". ` +
191
+ 'Refusing to fall back to a different version.',
192
+ )
193
+ }
194
+ const selectedVersion = meta.version
175
195
  const assetDependencies = meta.assetDependencies
176
196
  resolved.set(assetName, {
177
197
  name: assetName,
@@ -181,6 +201,7 @@ async function resolvePass(
181
201
  npmDependencies: meta.npmDependencies,
182
202
  assetDependencies,
183
203
  skillDependencies: meta.skillDependencies,
204
+ files: meta.files,
184
205
  })
185
206
 
186
207
  for (const [dependencyName, dependencyValue] of Object.entries(assetDependencies)) {
@@ -293,28 +314,8 @@ function pinSignature(pins: ExactPins): string {
293
314
  return JSON.stringify([...pins].sort(([left], [right]) => left.localeCompare(right)))
294
315
  }
295
316
 
296
- async function fetchMeta(
297
- client: MarketClient['asset'],
298
- cache: Map<string, AssetExactResult>,
299
- name: string,
300
- version: string | null,
301
- includeUnapproved: boolean,
302
- key?: string,
303
- ): Promise<AssetExactResult | null> {
304
- const cacheKey = version ? `${name}@${version}` : name
305
- const cached = cache.get(cacheKey)
306
- if (cached) return cached
307
- // The key is sent with every lookup in the tree; it only unlocks the asset it belongs to and is
308
- // inert for the rest, so no per-asset routing is needed.
309
- const meta = await client.exact({
310
- name,
311
- ...(version ? { version } : {}),
312
- includeUnapproved,
313
- ...(key ? { key } : {}),
314
- })
315
- if (meta) cache.set(cacheKey, meta)
316
- return meta
317
- }
317
+ const metaKey = (name: string, version: string | null): string =>
318
+ version ? `${name}@${version}` : name
318
319
 
319
320
  function requiredExactVersion(assetName: string, constraints: Constraint[]): string | null {
320
321
  const exactVersions = new Set(
@@ -335,24 +336,6 @@ function exactVersion(range: string): string | null {
335
336
  return semverSchema.safeParse(candidate).success ? candidate : null
336
337
  }
337
338
 
338
- function exactMetadataVersion(
339
- meta: AssetExactResult,
340
- assetName: string,
341
- requestedVersion: string,
342
- ): string {
343
- if (meta.version === requestedVersion) return meta.version
344
- throw new ResolutionError(
345
- `Market API did not return requested version "${assetName}@${requestedVersion}". ` +
346
- 'Refusing to fall back to a different version.',
347
- )
348
- }
349
-
350
- function latestMetadataVersion(meta: AssetExactResult): string {
351
- // Older workers do not return `version`. Range requests can retain their established latest-
352
- // version behavior; exact requests instead fail closed in exactMetadataVersion above.
353
- return meta.version ?? meta.latestVersion
354
- }
355
-
356
339
  /**
357
340
  * Merge npm dependency ranges from all resolved assets.
358
341
  * For each package, check that all declared ranges are compatible
@@ -38,6 +38,7 @@ export interface AssetVersionManifest {
38
38
  name: string
39
39
  type: string
40
40
  version: string
41
+ ownerId: string
41
42
  description: string | null
42
43
  approved: boolean
43
44
  access: 'public' | 'private'