From 977366f8723fd581b8ab2476e23539c401786602 Mon Sep 17 00:00:00 2001 From: Lucy Cifferello <12953208+elvece@users.noreply.github.com> Date: Mon, 30 Jan 2023 15:03:30 -0700 Subject: [PATCH 1/6] make name optional on union type --- types.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/types.ts b/types.ts index d3702a5..23cd071 100644 --- a/types.ts +++ b/types.ts @@ -318,8 +318,8 @@ export type ValueSpecUnion = { /** What tag for the specification, for tag unions */ tag: { id: string; - name: string; - description?: string; + name?: string; // optional for backwards compatibility - removed in 0.3.4 + description?: string; // optional for backwards compatibility - removed in 0.3.4 "variant-names": { [key: string]: string; }; From 2d41dd4c789314a16a81ce2a103c2f868fe90c6c Mon Sep 17 00:00:00 2001 From: Lucy Cifferello <12953208+elvece@users.noreply.github.com> Date: Mon, 30 Jan 2023 15:12:55 -0700 Subject: [PATCH 2/6] adjust comment --- types.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/types.ts b/types.ts index 23cd071..0677ac7 100644 --- a/types.ts +++ b/types.ts @@ -220,7 +220,7 @@ export type WithNullable = T & { export type DefaultString = | string | { - /** The chars available for the randome generation */ + /** The chars available for the random generation */ charset?: string; /** Length that we generate to */ len: number; @@ -318,8 +318,10 @@ export type ValueSpecUnion = { /** What tag for the specification, for tag unions */ tag: { id: string; - name?: string; // optional for backwards compatibility - removed in 0.3.4 - description?: string; // optional for backwards compatibility - removed in 0.3.4 + /** @deprecated - optional only for backwards compatibility */ + name?: string; + /** @deprecated - optional only for backwards compatibility */ + description?: string; "variant-names": { [key: string]: string; }; From 66f12ffa740dc085d5c52dd030d3627d43a884b4 Mon Sep 17 00:00:00 2001 From: Lucy Cifferello <12953208+elvece@users.noreply.github.com> Date: Mon, 30 Jan 2023 15:57:05 -0700 Subject: [PATCH 3/6] add wrapper type for optional description for unions --- types.ts | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/types.ts b/types.ts index 0677ac7..65479eb 100644 --- a/types.ts +++ b/types.ts @@ -190,6 +190,14 @@ export type WithDescription = T & { warning?: string; }; +export type WithOptionalDescription = T & { + /** @deprecated - optional only for backwards compatibility */ + description?: string; + /** @deprecated - optional only for backwards compatibility */ + name?: string; + warning?: string; +}; + export type ListSpec = { spec: T; range: string; @@ -275,7 +283,7 @@ export type ValueSpecAny = > | Tag<"list", ValueSpecList> | Tag<"object", WithDescription>> - | Tag<"union", WithDescription>> + | Tag<"union", WithOptionalDescription>> | Tag< "pointer", WithDescription< @@ -318,9 +326,7 @@ export type ValueSpecUnion = { /** What tag for the specification, for tag unions */ tag: { id: string; - /** @deprecated - optional only for backwards compatibility */ - name?: string; - /** @deprecated - optional only for backwards compatibility */ + name: string; description?: string; "variant-names": { [key: string]: string; From 8da2251238b80ca8fd3b5e86b23c47a13c3d8e71 Mon Sep 17 00:00:00 2001 From: BluJ Date: Mon, 13 Feb 2023 15:18:31 -0700 Subject: [PATCH 4/6] chore: Update default --- utils/propertiesMatcher.test.ts | 26 ++++++++++++++++++++++++++ utils/propertiesMatcher.ts | 6 ++++-- 2 files changed, 30 insertions(+), 2 deletions(-) diff --git a/utils/propertiesMatcher.test.ts b/utils/propertiesMatcher.test.ts index 7239228..6fb3707 100644 --- a/utils/propertiesMatcher.test.ts +++ b/utils/propertiesMatcher.test.ts @@ -596,6 +596,32 @@ const { test } = Deno; console.log("Checker = ", matches.Parser.parserAsString(checker.parser)); checker.unsafeCast({ mode: "automatic", size: 1234 }); }); + + test("Something that broke", () => { + const checker = PM.typeFromProps({ + pubkey_whitelist: { + name: "Pubkey Whitelist (hex)", + description: + "A list of pubkeys that are permitted to publish through your relay. A minimum, you need to enter your own Nostr hex (not npub) pubkey. Go to https://damus.io/key/ to convert from npub to hex.", + type: "list", + nullable: true, + range: "[1,*)", + subtype: "string", + spec: { + placeholder: "hex (not npub) pubkey", + pattern: "[0-9a-fA-F]{3}", + "pattern-description": + "Must be a valid 64-digit hexadecimal value (ie a Nostr hex pubkey, not an npub). Go to https://damus.io/key/ to convert npub to hex.", + }, + default: [] as string[], // [] as string [] + }, + } as const); + + checker.unsafeCast({ + pubkey_whitelist: ["aaa"], + }); + }); + test("Full spec", () => { const checker = PM.typeFromProps(bitcoinProperties); diff --git a/utils/propertiesMatcher.ts b/utils/propertiesMatcher.ts index e4ca9d1..e9a2862 100644 --- a/utils/propertiesMatcher.ts +++ b/utils/propertiesMatcher.ts @@ -168,7 +168,7 @@ const isGenerator = matches.shape({ charset: matches.string, len: matches.number function defaultNullable(parser: matches.Parser, value: unknown) { if (matchDefault.test(value)) { if (isGenerator(value.default)) return parser.defaultTo(parser.unsafeCast(generateDefault(value.default))); - return parser.defaultTo(parser.unsafeCast(value.default)); + return parser.defaultTo(value.default); } if (matchNullable.test(value)) return parser.optional(); return parser; @@ -215,11 +215,13 @@ export function guardAll(value: A): matches.Parser true); + const { default: _, ...arrayOfSpec } = spec; + const subtype = matchSubType.unsafeCast(value).subtype; return defaultNullable( matches // deno-lint-ignore no-explicit-any - .arrayOf(guardAll({ type: subtype, ...spec } as any)) + .arrayOf(guardAll({ type: subtype, ...arrayOfSpec } as any)) .validate((x) => rangeValidate(x.length), "valid length"), value // deno-lint-ignore no-explicit-any From 560ac13c2aacda5880d7e4d3ca551f98ced6c45b Mon Sep 17 00:00:00 2001 From: BluJ Date: Mon, 13 Feb 2023 15:21:04 -0700 Subject: [PATCH 5/6] chore: Change the message --- utils/propertiesMatcher.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/utils/propertiesMatcher.test.ts b/utils/propertiesMatcher.test.ts index 6fb3707..e462fef 100644 --- a/utils/propertiesMatcher.test.ts +++ b/utils/propertiesMatcher.test.ts @@ -597,7 +597,7 @@ const { test } = Deno; checker.unsafeCast({ mode: "automatic", size: 1234 }); }); - test("Something that broke", () => { + test("A default that is invalid according to the tests", () => { const checker = PM.typeFromProps({ pubkey_whitelist: { name: "Pubkey Whitelist (hex)", From c77eed0884ac91a6a2975aaed412a0427aa236e4 Mon Sep 17 00:00:00 2001 From: BluJ Date: Mon, 13 Feb 2023 17:02:43 -0700 Subject: [PATCH 6/6] chore: Add in the new effects --- compat/migrations.ts | 50 ++++------ types.ts | 217 +++++++++++++++++-------------------------- 2 files changed, 102 insertions(+), 165 deletions(-) diff --git a/compat/migrations.ts b/compat/migrations.ts index fac5034..98260a3 100644 --- a/compat/migrations.ts +++ b/compat/migrations.ts @@ -16,14 +16,11 @@ export interface NoRepeat { * @param noFail (optional, default:false) whether or not to fail the migration if fn throws an error * @returns a migraion function */ -export function updateConfig< - version extends string, - type extends "up" | "down", - >( - fn: (config: T.Config, effects: T.Effects) => T.Config | Promise, - configured: boolean, - noRepeat?: NoRepeat, - noFail = false, +export function updateConfig( + fn: (config: T.Config, effects: T.Effects) => T.Config | Promise, + configured: boolean, + noRepeat?: NoRepeat, + noFail = false ): M.MigrationFn { return M.migrationFn(async (effects: T.Effects) => { await noRepeatGuard(effects, noRepeat, async () => { @@ -45,20 +42,15 @@ export function updateConfig< }); } -export async function noRepeatGuard< - version extends string, - type extends "up" | "down", - >( - effects: T.Effects, - noRepeat: NoRepeat | undefined, - fn: () => Promise, +export async function noRepeatGuard( + effects: T.Effects, + noRepeat: NoRepeat | undefined, + fn: () => Promise ): Promise { if (!noRepeat) { return fn(); } - if ( - !await util.exists(effects, { path: "start9/migrations", volumeId: "main" }) - ) { + if (!(await util.exists(effects, { path: "start9/migrations", volumeId: "main" }))) { await effects.createDir({ path: "start9/migrations", volumeId: "main" }); } const migrationPath = { @@ -66,7 +58,7 @@ export async function noRepeatGuard< volumeId: "main", }; if (noRepeat.type === "up") { - if (!await util.exists(effects, migrationPath)) { + if (!(await util.exists(effects, migrationPath))) { await fn(); await effects.writeFile({ ...migrationPath, toWrite: "" }); } @@ -81,11 +73,9 @@ export async function noRepeatGuard< export async function initNoRepeat( effects: T.Effects, migrations: M.MigrationMapping, - startingVersion: string, + startingVersion: string ) { - if ( - !await util.exists(effects, { path: "start9/migrations", volumeId: "main" }) - ) { + if (!(await util.exists(effects, { path: "start9/migrations", volumeId: "main" }))) { const starting = EmVer.parse(startingVersion); await effects.createDir({ path: "start9/migrations", volumeId: "main" }); for (const version in migrations) { @@ -103,19 +93,11 @@ export async function initNoRepeat( export function fromMapping( migrations: M.MigrationMapping, - currentVersion: string, + currentVersion: string ): T.ExpectedExports.migration { const inner = M.fromMapping(migrations, currentVersion); - return async ( - effects: T.Effects, - version: string, - direction?: unknown, - ) => { - await initNoRepeat( - effects, - migrations, - direction === "from" ? version : currentVersion, - ); + return async (effects: T.Effects, version: string, direction?: unknown) => { + await initNoRepeat(effects, migrations, direction === "from" ? version : currentVersion); return inner(effects, version, direction); }; } diff --git a/types.ts b/types.ts index 65479eb..8dfe804 100644 --- a/types.ts +++ b/types.ts @@ -1,10 +1,8 @@ // deno-lint-ignore no-namespace export namespace ExpectedExports { + version: 2; /** Set configuration is called after we have modified and saved the configuration in the embassy ui. Use this to make a file for the docker to read from for configuration. */ - export type setConfig = ( - effects: Effects, - input: Config, - ) => Promise>; + export type setConfig = (effects: Effects, input: Config) => Promise>; /** Get configuration returns a shape that describes the format that the embassy ui will generate, and later send to the set config */ export type getConfig = (effects: Effects) => Promise>; /** These are how we make sure the our dependency configurations are valid and if not how to fix them. */ @@ -12,31 +10,17 @@ export namespace ExpectedExports { /** For backing up service data though the embassyOS UI */ export type createBackup = (effects: Effects) => Promise>; /** For restoring service data that was previously backed up using the embassyOS UI create backup flow. Backup restores are also triggered via the embassyOS UI, or doing a system restore flow during setup. */ - export type restoreBackup = ( - effects: Effects, - ) => Promise>; + export type restoreBackup = (effects: Effects) => Promise>; /** Properties are used to get values from the docker, like a username + password, what ports we are hosting from */ - export type properties = ( - effects: Effects, - ) => Promise>; + export type properties = (effects: Effects) => Promise>; export type health = { /** Should be the health check id */ - [id: string]: ( - effects: Effects, - dateMs: number, - ) => Promise>; + [id: string]: (effects: Effects, dateMs: number) => Promise>; }; - export type migration = ( - effects: Effects, - version: string, - ...args: unknown[] - ) => Promise>; + export type migration = (effects: Effects, version: string, ...args: unknown[]) => Promise>; export type action = { - [id: string]: ( - effects: Effects, - config?: Config, - ) => Promise>; + [id: string]: (effects: Effects, config?: Config) => Promise>; }; /** @@ -49,35 +33,32 @@ export namespace ExpectedExports { /** Used to reach out from the pure js runtime */ export type Effects = { /** Usable when not sandboxed */ - writeFile( - input: { path: string; volumeId: string; toWrite: string }, - ): Promise; + writeFile(input: { path: string; volumeId: string; toWrite: string }): Promise; readFile(input: { volumeId: string; path: string }): Promise; metadata(input: { volumeId: string; path: string }): Promise; /** Create a directory. Usable when not sandboxed */ createDir(input: { volumeId: string; path: string }): Promise; + + readDir(input: { volumeId: string; path: string }): Promise; /** Remove a directory. Usable when not sandboxed */ removeDir(input: { volumeId: string; path: string }): Promise; removeFile(input: { volumeId: string; path: string }): Promise; /** Write a json file into an object. Usable when not sandboxed */ - writeJsonFile( - input: { volumeId: string; path: string; toWrite: Record }, - ): Promise; + writeJsonFile(input: { volumeId: string; path: string; toWrite: Record }): Promise; /** Read a json file into an object */ - readJsonFile( - input: { volumeId: string; path: string }, - ): Promise>; + readJsonFile(input: { volumeId: string; path: string }): Promise>; - runCommand( - input: { command: string; args?: string[]; timeoutMillis?: number }, - ): Promise>; + runCommand(input: { command: string; args?: string[]; timeoutMillis?: number }): Promise>; runDaemon(input: { command: string; args?: string[] }): { wait(): Promise>; term(): Promise; }; + chown(input: { volumeId: string; path: string; uid: string }): Promise; + chmod(input: { volumeId: string; path: string; mode: string }): Promise; + sleep(timeMs: number): Promise; /** Log at the trace level */ @@ -95,6 +76,8 @@ export type Effects = { is_sandboxed(): boolean; exists(input: { volumeId: string; path: string }): Promise; + bindLocal(options: { internalPort: number; name: string; externalPort: number }): Promise; + bindTor(options: { internalPort: number; name: string; externalPort: number }): Promise; fetch( url: string, @@ -102,7 +85,7 @@ export type Effects = { method?: "GET" | "POST" | "PUT" | "DELETE" | "HEAD" | "PATCH"; headers?: Record; body?: string; - }, + } ): Promise<{ method: string; ok: boolean; @@ -217,8 +200,8 @@ export type Target = V & { export type UniqueBy = | { - any: UniqueBy[]; - } + any: UniqueBy[]; + } | string | null; @@ -228,19 +211,19 @@ export type WithNullable = T & { export type DefaultString = | string | { - /** The chars available for the random generation */ - charset?: string; - /** Length that we generate to */ - len: number; - }; + /** The chars available for the random generation */ + charset?: string; + /** Length that we generate to */ + len: number; + }; export type ValueSpecString = // deno-lint-ignore ban-types ( | {} | { - pattern: string; - "pattern-description": string; - } + pattern: string; + "pattern-description": string; + } ) & { copyable?: boolean; masked?: boolean; @@ -257,71 +240,63 @@ export type ValueSpecNumber = { export type ValueSpecBoolean = Record; export type ValueSpecAny = | Tag<"boolean", WithDescription>> + | Tag<"string", WithDescription, DefaultString>>> + | Tag<"number", WithDescription, number>>> | Tag< - "string", - WithDescription< - WithNullableDefault, DefaultString> - > - > - | Tag< - "number", - WithDescription, number>> - > - | Tag< - "enum", - WithDescription< - WithDefault< - { - values: readonly string[] | string[]; - "value-names": { - [key: string]: string; - }; - }, - string + "enum", + WithDescription< + WithDefault< + { + values: readonly string[] | string[]; + "value-names": { + [key: string]: string; + }; + }, + string + > > > - > | Tag<"list", ValueSpecList> | Tag<"object", WithDescription>> | Tag<"union", WithOptionalDescription>> | Tag< - "pointer", - WithDescription< - | Subtype< - "package", - | Target< - "tor-key", - { - "package-id": string; - interface: string; - } - > - | Target< - "tor-address", - { - "package-id": string; - interface: string; - } - > - | Target< - "lan-address", - { - "package-id": string; - interface: string; - } - > - | Target< - "config", - { - "package-id": string; - selector: string; - multi: boolean; - } - > + "pointer", + WithDescription< + | Subtype< + "package", + | Target< + "tor-key", + { + "package-id": string; + interface: string; + } + > + | Target< + "tor-address", + { + "package-id": string; + interface: string; + } + > + | Target< + "lan-address", + { + "package-id": string; + interface: string; + } + > + | Target< + "config", + { + "package-id": string; + selector: string; + multi: boolean; + } + > + > + | Subtype<"system", Record> > - | Subtype<"system", Record> - > - >; + >; export type ValueSpecUnion = { /** What tag for the specification, for tag unions */ tag: { @@ -345,32 +320,12 @@ export type ValueSpecObject = { "unique-by"?: UniqueBy; }; export type ValueSpecList = - | Subtype< - "boolean", - WithDescription, boolean[]>> - > - | Subtype< - "string", - WithDescription, string[]>> - > - | Subtype< - "number", - WithDescription, number[]>> - > - | Subtype< - "enum", - WithDescription, string[]>> - > - | Subtype< - "object", - WithDescription< - WithNullableDefault, Record[]> - > - > - | Subtype< - "union", - WithDescription, string[]>> - >; + | Subtype<"boolean", WithDescription, boolean[]>>> + | Subtype<"string", WithDescription, string[]>>> + | Subtype<"number", WithDescription, number[]>>> + | Subtype<"enum", WithDescription, string[]>>> + | Subtype<"object", WithDescription, Record[]>>> + | Subtype<"union", WithDescription, string[]>>>; export type ValueSpecEnum = { values: string[]; "value-names": { [key: string]: string }; @@ -422,8 +377,8 @@ export type DependsOn = { export type KnownError = | { error: string } | { - "error-code": [number, string] | readonly [number, string]; - }; + "error-code": [number, string] | readonly [number, string]; + }; export type ResultType = KnownError | { result: T }; export type PackagePropertiesV2 = {