@bridgething/catalog 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.
@@ -0,0 +1,7 @@
1
+ export type { AppEntry, AppVersion, Catalog, Download, InstallCount, RecommendedSource, Repo, SourceCatalog, } from './types.ts';
2
+ export { CatalogValidationError, validate, validateInvariants, validateSchema } from './validate.ts';
3
+ export { releasedAtInstant, sortNewestFirst } from './versions.ts';
4
+ export { aggregate, compareVersions, isListedWebapp, listedWebapps, newestCompatible, pinsFrom, recommendedSources, satisfies, updates, versionCompatible, type CatalogAppListing, type CatalogAppUpdate, type InstalledWebapp, } from './resolve.ts';
5
+ export { blendStoreListings, type StoreListings } from './blend.ts';
6
+ export { DIRECTORY_ORIGIN, OFFICIAL_CATALOG_URL, SOURCE_DIRECTORY_URL, SourceUrlError, fetchCatalog, fetchMergedApps, fetchSources, normalizeSourceUrl, parseSourceUrl, reportInstall, type CatalogSnapshot, type InstallReport, type MergedApps, type MergedCatalog, type SourceFailure, } from './sources.ts';
7
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,YAAY,EACV,QAAQ,EACR,UAAU,EACV,OAAO,EACP,QAAQ,EACR,YAAY,EACZ,iBAAiB,EACjB,IAAI,EACJ,aAAa,GACd,MAAM,YAAY,CAAC;AAEpB,OAAO,EAAE,sBAAsB,EAAE,QAAQ,EAAE,kBAAkB,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAErG,OAAO,EAAE,iBAAiB,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAEnE,OAAO,EACL,SAAS,EACT,eAAe,EACf,cAAc,EACd,aAAa,EACb,gBAAgB,EAChB,QAAQ,EACR,kBAAkB,EAClB,SAAS,EACT,OAAO,EACP,iBAAiB,EACjB,KAAK,iBAAiB,EACtB,KAAK,gBAAgB,EACrB,KAAK,eAAe,GACrB,MAAM,cAAc,CAAC;AAEtB,OAAO,EAAE,kBAAkB,EAAE,KAAK,aAAa,EAAE,MAAM,YAAY,CAAC;AAEpE,OAAO,EACL,gBAAgB,EAChB,oBAAoB,EACpB,oBAAoB,EACpB,cAAc,EACd,YAAY,EACZ,eAAe,EACf,YAAY,EACZ,kBAAkB,EAClB,cAAc,EACd,aAAa,EACb,KAAK,eAAe,EACpB,KAAK,aAAa,EAClB,KAAK,UAAU,EACf,KAAK,aAAa,EAClB,KAAK,aAAa,GACnB,MAAM,cAAc,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,6 @@
1
+ export { CatalogValidationError, validate, validateInvariants, validateSchema } from "./validate.js";
2
+ export { releasedAtInstant, sortNewestFirst } from "./versions.js";
3
+ export { aggregate, compareVersions, isListedWebapp, listedWebapps, newestCompatible, pinsFrom, recommendedSources, satisfies, updates, versionCompatible, } from "./resolve.js";
4
+ export { blendStoreListings } from "./blend.js";
5
+ export { DIRECTORY_ORIGIN, OFFICIAL_CATALOG_URL, SOURCE_DIRECTORY_URL, SourceUrlError, fetchCatalog, fetchMergedApps, fetchSources, normalizeSourceUrl, parseSourceUrl, reportInstall, } from "./sources.js";
6
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAWA,OAAO,EAAE,sBAAsB,EAAE,QAAQ,EAAE,kBAAkB,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAErG,OAAO,EAAE,iBAAiB,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAEnE,OAAO,EACL,SAAS,EACT,eAAe,EACf,cAAc,EACd,aAAa,EACb,gBAAgB,EAChB,QAAQ,EACR,kBAAkB,EAClB,SAAS,EACT,OAAO,EACP,iBAAiB,GAIlB,MAAM,cAAc,CAAC;AAEtB,OAAO,EAAE,kBAAkB,EAAsB,MAAM,YAAY,CAAC;AAEpE,OAAO,EACL,gBAAgB,EAChB,oBAAoB,EACpB,oBAAoB,EACpB,cAAc,EACd,YAAY,EACZ,eAAe,EACf,YAAY,EACZ,kBAAkB,EAClB,cAAc,EACd,aAAa,GAMd,MAAM,cAAc,CAAC"}
@@ -0,0 +1,55 @@
1
+ import type { AppEntry, AppVersion, Catalog, InstallCount, RecommendedSource, SourceCatalog } from './types.ts';
2
+ export type InstalledWebapp = {
3
+ id: string;
4
+ version: string;
5
+ source: 'builtin' | 'installed';
6
+ role: 'standard' | 'launcher';
7
+ provenance?: string | null;
8
+ };
9
+ export type CatalogAppListing = {
10
+ app: AppEntry;
11
+ sourceUrl: string;
12
+ newestCompatible: AppVersion | null;
13
+ installedVersion: string | null;
14
+ updateAvailable: boolean;
15
+ alsoAvailableFrom: string[];
16
+ installs: number;
17
+ };
18
+ export type CatalogAppUpdate = {
19
+ appId: string;
20
+ name: string;
21
+ installedVersion: string;
22
+ target: AppVersion;
23
+ sourceUrl: string;
24
+ };
25
+ export declare function satisfies(deviceVersion: string, minimum: string): boolean;
26
+ export declare function isUpgrade(candidate: string, installed: string): boolean;
27
+ export declare function compareVersions(a: string, b: string): number;
28
+ export declare function pinsFrom(installed: InstalledWebapp[]): Map<string, string>;
29
+ export declare function versionCompatible(version: AppVersion, deviceLibVersion: string | null): boolean;
30
+ export declare function newestCompatible(app: AppEntry, deviceLibVersion: string | null): AppVersion | null;
31
+ export declare function isListedWebapp(webapp: {
32
+ role: string;
33
+ source: string;
34
+ }): boolean;
35
+ export declare function listedWebapps<T extends {
36
+ role: string;
37
+ source: string;
38
+ }>(list: T[]): T[];
39
+ export declare function aggregate(args: {
40
+ orderedCatalogs: SourceCatalog[];
41
+ installed: InstalledWebapp[];
42
+ deviceLibVersion: string | null;
43
+ installs?: InstallCount[];
44
+ }): CatalogAppListing[];
45
+ export declare function updates(args: {
46
+ catalogs: Map<string, Catalog>;
47
+ installed: InstalledWebapp[];
48
+ deviceLibVersion: string | null;
49
+ }): CatalogAppUpdate[];
50
+ export declare function recommendedSources(args: {
51
+ directory: Catalog | null;
52
+ orderedCatalogs: SourceCatalog[];
53
+ subscribed: string[];
54
+ }): RecommendedSource[];
55
+ //# sourceMappingURL=resolve.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resolve.d.ts","sourceRoot":"","sources":["../src/resolve.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,UAAU,EAAE,OAAO,EAAE,YAAY,EAAE,iBAAiB,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAGhH,MAAM,MAAM,eAAe,GAAG;IAC5B,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,SAAS,GAAG,WAAW,CAAC;IAChC,IAAI,EAAE,UAAU,GAAG,UAAU,CAAC;IAC9B,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC5B,CAAC;AAEF,MAAM,MAAM,iBAAiB,GAAG;IAC9B,GAAG,EAAE,QAAQ,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;IAClB,gBAAgB,EAAE,UAAU,GAAG,IAAI,CAAC;IACpC,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,eAAe,EAAE,OAAO,CAAC;IACzB,iBAAiB,EAAE,MAAM,EAAE,CAAC;IAC5B,QAAQ,EAAE,MAAM,CAAC;CAClB,CAAC;AAEF,MAAM,MAAM,gBAAgB,GAAG;IAC7B,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,gBAAgB,EAAE,MAAM,CAAC;IACzB,MAAM,EAAE,UAAU,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;CACnB,CAAC;AAEF,wBAAgB,SAAS,CAAC,aAAa,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAEzE;AAED,wBAAgB,SAAS,CAAC,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAEvE;AAED,wBAAgB,eAAe,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,CAS5D;AAaD,wBAAgB,QAAQ,CAAC,SAAS,EAAE,eAAe,EAAE,GAAG,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAM1E;AAED,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,UAAU,EAAE,gBAAgB,EAAE,MAAM,GAAG,IAAI,GAAG,OAAO,CAE/F;AAED,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,QAAQ,EAAE,gBAAgB,EAAE,MAAM,GAAG,IAAI,GAAG,UAAU,GAAG,IAAI,CAElG;AAED,wBAAgB,cAAc,CAAC,MAAM,EAAE;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GAAG,OAAO,CAEhF;AAED,wBAAgB,aAAa,CAAC,CAAC,SAAS;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,EAAE,IAAI,EAAE,CAAC,EAAE,GAAG,CAAC,EAAE,CAExF;AAaD,wBAAgB,SAAS,CAAC,IAAI,EAAE;IAC9B,eAAe,EAAE,aAAa,EAAE,CAAC;IACjC,SAAS,EAAE,eAAe,EAAE,CAAC;IAC7B,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,QAAQ,CAAC,EAAE,YAAY,EAAE,CAAC;CAC3B,GAAG,iBAAiB,EAAE,CAuCtB;AAED,wBAAgB,OAAO,CAAC,IAAI,EAAE;IAC5B,QAAQ,EAAE,GAAG,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC/B,SAAS,EAAE,eAAe,EAAE,CAAC;IAC7B,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;CACjC,GAAG,gBAAgB,EAAE,CAwBrB;AAED,wBAAgB,kBAAkB,CAAC,IAAI,EAAE;IACvC,SAAS,EAAE,OAAO,GAAG,IAAI,CAAC;IAC1B,eAAe,EAAE,aAAa,EAAE,CAAC;IACjC,UAAU,EAAE,MAAM,EAAE,CAAC;CACtB,GAAG,iBAAiB,EAAE,CAiBtB"}
@@ -0,0 +1,142 @@
1
+ import { sortNewestFirst } from "./versions.js";
2
+ export function satisfies(deviceVersion, minimum) {
3
+ return compareVersions(deviceVersion, minimum) >= 0;
4
+ }
5
+ export function isUpgrade(candidate, installed) {
6
+ return compareVersions(candidate, installed) > 0;
7
+ }
8
+ export function compareVersions(a, b) {
9
+ const pa = versionComponents(a);
10
+ const pb = versionComponents(b);
11
+ for (let i = 0; i < Math.max(pa.length, pb.length); i += 1) {
12
+ const x = pa[i] ?? 0;
13
+ const y = pb[i] ?? 0;
14
+ if (x !== y)
15
+ return x < y ? -1 : 1;
16
+ }
17
+ return 0;
18
+ }
19
+ function versionComponents(raw) {
20
+ let v = raw.trim();
21
+ if (v.startsWith('v') || v.startsWith('V'))
22
+ v = v.slice(1);
23
+ const cut = v.search(/[-+]/);
24
+ if (cut !== -1)
25
+ v = v.slice(0, cut);
26
+ return v.split('.').map(part => {
27
+ const n = Number.parseInt(part, 10);
28
+ return Number.isNaN(n) ? 0 : n;
29
+ });
30
+ }
31
+ export function pinsFrom(installed) {
32
+ const out = new Map();
33
+ for (const info of installed) {
34
+ if (info.provenance)
35
+ out.set(info.id.toLowerCase(), info.provenance);
36
+ }
37
+ return out;
38
+ }
39
+ export function versionCompatible(version, deviceLibVersion) {
40
+ return deviceLibVersion === null || satisfies(deviceLibVersion, version.min_libbridgething_version);
41
+ }
42
+ export function newestCompatible(app, deviceLibVersion) {
43
+ return sortNewestFirst(app.versions).find(v => versionCompatible(v, deviceLibVersion)) ?? null;
44
+ }
45
+ export function isListedWebapp(webapp) {
46
+ return webapp.role !== 'launcher' || webapp.source === 'installed';
47
+ }
48
+ export function listedWebapps(list) {
49
+ return list.filter(isListedWebapp);
50
+ }
51
+ function installsById(counts) {
52
+ const out = new Map();
53
+ for (const entry of counts) {
54
+ const tally = Number(entry.count);
55
+ if (!Number.isFinite(tally) || tally <= 0)
56
+ continue;
57
+ const id = entry.app_id.toLowerCase();
58
+ out.set(id, (out.get(id) ?? 0) + tally);
59
+ }
60
+ return out;
61
+ }
62
+ export function aggregate(args) {
63
+ const { orderedCatalogs, installed, deviceLibVersion } = args;
64
+ const installedById = new Map(installed.map(i => [i.id.toLowerCase(), i]));
65
+ const tallies = installsById(args.installs ?? []);
66
+ const pins = pinsFrom(installed);
67
+ const offerings = new Map();
68
+ for (const { url, catalog } of orderedCatalogs) {
69
+ for (const app of catalog.apps) {
70
+ const list = offerings.get(app.id.toLowerCase());
71
+ if (list)
72
+ list.push({ url, app });
73
+ else
74
+ offerings.set(app.id.toLowerCase(), [{ url, app }]);
75
+ }
76
+ }
77
+ const listings = [];
78
+ for (const [id, offers] of offerings) {
79
+ if (offers.length === 0)
80
+ continue;
81
+ const pinned = pins.get(id);
82
+ const primary = offers.find(o => o.url === pinned) ?? offers[0];
83
+ const alsoAvailableFrom = offers.filter(o => o.url !== primary.url).map(o => o.url);
84
+ const newest = newestCompatible(primary.app, deviceLibVersion);
85
+ const installedVersion = installedById.get(id)?.version ?? null;
86
+ listings.push({
87
+ app: primary.app,
88
+ sourceUrl: primary.url,
89
+ newestCompatible: newest,
90
+ installedVersion,
91
+ updateAvailable: installedVersion !== null && newest !== null && isUpgrade(newest.version, installedVersion),
92
+ alsoAvailableFrom,
93
+ installs: tallies.get(id) ?? 0,
94
+ });
95
+ }
96
+ return listings.sort((a, b) => b.installs - a.installs || a.app.name.localeCompare(b.app.name) || a.app.id.localeCompare(b.app.id));
97
+ }
98
+ export function updates(args) {
99
+ const { catalogs, installed, deviceLibVersion } = args;
100
+ const pins = pinsFrom(installed);
101
+ const out = [];
102
+ for (const info of installed) {
103
+ if (info.source !== 'installed' || info.role !== 'standard')
104
+ continue;
105
+ const id = info.id.toLowerCase();
106
+ const sourceUrl = pins.get(id);
107
+ if (!sourceUrl)
108
+ continue;
109
+ const app = catalogs.get(sourceUrl)?.apps.find(a => a.id.toLowerCase() === id);
110
+ if (!app)
111
+ continue;
112
+ const newest = newestCompatible(app, deviceLibVersion);
113
+ if (!newest || !isUpgrade(newest.version, info.version))
114
+ continue;
115
+ out.push({
116
+ appId: id,
117
+ name: app.name,
118
+ installedVersion: info.version,
119
+ target: newest,
120
+ sourceUrl,
121
+ });
122
+ }
123
+ return out.sort((a, b) => a.name.localeCompare(b.name) || a.appId.localeCompare(b.appId));
124
+ }
125
+ export function recommendedSources(args) {
126
+ const subscribed = new Set(args.subscribed);
127
+ const byUrl = new Map();
128
+ for (const candidate of args.directory?.recommended_sources ?? []) {
129
+ if (subscribed.has(candidate.url) || byUrl.has(candidate.url))
130
+ continue;
131
+ byUrl.set(candidate.url, candidate);
132
+ }
133
+ for (const { catalog } of args.orderedCatalogs) {
134
+ for (const candidate of catalog.recommended_sources) {
135
+ if (subscribed.has(candidate.url) || byUrl.has(candidate.url))
136
+ continue;
137
+ byUrl.set(candidate.url, { ...candidate, attested: false });
138
+ }
139
+ }
140
+ return [...byUrl.values()].sort((a, b) => Number(b.attested) - Number(a.attested) || a.name.localeCompare(b.name));
141
+ }
142
+ //# 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,45 @@
1
+ import type { Catalog, InstallCount, SourceCatalog } from './types.ts';
2
+ export declare const OFFICIAL_CATALOG_URL = "https://apps.bridgething.com/catalog.json";
3
+ export declare const SOURCE_DIRECTORY_URL = "https://bridgething.com/api/sources.json";
4
+ export declare const DIRECTORY_ORIGIN = "https://bridgething.com";
5
+ export type SourceFailure = {
6
+ url: string;
7
+ reason: string;
8
+ };
9
+ export type CatalogSnapshot = {
10
+ catalogs: SourceCatalog[];
11
+ directory: Catalog | null;
12
+ failures: SourceFailure[];
13
+ };
14
+ export declare class SourceUrlError extends Error {
15
+ }
16
+ export declare function parseSourceUrl(raw: string): URL;
17
+ export declare function normalizeSourceUrl(raw: string): string;
18
+ export declare function fetchCatalog(url: string): Promise<Catalog>;
19
+ export type MergedCatalog = {
20
+ url: string;
21
+ official: boolean;
22
+ attested: boolean;
23
+ catalog: Catalog;
24
+ };
25
+ export type MergedApps = {
26
+ updated_at: string;
27
+ catalogs: MergedCatalog[];
28
+ failures: SourceFailure[];
29
+ skipped: string[];
30
+ installs: InstallCount[];
31
+ };
32
+ export declare function fetchMergedApps(init?: {
33
+ origin?: string;
34
+ signal?: AbortSignal;
35
+ }): Promise<MergedApps>;
36
+ export type InstallReport = {
37
+ appId: string;
38
+ sourceUrl: string;
39
+ version?: string | null;
40
+ };
41
+ export declare function reportInstall(install: InstallReport, init?: {
42
+ origin?: string;
43
+ }): void;
44
+ export declare function fetchSources(urls: string[]): Promise<CatalogSnapshot>;
45
+ //# sourceMappingURL=sources.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sources.d.ts","sourceRoot":"","sources":["../src/sources.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAGvE,eAAO,MAAM,oBAAoB,8CAA8C,CAAC;AAEhF,eAAO,MAAM,oBAAoB,6CAA6C,CAAC;AAE/E,eAAO,MAAM,gBAAgB,4BAA4B,CAAC;AAE1D,MAAM,MAAM,aAAa,GAAG;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AAE5D,MAAM,MAAM,eAAe,GAAG;IAC5B,QAAQ,EAAE,aAAa,EAAE,CAAC;IAC1B,SAAS,EAAE,OAAO,GAAG,IAAI,CAAC;IAC1B,QAAQ,EAAE,aAAa,EAAE,CAAC;CAC3B,CAAC;AAEF,qBAAa,cAAe,SAAQ,KAAK;CAAG;AAI5C,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAoB/C;AAED,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAOtD;AAED,wBAAsB,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAIhE;AAED,MAAM,MAAM,aAAa,GAAG;IAC1B,GAAG,EAAE,MAAM,CAAC;IACZ,QAAQ,EAAE,OAAO,CAAC;IAClB,QAAQ,EAAE,OAAO,CAAC;IAClB,OAAO,EAAE,OAAO,CAAC;CAClB,CAAC;AAEF,MAAM,MAAM,UAAU,GAAG;IACvB,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,aAAa,EAAE,CAAC;IAC1B,QAAQ,EAAE,aAAa,EAAE,CAAC;IAC1B,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,QAAQ,EAAE,YAAY,EAAE,CAAC;CAC1B,CAAC;AAEF,wBAAsB,eAAe,CAAC,IAAI,CAAC,EAAE;IAAE,MAAM,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,WAAW,CAAA;CAAE,GAAG,OAAO,CAAC,UAAU,CAAC,CAY3G;AAED,MAAM,MAAM,aAAa,GAAG;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,CAAC;AAE1F,wBAAgB,aAAa,CAAC,OAAO,EAAE,aAAa,EAAE,IAAI,CAAC,EAAE;IAAE,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAkBtF;AAED,wBAAsB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,eAAe,CAAC,CAsB3E"}
@@ -0,0 +1,95 @@
1
+ import { validate } from "./validate.js";
2
+ export const OFFICIAL_CATALOG_URL = 'https://apps.bridgething.com/catalog.json';
3
+ export const SOURCE_DIRECTORY_URL = 'https://bridgething.com/api/sources.json';
4
+ export const DIRECTORY_ORIGIN = 'https://bridgething.com';
5
+ export class SourceUrlError extends Error {
6
+ }
7
+ const HAS_SCHEME = /^[a-z][a-z0-9+.-]*:\/\//i;
8
+ export function parseSourceUrl(raw) {
9
+ const trimmed = raw.trim();
10
+ if (!trimmed)
11
+ throw new SourceUrlError('a source url cannot be empty');
12
+ let url;
13
+ try {
14
+ url = new URL(trimmed);
15
+ }
16
+ catch {
17
+ throw new SourceUrlError(`"${trimmed}" is not a url`);
18
+ }
19
+ if (url.protocol !== 'https:' && url.protocol !== 'http:') {
20
+ throw new SourceUrlError(`a source url must be http or https, not ${url.protocol.replace(':', '')}`);
21
+ }
22
+ if (url.username || url.password) {
23
+ throw new SourceUrlError('a source url must not carry credentials');
24
+ }
25
+ url.hash = '';
26
+ return url;
27
+ }
28
+ export function normalizeSourceUrl(raw) {
29
+ const trimmed = raw.trim();
30
+ if (!trimmed)
31
+ throw new SourceUrlError('a source url cannot be empty');
32
+ const url = parseSourceUrl(HAS_SCHEME.test(trimmed) ? trimmed : `https://${trimmed}`);
33
+ if (url.pathname.endsWith('/'))
34
+ url.pathname += 'catalog.json';
35
+ return url.toString();
36
+ }
37
+ export async function fetchCatalog(url) {
38
+ const response = await fetch(url, { headers: { accept: 'application/json' } });
39
+ if (!response.ok)
40
+ throw new Error(`${url} returned ${response.status}`);
41
+ return validate(await response.json());
42
+ }
43
+ export async function fetchMergedApps(init) {
44
+ const origin = init?.origin ?? DIRECTORY_ORIGIN;
45
+ const response = await fetch(`${origin}/api/apps.json`, {
46
+ headers: { accept: 'application/json' },
47
+ signal: init?.signal,
48
+ });
49
+ if (!response.ok)
50
+ throw new Error(`the app directory returned ${response.status}`);
51
+ const body = (await response.json());
52
+ if (!body || !Array.isArray(body.catalogs)) {
53
+ throw new Error('the app directory returned an unexpected shape');
54
+ }
55
+ return { ...body, installs: Array.isArray(body.installs) ? body.installs : [] };
56
+ }
57
+ export function reportInstall(install, init) {
58
+ const origin = init?.origin ?? DIRECTORY_ORIGIN;
59
+ const body = JSON.stringify({
60
+ app_id: install.appId,
61
+ source_url: install.sourceUrl,
62
+ version: install.version ?? null,
63
+ });
64
+ void Promise.resolve()
65
+ .then(() => fetch(`${origin}/api/installs`, {
66
+ method: 'POST',
67
+ headers: { 'content-type': 'application/json' },
68
+ keepalive: true,
69
+ body,
70
+ }))
71
+ .catch(() => undefined);
72
+ }
73
+ export async function fetchSources(urls) {
74
+ const results = await Promise.all([...urls, SOURCE_DIRECTORY_URL].map(async (url) => {
75
+ try {
76
+ return { url, catalog: await fetchCatalog(url) };
77
+ }
78
+ catch (reason) {
79
+ return { url, reason: reason instanceof Error ? reason.message : String(reason) };
80
+ }
81
+ }));
82
+ const catalogs = [];
83
+ const failures = [];
84
+ let directory = null;
85
+ for (const result of results) {
86
+ if ('reason' in result)
87
+ failures.push(result);
88
+ else if (result.url === SOURCE_DIRECTORY_URL)
89
+ directory = result.catalog;
90
+ else
91
+ catalogs.push(result);
92
+ }
93
+ return { catalogs, directory, failures };
94
+ }
95
+ //# sourceMappingURL=sources.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sources.js","sourceRoot":"","sources":["../src/sources.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,55 @@
1
+ export type Catalog = {
2
+ $schema?: string;
3
+ schema: 'catalog.v1';
4
+ updated_at: string;
5
+ repo: Repo;
6
+ apps: AppEntry[];
7
+ recommended_sources: RecommendedSource[];
8
+ };
9
+ export type Repo = {
10
+ name: string;
11
+ description: string;
12
+ homepage: string | null;
13
+ icon: string | null;
14
+ };
15
+ export type AppEntry = {
16
+ id: string;
17
+ name: string;
18
+ description: string;
19
+ author: string;
20
+ icon: string | null;
21
+ homepage: string | null;
22
+ source: string | null;
23
+ versions: AppVersion[];
24
+ };
25
+ export type AppVersion = {
26
+ version: string;
27
+ released_at: string;
28
+ download: Download;
29
+ permissions: string[];
30
+ role?: 'standard' | 'launcher';
31
+ provides_overlay?: boolean;
32
+ min_libbridgething_version: string;
33
+ changelog: string | null;
34
+ };
35
+ export type Download = {
36
+ url: string;
37
+ size: number;
38
+ sha256: string;
39
+ };
40
+ export type RecommendedSource = {
41
+ name: string;
42
+ url: string;
43
+ description: string | null;
44
+ attested: boolean;
45
+ };
46
+ export type SourceCatalog = {
47
+ url: string;
48
+ catalog: Catalog;
49
+ };
50
+ export type InstallCount = {
51
+ app_id: string;
52
+ source_url: string;
53
+ count: number;
54
+ };
55
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,OAAO,GAAG;IACpB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,YAAY,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,IAAI,CAAC;IACX,IAAI,EAAE,QAAQ,EAAE,CAAC;IACjB,mBAAmB,EAAE,iBAAiB,EAAE,CAAC;CAC1C,CAAC;AAEF,MAAM,MAAM,IAAI,GAAG;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;CACrB,CAAC;AAEF,MAAM,MAAM,QAAQ,GAAG;IACrB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,QAAQ,EAAE,UAAU,EAAE,CAAC;CACxB,CAAC;AAEF,MAAM,MAAM,UAAU,GAAG;IACvB,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,QAAQ,CAAC;IACnB,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,IAAI,CAAC,EAAE,UAAU,GAAG,UAAU,CAAC;IAC/B,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,0BAA0B,EAAE,MAAM,CAAC;IACnC,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;CAC1B,CAAC;AAEF,MAAM,MAAM,QAAQ,GAAG;IACrB,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF,MAAM,MAAM,iBAAiB,GAAG;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,MAAM,CAAC;IACZ,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,QAAQ,EAAE,OAAO,CAAC;CACnB,CAAC;AAEF,MAAM,MAAM,aAAa,GAAG;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,OAAO,CAAA;CAAE,CAAC;AAE9D,MAAM,MAAM,YAAY,GAAG;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC"}
package/dist/types.js ADDED
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
@@ -0,0 +1,9 @@
1
+ import type { Catalog } from './types.ts';
2
+ export declare class CatalogValidationError extends Error {
3
+ readonly errors: string[];
4
+ constructor(message: string, errors: string[]);
5
+ }
6
+ export declare function validateSchema(catalog: unknown): asserts catalog is Catalog;
7
+ export declare function validateInvariants(catalog: Catalog): void;
8
+ export declare function validate(catalog: unknown): Catalog;
9
+ //# sourceMappingURL=validate.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validate.d.ts","sourceRoot":"","sources":["../src/validate.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AAI1C,qBAAa,sBAAuB,SAAQ,KAAK;aAG7B,MAAM,EAAE,MAAM,EAAE;gBADhC,OAAO,EAAE,MAAM,EACC,MAAM,EAAE,MAAM,EAAE;CAKnC;AAED,wBAAgB,cAAc,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,OAAO,IAAI,OAAO,CAK3E;AAED,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAuCzD;AAED,wBAAgB,QAAQ,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAIlD"}
@@ -0,0 +1,57 @@
1
+ import validateSchemaFn from './generated/schema.v1.validator.mjs';
2
+ const UUIDV7 = /^[0-9a-f]{8}-[0-9a-f]{4}-7[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
3
+ export class CatalogValidationError extends Error {
4
+ errors;
5
+ constructor(message, errors) {
6
+ super(`${message}\n - ${errors.join('\n - ')}`);
7
+ this.errors = errors;
8
+ this.name = 'CatalogValidationError';
9
+ }
10
+ }
11
+ export function validateSchema(catalog) {
12
+ if (!validateSchemaFn(catalog)) {
13
+ const errs = (validateSchemaFn.errors ?? []).map(e => `${e.instancePath || '<root>'}: ${e.message ?? 'invalid'}`);
14
+ throw new CatalogValidationError('catalog failed schema validation', errs);
15
+ }
16
+ }
17
+ export function validateInvariants(catalog) {
18
+ const errors = [];
19
+ const seenIds = new Map();
20
+ for (const app of catalog.apps) {
21
+ const prior = seenIds.get(app.id);
22
+ if (prior) {
23
+ errors.push(`app id "${app.id}" used by both "${prior}" and "${app.name}"`);
24
+ }
25
+ else {
26
+ seenIds.set(app.id, app.name);
27
+ }
28
+ if (!UUIDV7.test(app.id)) {
29
+ errors.push(`app "${app.name}" id "${app.id}" is not a valid uuidv7`);
30
+ }
31
+ const seenVersions = new Set();
32
+ for (const v of app.versions) {
33
+ if (seenVersions.has(v.version)) {
34
+ errors.push(`app "${app.name}" lists version "${v.version}" more than once`);
35
+ }
36
+ else {
37
+ seenVersions.add(v.version);
38
+ }
39
+ }
40
+ for (let i = 1; i < app.versions.length; i++) {
41
+ const prev = app.versions[i - 1];
42
+ const cur = app.versions[i];
43
+ if (prev.released_at < cur.released_at) {
44
+ errors.push(`app "${app.name}" versions are not newest-first: "${prev.version}" (${prev.released_at}) precedes "${cur.version}" (${cur.released_at})`);
45
+ }
46
+ }
47
+ }
48
+ if (errors.length) {
49
+ throw new CatalogValidationError('catalog failed cross-reference invariants', errors);
50
+ }
51
+ }
52
+ export function validate(catalog) {
53
+ validateSchema(catalog);
54
+ validateInvariants(catalog);
55
+ return catalog;
56
+ }
57
+ //# sourceMappingURL=validate.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validate.js","sourceRoot":"","sources":["../src/validate.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,5 @@
1
+ export declare function releasedAtInstant(raw: string): number | null;
2
+ export declare function sortNewestFirst<T extends {
3
+ released_at: string;
4
+ }>(items: T[]): T[];
5
+ //# sourceMappingURL=versions.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"versions.d.ts","sourceRoot":"","sources":["../src/versions.ts"],"names":[],"mappings":"AAAA,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAG5D;AAED,wBAAgB,eAAe,CAAC,CAAC,SAAS;IAAE,WAAW,EAAE,MAAM,CAAA;CAAE,EAAE,KAAK,EAAE,CAAC,EAAE,GAAG,CAAC,EAAE,CAUlF"}
@@ -0,0 +1,19 @@
1
+ export function releasedAtInstant(raw) {
2
+ const parsed = Date.parse(raw);
3
+ return Number.isNaN(parsed) ? null : parsed;
4
+ }
5
+ export function sortNewestFirst(items) {
6
+ return items
7
+ .map((item, index) => ({ item, index, at: releasedAtInstant(item.released_at) }))
8
+ .sort((a, b) => {
9
+ if (a.at === null && b.at === null)
10
+ return a.index - b.index;
11
+ if (a.at === null)
12
+ return 1;
13
+ if (b.at === null)
14
+ return -1;
15
+ return a.at === b.at ? a.index - b.index : b.at - a.at;
16
+ })
17
+ .map(entry => entry.item);
18
+ }
19
+ //# sourceMappingURL=versions.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"versions.js","sourceRoot":"","sources":["../src/versions.ts"],"names":[],"mappings":"AAAA,MAAM,UAAU,iBAAiB,CAAC,GAAW;IAC3C,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IAC/B,OAAO,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC;AAC9C,CAAC;AAED,MAAM,UAAU,eAAe,CAAoC,KAAU;IAC3E,OAAO,KAAK;SACT,GAAG,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,EAAE,EAAE,iBAAiB,CAAC,IAAI,CAAC,WAAW,CAAC,EAAE,CAAC,CAAC;SAChF,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE;QACb,IAAI,CAAC,CAAC,EAAE,KAAK,IAAI,IAAI,CAAC,CAAC,EAAE,KAAK,IAAI;YAAE,OAAO,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,KAAK,CAAC;QAC7D,IAAI,CAAC,CAAC,EAAE,KAAK,IAAI;YAAE,OAAO,CAAC,CAAC;QAC5B,IAAI,CAAC,CAAC,EAAE,KAAK,IAAI;YAAE,OAAO,CAAC,CAAC,CAAC;QAC7B,OAAO,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,EAAE,CAAC;IACzD,CAAC,CAAC;SACD,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;AAC9B,CAAC"}
package/package.json ADDED
@@ -0,0 +1,30 @@
1
+ {
2
+ "name": "@bridgething/catalog",
3
+ "type": "module",
4
+ "private": false,
5
+ "version": "0.1.0",
6
+ "description": "catalog.v1 schema, validator, and resolution rules for the bridgething webapp appstore",
7
+ "exports": {
8
+ ".": {
9
+ "types": "./dist/index.d.ts",
10
+ "default": "./dist/index.js"
11
+ },
12
+ "./schema.v1.json": "./schema.v1.json"
13
+ },
14
+ "files": ["dist", "schema.v1.json"],
15
+ "scripts": {
16
+ "prebuild": "rm -rf dist",
17
+ "build": "tsc -p tsconfig.build.json && mkdir -p dist/generated && cp src/generated/schema.v1.validator.mjs src/generated/schema.v1.validator.d.mts dist/generated/",
18
+ "validator": "bun run scripts/compile-validator.ts",
19
+ "typecheck": "tsc --noEmit",
20
+ "test": "bun test"
21
+ },
22
+ "devDependencies": {
23
+ "@types/bun": "^1.3.14",
24
+ "ajv": "^8.20.0",
25
+ "ajv-formats": "^3.0.1"
26
+ },
27
+ "dependencies": {
28
+ "@babel/runtime": "^7.29.2"
29
+ }
30
+ }