From 280a1583d9fdfed72840a6f6b47ce2610db53cdb Mon Sep 17 00:00:00 2001 From: julienmalard Date: Sun, 10 Nov 2024 13:49:59 +0530 Subject: [PATCH] Progress on automatic config --- .gitignore | 4 +- package.json | 3 + pnpm-lock.yaml | 22 +- src/bin.ts | 34 +- src/config.ts | 106 ++++++ src/consts.ts | 54 +-- src/index.ts | 4 +- src/orbiter.ts | 858 ++++++++++++++++++++++++++----------------- src/types.ts | 79 ++-- src/utils.ts | 8 +- src/version.ts | 2 +- test/orbiter.test.ts | 4 +- 12 files changed, 767 insertions(+), 411 deletions(-) create mode 100644 src/config.ts diff --git a/.gitignore b/.gitignore index 4807e81..98705ae 100644 --- a/.gitignore +++ b/.gitignore @@ -31,4 +31,6 @@ node_modules/ # JS node_modules -dist \ No newline at end of file +dist + +.orbiter \ No newline at end of file diff --git a/package.json b/package.json index 6e837c1..ffa9f4e 100644 --- a/package.json +++ b/package.json @@ -33,6 +33,7 @@ "@constl/orbit-db-types": "^2.0.6", "@constl/utils-tests": "^1.6.2", "@eslint/js": "^9.14.0", + "@types/lodash-es": "^4.17.12", "@types/mocha": "^10.0.9", "@types/yargs": "^17.0.33", "aegir": "^44.1.4", @@ -66,7 +67,9 @@ "@constl/utils-ipa": "^1.0.3", "@orbitdb/core": "^2.4.3", "chalk": "^5.3.0", + "change-case": "^5.4.4", "deepcopy": "^2.1.0", + "lodash-es": "^4.17.21", "log-update": "^6.1.0", "ora": "^8.1.1", "semaphore-async-await": "^1.5.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index bed2543..c698d28 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -17,9 +17,15 @@ importers: chalk: specifier: ^5.3.0 version: 5.3.0 + change-case: + specifier: ^5.4.4 + version: 5.4.4 deepcopy: specifier: ^2.1.0 version: 2.1.0 + lodash-es: + specifier: ^4.17.21 + version: 4.17.21 log-update: specifier: ^6.1.0 version: 6.1.0 @@ -45,6 +51,9 @@ importers: '@eslint/js': specifier: ^9.14.0 version: 9.14.0 + '@types/lodash-es': + specifier: ^4.17.12 + version: 4.17.12 '@types/mocha': specifier: ^10.0.9 version: 10.0.9 @@ -2799,6 +2808,9 @@ packages: resolution: {integrity: sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==} engines: {node: ^12.17.0 || ^14.13 || >=16.0.0} + change-case@5.4.4: + resolution: {integrity: sha512-HRQyTk2/YPEkt9TnUPbOpr64Uw3KOicFWPVBb+xiHvd6eBx/qPr9xqfBFDT8P2vWsvvz4jbEkfDe71W3VyNu2w==} + char-regex@1.0.2: resolution: {integrity: sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==} engines: {node: '>=10'} @@ -11902,6 +11914,8 @@ snapshots: chalk@5.3.0: {} + change-case@5.4.4: {} + char-regex@1.0.2: {} character-entities@2.0.2: {} @@ -12798,8 +12812,8 @@ snapshots: dependencies: '@typescript-eslint/eslint-plugin': 7.18.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.6.3))(eslint@8.57.1)(typescript@5.6.3) '@typescript-eslint/parser': 7.18.0(eslint@8.57.1)(typescript@5.6.3) - eslint-config-love: 47.0.0(@typescript-eslint/eslint-plugin@7.18.0(@typescript-eslint/parser@8.12.2(eslint@9.14.0)(typescript@5.6.3))(eslint@9.14.0)(typescript@5.6.3))(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.12.2(eslint@9.14.0)(typescript@5.6.3))(eslint@9.14.0))(eslint-plugin-n@16.6.2(eslint@9.14.0))(eslint-plugin-promise@6.6.0(eslint@9.14.0))(eslint@8.57.1)(typescript@5.6.3) - eslint-config-standard: 17.1.0(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.12.2(eslint@9.14.0)(typescript@5.6.3))(eslint@9.14.0))(eslint-plugin-n@16.6.2(eslint@9.14.0))(eslint-plugin-promise@6.6.0(eslint@9.14.0))(eslint@8.57.1) + eslint-config-love: 47.0.0(@typescript-eslint/eslint-plugin@7.18.0(@typescript-eslint/parser@8.12.2(eslint@9.14.0)(typescript@5.6.3))(eslint@9.14.0)(typescript@5.6.3))(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.12.2(eslint@9.14.0)(typescript@5.6.3))(eslint@9.14.0))(eslint-plugin-n@16.6.2(eslint@9.14.0))(eslint-plugin-promise@6.6.0(eslint@8.57.1))(eslint@8.57.1)(typescript@5.6.3) + eslint-config-standard: 17.1.0(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.12.2(eslint@9.14.0)(typescript@5.6.3))(eslint@9.14.0))(eslint-plugin-n@16.6.2(eslint@9.14.0))(eslint-plugin-promise@6.6.0(eslint@8.57.1))(eslint@8.57.1) eslint-plugin-etc: 2.0.3(eslint@8.57.1)(typescript@5.6.3) eslint-plugin-import: 2.31.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.6.3))(eslint@8.57.1) eslint-plugin-jsdoc: 48.11.0(eslint@8.57.1) @@ -12815,7 +12829,7 @@ snapshots: - eslint-plugin-n - supports-color - eslint-config-love@47.0.0(@typescript-eslint/eslint-plugin@7.18.0(@typescript-eslint/parser@8.12.2(eslint@9.14.0)(typescript@5.6.3))(eslint@9.14.0)(typescript@5.6.3))(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.12.2(eslint@9.14.0)(typescript@5.6.3))(eslint@9.14.0))(eslint-plugin-n@16.6.2(eslint@9.14.0))(eslint-plugin-promise@6.6.0(eslint@9.14.0))(eslint@8.57.1)(typescript@5.6.3): + eslint-config-love@47.0.0(@typescript-eslint/eslint-plugin@7.18.0(@typescript-eslint/parser@8.12.2(eslint@9.14.0)(typescript@5.6.3))(eslint@9.14.0)(typescript@5.6.3))(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.12.2(eslint@9.14.0)(typescript@5.6.3))(eslint@9.14.0))(eslint-plugin-n@16.6.2(eslint@9.14.0))(eslint-plugin-promise@6.6.0(eslint@8.57.1))(eslint@8.57.1)(typescript@5.6.3): dependencies: '@typescript-eslint/eslint-plugin': 7.18.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.6.3))(eslint@8.57.1)(typescript@5.6.3) '@typescript-eslint/parser': 7.18.0(eslint@8.57.1)(typescript@5.6.3) @@ -12831,7 +12845,7 @@ snapshots: dependencies: eslint: 9.14.0 - eslint-config-standard@17.1.0(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.12.2(eslint@9.14.0)(typescript@5.6.3))(eslint@9.14.0))(eslint-plugin-n@16.6.2(eslint@9.14.0))(eslint-plugin-promise@6.6.0(eslint@9.14.0))(eslint@8.57.1): + eslint-config-standard@17.1.0(eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.12.2(eslint@9.14.0)(typescript@5.6.3))(eslint@9.14.0))(eslint-plugin-n@16.6.2(eslint@9.14.0))(eslint-plugin-promise@6.6.0(eslint@8.57.1))(eslint@8.57.1): dependencies: eslint: 8.57.1 eslint-plugin-import: 2.31.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.6.3))(eslint@8.57.1) diff --git a/src/bin.ts b/src/bin.ts index dd3d374..d18a155 100644 --- a/src/bin.ts +++ b/src/bin.ts @@ -15,13 +15,13 @@ import { } from "@constl/ipa"; import { createOrbiter } from "@/orbiter.js"; -const MACHINE_PREFIX = "MACHINE MESSAGE:" +const MACHINE_PREFIX = "MACHINE MESSAGE:"; const baseDir = url.fileURLToPath(new URL("..", import.meta.url)); const packageJsonFile = path.join(baseDir, "./package.json"); const packageJson = JSON.parse(fs.readFileSync(packageJsonFile, "utf8")); -const sendMachineMessage = ({ message }: { message: {type: string} }) => { +const sendMachineMessage = ({ message }: { message: { type: string } }) => { console.log(MACHINE_PREFIX + JSON.stringify(message)); }; @@ -37,18 +37,19 @@ const followConnections = async ({ ipa }: { ipa: Constellation }) => { const fFinale = () => { const nIpfsConnections = connexions.sfip.length; - const nConstellationConnections = connexions.constellation.filter((c) => c.infoMembre.idCompte !== connexions.monId && !c.vuÀ).length; + const nConstellationConnections = connexions.constellation.filter( + (c) => c.infoMembre.idCompte !== connexions.monId && !c.vuÀ, + ).length; logUpdate( chalk.yellow( - // eslint-disable-next-line no-irregular-whitespace `Network connections: ${nIpfsConnections}\nConstellation nodes online: ${nConstellationConnections}`, ), ); }; const forgetMyId = await ipa.suivreIdCompte({ - f: id => connexions.monId = id, + f: (id) => (connexions.monId = id), }); const forgetIpfsConnections = await ipa.réseau.suivreConnexionsPostesSFIP({ f: (x) => { @@ -62,7 +63,7 @@ const followConnections = async ({ ipa }: { ipa: Constellation }) => { connexions.constellation = x; fFinale(); }, - }); // TODO: check specifically for Orbiter instances on the Constellation network + }); // TODO: check specifically for Orbiter instances on the Constellation network return async () => { await Promise.all([ forgetMyId(), @@ -86,7 +87,8 @@ yargs(hideBin(process.argv)) }) .option("machine", { alias: "m", - describe: "Machine communication mode (useful for programmatic access).", + describe: + "Machine communication mode (useful for programmatic access).", type: "boolean", }); }, @@ -98,17 +100,19 @@ yargs(hideBin(process.argv)) if (argv.machine) { sendMachineMessage({ message: { type: "STARTING ORBITER" } }); } else { - wheel = ora(chalk.yellow(`Initialising Orbiter`)); // .start() + wheel = ora(chalk.yellow(`Initialising Orbiter`)); // .start() } const constellation = créerConstellation({ - dossier: argv.dir || '.orbiter', - }) + dossier: argv.dir || ".orbiter", + }); - await createOrbiter({ + const { config } = await createOrbiter({ constellation, }); + console.log(config); + process.stdin.on("data", async () => { if (argv.machine) { sendMachineMessage({ message: { type: "Closing Orbiter" } }); @@ -133,7 +137,7 @@ yargs(hideBin(process.argv)) }); } else { wheel!.succeed( - chalk.yellow("Orbiter is running. Press any key to close."), + chalk.yellow("Orbiter is running. Press `enter` to close."), ); oublierConnexions = await followConnections({ ipa: constellation }); } @@ -151,7 +155,5 @@ yargs(hideBin(process.argv)) ) .demandCommand() .help("help", "Get command line help") - .epilog( - "Source code and bug reports: https://github.com/riffcc/orbiter", - ) - .parse(); \ No newline at end of file + .epilog("Source code and bug reports: https://github.com/riffcc/orbiter") + .parse(); diff --git a/src/config.ts b/src/config.ts new file mode 100644 index 0000000..8d32f28 --- /dev/null +++ b/src/config.ts @@ -0,0 +1,106 @@ +import { + ConfigMode, + VariableIds, + possiblyIncompleteOrbiterConfigSchema, + type OrbiterConfig, + type PossiblyIncompleteOrbiterConfig, +} from "./types"; + +import {constantCase} from 'change-case'; +import { + isBrowser, + isElectronRenderer, + isReactNative, + isWebWorker, +} from "wherearewe"; +import Ajv from "ajv/dist/jtd"; + +import { CONFIG_FILE_NAME } from "./consts.js"; + +const ajv = new Ajv(); +const validateConfig = ajv.compile(possiblyIncompleteOrbiterConfigSchema); + +export const getConfig = async ({ + dir, +}: { + dir: string; +}): Promise => { + if (isBrowser || isElectronRenderer || isReactNative || isWebWorker) { + throw new Error( + "The `getConfig` function is only available in Node and Electron main environments.", + ); + } + const { existsSync, readFileSync } = await import("fs"); + const { join } = await import("path"); + const configFilePath = join(dir, CONFIG_FILE_NAME); + if (existsSync(configFilePath)) { + const data = readFileSync(configFilePath); + try { + const jsonConfig = JSON.parse(new TextDecoder().decode(data)) as unknown; + if (validateConfig(jsonConfig)) { + return jsonConfig; + } else { + return {}; + } + } catch { + return {}; + } + } + return {}; +}; + +export const saveConfig = async ({ + dir, + config, + mode = "vite", +}: { + dir: string; + config: OrbiterConfig; + mode: ConfigMode; +}) => { + const configFileText = exportConfig({ config, mode }); + if (isBrowser || isElectronRenderer || isReactNative || isWebWorker) { + throw new Error( + "The `saveConfig` function is only available in Node and Electron main environments.", + ); + } + const { writeFileSync } = await import("fs"); + const { join } = await import("path"); + const configFilePath = join(dir, CONFIG_FILE_NAME); + writeFileSync(configFilePath, configFileText); +}; + +export const exportConfig = ({ + config, + mode = "vite", +}: { + config: OrbiterConfig; + mode: ConfigMode; +}): string => { + if (mode === "vite") return exportViteConfig({ config }); + else throw new Error(`Unknown exportation mode ${mode}.`); +}; + +export const exportViteConfig = ({ + config, +}: { + config: OrbiterConfig; +}): string => { + const { siteId, swarmId, variableIds } = config; + let envFileText = '# The address below should be regenerated for each Orbiter site. If you are setting up an independent site, erase the value below and run the site in development mode (`pnpm dev`) to automatically regenerate. \n' + + 'VITE_SITE_ID=' + siteId + + '\n'; + const variableIdsList = Object.keys(variableIds).map( + k => `VITE_${constantCase(k)}_ID=${variableIds[k as keyof VariableIds]}`, + ); + + envFileText += + '# These should ideally stay the same for all Orbiter site. Changing these will create a parallel network and thereby keep your lens from syncing and interacting with the main network.\n' + + 'VITE_SWARM_ID=' + swarmId + + '\n' + + '\n' + + variableIdsList.join('\n') + + '\n' + ; + return envFileText; +}; diff --git a/src/consts.ts b/src/consts.ts index 44895b6..ced9340 100644 --- a/src/consts.ts +++ b/src/consts.ts @@ -1,31 +1,33 @@ -export const TRUSTED_SITES_TABLE_KEY = 'trustedSites'; -export const TRUSTED_SITES_SITE_ID_COL = 'siteId'; -export const TRUSTED_SITES_NAME_COL = 'siteName'; +export const TRUSTED_SITES_TABLE_KEY = "trustedSites"; +export const TRUSTED_SITES_SITE_ID_COL = "siteId"; +export const TRUSTED_SITES_NAME_COL = "siteName"; -export const FEATURED_RELEASES_TABLE_KEY = 'featuredReleases'; -export const FEATURED_RELEASES_RELEASE_ID_COLUMN = 'releaseId'; -export const FEATURED_RELEASES_START_TIME_COLUMN = 'startTime'; -export const FEATURED_RELEASES_END_TIME_COLUMN = 'endTime'; +export const FEATURED_RELEASES_TABLE_KEY = "featuredReleases"; +export const FEATURED_RELEASES_RELEASE_ID_COLUMN = "releaseId"; +export const FEATURED_RELEASES_START_TIME_COLUMN = "startTime"; +export const FEATURED_RELEASES_END_TIME_COLUMN = "endTime"; -export const BLOCKED_RELEASES_TABLE_KEY = 'blockedReleases'; -export const BLOCKED_RELEASES_RELEASE_ID_COLUMN = 'releaseId'; +export const BLOCKED_RELEASES_TABLE_KEY = "blockedReleases"; +export const BLOCKED_RELEASES_RELEASE_ID_COLUMN = "releaseId"; -export const RELEASES_FILE_COLUMN = 'file'; -export const RELEASES_AUTHOR_COLUMN = 'author'; -export const RELEASES_NAME_COLUMN = 'contentName'; -export const RELEASES_METADATA_COLUMN = 'metadata'; -export const RELEASES_THUMBNAIL_COLUMN = 'thumbnail'; -export const RELEASES_CATEGORY_COLUMN = 'category'; -export const RELEASES_STATUS_COLUMN = 'status'; -export const RELEASES_COVER_COLUMN = 'cover'; +export const RELEASES_FILE_COLUMN = "file"; +export const RELEASES_AUTHOR_COLUMN = "author"; +export const RELEASES_NAME_COLUMN = "contentName"; +export const RELEASES_METADATA_COLUMN = "metadata"; +export const RELEASES_THUMBNAIL_COLUMN = "thumbnail"; +export const RELEASES_CATEGORY_COLUMN = "category"; +export const RELEASES_STATUS_COLUMN = "status"; +export const RELEASES_COVER_COLUMN = "cover"; -export const COLLECTIONS_RELEASES_COLUMN = 'releases'; -export const COLLECTIONS_AUTHOR_COLUMN = 'author'; -export const COLLECTIONS_NAME_COLUMN = 'contentName'; -export const COLLECTIONS_METADATA_COLUMN = 'metadata'; -export const COLLECTIONS_THUMBNAIL_COLUMN = 'thumbnail'; -export const COLLECTIONS_CATEGORY_COLUMN = 'category'; -export const COLLECTIONS_STATUS_COLUMN = 'status'; +export const COLLECTIONS_RELEASES_COLUMN = "releases"; +export const COLLECTIONS_AUTHOR_COLUMN = "author"; +export const COLLECTIONS_NAME_COLUMN = "contentName"; +export const COLLECTIONS_METADATA_COLUMN = "metadata"; +export const COLLECTIONS_THUMBNAIL_COLUMN = "thumbnail"; +export const COLLECTIONS_CATEGORY_COLUMN = "category"; +export const COLLECTIONS_STATUS_COLUMN = "status"; -export const RELEASES_DB_TABLE_KEY = 'releases'; -export const COLLECTIONS_DB_TABLE_KEY = 'collections'; +export const RELEASES_DB_TABLE_KEY = "releases"; +export const COLLECTIONS_DB_TABLE_KEY = "collections"; + +export const CONFIG_FILE_NAME = ".orbiter-config.json"; diff --git a/src/index.ts b/src/index.ts index 2feb105..48e90f6 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,4 +1,4 @@ export { Orbiter } from "./orbiter.js"; -export { version } from "./version.js"; +export { version } from "./version.js"; export * as types from "./types.js"; -export * as consts from "./consts.js"; \ No newline at end of file +export * as consts from "./consts.js"; diff --git a/src/orbiter.ts b/src/orbiter.ts index 7aabb5f..9a534cb 100644 --- a/src/orbiter.ts +++ b/src/orbiter.ts @@ -1,11 +1,15 @@ -import {TypedEmitter} from 'tiny-typed-emitter'; +import { TypedEmitter } from "tiny-typed-emitter"; -import {Lock} from 'semaphore-async-await'; +import { Lock } from "semaphore-async-await"; -import type {Constellation, bds, tableaux, types} from '@constl/ipa'; -import {ignorerNonDéfinis, suivreBdDeFonction, uneFois} from '@constl/utils-ipa'; - -import type {JSONSchemaType} from 'ajv'; +import type { Constellation, bds, tableaux, types } from "@constl/ipa"; +import { + ignorerNonDéfinis, + suivreBdDeFonction, + uneFois, +} from "@constl/utils-ipa"; +import { isEqual } from "lodash-es"; +import type { JSONSchemaType } from "ajv"; import { BLOCKED_RELEASES_RELEASE_ID_COLUMN, @@ -34,7 +38,7 @@ import { TRUSTED_SITES_NAME_COL, TRUSTED_SITES_SITE_ID_COL, TRUSTED_SITES_TABLE_KEY, -} from './consts.js'; +} from "./consts.js"; import type { BlockedRelease, Collection, @@ -44,15 +48,20 @@ import type { ReleaseWithId, TrustedSite, VariableIds, - possiblyIncompleteVariableIds, -} from './types.js'; -import {variableIdKeys} from './types.js'; -import {removeUndefined} from './utils.js'; + PossiblyIncompleteVariableIds, +} from "./types.js"; +import { variableIdKeys } from "./types.js"; +import { removeUndefined } from "./utils.js"; +import { getConfig } from "./config.js"; type forgetFunction = () => Promise; interface OrbiterEvents { - 'site configured': (args: {siteId: string; variableIds: VariableIds}) => void; + "site configured": (args: { + siteId: string; + swarmId: string; + variableIds: VariableIds; + }) => void; } type RootDbSchema = { @@ -60,39 +69,49 @@ type RootDbSchema = { modDb: string; }; const ROOT_DB_JSON_SCHEMA: JSONSchemaType> = { - type: 'object', + type: "object", properties: { - modDb: {type: 'string', nullable: true}, - swarmId: {type: 'string', nullable: true}, + modDb: { type: "string", nullable: true }, + swarmId: { type: "string", nullable: true }, }, required: [], }; -const OrbiterSiteDbSchema: JSONSchemaType<{modDb: string; swarmId: string}> = { - type: 'object', - properties: { - modDb: { - type: 'string', - }, - swarmId: { - type: 'string', +const OrbiterSiteDbSchema: JSONSchemaType<{ modDb: string; swarmId: string }> = + { + type: "object", + properties: { + modDb: { + type: "string", + }, + swarmId: { + type: "string", + }, }, - }, - required: ['modDb', 'swarmId'], -}; + required: ["modDb", "swarmId"], + }; export class Orbiter { siteId?: string; swarmId?: string; - initialVariableIds: possiblyIncompleteVariableIds; + initialVariableIds: PossiblyIncompleteVariableIds; variableIds?: VariableIds; constellation: Constellation; events: TypedEmitter; - statusType = ['approved', 'deleted', 'pending', 'rejected']; - contentCategories = ['tvShow', 'movie', 'audiobook', 'game', 'book', 'music', 'video', 'other']; + statusType = ["approved", "deleted", "pending", "rejected"]; + contentCategories = [ + "tvShow", + "movie", + "audiobook", + "game", + "book", + "music", + "video", + "other", + ]; constructor({ siteId, @@ -102,14 +121,14 @@ export class Orbiter { }: { siteId?: string; swarmId?: string; - variableIds: possiblyIncompleteVariableIds; + variableIds?: PossiblyIncompleteVariableIds; constellation: Constellation; }) { this.events = new TypedEmitter(); this.siteId = siteId; this.swarmId = swarmId; - this.initialVariableIds = variableIds; + this.initialVariableIds = variableIds || {}; if (this.checkVariableIdsComplete(variableIds)) { this.variableIds = variableIds; @@ -122,60 +141,71 @@ export class Orbiter { async setUpSite(): Promise<{ siteId: string; + swarmId: string; variableIds: VariableIds; }> { // Variables for moderation database const trustedSitesSiteIdVar = this.initialVariableIds.trustedSitesSiteIdVar || - (await this.constellation.variables.créerVariable({catégorie: 'chaîneNonTraductible'})); + (await this.constellation.variables.créerVariable({ + catégorie: "chaîneNonTraductible", + })); const trustedSitesNameVar = this.initialVariableIds.trustedSitesNameVar || - (await this.constellation.variables.créerVariable({catégorie: 'chaîneNonTraductible'})); + (await this.constellation.variables.créerVariable({ + catégorie: "chaîneNonTraductible", + })); const featuredReleasesReleaseIdVar = this.initialVariableIds.featuredReleasesReleaseIdVar || - (await this.constellation.variables.créerVariable({catégorie: 'chaîneNonTraductible'})); + (await this.constellation.variables.créerVariable({ + catégorie: "chaîneNonTraductible", + })); const featuredReleasesStartTimeVar = this.initialVariableIds.featuredReleasesStartTimeVar || - (await this.constellation.variables.créerVariable({catégorie: 'horoDatage'})); + (await this.constellation.variables.créerVariable({ + catégorie: "horoDatage", + })); const featuredReleasesEndTimeVar = this.initialVariableIds.featuredReleasesEndTimeVar || (await this.constellation.variables.créerVariable({ - catégorie: 'horoDatage', + catégorie: "horoDatage", })); const blockedReleasesReleaseIdVar = this.initialVariableIds.blockedReleasesReleaseIdVar || - (await this.constellation.variables.créerVariable({catégorie: 'chaîneNonTraductible'})); + (await this.constellation.variables.créerVariable({ + catégorie: "chaîneNonTraductible", + })); // Variables for releases table const releasesFileVar = this.initialVariableIds.releasesFileVar || (await this.constellation.variables.créerVariable({ - catégorie: 'fichier', + catégorie: "fichier", })); const releasesThumbnailVar = this.initialVariableIds.releasesThumbnailVar || (await this.constellation.variables.créerVariable({ - catégorie: 'fichier', + catégorie: "fichier", })); const releasesCoverVar = this.initialVariableIds.releasesCoverVar || (await this.constellation.variables.créerVariable({ - catégorie: 'chaîneNonTraductible', + catégorie: "chaîneNonTraductible", })); const releasesAuthorVar = this.initialVariableIds.releasesAuthorVar || (await this.constellation.variables.créerVariable({ - catégorie: 'chaîneNonTraductible', + catégorie: "chaîneNonTraductible", })); const releasesMetadataVar = this.initialVariableIds.releasesMetadataVar || (await this.constellation.variables.créerVariable({ - catégorie: 'chaîneNonTraductible', + catégorie: "chaîneNonTraductible", })); const releasesContentNameVar = this.initialVariableIds.releasesContentNameVar || (await this.constellation.variables.créerVariable({ - catégorie: 'chaîneNonTraductible', + catégorie: "chaîneNonTraductible", })); // The release type variable is a bit more complicated, because we need to specify @@ -185,15 +215,15 @@ export class Orbiter { releasesCategoryVar = this.initialVariableIds.releasesCategoryVar; } else { releasesCategoryVar = await this.constellation.variables.créerVariable({ - catégorie: 'chaîneNonTraductible', + catégorie: "chaîneNonTraductible", }); // Specify allowed categories await this.constellation.variables.ajouterRègleVariable({ idVariable: releasesCategoryVar, règle: { - typeRègle: 'valeurCatégorique', + typeRègle: "valeurCatégorique", détails: { - type: 'fixe', + type: "fixe", options: this.contentCategories, }, }, @@ -204,15 +234,15 @@ export class Orbiter { releasesStatusVar = this.initialVariableIds.releasesStatusVar; } else { releasesStatusVar = await this.constellation.variables.créerVariable({ - catégorie: 'chaîneNonTraductible', + catégorie: "chaîneNonTraductible", }); // Specify allowed categories await this.constellation.variables.ajouterRègleVariable({ idVariable: releasesStatusVar, règle: { - typeRègle: 'valeurCatégorique', + typeRègle: "valeurCatégorique", détails: { - type: 'fixe', + type: "fixe", options: this.statusType, }, }, @@ -223,27 +253,27 @@ export class Orbiter { const collectionsNameVar = this.initialVariableIds.collectionsNameVar || (await this.constellation.variables.créerVariable({ - catégorie: 'chaîneNonTraductible', + catégorie: "chaîneNonTraductible", })); const collectionsAuthorVar = this.initialVariableIds.collectionsAuthorVar || (await this.constellation.variables.créerVariable({ - catégorie: 'chaîneNonTraductible', + catégorie: "chaîneNonTraductible", })); const collectionsMetadataVar = this.initialVariableIds.collectionsMetadataVar || (await this.constellation.variables.créerVariable({ - catégorie: 'chaîneNonTraductible', + catégorie: "chaîneNonTraductible", })); const collectionsReleasesVar = this.initialVariableIds.collectionsReleasesVar || (await this.constellation.variables.créerVariable({ - catégorie: 'chaîneNonTraductible', + catégorie: "chaîneNonTraductible", })); const collectionsThumbnailVar = this.initialVariableIds.collectionsThumbnailVar || (await this.constellation.variables.créerVariable({ - catégorie: 'fichier', + catégorie: "fichier", })); // Same thing for collections type variable. @@ -251,16 +281,18 @@ export class Orbiter { if (this.initialVariableIds.collectionsCategoryVar) { collectionsCategoryVar = this.initialVariableIds.collectionsCategoryVar; } else { - collectionsCategoryVar = await this.constellation.variables.créerVariable({ - catégorie: 'chaîneNonTraductible', - }); + collectionsCategoryVar = await this.constellation.variables.créerVariable( + { + catégorie: "chaîneNonTraductible", + }, + ); // Specify allowed categories await this.constellation.variables.ajouterRègleVariable({ idVariable: collectionsCategoryVar, règle: { - typeRègle: 'valeurCatégorique', + typeRègle: "valeurCatégorique", détails: { - type: 'fixe', + type: "fixe", options: this.contentCategories, }, }, @@ -271,15 +303,15 @@ export class Orbiter { collectionsStatusVar = this.initialVariableIds.collectionsStatusVar; } else { collectionsStatusVar = await this.constellation.variables.créerVariable({ - catégorie: 'chaîneNonTraductible', + catégorie: "chaîneNonTraductible", }); // Specify allowed categories await this.constellation.variables.ajouterRègleVariable({ idVariable: collectionsStatusVar, règle: { - typeRègle: 'valeurCatégorique', + typeRègle: "valeurCatégorique", détails: { - type: 'fixe', + type: "fixe", options: this.statusType, }, }, @@ -330,7 +362,7 @@ export class Orbiter { const modDbId = await this.constellation.bds.créerBdDeSchéma({ schéma: { - licence: 'ODbl-1_0', + licence: "ODbl-1_0", tableaux: [ { cols: [ @@ -409,23 +441,24 @@ export class Orbiter { }; const siteId = await this.constellation.créerBdIndépendante({ - type: 'keyvalue', + type: "keyvalue", }); await this.constellation.orbite.appliquerFonctionBdOrbite({ idBd: siteId, - fonction: 'put', - args: ['modDb', modDbId], + fonction: "put", + args: ["modDb", modDbId], }); await this.constellation.orbite.appliquerFonctionBdOrbite({ idBd: siteId, - fonction: 'put', - args: ['swarmId', swarmId], + fonction: "put", + args: ["swarmId", swarmId], }); - this.events.emit('site configured', { + this.events.emit("site configured", { siteId, + swarmId, variableIds, }); @@ -434,12 +467,18 @@ export class Orbiter { return { siteId, + swarmId, variableIds, }; } - checkVariableIdsComplete(ids?: possiblyIncompleteVariableIds): ids is VariableIds { - return !!ids && variableIdKeys.every(k => Object.keys(ids).includes(k) && ids[k]); + checkVariableIdsComplete( + ids?: PossiblyIncompleteVariableIds, + ): ids is VariableIds { + return ( + !!ids && + variableIdKeys.every((k) => Object.keys(ids).includes(k) && ids[k]) + ); } getSwarmDbSchema({ @@ -478,7 +517,7 @@ export class Orbiter { swarmId: string; }): bds.schémaSpécificationBd { return { - licence: 'ODbl-1_0', + licence: "ODbl-1_0", nuées: [swarmId], tableaux: [ { @@ -559,24 +598,40 @@ export class Orbiter { }; } - async listenForSiteConfigured({f}: {f: (x: boolean) => void}): Promise { + async listenForSiteConfigured({ + f, + }: { + f: (x: boolean) => void; + }): Promise { const configured = () => { return !!(this.siteId && this.checkVariableIdsComplete(this.variableIds)); }; f(configured()); const fFinal = () => f(configured()); - this.events.on('site configured', fFinal); + this.events.on("site configured", fFinal); return async () => { - this.events.off('site configured', fFinal); + this.events.off("site configured", fFinal); }; } - async siteConfigured(): Promise<{siteId: string; variableIds: VariableIds}> { - if (this.siteId && this.checkVariableIdsComplete(this.variableIds)) { - return {siteId: this.siteId, variableIds: this.variableIds}; + async siteConfigured(): Promise<{ + siteId: string; + swarmId: string; + variableIds: VariableIds; + }> { + if ( + this.siteId && + this.swarmId && + this.checkVariableIdsComplete(this.variableIds) + ) { + return { + siteId: this.siteId, + swarmId: this.swarmId, + variableIds: this.variableIds, + }; } - return new Promise(resolve => { - this.events.once('site configured', resolve); + return new Promise((resolve) => { + this.events.once("site configured", resolve); }); } @@ -585,14 +640,14 @@ export class Orbiter { swarmId: string; swarmSchema: bds.schémaSpécificationBd; }> { - const {siteId, variableIds} = await this.siteConfigured(); + const { siteId, variableIds } = await this.siteConfigured(); const modDbId = (await uneFois( async (fSuivi: types.schémaFonctionSuivi) => { return await this.constellation.suivreBd({ id: siteId, - type: 'keyvalue', - f: async x => fSuivi(await x.get('modDb')), + type: "keyvalue", + f: async (x) => fSuivi(await x.get("modDb")), schéma: OrbiterSiteDbSchema, }); }, @@ -602,12 +657,12 @@ export class Orbiter { async (fSuivi: types.schémaFonctionSuivi) => { return await this.constellation.suivreBd({ id: siteId, - type: 'keyvalue', - f: async x => fSuivi(await x.get('swarmId')), + type: "keyvalue", + f: async (x) => fSuivi(await x.get("swarmId")), schéma: OrbiterSiteDbSchema, }); }, - x => typeof x === 'string', + (x) => typeof x === "string", )) as string; const swarmSchema = this.getSwarmDbSchema({ @@ -630,14 +685,14 @@ export class Orbiter { siteId?: string; }): Promise { // Use this site's id if none is given - if (!siteId) ({siteId} = await this.siteConfigured()); + if (!siteId) ({ siteId } = await this.siteConfigured()); return await this.constellation.suivreBdDic({ id: siteId, schéma: ROOT_DB_JSON_SCHEMA, - f: x => { - const swarmId = x['swarmId']; - if (typeof swarmId === 'string') f(swarmId); + f: (x) => { + const swarmId = x["swarmId"]; + if (typeof swarmId === "string") f(swarmId); }, }); } @@ -650,14 +705,14 @@ export class Orbiter { siteId?: string; }): Promise { // Use this site's id if none is given - if (!siteId) ({siteId} = await this.siteConfigured()); + if (!siteId) ({ siteId } = await this.siteConfigured()); return await this.constellation.suivreBdDic({ id: siteId, schéma: ROOT_DB_JSON_SCHEMA, - f: x => { - const swarmId = x['modDb']; - if (typeof swarmId === 'string') f(swarmId); + f: (x) => { + const swarmId = x["modDb"]; + if (typeof swarmId === "string") f(swarmId); }, }); } @@ -668,7 +723,7 @@ export class Orbiter { }: { f: (sites?: tableaux.élémentDonnées[]) => void; }): Promise { - const {siteId} = await this.siteConfigured(); + const { siteId } = await this.siteConfigured(); return await suivreBdDeFonction({ fRacine: async ({ @@ -678,8 +733,8 @@ export class Orbiter { }): Promise => { return await this.constellation.suivreBd({ id: siteId, - f: async x => await fSuivreRacine(await x.get('modDb')), - type: 'keyvalue', + f: async (x) => await fSuivreRacine(await x.get("modDb")), + type: "keyvalue", schéma: OrbiterSiteDbSchema, }); }, @@ -689,13 +744,17 @@ export class Orbiter { fSuivreBd, }: { id: string; - fSuivreBd: types.schémaFonctionSuivi[] | undefined>; + fSuivreBd: types.schémaFonctionSuivi< + tableaux.élémentDonnées[] | undefined + >; }) => { - return this.constellation.bds.suivreDonnéesDeTableauParClef({ - idBd: id, - clefTableau: TRUSTED_SITES_TABLE_KEY, - f: fSuivreBd, - }); + return this.constellation.bds.suivreDonnéesDeTableauParClef( + { + idBd: id, + clefTableau: TRUSTED_SITES_TABLE_KEY, + f: fSuivreBd, + }, + ); }, }); } @@ -704,7 +763,7 @@ export class Orbiter { f, siteId, }: { - f: (releases?: {cid: string; id: string}[]) => void; + f: (releases?: { cid: string; id: string }[]) => void; siteId?: string; }): Promise { return await suivreBdDeFonction({ @@ -724,23 +783,27 @@ export class Orbiter { fSuivreBd, }: { id: string; - fSuivreBd: types.schémaFonctionSuivi<{cid: string; id: string}[] | undefined>; + fSuivreBd: types.schémaFonctionSuivi< + { cid: string; id: string }[] | undefined + >; }): Promise => { - return await this.constellation.bds.suivreDonnéesDeTableauParClef({ - idBd: id, - clefTableau: BLOCKED_RELEASES_TABLE_KEY, - f: async blocked => { - if (blocked) - await fSuivreBd( - blocked.map(b => { - return { - cid: b.données[BLOCKED_RELEASES_RELEASE_ID_COLUMN], - id: b.id, - }; - }), - ); + return await this.constellation.bds.suivreDonnéesDeTableauParClef( + { + idBd: id, + clefTableau: BLOCKED_RELEASES_TABLE_KEY, + f: async (blocked) => { + if (blocked) + await fSuivreBd( + blocked.map((b) => { + return { + cid: b.données[BLOCKED_RELEASES_RELEASE_ID_COLUMN], + id: b.id, + }; + }), + ); + }, }, - }); + ); }, }); } @@ -750,7 +813,9 @@ export class Orbiter { siteId, desiredNResults = 1000, }: { - f: types.schémaFonctionSuivi<{release: ReleaseWithId; contributor: string}[]>; + f: types.schémaFonctionSuivi< + { release: ReleaseWithId; contributor: string }[] + >; siteId?: string; desiredNResults?: number; }): Promise { @@ -771,24 +836,27 @@ export class Orbiter { fSuivreBd, }: { id: string; - fSuivreBd: types.schémaFonctionSuivi<{release: ReleaseWithId; contributor: string}[]>; + fSuivreBd: types.schémaFonctionSuivi< + { release: ReleaseWithId; contributor: string }[] + >; }): Promise => { - const {fOublier} = await this.constellation.nuées.suivreDonnéesTableauNuée({ - idNuée: id, - clefTableau: RELEASES_DB_TABLE_KEY, - f: releases => - fSuivreBd( - releases.map(r => ({ - release: { - release: r.élément.données, - id: r.élément.id, - }, - contributor: r.idCompte, - })), - ), - nRésultatsDésirés: desiredNResults, - clefsSelonVariables: false, - }); + const { fOublier } = + await this.constellation.nuées.suivreDonnéesTableauNuée({ + idNuée: id, + clefTableau: RELEASES_DB_TABLE_KEY, + f: (releases) => + fSuivreBd( + releases.map((r) => ({ + release: { + release: r.élément.données, + id: r.élément.id, + }, + contributor: r.idCompte, + })), + ), + nRésultatsDésirés: desiredNResults, + clefsSelonVariables: false, + }); return fOublier; }, }); @@ -799,7 +867,9 @@ export class Orbiter { siteId, desiredNResults = 1000, }: { - f: types.schémaFonctionSuivi<{collection: CollectionWithId; contributor: string}[]>; + f: types.schémaFonctionSuivi< + { collection: CollectionWithId; contributor: string }[] + >; siteId?: string; desiredNResults?: number; }): Promise { @@ -820,24 +890,27 @@ export class Orbiter { fSuivreBd, }: { id: string; - fSuivreBd: types.schémaFonctionSuivi<{collection: CollectionWithId; contributor: string}[]>; + fSuivreBd: types.schémaFonctionSuivi< + { collection: CollectionWithId; contributor: string }[] + >; }): Promise => { - const {fOublier} = await this.constellation.nuées.suivreDonnéesTableauNuée({ - idNuée: id, - clefTableau: COLLECTIONS_DB_TABLE_KEY, - f: collections => - fSuivreBd( - collections.map(c => ({ - collection: { - collection: c.élément.données, - id: c.élément.id, - }, - contributor: c.idCompte, - })), - ), - nRésultatsDésirés: desiredNResults, - clefsSelonVariables: false, - }); + const { fOublier } = + await this.constellation.nuées.suivreDonnéesTableauNuée({ + idNuée: id, + clefTableau: COLLECTIONS_DB_TABLE_KEY, + f: (collections) => + fSuivreBd( + collections.map((c) => ({ + collection: { + collection: c.élément.données, + id: c.élément.id, + }, + contributor: c.idCompte, + })), + ), + nRésultatsDésirés: desiredNResults, + clefsSelonVariables: false, + }); return fOublier; }, }); @@ -847,7 +920,7 @@ export class Orbiter { f, siteId, }: { - f: types.schémaFonctionSuivi<{id: string; featured: FeaturedRelease}[]>; + f: types.schémaFonctionSuivi<{ id: string; featured: FeaturedRelease }[]>; siteId?: string; }): Promise { return await suivreBdDeFonction({ @@ -867,17 +940,25 @@ export class Orbiter { fSuivreBd, }: { id: string; - fSuivreBd: types.schémaFonctionSuivi<{id: string; featured: FeaturedRelease}[]>; + fSuivreBd: types.schémaFonctionSuivi< + { id: string; featured: FeaturedRelease }[] + >; }): Promise => { - const {fOublier} = await this.constellation.nuées.suivreDonnéesTableauNuée( - { - idNuée: id, - clefTableau: FEATURED_RELEASES_TABLE_KEY, - f: featured => - fSuivreBd(featured.map(x => ({id: x.élément.id, featured: x.élément.données}))), - clefsSelonVariables: false, - }, - ); + const { fOublier } = + await this.constellation.nuées.suivreDonnéesTableauNuée( + { + idNuée: id, + clefTableau: FEATURED_RELEASES_TABLE_KEY, + f: (featured) => + fSuivreBd( + featured.map((x) => ({ + id: x.élément.id, + featured: x.élément.données, + })), + ), + clefsSelonVariables: false, + }, + ); return fOublier; }, }); @@ -886,74 +967,80 @@ export class Orbiter { async listenForReleases({ f, }: { - f: types.schémaFonctionSuivi<{release: ReleaseWithId; contributor: string; site: string}[]>; + f: types.schémaFonctionSuivi< + { release: ReleaseWithId; contributor: string; site: string }[] + >; }): Promise { - const {siteId} = await this.siteConfigured(); + const { siteId } = await this.siteConfigured(); type SiteInfo = { blockedCids?: string[]; - entries?: {release: ReleaseWithId; contributor: string}[]; + entries?: { release: ReleaseWithId; contributor: string }[]; fForget?: forgetFunction; }; - const siteInfos: {[site: string]: SiteInfo} = {}; + const siteInfos: { [site: string]: SiteInfo } = {}; let cancelled = false; const lock = new Lock(); const fFinal = async () => { const blockedCids = Object.values(siteInfos) - .map(s => s.blockedCids || []) + .map((s) => s.blockedCids || []) .flat(); const releases = Object.entries(siteInfos) - .map(s => (s[1].entries || []).map(r => ({...r, site: s[0]}))) + .map((s) => (s[1].entries || []).map((r) => ({ ...r, site: s[0] }))) .flat() - .filter(r => !blockedCids.includes(r.release.release.file)); + .filter((r) => !blockedCids.includes(r.release.release.file)); await f(releases); }; - const fFollowTrustedSites = async (sites?: tableaux.élémentDonnées[]) => { - const sitesList = (sites || []).map(s => s.données); + const fFollowTrustedSites = async ( + sites?: tableaux.élémentDonnées[], + ) => { + const sitesList = (sites || []).map((s) => s.données); sitesList.push({ [TRUSTED_SITES_SITE_ID_COL]: siteId, - [TRUSTED_SITES_NAME_COL]: 'Me !', + [TRUSTED_SITES_NAME_COL]: "Me !", }); await lock.acquire(); if (cancelled) return; - const newSites = sitesList.filter(s => !Object.keys(siteInfos).includes(s.siteName)); + const newSites = sitesList.filter( + (s) => !Object.keys(siteInfos).includes(s.siteName), + ); const obsoleteSites = Object.keys(siteInfos).filter( - s => !sitesList.some(x => x.siteName === s), + (s) => !sitesList.some((x) => x.siteName === s), ); for (const site of newSites) { const fsForgetSite: types.schémaFonctionOublier[] = []; - const {siteName} = site; + const { siteName } = site; siteInfos[siteName] = {}; this.listenForSiteBlockedReleases({ - f: async cids => { - siteInfos[siteName].blockedCids = cids?.map(c => c.cid); + f: async (cids) => { + siteInfos[siteName].blockedCids = cids?.map((c) => c.cid); await fFinal(); }, siteId: site.siteId, - }).then(fForget => fsForgetSite.push(fForget)); + }).then((fForget) => fsForgetSite.push(fForget)); this.listenForSiteReleases({ - f: async entries => { + f: async (entries) => { siteInfos[siteName].entries = entries; await fFinal(); }, siteId: site.siteId, - }).then(fOublier => fsForgetSite.push(fOublier)); + }).then((fOublier) => fsForgetSite.push(fOublier)); siteInfos[siteName].fForget = async () => { - await Promise.all(fsForgetSite.map(f => f())); + await Promise.all(fsForgetSite.map((f) => f())); }; await fFinal(); } for (const site of obsoleteSites) { - const {fForget} = siteInfos[site]; + const { fForget } = siteInfos[site]; if (fForget) await fForget(); delete siteInfos[site]; } @@ -967,15 +1054,17 @@ export class Orbiter { await fFollowTrustedSites(); let forgetTrustedSites: types.schémaFonctionOublier; - this.followTrustedSites({f: fFollowTrustedSites}).then( - fForget => (forgetTrustedSites = fForget), + this.followTrustedSites({ f: fFollowTrustedSites }).then( + (fForget) => (forgetTrustedSites = fForget), ); const fForget = async () => { cancelled = true; if (forgetTrustedSites) await forgetTrustedSites(); await Promise.all( - Object.values(siteInfos).map(s => (s.fForget ? s.fForget() : Promise.resolve())), + Object.values(siteInfos).map((s) => + s.fForget ? s.fForget() : Promise.resolve(), + ), ); }; @@ -987,63 +1076,67 @@ export class Orbiter { f, }: { f: types.schémaFonctionSuivi< - {collection: CollectionWithId; contributor: string; site: string}[] + { collection: CollectionWithId; contributor: string; site: string }[] >; }): Promise { - const {siteId} = await this.siteConfigured(); + const { siteId } = await this.siteConfigured(); type SiteInfo = { - entries?: {collection: CollectionWithId; contributor: string}[]; + entries?: { collection: CollectionWithId; contributor: string }[]; fForget?: forgetFunction; }; - const siteInfos: {[site: string]: SiteInfo} = {}; + const siteInfos: { [site: string]: SiteInfo } = {}; let cancelled = false; const lock = new Lock(); const fFinal = async () => { const collections = Object.entries(siteInfos) - .map(s => (s[1].entries || []).map(r => ({...r, site: s[0]}))) + .map((s) => (s[1].entries || []).map((r) => ({ ...r, site: s[0] }))) .flat(); await f(collections); }; - const fFollowTrustedSites = async (sites?: tableaux.élémentDonnées[]) => { - const sitesList = (sites || []).map(s => s.données); + const fFollowTrustedSites = async ( + sites?: tableaux.élémentDonnées[], + ) => { + const sitesList = (sites || []).map((s) => s.données); sitesList.push({ [TRUSTED_SITES_SITE_ID_COL]: siteId, - [TRUSTED_SITES_NAME_COL]: 'Me !', + [TRUSTED_SITES_NAME_COL]: "Me !", }); await lock.acquire(); if (cancelled) return; - const newSites = sitesList.filter(s => !Object.keys(siteInfos).includes(s.siteName)); + const newSites = sitesList.filter( + (s) => !Object.keys(siteInfos).includes(s.siteName), + ); const obsoleteSites = Object.keys(siteInfos).filter( - s => !sitesList.some(x => x.siteName === s), + (s) => !sitesList.some((x) => x.siteName === s), ); for (const site of newSites) { const fsForgetSite: types.schémaFonctionOublier[] = []; - const {siteName} = site; + const { siteName } = site; siteInfos[siteName] = {}; this.listenForSiteCollections({ - f: async entries => { + f: async (entries) => { siteInfos[siteName].entries = entries; await fFinal(); }, siteId: site.siteId, - }).then(fOublier => fsForgetSite.push(fOublier)); + }).then((fOublier) => fsForgetSite.push(fOublier)); siteInfos[siteName].fForget = async () => { - await Promise.all(fsForgetSite.map(f => f())); + await Promise.all(fsForgetSite.map((f) => f())); }; await fFinal(); } for (const site of obsoleteSites) { - const {fForget} = siteInfos[site]; + const { fForget } = siteInfos[site]; if (fForget) await fForget(); delete siteInfos[site]; } @@ -1057,15 +1150,17 @@ export class Orbiter { await fFollowTrustedSites(); let forgetTrustedSites: types.schémaFonctionOublier; - this.followTrustedSites({f: fFollowTrustedSites}).then( - fForget => (forgetTrustedSites = fForget), + this.followTrustedSites({ f: fFollowTrustedSites }).then( + (fForget) => (forgetTrustedSites = fForget), ); const fForget = async () => { cancelled = true; if (forgetTrustedSites) await forgetTrustedSites(); await Promise.all( - Object.values(siteInfos).map(s => (s.fForget ? s.fForget() : Promise.resolve())), + Object.values(siteInfos).map((s) => + s.fForget ? s.fForget() : Promise.resolve(), + ), ); }; @@ -1077,61 +1172,69 @@ export class Orbiter { }: { f: types.schémaFonctionSuivi; }): Promise { - const {siteId} = await this.siteConfigured(); + const { siteId } = await this.siteConfigured(); // Todo: implement filtering of other sites' features according to our blocked CID list? type SiteInfo = { featuredReleases?: FeaturedRelease[]; fForget?: forgetFunction; }; - const siteInfos: {[site: string]: SiteInfo} = {}; + const siteInfos: { [site: string]: SiteInfo } = {}; let cancelled = false; const lock = new Lock(); const fFinal = async () => { const releases = Object.entries(siteInfos) - .map(s => (s[1].featuredReleases || []).map(r => ({...r, site: s[0]}))) + .map((s) => + (s[1].featuredReleases || []).map((r) => ({ ...r, site: s[0] })), + ) .flat(); await f(releases); }; - const fFollowTrustedSites = async (sites?: tableaux.élémentDonnées[]) => { - const sitesList = (sites || []).map(s => s.données); + const fFollowTrustedSites = async ( + sites?: tableaux.élémentDonnées[], + ) => { + const sitesList = (sites || []).map((s) => s.données); sitesList.push({ [TRUSTED_SITES_SITE_ID_COL]: siteId, - [TRUSTED_SITES_NAME_COL]: 'Me !', + [TRUSTED_SITES_NAME_COL]: "Me !", }); await lock.acquire(); if (cancelled) return; - const newSites = sitesList.filter(s => !Object.keys(siteInfos).includes(s.siteName)); + const newSites = sitesList.filter( + (s) => !Object.keys(siteInfos).includes(s.siteName), + ); const obsoleteSites = Object.keys(siteInfos).filter( - s => !sitesList.some(x => x.siteName === s), + (s) => !sitesList.some((x) => x.siteName === s), ); for (const site of newSites) { const fsForgetSite: types.schémaFonctionOublier[] = []; - const {siteName} = site; + const { siteName } = site; siteInfos[siteName] = {}; this.listenForSiteFeaturedReleases({ - f: async entries => { - siteInfos[siteName].featuredReleases = entries.map(x => x.featured); + f: async (entries) => { + siteInfos[siteName].featuredReleases = entries.map( + (x) => x.featured, + ); await fFinal(); }, siteId: site.siteId, - }).then(fOublier => fsForgetSite.push(fOublier)); + }).then((fOublier) => fsForgetSite.push(fOublier)); siteInfos[siteName].fForget = async () => { - await Promise.all(fsForgetSite.map(f => f())); + await Promise.all(fsForgetSite.map((f) => f())); }; await fFinal(); } for (const site of obsoleteSites) { - const {fForget} = siteInfos[site]; + const { fForget } = siteInfos[site]; if (fForget) await fForget(); delete siteInfos[site]; } @@ -1145,15 +1248,17 @@ export class Orbiter { await fFollowTrustedSites(); let forgetTrustedSites: types.schémaFonctionOublier; - this.followTrustedSites({f: fFollowTrustedSites}).then( - fForget => (forgetTrustedSites = fForget), + this.followTrustedSites({ f: fFollowTrustedSites }).then( + (fForget) => (forgetTrustedSites = fForget), ); const fForget = async () => { cancelled = true; if (forgetTrustedSites) await forgetTrustedSites(); await Promise.all( - Object.values(siteInfos).map(s => (s.fForget ? s.fForget() : Promise.resolve())), + Object.values(siteInfos).map((s) => + s.fForget ? s.fForget() : Promise.resolve(), + ), ); }; @@ -1163,7 +1268,7 @@ export class Orbiter { // User functionalities - adding and editing content async addRelease(release: Release): Promise { - const {swarmId, swarmSchema} = await this.orbiterConfig(); + const { swarmId, swarmSchema } = await this.orbiterConfig(); await this.constellation.bds.ajouterÉlémentÀTableauUnique({ schémaBd: swarmSchema, @@ -1174,7 +1279,7 @@ export class Orbiter { } async removeRelease(releaseId: string) { - const {swarmId, swarmSchema} = await this.orbiterConfig(); + const { swarmId, swarmSchema } = await this.orbiterConfig(); await this.constellation.bds.effacerÉlémentDeTableauUnique({ schémaBd: swarmSchema, @@ -1191,7 +1296,7 @@ export class Orbiter { release: Partial; releaseId: string; }): Promise { - const {swarmId, swarmSchema} = await this.orbiterConfig(); + const { swarmId, swarmSchema } = await this.orbiterConfig(); return await this.constellation.bds.modifierÉlémentDeTableauUnique({ vals: release, @@ -1203,7 +1308,7 @@ export class Orbiter { } async addCollection(collection: Collection): Promise { - const {swarmId, swarmSchema} = await this.orbiterConfig(); + const { swarmId, swarmSchema } = await this.orbiterConfig(); await this.constellation.bds.ajouterÉlémentÀTableauUnique({ schémaBd: swarmSchema, @@ -1214,7 +1319,7 @@ export class Orbiter { } async removeCollection(collectionId: string) { - const {swarmId, swarmSchema} = await this.orbiterConfig(); + const { swarmId, swarmSchema } = await this.orbiterConfig(); await this.constellation.bds.effacerÉlémentDeTableauUnique({ schémaBd: swarmSchema, @@ -1231,7 +1336,7 @@ export class Orbiter { collection: Partial; collectionId: string; }): Promise { - const {swarmId, swarmSchema} = await this.orbiterConfig(); + const { swarmId, swarmSchema } = await this.orbiterConfig(); return await this.constellation.bds.modifierÉlémentDeTableauUnique({ vals: collection, @@ -1242,18 +1347,23 @@ export class Orbiter { }); } - async getCollectionReleasesSetId({collectionId}: {collectionId: string}): Promise { + async getCollectionReleasesSetId({ + collectionId, + }: { + collectionId: string; + }): Promise { const collections = await uneFois( async ( fSuivi: types.schémaFonctionSuivi, ): Promise => { return await this.listenForCollections({ - f: async collections => fSuivi(collections.map(c => c.collection)), + f: async (collections) => + fSuivi(collections.map((c) => c.collection)), }); }, ); - const collection = collections.find(c => c.id === collectionId); - if (!collection) throw new Error('Collection not found.'); + const collection = collections.find((c) => c.id === collectionId); + if (!collection) throw new Error("Collection not found."); return collection.collection[COLLECTIONS_RELEASES_COLUMN]; } @@ -1264,10 +1374,12 @@ export class Orbiter { releaseId: string; collectionId: string; }): Promise { - const collectionReleases = await this.getCollectionReleasesSetId({collectionId}); + const collectionReleases = await this.getCollectionReleasesSetId({ + collectionId, + }); await this.constellation.orbite.appliquerFonctionBdOrbite({ idBd: collectionReleases, - fonction: 'add', + fonction: "add", args: [releaseId], }); } @@ -1279,54 +1391,90 @@ export class Orbiter { releaseId: string; collectionId: string; }): Promise { - const collectionReleases = await this.getCollectionReleasesSetId({collectionId}); + const collectionReleases = await this.getCollectionReleasesSetId({ + collectionId, + }); await this.constellation.orbite.appliquerFonctionBdOrbite({ idBd: collectionReleases, - fonction: 'remove', + fonction: "remove", args: [releaseId], }); } // User profile functions - async changeName({name, language}: {name?: string; language: string}): Promise { - if (name) await this.constellation.profil.sauvegarderNom({langue: language, nom: name}); - else await this.constellation.profil.effacerNom({langue: language}); + async changeName({ + name, + language, + }: { + name?: string; + language: string; + }): Promise { + if (name) + await this.constellation.profil.sauvegarderNom({ + langue: language, + nom: name, + }); + else await this.constellation.profil.effacerNom({ langue: language }); } async changeProfilePhoto({ image, }: { - image?: {contenu: Uint8Array; nomFichier: string}; + image?: { contenu: Uint8Array; nomFichier: string }; }): Promise { - if (image) return await this.constellation.profil.sauvegarderImage({image}); + if (image) + return await this.constellation.profil.sauvegarderImage({ image }); else return await this.constellation.profil.effacerImage(); } - async addContactInfo({type, contact}: {type: string; contact: string}): Promise { - return await this.constellation.profil.sauvegarderContact({type, contact}); + async addContactInfo({ + type, + contact, + }: { + type: string; + contact: string; + }): Promise { + return await this.constellation.profil.sauvegarderContact({ + type, + contact, + }); } - async removeContactInfo({type, contact}: {type: string; contact?: string}): Promise { - return await this.constellation.profil.effacerContact({type, contact}); + async removeContactInfo({ + type, + contact, + }: { + type: string; + contact?: string; + }): Promise { + return await this.constellation.profil.effacerContact({ type, contact }); } async deleteAccount(): Promise { return await this.constellation.fermerCompte(); } - async listenForAccountId({f}: {f: (account?: string) => void}): Promise { - return await this.constellation.suivreIdCompte({f}); + async listenForAccountId({ + f, + }: { + f: (account?: string) => void; + }): Promise { + return await this.constellation.suivreIdCompte({ f }); } - async listenForAccountExists({f}: {f: (exists: boolean) => void}): Promise { - return await this.constellation.profil.suivreInitialisé({f}); + async listenForAccountExists({ + f, + }: { + f: (exists: boolean) => void; + }): Promise { + return await this.constellation.profil.suivreInitialisé({ f }); } async listenForNameChange({ f, accountId, }: { - f: (name: {[language: string]: string}) => void; + f: (name: { [language: string]: string }) => void; accountId?: string; }): Promise { return await this.constellation.profil.suivreNoms({ @@ -1339,7 +1487,7 @@ export class Orbiter { f, accountId, }: { - f: types.schémaFonctionSuivi<{type: string; contact: string}[]>; + f: types.schémaFonctionSuivi<{ type: string; contact: string }[]>; accountId?: string; }): Promise { return await this.constellation.profil.suivreContacts({ @@ -1355,35 +1503,50 @@ export class Orbiter { f: types.schémaFonctionSuivi; accountId?: string; }): Promise { - return await this.constellation.profil.suivreImage({f, idCompte: accountId}); + return await this.constellation.profil.suivreImage({ + f, + idCompte: accountId, + }); } - async followCanUpload({f, userId}: {f: (canUpload: boolean) => void; userId?: string}) { - const {swarmId} = await this.orbiterConfig(); + async followCanUpload({ + f, + userId, + }: { + f: (canUpload: boolean) => void; + userId?: string; + }) { + const { swarmId } = await this.orbiterConfig(); userId = userId || (await this.constellation.obtIdCompte()); // TODO: this should be refactored into Constellation - const info: {philosophy?: 'IUPG' | 'GUPI'; memberStatus?: 'exclus' | 'accepté' | undefined} = - {}; + const info: { + philosophy?: "IUPG" | "GUPI"; + memberStatus?: "exclus" | "accepté" | undefined; + } = {}; const fFinal = () => { - if (info.philosophy === 'IUPG') f(info.memberStatus !== 'exclus'); - else f(info.memberStatus === 'accepté'); + if (info.philosophy === "IUPG") f(info.memberStatus !== "exclus"); + else f(info.memberStatus === "accepté"); }; - const forgetPhilosophy = await this.constellation.nuées.suivrePhilosophieAutorisation({ - idNuée: swarmId, - f: philosophy => { - // Guilty until proven innocent (invitation-only) or innocent until proven guilty (open by default) - info.philosophy = philosophy === 'CJPI' ? 'GUPI' : 'IUPG'; - fFinal(); - }, - }); - const forgetAuthorisations = await this.constellation.nuées.suivreAutorisationsMembresDeNuée({ - idNuée: swarmId, - f: members => { - info.memberStatus = members.find(m => m.idCompte === userId)?.statut; - fFinal(); - }, - }); + const forgetPhilosophy = + await this.constellation.nuées.suivrePhilosophieAutorisation({ + idNuée: swarmId, + f: (philosophy) => { + // Guilty until proven innocent (invitation-only) or innocent until proven guilty (open by default) + info.philosophy = philosophy === "CJPI" ? "GUPI" : "IUPG"; + fFinal(); + }, + }); + const forgetAuthorisations = + await this.constellation.nuées.suivreAutorisationsMembresDeNuée({ + idNuée: swarmId, + f: (members) => { + info.memberStatus = members.find( + (m) => m.idCompte === userId, + )?.statut; + fFinal(); + }, + }); return async () => { await forgetPhilosophy(); await forgetAuthorisations(); @@ -1402,7 +1565,7 @@ export class Orbiter { startTime: number; endTime: number; }) { - const {modDbId} = await this.orbiterConfig(); + const { modDbId } = await this.orbiterConfig(); return ( await this.constellation.bds.ajouterÉlémentÀTableauParClef({ @@ -1421,58 +1584,74 @@ export class Orbiter { f, userId, }: { - f: (isMod: 'ADMIN' | 'MODERATOR' | undefined) => void; + f: (isMod: "ADMIN" | "MODERATOR" | undefined) => void; userId?: string; }): Promise { // User current user if none is specified. userId = userId || (await this.constellation.obtIdCompte()); - const {siteId} = await this.siteConfigured(); - - const resolveModType = (x?: 'MODÉRATEUR' | 'MEMBRE'): 'ADMIN' | 'MODERATOR' | undefined => { - return x === 'MODÉRATEUR' ? 'ADMIN' : x === 'MEMBRE' ? 'MODERATOR' : undefined; + const { siteId } = await this.siteConfigured(); + + const resolveModType = ( + x?: "MODÉRATEUR" | "MEMBRE", + ): "ADMIN" | "MODERATOR" | undefined => { + return x === "MODÉRATEUR" + ? "ADMIN" + : x === "MEMBRE" + ? "MODERATOR" + : undefined; }; return await this.constellation.suivreAccèsBd({ id: siteId, - f: x => f(resolveModType(x.find(y => y.idCompte === userId)?.rôle)), + f: (x) => f(resolveModType(x.find((y) => y.idCompte === userId)?.rôle)), }); } - async inviteModerator({userId, admin = false}: {userId: string; admin?: boolean}): Promise { + async inviteModerator({ + userId, + admin = false, + }: { + userId: string; + admin?: boolean; + }): Promise { // Invitations are not revocable ! They can, however, be upgraded (moderator => admin), though not downgraded. - const {siteId} = await this.siteConfigured(); - const {modDbId, swarmId} = await this.orbiterConfig(); + const { siteId } = await this.siteConfigured(); + const { modDbId, swarmId } = await this.orbiterConfig(); await this.constellation.nuées.inviterAuteur({ idNuée: swarmId, idCompteAuteur: userId, - rôle: admin ? 'MODÉRATEUR' : 'MEMBRE', + rôle: admin ? "MODÉRATEUR" : "MEMBRE", }); await this.constellation.bds.inviterAuteur({ idBd: modDbId, idCompteAuteur: userId, - rôle: admin ? 'MODÉRATEUR' : 'MEMBRE', + rôle: admin ? "MODÉRATEUR" : "MEMBRE", }); if (admin) { - await this.constellation.donnerAccès({idBd: siteId, identité: userId, rôle: 'MODÉRATEUR'}); + await this.constellation.donnerAccès({ + idBd: siteId, + identité: userId, + rôle: "MODÉRATEUR", + }); } } - async blockRelease({cid}: {cid: string}): Promise { - const {modDbId} = await this.orbiterConfig(); + async blockRelease({ cid }: { cid: string }): Promise { + const { modDbId } = await this.orbiterConfig(); return ( await this.constellation.bds.ajouterÉlémentÀTableauParClef({ idBd: modDbId, clefTableau: BLOCKED_RELEASES_TABLE_KEY, - vals: {[BLOCKED_RELEASES_RELEASE_ID_COLUMN]: cid}, + vals: { [BLOCKED_RELEASES_RELEASE_ID_COLUMN]: cid }, }) )[0]; } - async unblockRelease({id}: {id: string}): Promise { - const {modDbId} = await this.orbiterConfig(); + async unblockRelease({ id }: { id: string }): Promise { + const { modDbId } = await this.orbiterConfig(); await this.constellation.bds.effacerÉlémentDeTableauParClef({ idBd: modDbId, @@ -1482,16 +1661,17 @@ export class Orbiter { } async makeSitePrivate(): Promise { - const {swarmId} = await this.orbiterConfig(); + const { swarmId } = await this.orbiterConfig(); const userId = await this.constellation.obtIdCompte(); // Both releases and collections swarms share the same swarm and authorisation rules, so changing one will update both - const authId = await this.constellation.nuées.obtGestionnaireAutorisationsDeNuée({ - idNuée: swarmId, - }); + const authId = + await this.constellation.nuées.obtGestionnaireAutorisationsDeNuée({ + idNuée: swarmId, + }); await this.constellation.nuées.changerPhisolophieAutorisation({ idAutorisation: authId, - philosophie: 'CJPI', + philosophie: "CJPI", }); await this.constellation.nuées.accepterMembreNuée({ idNuée: swarmId, @@ -1500,20 +1680,21 @@ export class Orbiter { } async makeSitePublic(): Promise { - const {swarmId} = await this.orbiterConfig(); + const { swarmId } = await this.orbiterConfig(); // Both releases and collections swarms share the same swarm and authorisation rules, so changing one will update both - const authId = await this.constellation.nuées.obtGestionnaireAutorisationsDeNuée({ - idNuée: swarmId, - }); + const authId = + await this.constellation.nuées.obtGestionnaireAutorisationsDeNuée({ + idNuée: swarmId, + }); await this.constellation.nuées.changerPhisolophieAutorisation({ idAutorisation: authId, - philosophie: 'IJPC', + philosophie: "IJPC", }); } - async inviteUser({userId}: {userId: string}): Promise { - const {swarmId} = await this.orbiterConfig(); + async inviteUser({ userId }: { userId: string }): Promise { + const { swarmId } = await this.orbiterConfig(); // Both releases and collections swarms share the same swarm and authorisation rules, so changing one will update both await this.constellation.nuées.accepterMembreNuée({ @@ -1522,8 +1703,8 @@ export class Orbiter { }); } - async blockUser({userId}: {userId: string}): Promise { - const {swarmId} = await this.orbiterConfig(); + async blockUser({ userId }: { userId: string }): Promise { + const { swarmId } = await this.orbiterConfig(); // Both releases and collections swarms share the same swarm and authorisation rules, so changing one will update both await this.constellation.nuées.exclureMembreDeNuée({ @@ -1532,22 +1713,35 @@ export class Orbiter { }); } - async trustSite({siteId, siteName}: {siteName: string; siteId: string}): Promise { - const {modDbId} = await this.orbiterConfig(); + async trustSite({ + siteId, + siteName, + }: { + siteName: string; + siteId: string; + }): Promise { + const { modDbId } = await this.orbiterConfig(); - const elementIds = await this.constellation.bds.ajouterÉlémentÀTableauParClef({ - idBd: modDbId, - clefTableau: TRUSTED_SITES_TABLE_KEY, - vals: { - [TRUSTED_SITES_SITE_ID_COL]: siteId, - [TRUSTED_SITES_NAME_COL]: siteName, - }, - }); + const elementIds = + await this.constellation.bds.ajouterÉlémentÀTableauParClef({ + idBd: modDbId, + clefTableau: TRUSTED_SITES_TABLE_KEY, + vals: { + [TRUSTED_SITES_SITE_ID_COL]: siteId, + [TRUSTED_SITES_NAME_COL]: siteName, + }, + }); return elementIds[0]; } - async editTrustedSite({elementId, site}: {elementId: string; site: Partial}) { - const {modDbId} = await this.orbiterConfig(); + async editTrustedSite({ + elementId, + site, + }: { + elementId: string; + site: Partial; + }) { + const { modDbId } = await this.orbiterConfig(); await this.constellation.bds.modifierÉlémentDeTableauParClef({ idBd: modDbId, @@ -1557,29 +1751,27 @@ export class Orbiter { }); } - async untrustSite({elementId}: {elementId: string}) { - const {modDbId} = await this.orbiterConfig(); + async untrustSite({ elementId }: { elementId: string }) { + const { modDbId } = await this.orbiterConfig(); await this.constellation.bds.effacerÉlémentDeTableauParClef({ idBd: modDbId, clefTableau: TRUSTED_SITES_TABLE_KEY, idÉlément: elementId, }); } -}; +} export const createOrbiter = async ({ - siteId, - swarmId, - variableIds = {}, constellation, }: { - siteId?: string; - swarmId?: string; - variableIds?: possiblyIncompleteVariableIds; constellation: Constellation; }) => { - - const orbiter = new Orbiter({siteId, swarmId, variableIds, constellation}) - await orbiter.setUpSite(); - return orbiter; -} \ No newline at end of file + const existingConfig = getConfig({ + dir: await constellation.dossier(), + }); + + const orbiter = new Orbiter({ constellation, ...existingConfig }); + const config = await orbiter.setUpSite(); + if (!isEqual(existingConfig, config)) saveConfig(config); + return { config, orbiter }; +}; diff --git a/src/types.ts b/src/types.ts index 5ee3e6e..d0bac51 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,3 +1,5 @@ +import { type JTDSchemaType } from "ajv/dist/jtd"; + import type { BLOCKED_RELEASES_RELEASE_ID_COLUMN, COLLECTIONS_AUTHOR_COLUMN, @@ -20,35 +22,66 @@ import type { RELEASES_THUMBNAIL_COLUMN, TRUSTED_SITES_NAME_COL, TRUSTED_SITES_SITE_ID_COL, -} from './consts'; +} from "./consts"; export const variableIdKeys = [ - 'trustedSitesSiteIdVar', - 'trustedSitesNameVar', - 'releasesContentNameVar', - 'releasesFileVar', - 'releasesThumbnailVar', - 'releasesCoverVar', - 'releasesAuthorVar', - 'releasesMetadataVar', - 'releasesCategoryVar', - 'releasesStatusVar', - 'collectionsNameVar', - 'collectionsAuthorVar', - 'collectionsThumbnailVar', - 'collectionsMetadataVar', - 'collectionsCategoryVar', - 'collectionsReleasesVar', - 'collectionsStatusVar', - 'featuredReleasesReleaseIdVar', - 'featuredReleasesStartTimeVar', - 'featuredReleasesEndTimeVar', - 'blockedReleasesReleaseIdVar', + "trustedSitesSiteIdVar", + "trustedSitesNameVar", + "releasesContentNameVar", + "releasesFileVar", + "releasesThumbnailVar", + "releasesCoverVar", + "releasesAuthorVar", + "releasesMetadataVar", + "releasesCategoryVar", + "releasesStatusVar", + "collectionsNameVar", + "collectionsAuthorVar", + "collectionsThumbnailVar", + "collectionsMetadataVar", + "collectionsCategoryVar", + "collectionsReleasesVar", + "collectionsStatusVar", + "featuredReleasesReleaseIdVar", + "featuredReleasesStartTimeVar", + "featuredReleasesEndTimeVar", + "blockedReleasesReleaseIdVar", ] as const; export type VariableIds = Record<(typeof variableIdKeys)[number], string>; -export type possiblyIncompleteVariableIds = Partial; +export type PossiblyIncompleteVariableIds = Partial; + +export type OrbiterConfig = { + siteId: string; + swarmId: string; + variableIds: VariableIds; +}; + +export type RecursivePartial = { + // From https://stackoverflow.com/questions/41980195/recursive-partialt-in-typescript + [P in keyof T]?: T[P] extends (infer U)[] + ? RecursivePartial[] + : T[P] extends object | undefined + ? RecursivePartial + : T[P]; +}; + +export type PossiblyIncompleteOrbiterConfig = RecursivePartial; + +export const possiblyIncompleteOrbiterConfigSchema: JTDSchemaType = + { + optionalProperties: { + siteId: { type: "string" }, + swarmId: { type: "string" }, + variableIds: { + optionalProperties: Object.fromEntries( + variableIdKeys.map((v) => [v, { type: "string" }]), + ) as { [P in keyof VariableIds]: { type: "string" } }, + }, + }, + }; +export type ConfigMode = "vite"; // Todo: add for other compilers? export type Release = { [RELEASES_NAME_COLUMN]: string; diff --git a/src/utils.ts b/src/utils.ts index ebc27c1..193acee 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -1,3 +1,7 @@ -export const removeUndefined = (vals: T): T => { - return Object.fromEntries(Object.entries(vals).filter(x => x[1] !== undefined)) as T; // Not exactly precise, but depends on [this problem](https://stackoverflow.com/questions/54489817/typescript-partialt-type-without-undefined) to be fixed. +export const removeUndefined = ( + vals: T, +): T => { + return Object.fromEntries( + Object.entries(vals).filter((x) => x[1] !== undefined), + ) as T; // Not exactly precise, but depends on [this problem](https://stackoverflow.com/questions/54489817/typescript-partialt-type-without-undefined) to be fixed. }; diff --git a/src/version.ts b/src/version.ts index 719e000..8e0543d 100644 --- a/src/version.ts +++ b/src/version.ts @@ -1,2 +1,2 @@ // Generated by genversion. -export const version = '0.0.3'; +export const version = "0.0.3"; diff --git a/test/orbiter.test.ts b/test/orbiter.test.ts index 0f99b7e..6a347fb 100644 --- a/test/orbiter.test.ts +++ b/test/orbiter.test.ts @@ -1,6 +1,4 @@ -import { - version, -} from "@/index.js"; +import { version } from "@/index.js"; import { expect } from "aegir/chai"; describe("Orbiter", () => {