add identify to js lib, change up ui to work with new api
parent
e7d206beb2
commit
e2331981d0
|
@ -1,25 +1,19 @@
|
|||
import type {
|
||||
Address,
|
||||
IEntry,
|
||||
ListingResult,
|
||||
OrderedListing,
|
||||
VALUE_TYPE,
|
||||
} from "./types";
|
||||
import type { IEntry, ListingResult, VALUE_TYPE } from "./types";
|
||||
|
||||
export function listingAsOrdered(listing: ListingResult): OrderedListing {
|
||||
const entries = Object.entries(listing) as [Address, IEntry][];
|
||||
return entries
|
||||
.sort(([_, a], [__, b]) =>
|
||||
String(a.value.c).localeCompare(String(b.value.c))
|
||||
)
|
||||
.sort(([_, a], [__, b]) =>
|
||||
String(a.value.t).localeCompare(String(b.value.t))
|
||||
)
|
||||
.sort(([_, a], [__, b]) => a.attribute.localeCompare(b.attribute));
|
||||
}
|
||||
// export function listingAsOrdered(listing: ListingResult): OrderedListing {
|
||||
// const entries = Object.entries(listing) as [Address, IEntry][];
|
||||
// return entries
|
||||
// .sort(([_, a], [__, b]) =>
|
||||
// String(a.value.c).localeCompare(String(b.value.c))
|
||||
// )
|
||||
// .sort(([_, a], [__, b]) =>
|
||||
// String(a.value.t).localeCompare(String(b.value.t))
|
||||
// )
|
||||
// .sort(([_, a], [__, b]) => a.attribute.localeCompare(b.attribute));
|
||||
// }
|
||||
|
||||
export class UpListing {
|
||||
private entries: UpEntry[];
|
||||
public readonly entries: UpEntry[];
|
||||
|
||||
constructor(listing: ListingResult) {
|
||||
this.entries = Object.entries(listing).map((lr) => new UpEntry(...lr));
|
||||
|
@ -46,17 +40,27 @@ export class UpObject {
|
|||
this.entries = entries;
|
||||
}
|
||||
|
||||
public bindAppend(entries: UpEntry[]) {
|
||||
this.entries.push(...entries);
|
||||
}
|
||||
|
||||
public get attributes() {
|
||||
return this.entries.filter((e) => e.entity === this.address);
|
||||
}
|
||||
|
||||
public get backlinks() {
|
||||
return this.entries.filter((e) => e.value.c === this.address);
|
||||
}
|
||||
|
||||
public get attr() {
|
||||
const result = {} as { [key: string]: UpEntry[] };
|
||||
this.entries
|
||||
.filter((e) => e.entity == this.address)
|
||||
.forEach((entry) => {
|
||||
if (!result[entry.attribute]) {
|
||||
result[entry.attribute] = [];
|
||||
}
|
||||
this.attributes.forEach((entry) => {
|
||||
if (!result[entry.attribute]) {
|
||||
result[entry.attribute] = [];
|
||||
}
|
||||
|
||||
result[entry.attribute].push(entry);
|
||||
});
|
||||
result[entry.attribute].push(entry);
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
|
@ -65,6 +69,25 @@ export class UpObject {
|
|||
return this.attr[attr] ? this.attr[attr][0].value.c : undefined;
|
||||
}
|
||||
|
||||
public identify(): string[] {
|
||||
// Get all places where this Object is "had"
|
||||
const hasEntries = this.backlinks
|
||||
.filter((entry) => entry.attribute === "HAS")
|
||||
.map((entry) => entry.address);
|
||||
|
||||
// Out of those relations, retrieve their ALIAS attrs
|
||||
const hasAliases = this.entries
|
||||
.filter(
|
||||
(entry) =>
|
||||
entry.attribute === "ALIAS" && hasEntries.includes(entry.entity)
|
||||
)
|
||||
.map((entry) => String(entry.value.c));
|
||||
|
||||
const lblValues = (this.attr["LBL"] || []).map((e) => String(e.value.c));
|
||||
|
||||
return lblValues.concat(hasAliases);
|
||||
}
|
||||
|
||||
public asDict() {
|
||||
return {
|
||||
address: this.address,
|
||||
|
|
|
@ -11,7 +11,7 @@ export interface ListingResult {
|
|||
[key: string]: IEntry;
|
||||
}
|
||||
|
||||
export type OrderedListing = [Address, IEntry][];
|
||||
// export type OrderedListing = [Address, IEntry][];
|
||||
|
||||
export interface Job {
|
||||
title: string;
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
<script lang="ts">
|
||||
import { createEventDispatcher } from "svelte";
|
||||
import { BLOB_TYPE_ADDR } from "upend/constants";
|
||||
import { identify, useEntity } from "../lib/entity";
|
||||
import HashBadge from "./HashBadge.svelte";
|
||||
import Ellipsis from "./Ellipsis.svelte";
|
||||
import UpLink from "./UpLink.svelte";
|
||||
import { useEntity } from "../lib/entity";
|
||||
const dispatch = createEventDispatcher();
|
||||
|
||||
export let address: string;
|
||||
|
@ -14,22 +14,18 @@
|
|||
|
||||
let resolving = resolve;
|
||||
|
||||
$: ({ attributes, backlinks } = useEntity(address, () => resolve));
|
||||
$: ({ entity } = useEntity(address));
|
||||
|
||||
// isFile
|
||||
$: isFile = $attributes.some(
|
||||
([_, attr]) => attr.attribute === "IS" && attr.value.c === BLOB_TYPE_ADDR
|
||||
);
|
||||
$: isFile = $entity?.get("IS") === BLOB_TYPE_ADDR;
|
||||
|
||||
// Identification
|
||||
let inferredIds: string[] = [];
|
||||
$: {
|
||||
if (resolve) {
|
||||
resolving = true;
|
||||
identify($attributes, $backlinks).then((inferredEntries) => {
|
||||
inferredIds = inferredEntries;
|
||||
resolving = false;
|
||||
});
|
||||
inferredIds = $entity?.identify() || [];
|
||||
resolving = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -41,19 +37,19 @@
|
|||
<div class="address" class:identified={Boolean(inferredIds)} class:banner>
|
||||
<HashBadge {address} />
|
||||
<div class="separator" />
|
||||
<div class="label" class:resolving title={label}>
|
||||
{#if banner && isFile}
|
||||
<a href="/api/raw/{address}" target="_blank">
|
||||
<Ellipsis value={label} />
|
||||
</a>
|
||||
{:else if link}
|
||||
<UpLink to={{ entity: address }}>
|
||||
<Ellipsis value={label} />
|
||||
</UpLink>
|
||||
{:else}
|
||||
<div class="label" class:resolving title={label}>
|
||||
{#if banner && isFile}
|
||||
<a href="/api/raw/{address}" target="_blank">
|
||||
<Ellipsis value={label} />
|
||||
</a>
|
||||
{:else if link}
|
||||
<UpLink to={{ entity: address }}>
|
||||
<Ellipsis value={label} />
|
||||
</UpLink>
|
||||
{:else}
|
||||
<Ellipsis value={label} />
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style scoped lang="scss">
|
||||
|
|
|
@ -1,14 +1,14 @@
|
|||
<script lang="ts">
|
||||
import { createEventDispatcher } from "svelte";
|
||||
import type { IEntry } from "upend/types";
|
||||
import UpLink from "./UpLink.svelte";
|
||||
import type { Component, UpType, Widget } from "../lib/types";
|
||||
import Table from "./widgets/Table.svelte";
|
||||
import TableComponent from "./widgets/Table.svelte"; // silence false svelte(reactive-component) warnings
|
||||
import type { AttributeChange } from "../types/base";
|
||||
import type { UpEntry } from "upend";
|
||||
const dispatch = createEventDispatcher();
|
||||
|
||||
export let attributes: [string, IEntry][];
|
||||
export let attributes: UpEntry[];
|
||||
export let type: UpType | undefined = undefined;
|
||||
export let address: String;
|
||||
export let title: String | undefined = undefined;
|
||||
|
|
|
@ -2,10 +2,9 @@
|
|||
import { useEntity } from "../lib/entity";
|
||||
export let address: string;
|
||||
|
||||
$: ({ attributes } = useEntity(address));
|
||||
$: ({ entity } = useEntity(address));
|
||||
|
||||
$: mimeType = String($attributes.find(([_, e]) => e.attribute === "FILE_MIME")?.[1]
|
||||
.value.c);
|
||||
$: mimeType = String($entity?.get("FILE_MIME"));
|
||||
</script>
|
||||
|
||||
<div class="preview" v-if="mimeType">
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
import BlobPreview from "./BlobPreview.svelte";
|
||||
import { setContext } from "svelte";
|
||||
import { writable } from "svelte/store";
|
||||
import type { UpEntry } from "upend";
|
||||
|
||||
export let address: string;
|
||||
export let index: number | undefined;
|
||||
|
@ -17,12 +18,9 @@
|
|||
|
||||
setContext("browse", { index: indexStore });
|
||||
|
||||
$: ({ error, revalidate, attributes, backlinks } = useEntity(address));
|
||||
$: ({ entity, error, revalidate } = useEntity(address));
|
||||
|
||||
$: allTypeAddresses = $attributes
|
||||
.map(([_, attr]) => attr)
|
||||
.filter((attr) => attr.attribute == "IS")
|
||||
.map((attr) => attr.value.c);
|
||||
$: allTypeAddresses = ($entity?.attr["IS"] || []).map((attr) => attr.value.c);
|
||||
|
||||
$: allTypeEntries = query(
|
||||
() =>
|
||||
|
@ -34,19 +32,19 @@
|
|||
let allTypes: { [key: string]: UpType } = {};
|
||||
$: {
|
||||
allTypes = {};
|
||||
$allTypeEntries.forEach(([_, entry]) => {
|
||||
($allTypeEntries?.entries || []).forEach((entry) => {
|
||||
if (allTypes[entry.entity] === undefined) {
|
||||
allTypes[entry.entity] = new UpType(entry.entity);
|
||||
}
|
||||
|
||||
switch (entry.attribute) {
|
||||
case "TYPE":
|
||||
allTypes[entry.entity].name = entry.value.c;
|
||||
allTypes[entry.entity].name = String(entry.value.c);
|
||||
break;
|
||||
case "TYPE_HAS":
|
||||
case "TYPE_REQUIRES":
|
||||
case "TYPE_ID":
|
||||
allTypes[entry.entity].attributes.push(entry.value.c);
|
||||
allTypes[entry.entity].attributes.push(String(entry.value.c));
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
@ -54,14 +52,14 @@
|
|||
allTypes = allTypes;
|
||||
}
|
||||
|
||||
let typedAttributes = {} as { [key: string]: [string, IEntry][] };
|
||||
let untypedAttributes = [] as [string, IEntry][];
|
||||
let typedAttributes = {} as { [key: string]: UpEntry[] };
|
||||
let untypedAttributes = [] as UpEntry[];
|
||||
|
||||
$: {
|
||||
typedAttributes = {};
|
||||
untypedAttributes = [];
|
||||
|
||||
$attributes.forEach(([entryAddr, entry]) => {
|
||||
($entity?.attributes || []).forEach((entry) => {
|
||||
const entryTypes = Object.entries(allTypes).filter(([_, t]) =>
|
||||
t.attributes.includes(entry.attribute)
|
||||
);
|
||||
|
@ -70,10 +68,10 @@
|
|||
if (typedAttributes[addr] == undefined) {
|
||||
typedAttributes[addr] = [];
|
||||
}
|
||||
typedAttributes[addr].push([entryAddr, entry]);
|
||||
typedAttributes[addr].push(entry);
|
||||
});
|
||||
} else {
|
||||
untypedAttributes.push([entryAddr, entry]);
|
||||
untypedAttributes.push(entry);
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -82,9 +80,9 @@
|
|||
}
|
||||
|
||||
$: filteredUntypedAttributes = untypedAttributes.filter(
|
||||
([_, entry]) =>
|
||||
(entry) =>
|
||||
entry.attribute !== "IS" ||
|
||||
!Object.keys(typedAttributes).includes(entry.value.c)
|
||||
!Object.keys(typedAttributes).includes(String(entry.value.c))
|
||||
);
|
||||
</script>
|
||||
|
||||
|
@ -113,11 +111,11 @@
|
|||
on:changed={revalidate}
|
||||
/>
|
||||
{/if}
|
||||
{#if $backlinks.length > 0}
|
||||
{#if $entity?.backlinks.length > 0}
|
||||
<AttributeView
|
||||
title={`Referred to (${$backlinks.length})`}
|
||||
title={`Referred to (${$entity.backlinks.length})`}
|
||||
{address}
|
||||
attributes={$backlinks}
|
||||
attributes={$entity.backlinks}
|
||||
reverse
|
||||
on:changed={revalidate}
|
||||
/>
|
||||
|
|
|
@ -1,13 +1,14 @@
|
|||
<script lang="ts">
|
||||
import type { OrderedListing } from "upend/types";
|
||||
import type { UpEntry } from "upend";
|
||||
|
||||
import Table from "./Table.svelte";
|
||||
|
||||
export let attributes: OrderedListing;
|
||||
export let attributes: UpEntry[];
|
||||
export let attribute: string;
|
||||
export let editable = false;
|
||||
|
||||
$: filteredAttributes = attributes.filter(
|
||||
([_, entry]) => entry.attribute === attribute
|
||||
(entry) => entry.attribute === attribute
|
||||
);
|
||||
</script>
|
||||
|
||||
|
|
|
@ -1,20 +1,20 @@
|
|||
<script lang="ts">
|
||||
import filesize from "filesize";
|
||||
import { format, fromUnixTime } from "date-fns";
|
||||
import type { OrderedListing } from "upend/types";
|
||||
import Ellipsis from "../Ellipsis.svelte";
|
||||
import Address from "../Address.svelte";
|
||||
import { createEventDispatcher, getContext } from "svelte";
|
||||
import type { AttributeChange } from "../../types/base";
|
||||
import { useParams } from "svelte-navigator";
|
||||
import type { Writable } from "svelte/store";
|
||||
import type { UpEntry } from "upend";
|
||||
const dispatch = createEventDispatcher();
|
||||
const params = useParams();
|
||||
|
||||
export let columns = "attribute, value";
|
||||
export let header = true;
|
||||
|
||||
export let attributes: OrderedListing;
|
||||
export let attributes: UpEntry[];
|
||||
export let editable = false;
|
||||
|
||||
// Display
|
||||
|
@ -61,7 +61,7 @@
|
|||
let sortKeys: { [key: string]: string } = {};
|
||||
$: sortedAttributes = attributes
|
||||
.concat()
|
||||
.sort(([_, aEntry], [__, bEntry]) => {
|
||||
.sort((aEntry, bEntry) => {
|
||||
if (
|
||||
sortKeys[aEntry.value.c] === undefined ||
|
||||
sortKeys[bEntry.value.c] === undefined
|
||||
|
@ -71,16 +71,16 @@
|
|||
} else if (!sortKeys[aEntry.value.c] && sortKeys[bEntry.value.c]) {
|
||||
return 1;
|
||||
} else {
|
||||
return aEntry.value.c.localeCompare(bEntry.value.c);
|
||||
return String(aEntry.value.c).localeCompare(String(bEntry.value.c));
|
||||
}
|
||||
} else {
|
||||
return sortKeys[aEntry.value.c].localeCompare(sortKeys[bEntry.value.c]);
|
||||
}
|
||||
})
|
||||
.sort(([_, aEntry], [__, bEntry]) => {
|
||||
.sort((aEntry, bEntry) => {
|
||||
return aEntry.attribute.localeCompare(bEntry.attribute);
|
||||
})
|
||||
.sort(([_, aEntry], [__, bEntry]) => {
|
||||
.sort((aEntry, bEntry) => {
|
||||
if (
|
||||
sortKeys[aEntry.entity] === undefined ||
|
||||
sortKeys[bEntry.entity] === undefined
|
||||
|
@ -108,16 +108,21 @@
|
|||
FILE_SIZE: "File size",
|
||||
};
|
||||
|
||||
const VALUE_FORMATTERS: { [key: string]: (val: string) => string } = {
|
||||
FILE_MTIME: (val) => format(fromUnixTime(parseInt(val, 10)), "PPpp"),
|
||||
FILE_SIZE: (val) => filesize(parseInt(val, 10), { base: 2 }),
|
||||
};
|
||||
const VALUE_FORMATTERS: { [key: string]: (val: string | number) => string } =
|
||||
{
|
||||
FILE_MTIME: (val) =>
|
||||
format(fromUnixTime(parseInt(String(val), 10)), "PPpp"),
|
||||
FILE_SIZE: (val) => filesize(parseInt(String(val), 10), { base: 2 }),
|
||||
};
|
||||
|
||||
function formatAttribute(attribute: string) {
|
||||
return ATTRIBUTE_LABELS[attribute];
|
||||
}
|
||||
|
||||
function formatValue(value: string, attribute: string): string | undefined {
|
||||
function formatValue(
|
||||
value: string | number,
|
||||
attribute: string
|
||||
): string | undefined {
|
||||
const handler = VALUE_FORMATTERS[attribute];
|
||||
if (handler) {
|
||||
return handler(value);
|
||||
|
@ -161,7 +166,7 @@
|
|||
</tr>
|
||||
{/if}
|
||||
|
||||
{#each sortedAttributes as [id, entry] (id)}
|
||||
{#each sortedAttributes as entry (entry.address)}
|
||||
<tr
|
||||
class:left-active={entry.entity == addresses[$index - 1] ||
|
||||
entry.value.c == addresses[$index - 1]}
|
||||
|
@ -170,7 +175,10 @@
|
|||
>
|
||||
{#if editable}
|
||||
<td class="attr-action">
|
||||
<sl-icon-button name="x-circle" on:click={removeEntry(id)} />
|
||||
<sl-icon-button
|
||||
name="x-circle"
|
||||
on:click={removeEntry(entry.address)}
|
||||
/>
|
||||
</td>
|
||||
{/if}
|
||||
|
||||
|
@ -199,13 +207,13 @@
|
|||
<text-input
|
||||
{editable}
|
||||
value={entry.value.c}
|
||||
on:edit={(val) => updateEntry(id, entry.attribute, val)}
|
||||
on:edit={(val) => updateEntry(entry.address, entry.attribute, val)}
|
||||
>
|
||||
{#if entry.value.t === "Address"}
|
||||
<Address
|
||||
link
|
||||
address={entry.value.c}
|
||||
resolve={Boolean(resolve[id]) || true}
|
||||
address={String(entry.value.c)}
|
||||
resolve={Boolean(resolve[entry.address]) || true}
|
||||
on:resolved={(event) => {
|
||||
sortKeys[entry.value.c] = event.detail[0];
|
||||
}}
|
||||
|
@ -218,7 +226,7 @@
|
|||
>
|
||||
<Ellipsis
|
||||
value={formatValue(entry.value.c, entry.attribute) ||
|
||||
entry.value.c}
|
||||
String(entry.value.c)}
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
|
|
|
@ -1,37 +1,27 @@
|
|||
// import { useSWR } from "sswr";
|
||||
import { useSWR } from "../util/fetch";
|
||||
import { derived, Readable, readable, writable } from "svelte/store";
|
||||
import type { IEntry, ListingResult, OrderedListing } from "upend/types";
|
||||
import { listingAsOrdered, UpEntry } from "upend";
|
||||
import LRU from "lru-cache";
|
||||
import { derived, Readable, Writable, writable } from "svelte/store";
|
||||
import { UpListing, UpObject } from "upend";
|
||||
import type { ListingResult } from "upend/types";
|
||||
import { useSWR } from "../util/fetch";
|
||||
|
||||
export function useEntity(
|
||||
address: string | (() => string),
|
||||
condition?: () => Boolean
|
||||
) {
|
||||
const { data, error, revalidate } = useSWR<ListingResult, unknown>(() =>
|
||||
condition === undefined || condition()
|
||||
? `/api/obj/${typeof address === "string" ? address : address()}`
|
||||
: null
|
||||
const queryOnceLRU = new LRU<string, UpListing>(128);
|
||||
const inFlightRequests: { [key: string]: Promise<UpListing> } = {};
|
||||
|
||||
export function useEntity(address: string) {
|
||||
const { data, error, revalidate } = useSWR<ListingResult, unknown>(
|
||||
() => `/api/obj/${address}`
|
||||
);
|
||||
|
||||
const entries = derived(data, ($values) =>
|
||||
$values ? listingAsOrdered($values) : []
|
||||
);
|
||||
const attributes = derived(entries, ($entries) => {
|
||||
const addr = typeof address === "string" ? address : address();
|
||||
return $entries.filter(([_, e]) => e.entity === addr);
|
||||
});
|
||||
const backlinks = derived(entries, ($entries) => {
|
||||
const addr = typeof address === "string" ? address : address();
|
||||
return $entries.filter(([_, e]) => e.entity !== addr);
|
||||
const entity: Readable<UpObject | undefined> = derived(data, ($listing) => {
|
||||
if ($listing) {
|
||||
const listing = new UpListing($listing);
|
||||
return listing.objects.find((obj) => obj.address == address);
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
entries,
|
||||
attributes,
|
||||
backlinks,
|
||||
data,
|
||||
entity,
|
||||
error,
|
||||
revalidate,
|
||||
};
|
||||
|
@ -45,7 +35,7 @@ export function query(query: () => string) {
|
|||
);
|
||||
|
||||
const result = derived(data, ($values) => {
|
||||
return $values ? listingAsOrdered($values) : [];
|
||||
return $values ? new UpListing($values) : undefined;
|
||||
});
|
||||
|
||||
return {
|
||||
|
@ -56,19 +46,15 @@ export function query(query: () => string) {
|
|||
};
|
||||
}
|
||||
|
||||
const queryOnceLRU = new LRU<string, OrderedListing>(128);
|
||||
const inFlightRequests: { [key: string]: Promise<OrderedListing> } = {};
|
||||
|
||||
export async function queryOnce(query: string): Promise<OrderedListing> {
|
||||
export async function queryOnce(query: string): Promise<UpListing> {
|
||||
const cacheResult = queryOnceLRU.get(query);
|
||||
if (!cacheResult) {
|
||||
const url = `/api/obj?query=${query}`;
|
||||
let response;
|
||||
if (!inFlightRequests[url]) {
|
||||
console.debug(`Querying: ${query}`);
|
||||
inFlightRequests[url] = new Promise(async (resolve, reject) => {
|
||||
const response = await fetch(url, { keepalive: true });
|
||||
resolve(listingAsOrdered(await response.json()));
|
||||
resolve(new UpListing(await response.json()));
|
||||
});
|
||||
} else {
|
||||
console.debug(`Chaining request for ${query}...`);
|
||||
|
@ -79,28 +65,3 @@ export async function queryOnce(query: string): Promise<OrderedListing> {
|
|||
return cacheResult;
|
||||
}
|
||||
}
|
||||
|
||||
export async function identify(
|
||||
attributes: UpEntry[],
|
||||
backlinks: UpEntry[]
|
||||
): Promise<string[]> {
|
||||
// Get all entries where the object is linked
|
||||
const hasEntries = backlinks
|
||||
.filter((entry) => entry.attribute === "HAS")
|
||||
.map((entry) => entry.address);
|
||||
|
||||
// Out of those relations, retrieve their ALIAS attrs
|
||||
const hasAliases = hasEntries.length
|
||||
? await queryOnce(
|
||||
`(matches (in ${hasEntries.map((e) => `"${e}"`).join(" ")}) "ALIAS" ?)`
|
||||
)
|
||||
: [];
|
||||
|
||||
const aliasValues = hasAliases.map(([_, entry]) => String(entry.value.c));
|
||||
|
||||
// Return all LBLs concatenated with named aliases
|
||||
return attributes
|
||||
.filter((attr) => attr.attribute === "LBL")
|
||||
.map((attr) => String(attr.value.c))
|
||||
.concat(aliasValues);
|
||||
}
|
||||
|
|
|
@ -3301,8 +3301,8 @@ __metadata:
|
|||
|
||||
"upend@file:../tools/upend_js::locator=svelte-app%40workspace%3A.":
|
||||
version: 0.0.1
|
||||
resolution: "upend@file:../tools/upend_js#../tools/upend_js::hash=0110ea&locator=svelte-app%40workspace%3A."
|
||||
checksum: 52a706bb4738034b16ec95a0e569fc38597177fadc0a2b327ac59889b19b06d032554e0e4f47b978416f65ac96341ad5c4b6bf2c56a59d0bdafc4bcfabf1681b
|
||||
resolution: "upend@file:../tools/upend_js#../tools/upend_js::hash=88b9af&locator=svelte-app%40workspace%3A."
|
||||
checksum: 9076efc8b84c0c96f5d48ce092320832be93d18529af48d6ab623a3adb698401b78913ceac95439ddaaf3232a6a41ec886ad263ce2f05646ad6460d98627f8e9
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
|
|
Loading…
Reference in New Issue