@pathmx/core 0.5.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/LICENSE.md +172 -0
- package/README.md +111 -0
- package/actions/actions.ts +307 -0
- package/actions/draft.ts +105 -0
- package/actions/transaction.ts +164 -0
- package/assets.ts +149 -0
- package/authority/access.ts +820 -0
- package/authority/actors.ts +271 -0
- package/authority/agents.ts +95 -0
- package/authority/credentials.ts +144 -0
- package/authority/model.ts +71 -0
- package/authority/sharing.ts +89 -0
- package/browser-bundle.ts +61 -0
- package/canonical.ts +41 -0
- package/cli/dev.ts +134 -0
- package/cli/export.ts +74 -0
- package/cli/index.ts +52 -0
- package/cli/lint.ts +164 -0
- package/cli/mv.ts +30 -0
- package/cli/perms.ts +182 -0
- package/cli/plugin-packages.ts +159 -0
- package/cli/plugins.ts +95 -0
- package/cli/query.ts +225 -0
- package/cli/readme.md +95 -0
- package/cli/release.ts +75 -0
- package/cli/rm.ts +22 -0
- package/cli/serve.ts +100 -0
- package/cli/tree.ts +131 -0
- package/cli/update.ts +78 -0
- package/cli/utils.ts +53 -0
- package/core.ts +96 -0
- package/database.ts +189 -0
- package/dependencies.ts +60 -0
- package/dist/pathmx.js +1224 -0
- package/document.ts +194 -0
- package/environment.ts +110 -0
- package/environments/bun-plugins.ts +68 -0
- package/environments/bun-publish.ts +194 -0
- package/environments/bun.ts +223 -0
- package/globals.d.ts +14 -0
- package/graph.ts +153 -0
- package/host/compiler.ts +314 -0
- package/host/create.ts +66 -0
- package/host/engine.ts +630 -0
- package/host/plugin-host.ts +487 -0
- package/host/render.ts +102 -0
- package/host/response.ts +20 -0
- package/host/shell.html +15 -0
- package/host/source-sync.ts +254 -0
- package/html-attributes.ts +15 -0
- package/html.ts +48 -0
- package/icons/LICENSE +43 -0
- package/icons/lucide.ts +54 -0
- package/index.ts +75 -0
- package/invalidation.ts +85 -0
- package/log.ts +84 -0
- package/ops/href.ts +87 -0
- package/ops/index.ts +9 -0
- package/ops/move-source.ts +114 -0
- package/ops/plan.ts +47 -0
- package/ops/remove-source.ts +70 -0
- package/package.json +46 -0
- package/path.ts +75 -0
- package/pattern.ts +314 -0
- package/perf.ts +88 -0
- package/plugins/actor-path.ts +43 -0
- package/plugins/actor-profile.ts +55 -0
- package/plugins/agent-browsing.ts +183 -0
- package/plugins/app-component.ts +23 -0
- package/plugins/block-layout/index.ts +78 -0
- package/plugins/block-layout/theme.css +90 -0
- package/plugins/callout/index.ts +236 -0
- package/plugins/callout/theme.css +138 -0
- package/plugins/canonical-links.ts +144 -0
- package/plugins/code-highlight/client.ts +89 -0
- package/plugins/code-highlight/index.ts +147 -0
- package/plugins/code-highlight/theme.css +89 -0
- package/plugins/context.ts +131 -0
- package/plugins/datatype/Datatype[wdth,wght].woff2 +0 -0
- package/plugins/datatype/OFL.txt +96 -0
- package/plugins/datatype/index.ts +287 -0
- package/plugins/definition-list.ts +236 -0
- package/plugins/directive-definitions.ts +13 -0
- package/plugins/embedding/client.ts +88 -0
- package/plugins/embedding/index.ts +43 -0
- package/plugins/image/index.ts +68 -0
- package/plugins/image/theme.css +43 -0
- package/plugins/include.ts +119 -0
- package/plugins/index.ts +91 -0
- package/plugins/input/authoring.ts +236 -0
- package/plugins/input/config.ts +85 -0
- package/plugins/input/index.ts +359 -0
- package/plugins/input/model.ts +173 -0
- package/plugins/input/state.ts +272 -0
- package/plugins/layout.ts +313 -0
- package/plugins/literate/client.ts +208 -0
- package/plugins/literate/index.ts +742 -0
- package/plugins/math/index.ts +140 -0
- package/plugins/meta.ts +42 -0
- package/plugins/navigation/index.ts +158 -0
- package/plugins/navigation/model.ts +364 -0
- package/plugins/preset.ts +65 -0
- package/plugins/query/index.ts +322 -0
- package/plugins/rss/index.ts +46 -0
- package/plugins/runtime/actions.ts +62 -0
- package/plugins/runtime/client.ts +8 -0
- package/plugins/runtime/document.ts +506 -0
- package/plugins/runtime/error.ts +154 -0
- package/plugins/runtime/heading-links.ts +31 -0
- package/plugins/runtime/index.ts +104 -0
- package/plugins/runtime/live.ts +51 -0
- package/plugins/runtime/navigation.ts +113 -0
- package/plugins/runtime/pending.ts +53 -0
- package/plugins/runtime/publication.ts +19 -0
- package/plugins/runtime/start.ts +177 -0
- package/plugins/runtime/static-client.ts +6 -0
- package/plugins/runtime/static-live.ts +188 -0
- package/plugins/runtime/view-transition.ts +72 -0
- package/plugins/runtime/viewport.ts +110 -0
- package/plugins/schema.ts +24 -0
- package/plugins/scripts.ts +140 -0
- package/plugins/source-preview/client.ts +256 -0
- package/plugins/source-preview/index.ts +132 -0
- package/plugins/source-preview/model.ts +11 -0
- package/plugins/source-preview/preview.css +107 -0
- package/plugins/styles.ts +145 -0
- package/plugins/theme/components.ts +10 -0
- package/plugins/theme/foundation.css +358 -0
- package/plugins/theme/index.ts +333 -0
- package/plugins/theme/prose.css +284 -0
- package/plugins/theme/scheme.js +63 -0
- package/plugins/theme/toggle.css +44 -0
- package/plugins/theme/view-transition.css +68 -0
- package/plugins/toc/client.ts +146 -0
- package/plugins/toc/index.ts +232 -0
- package/plugins/toc/theme.css +172 -0
- package/plugins/types.ts +538 -0
- package/problem.ts +41 -0
- package/project.ts +6 -0
- package/publish/build.ts +402 -0
- package/publish/concurrent.ts +32 -0
- package/publish/identity.ts +63 -0
- package/publish/index.ts +11 -0
- package/publish/location.ts +167 -0
- package/publish/model.ts +58 -0
- package/publish/plan.ts +300 -0
- package/publish/validate.ts +150 -0
- package/query/candidate-index.ts +101 -0
- package/query/dependency-index.ts +99 -0
- package/query/fields.ts +216 -0
- package/query/invalidation.ts +48 -0
- package/query/kernel.ts +776 -0
- package/query/model.ts +79 -0
- package/repository.ts +374 -0
- package/runtime-profile.ts +34 -0
- package/schema/model.ts +92 -0
- package/schema/registry.ts +642 -0
- package/schema/standard.ts +226 -0
- package/server/context.ts +94 -0
- package/server/css-imports.ts +66 -0
- package/server/http.ts +333 -0
- package/server/page.ts +21 -0
- package/server/protocols.ts +12 -0
- package/server/representations.ts +66 -0
- package/server/router.ts +131 -0
- package/server/routes.ts +202 -0
- package/server/serve.ts +5 -0
- package/server/watch.ts +120 -0
- package/source/blocks.ts +71 -0
- package/source/format.ts +164 -0
- package/source/index.ts +67 -0
- package/source/interpolate.ts +63 -0
- package/source/markdown/blocks.ts +46 -0
- package/source/markdown/comments.ts +32 -0
- package/source/markdown/fences.ts +67 -0
- package/source/markdown/headings.ts +88 -0
- package/source/markdown/index.ts +34 -0
- package/source/markdown/inline-code.ts +6 -0
- package/source/markdown/links.ts +326 -0
- package/source/markdown/lists.ts +223 -0
- package/source/markdown/prose.ts +74 -0
- package/source/markdown/scan.ts +79 -0
- package/source/markdown/text.ts +82 -0
- package/source/meta.ts +87 -0
- package/source/source.ts +121 -0
- package/source/topmatter.ts +57 -0
- package/source/utils.ts +21 -0
- package/source-diagnostic.ts +63 -0
- package/text-file.ts +26 -0
- package/util.ts +35 -0
- package/view/components.ts +499 -0
- package/view/model.ts +91 -0
- package/view/projection.ts +162 -0
- package/view/session.ts +146 -0
- package/view/syntax.ts +372 -0
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
import type { ProposedSourceChange } from "../authority/access.ts"
|
|
2
|
+
import { AccessDeniedError } from "../authority/access.ts"
|
|
3
|
+
import type { Engine } from "../host/engine.ts"
|
|
4
|
+
import type { SourceChange, SourceChangeSet } from "../repository.ts"
|
|
5
|
+
import { createSource } from "../source/index.ts"
|
|
6
|
+
import type { Actor } from "../authority/model.ts"
|
|
7
|
+
import { canonicalSourceId } from "../canonical.ts"
|
|
8
|
+
import { createSourceDraft, type SourceDraft } from "./draft.ts"
|
|
9
|
+
|
|
10
|
+
export type SourceTransaction = {
|
|
11
|
+
create(path: string, content: string): Promise<void>
|
|
12
|
+
update(path: string, content: string): Promise<void>
|
|
13
|
+
delete(path: string): Promise<void>
|
|
14
|
+
read(path: string): Promise<string>
|
|
15
|
+
exists(path: string): Promise<boolean>
|
|
16
|
+
source(path: string): SourceDraft
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export type StagedTransaction = SourceTransaction & {
|
|
20
|
+
changeSet(): Promise<SourceChangeSet>
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
type TransactionAccess = Readonly<{
|
|
24
|
+
readable(path: string): boolean
|
|
25
|
+
authorize(change: ProposedSourceChange): void
|
|
26
|
+
}>
|
|
27
|
+
|
|
28
|
+
function createStagedTransaction(
|
|
29
|
+
engine: Engine,
|
|
30
|
+
access: TransactionAccess,
|
|
31
|
+
): StagedTransaction {
|
|
32
|
+
const repo = engine.repo
|
|
33
|
+
const overlay = new Map<string, string | null>()
|
|
34
|
+
const drafts = new Map<string, ReturnType<typeof createSourceDraft>>()
|
|
35
|
+
|
|
36
|
+
const keyFor = (sourceId: string) => {
|
|
37
|
+
const normalized = canonicalSourceId(sourceId)
|
|
38
|
+
return repo.get(normalized)?.path ?? `${normalized}.md`
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const existsKey = async (key: string) => {
|
|
42
|
+
if (!access.readable(key)) return false
|
|
43
|
+
if (overlay.has(key)) return overlay.get(key) !== null
|
|
44
|
+
return Boolean(repo.atPath(key) ?? repo.get(repo.pathToId(key)))
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const readKey = async (key: string) => {
|
|
48
|
+
if (!access.readable(key)) throw new AccessDeniedError()
|
|
49
|
+
if (overlay.has(key)) {
|
|
50
|
+
const pending = overlay.get(key)
|
|
51
|
+
if (pending == null) throw new Error(`Path does not exist: ${key}`)
|
|
52
|
+
return pending
|
|
53
|
+
}
|
|
54
|
+
const source = repo.atPath(key) ?? repo.get(repo.pathToId(key))
|
|
55
|
+
if (source) return source.content
|
|
56
|
+
throw new Error(`Path does not exist: ${key}`)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const createKey = async (key: string, content: string) => {
|
|
60
|
+
if (await existsKey(key)) throw new Error(`Path already exists: ${key}`)
|
|
61
|
+
overlay.set(key, content)
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const updateKey = async (key: string, content: string) => {
|
|
65
|
+
if (!access.readable(key)) throw new AccessDeniedError()
|
|
66
|
+
if (!(await existsKey(key))) throw new Error(`Path does not exist: ${key}`)
|
|
67
|
+
overlay.set(key, content)
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const tx: StagedTransaction = {
|
|
71
|
+
create(sourceId, content) {
|
|
72
|
+
return createKey(keyFor(sourceId), content)
|
|
73
|
+
},
|
|
74
|
+
update(sourceId, content) {
|
|
75
|
+
return updateKey(keyFor(sourceId), content)
|
|
76
|
+
},
|
|
77
|
+
async delete(path) {
|
|
78
|
+
const key = keyFor(path)
|
|
79
|
+
if (!access.readable(key)) throw new AccessDeniedError()
|
|
80
|
+
if (!(await existsKey(key)))
|
|
81
|
+
throw new Error(`Path does not exist: ${key}`)
|
|
82
|
+
overlay.set(key, null)
|
|
83
|
+
},
|
|
84
|
+
read(sourceId) {
|
|
85
|
+
return readKey(keyFor(sourceId))
|
|
86
|
+
},
|
|
87
|
+
exists(sourceId) {
|
|
88
|
+
return existsKey(keyFor(sourceId))
|
|
89
|
+
},
|
|
90
|
+
source(path) {
|
|
91
|
+
const key = keyFor(path)
|
|
92
|
+
let draft = drafts.get(key)
|
|
93
|
+
if (!draft) {
|
|
94
|
+
draft = createSourceDraft(
|
|
95
|
+
key,
|
|
96
|
+
{ create: createKey, update: updateKey, read: readKey },
|
|
97
|
+
engine.environment.yaml,
|
|
98
|
+
)
|
|
99
|
+
drafts.set(key, draft)
|
|
100
|
+
}
|
|
101
|
+
return draft
|
|
102
|
+
},
|
|
103
|
+
async changeSet() {
|
|
104
|
+
for (const draft of drafts.values()) await draft.flush()
|
|
105
|
+
const changes: SourceChange[] = []
|
|
106
|
+
for (const [path, content] of overlay) {
|
|
107
|
+
const before = repo.atPath(path)
|
|
108
|
+
const sourceId = before?.id ?? repo.pathToId(path)
|
|
109
|
+
const proposed =
|
|
110
|
+
content == null
|
|
111
|
+
? before
|
|
112
|
+
? { type: "delete" as const, sourceId, before }
|
|
113
|
+
: undefined
|
|
114
|
+
: (() => {
|
|
115
|
+
const after = createSource(
|
|
116
|
+
path,
|
|
117
|
+
content,
|
|
118
|
+
engine.environment.yaml,
|
|
119
|
+
)
|
|
120
|
+
if (!before)
|
|
121
|
+
return { type: "create" as const, sourceId: after.id, after }
|
|
122
|
+
return {
|
|
123
|
+
type: "update" as const,
|
|
124
|
+
operation: "content" as const,
|
|
125
|
+
sourceId: before.id,
|
|
126
|
+
before,
|
|
127
|
+
after,
|
|
128
|
+
}
|
|
129
|
+
})()
|
|
130
|
+
if (!proposed) throw new Error(`Path does not exist: ${sourceId}`)
|
|
131
|
+
access.authorize(proposed)
|
|
132
|
+
if (content == null) changes.push({ type: "delete", path })
|
|
133
|
+
else
|
|
134
|
+
changes.push({ type: before ? "replace" : "create", path, content })
|
|
135
|
+
}
|
|
136
|
+
overlay.clear()
|
|
137
|
+
drafts.clear()
|
|
138
|
+
return { changes }
|
|
139
|
+
},
|
|
140
|
+
}
|
|
141
|
+
return tx
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
export function createTransaction(
|
|
145
|
+
engine: Engine,
|
|
146
|
+
actor: Actor,
|
|
147
|
+
): StagedTransaction {
|
|
148
|
+
return createStagedTransaction(engine, {
|
|
149
|
+
readable(path) {
|
|
150
|
+
return (
|
|
151
|
+
engine.readAccess(
|
|
152
|
+
{ type: "actor", actor },
|
|
153
|
+
engine.repo.atPath(path) ?? engine.repo.pathToId(path),
|
|
154
|
+
) !== "denied"
|
|
155
|
+
)
|
|
156
|
+
},
|
|
157
|
+
authorize(change) {
|
|
158
|
+
const decision = engine.authorizeChange(actor, change)
|
|
159
|
+
if (decision.type === "denied") {
|
|
160
|
+
throw new AccessDeniedError(decision.code)
|
|
161
|
+
}
|
|
162
|
+
},
|
|
163
|
+
})
|
|
164
|
+
}
|
package/assets.ts
ADDED
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
const ASSET_NAME = /^[a-zA-Z0-9][a-zA-Z0-9._-]*$/
|
|
2
|
+
export const GENERATED_ASSET_LIMIT = 128
|
|
3
|
+
const encoder = new TextEncoder()
|
|
4
|
+
|
|
5
|
+
export type ServedAsset = Readonly<{
|
|
6
|
+
body: string | Blob
|
|
7
|
+
contentType: string
|
|
8
|
+
}>
|
|
9
|
+
|
|
10
|
+
type StoredAsset = {
|
|
11
|
+
name: string
|
|
12
|
+
asset: ServedAsset
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function avalanche(value: number) {
|
|
16
|
+
value ^= value >>> 16
|
|
17
|
+
value = Math.imul(value, 0x85ebca6b)
|
|
18
|
+
value ^= value >>> 13
|
|
19
|
+
value = Math.imul(value, 0xc2b2ae35)
|
|
20
|
+
return (value ^ (value >>> 16)) >>> 0
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** Fast streaming 64-bit fingerprint; cache identity, not authentication. */
|
|
24
|
+
class AssetFingerprint {
|
|
25
|
+
private fnv = 0x811c9dc5
|
|
26
|
+
private djb = 0x1505
|
|
27
|
+
|
|
28
|
+
update(bytes: Uint8Array) {
|
|
29
|
+
for (let index = 0; index < bytes.length; index++) {
|
|
30
|
+
const byte = bytes[index]!
|
|
31
|
+
this.fnv = Math.imul(this.fnv ^ byte, 0x01000193)
|
|
32
|
+
this.djb = Math.imul(this.djb, 33) ^ byte
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
digest() {
|
|
37
|
+
return [avalanche(this.fnv), avalanche(this.djb)]
|
|
38
|
+
.map((value) => value.toString(16).padStart(8, "0"))
|
|
39
|
+
.join("")
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function fingerprintHeader(fingerprint: AssetFingerprint, contentType: string) {
|
|
44
|
+
fingerprint.update(encoder.encode(contentType))
|
|
45
|
+
fingerprint.update(new Uint8Array([0]))
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function textFingerprint(text: string, contentType: string) {
|
|
49
|
+
const fingerprint = new AssetFingerprint()
|
|
50
|
+
fingerprintHeader(fingerprint, contentType)
|
|
51
|
+
fingerprint.update(encoder.encode(text))
|
|
52
|
+
return fingerprint.digest()
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export async function blobFingerprint(blob: Blob, contentType: string) {
|
|
56
|
+
const fingerprint = new AssetFingerprint()
|
|
57
|
+
fingerprintHeader(fingerprint, contentType)
|
|
58
|
+
const reader = blob.stream().getReader()
|
|
59
|
+
try {
|
|
60
|
+
while (true) {
|
|
61
|
+
const chunk = await reader.read()
|
|
62
|
+
if (chunk.done) return fingerprint.digest()
|
|
63
|
+
fingerprint.update(chunk.value)
|
|
64
|
+
}
|
|
65
|
+
} finally {
|
|
66
|
+
reader.releaseLock()
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function validateAssetName(name: string) {
|
|
71
|
+
if (!ASSET_NAME.test(name)) {
|
|
72
|
+
throw new Error(`Invalid generated asset name: ${name}`)
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function assetHref(name: string, hash: string) {
|
|
77
|
+
const dot = name.lastIndexOf(".")
|
|
78
|
+
const stem = dot === -1 ? name : name.slice(0, dot)
|
|
79
|
+
const ext = dot === -1 ? "" : name.slice(dot)
|
|
80
|
+
return `/.pmx/${stem}-${hash}${ext}`
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Publishes generated assets into the local bounded cache. A future CDN or
|
|
85
|
+
* object-storage backend should extract byte retention behind a general
|
|
86
|
+
* `AssetStore` (`put`, `get`, `href`) while keeping fingerprinting and key
|
|
87
|
+
* policy here. Repository assets may share that store, but keep their own
|
|
88
|
+
* routing and access rules; this LRU must not be their durable CDN origin.
|
|
89
|
+
*/
|
|
90
|
+
export class GeneratedAssets {
|
|
91
|
+
private assets = new Map<string, StoredAsset>()
|
|
92
|
+
private latest = new Map<string, ServedAsset & { href: string }>()
|
|
93
|
+
|
|
94
|
+
constructor(private limit = GENERATED_ASSET_LIMIT) {}
|
|
95
|
+
|
|
96
|
+
get(href: string) {
|
|
97
|
+
const stored = this.assets.get(href)
|
|
98
|
+
if (!stored) return
|
|
99
|
+
this.assets.delete(href)
|
|
100
|
+
this.assets.set(href, stored)
|
|
101
|
+
return stored.asset
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
entries() {
|
|
105
|
+
return [...this.assets].map(
|
|
106
|
+
([href, stored]) => [href, stored.asset] as const,
|
|
107
|
+
)
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
publishText(name: string, text: string, contentType: string) {
|
|
111
|
+
validateAssetName(name)
|
|
112
|
+
const current = this.latest.get(name)
|
|
113
|
+
if (current?.body === text && current.contentType === contentType) {
|
|
114
|
+
this.get(current.href)
|
|
115
|
+
return current.href
|
|
116
|
+
}
|
|
117
|
+
const href = assetHref(name, textFingerprint(text, contentType))
|
|
118
|
+
this.retain(name, href, { body: text, contentType })
|
|
119
|
+
return href
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
async publishBlob(name: string, blob: Blob, contentType = blob.type) {
|
|
123
|
+
validateAssetName(name)
|
|
124
|
+
contentType ||= "application/octet-stream"
|
|
125
|
+
const current = this.latest.get(name)
|
|
126
|
+
if (current?.body === blob && current.contentType === contentType) {
|
|
127
|
+
this.get(current.href)
|
|
128
|
+
return current.href
|
|
129
|
+
}
|
|
130
|
+
const href = assetHref(name, await blobFingerprint(blob, contentType))
|
|
131
|
+
this.retain(name, href, { body: blob, contentType })
|
|
132
|
+
return href
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
private retain(name: string, href: string, asset: ServedAsset) {
|
|
136
|
+
this.assets.delete(href)
|
|
137
|
+
this.assets.set(href, { name, asset })
|
|
138
|
+
this.latest.set(name, { ...asset, href })
|
|
139
|
+
|
|
140
|
+
while (this.assets.size > this.limit) {
|
|
141
|
+
const oldestHref = this.assets.keys().next().value!
|
|
142
|
+
const { name: oldestName } = this.assets.get(oldestHref)!
|
|
143
|
+
this.assets.delete(oldestHref)
|
|
144
|
+
if (this.latest.get(oldestName)?.href === oldestHref) {
|
|
145
|
+
this.latest.delete(oldestName)
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|